diff --git a/.github/ISSUE_TEMPLATE/bug_report.md b/.github/ISSUE_TEMPLATE/bug_report.md new file mode 100644 index 00000000000..c145291b57c --- /dev/null +++ b/.github/ISSUE_TEMPLATE/bug_report.md @@ -0,0 +1,47 @@ +--- +name: Bug report +about: Report a bug encountered while using ERPNext +labels: bug +--- + + + +## Description of the issue + +## Context information (for bug reports) + +**Output of `bench version`** +``` +(paste here) +``` + +## Steps to reproduce the issue + +1. +2. +3. + +### Observed result + +### Expected result + +### Stacktrace / full error message + +``` +(paste here) +``` + +## Additional information + +OS version / distribution, `ERPNext` install method, etc. diff --git a/.github/ISSUE_TEMPLATE/feature_request.md b/.github/ISSUE_TEMPLATE/feature_request.md new file mode 100644 index 00000000000..6cdad356cd0 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/feature_request.md @@ -0,0 +1,28 @@ +--- +name: Feature request +about: Suggest an idea to improve ERPNext +labels: feature-request +--- + + + +**Is your feature request related to a problem? Please describe.** +A clear and concise description of what the problem is. Ex. I'm always frustrated when [...] + +**Describe the solution you'd like** +A clear and concise description of what you want to happen. + +**Describe alternatives you've considered** +A clear and concise description of any alternative solutions or features you've considered. + +**Additional context** +Add any other context or screenshots about the feature request here. diff --git a/.github/ISSUE_TEMPLATE/question-about-using-erpnext.md b/.github/ISSUE_TEMPLATE/question-about-using-erpnext.md new file mode 100644 index 00000000000..455c20ebda4 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/question-about-using-erpnext.md @@ -0,0 +1,17 @@ +--- +name: Question about using ERPNext +about: This is not the appropriate channel +labels: invalid +--- + +Please post on our forums: + +for questions about using `ERPNext`: https://discuss.erpnext.com + +for questions about using the `Frappe Framework`: https://discuss.frappe.io + +for questions about using `bench`, probably the best place to start is the [bench repo](https://github.com/frappe/bench) + +For documentation issues, use the [ERPNext Documentation](https://erpnext.com/docs/) or [Frappe Framework Documentation](https://frappe.io/docs/user/en) or the [developer cheetsheet](https://github.com/frappe/frappe/wiki/Developer-Cheatsheet) + +> **Posts that are not bug reports or feature requests will not be addressed on this issue tracker.** \ No newline at end of file diff --git a/.github/PULL_REQUEST_TEMPLATE.md b/.github/PULL_REQUEST_TEMPLATE.md index c0e186dbfd9..091cf4715a2 100644 --- a/.github/PULL_REQUEST_TEMPLATE.md +++ b/.github/PULL_REQUEST_TEMPLATE.md @@ -1,2 +1,33 @@ -Please read the pull request checklist to make sure your changes are merged: https://github.com/frappe/erpnext/wiki/Pull-Request-Checklist + + +> Please provide enough information so that others can review your pull request: + + + +> Explain the **details** for making this change. What existing problem does the pull request solve? + + + +> Screenshots/GIFs + + diff --git a/SECURITY.md b/SECURITY.md new file mode 100644 index 00000000000..46ed43725be --- /dev/null +++ b/SECURITY.md @@ -0,0 +1,7 @@ +# Security Policy + +The ERPNext team and community take security issues seriously. To report a security issue, fill out the form at [https://erpnext.com/security/report](https://erpnext.com/security/report). + +You can help us make ERPNext and all it's users more secure by following the [Reporting guidelines](https://erpnext.com/security). + +We appreciate your efforts to responsibly disclose your findings. We'll endeavor to respond quickly, and will keep you updated throughout the process. \ No newline at end of file diff --git a/erpnext/__init__.py b/erpnext/__init__.py index 686d5492ca0..d031bc5bb17 100644 --- a/erpnext/__init__.py +++ b/erpnext/__init__.py @@ -5,7 +5,7 @@ import frappe from erpnext.hooks import regional_overrides from frappe.utils import getdate -__version__ = '12.1.6' +__version__ = '12.1.8' def get_default_company(user=None): '''Get default company for user''' diff --git a/erpnext/accounts/dashboard_chart_source/account_balance_timeline/account_balance_timeline.py b/erpnext/accounts/dashboard_chart_source/account_balance_timeline/account_balance_timeline.py index bc07b6d8077..d098d8421c7 100644 --- a/erpnext/accounts/dashboard_chart_source/account_balance_timeline/account_balance_timeline.py +++ b/erpnext/accounts/dashboard_chart_source/account_balance_timeline/account_balance_timeline.py @@ -3,6 +3,7 @@ from __future__ import unicode_literals import frappe, json +from frappe import _ from frappe.utils import add_to_date, date_diff, getdate, nowdate, get_last_day, formatdate from erpnext.accounts.report.general_ledger.general_ledger import execute from frappe.core.page.dashboard.dashboard import cache_source, get_from_date_from_timespan @@ -24,6 +25,9 @@ def get(chart_name = None, chart = None, no_cache = None, from_date = None, to_d account = filters.get("account") company = filters.get("company") + if not account and chart: + frappe.throw(_("Account is not set for the dashboard chart {0}").format(chart)) + if not to_date: to_date = nowdate() if not from_date: diff --git a/erpnext/accounts/doctype/account/account.py b/erpnext/accounts/doctype/account/account.py index 7cca8d2003b..cccced8e0be 100644 --- a/erpnext/accounts/doctype/account/account.py +++ b/erpnext/accounts/doctype/account/account.py @@ -117,7 +117,7 @@ class Account(NestedSet): if not parent_acc_name_map: return - self.create_account_for_child_company(parent_acc_name_map, descendants) + self.create_account_for_child_company(parent_acc_name_map, descendants, parent_acc_name) def validate_group_or_ledger(self): if self.get("__islocal"): @@ -159,7 +159,7 @@ class Account(NestedSet): if frappe.db.get_value("GL Entry", {"account": self.name}): frappe.throw(_("Currency can not be changed after making entries using some other currency")) - def create_account_for_child_company(self, parent_acc_name_map, descendants): + def create_account_for_child_company(self, parent_acc_name_map, descendants, parent_acc_name): for company in descendants: if not parent_acc_name_map.get(company): frappe.throw(_("While creating account for child Company {0}, parent account {1} not found. Please create the parent account in corresponding COA") diff --git a/erpnext/accounts/doctype/account/test_account.py b/erpnext/accounts/doctype/account/test_account.py index 4ee55736fee..dc23b2b2d05 100644 --- a/erpnext/accounts/doctype/account/test_account.py +++ b/erpnext/accounts/doctype/account/test_account.py @@ -160,7 +160,7 @@ def _make_test_records(verbose): ["_Test Payable USD", "Current Liabilities", 0, "Payable", "USD"] ] - for company, abbr in [["_Test Company", "_TC"], ["_Test Company 1", "_TC1"]]: + for company, abbr in [["_Test Company", "_TC"], ["_Test Company 1", "_TC1"], ["_Test Company with perpetual inventory", "TCP1"]]: test_objects = make_test_objects("Account", [{ "doctype": "Account", "account_name": account_name, diff --git a/erpnext/accounts/doctype/coupon_code/coupon_code.js b/erpnext/accounts/doctype/coupon_code/coupon_code.js index 0bf097f8d55..da3a9f8132f 100644 --- a/erpnext/accounts/doctype/coupon_code/coupon_code.js +++ b/erpnext/accounts/doctype/coupon_code/coupon_code.js @@ -2,6 +2,15 @@ // For license information, please see license.txt frappe.ui.form.on('Coupon Code', { + setup: function(frm) { + frm.set_query("pricing_rule", function() { + return { + filters: [ + ["Pricing Rule","coupon_code_based", "=", "1"] + ] + }; + }); + }, coupon_name:function(frm){ if (frm.doc.__islocal===1) { frm.trigger("make_coupon_code"); diff --git a/erpnext/accounts/doctype/coupon_code/coupon_code.json b/erpnext/accounts/doctype/coupon_code/coupon_code.json index fafc63531f0..7dc5e9dc787 100644 --- a/erpnext/accounts/doctype/coupon_code/coupon_code.json +++ b/erpnext/accounts/doctype/coupon_code/coupon_code.json @@ -24,6 +24,7 @@ ], "fields": [ { + "description": "e.g. \"Summer Holiday 2019 Offer 20\"", "fieldname": "coupon_name", "fieldtype": "Data", "label": "Coupon Name", @@ -50,7 +51,7 @@ "fieldtype": "Column Break" }, { - "description": "To be used to get discount", + "description": "unique e.g. SAVE20 To be used to get discount", "fieldname": "coupon_code", "fieldtype": "Data", "label": "Coupon Code", @@ -62,12 +63,13 @@ "fieldname": "pricing_rule", "fieldtype": "Link", "label": "Pricing Rule", - "options": "Pricing Rule" + "options": "Pricing Rule", + "reqd": 1 }, { "fieldname": "uses", "fieldtype": "Section Break", - "label": "Uses" + "label": "Validity and Usage" }, { "fieldname": "valid_from", @@ -113,7 +115,7 @@ "read_only": 1 } ], - "modified": "2019-10-15 14:12:22.686986", + "modified": "2019-10-19 14:48:14.602481", "modified_by": "Administrator", "module": "Accounts", "name": "Coupon Code", diff --git a/erpnext/accounts/doctype/loyalty_program/test_loyalty_program.py b/erpnext/accounts/doctype/loyalty_program/test_loyalty_program.py index 4a7406e0cb4..341884c1901 100644 --- a/erpnext/accounts/doctype/loyalty_program/test_loyalty_program.py +++ b/erpnext/accounts/doctype/loyalty_program/test_loyalty_program.py @@ -8,10 +8,12 @@ import unittest from frappe.utils import today, cint, flt, getdate from erpnext.accounts.doctype.loyalty_program.loyalty_program import get_loyalty_program_details_with_points from erpnext.accounts.party import get_dashboard_info +from erpnext.stock.doctype.purchase_receipt.test_purchase_receipt import set_perpetual_inventory class TestLoyaltyProgram(unittest.TestCase): @classmethod def setUpClass(self): + set_perpetual_inventory(0) # create relevant item, customer, loyalty program, etc create_records() diff --git a/erpnext/accounts/doctype/payment_entry/payment_entry.js b/erpnext/accounts/doctype/payment_entry/payment_entry.js index 172d5372a61..1e0b1bcbf16 100644 --- a/erpnext/accounts/doctype/payment_entry/payment_entry.js +++ b/erpnext/accounts/doctype/payment_entry/payment_entry.js @@ -308,7 +308,7 @@ frappe.ui.form.on('Payment Entry', { () => { frm.set_party_account_based_on_party = false; if (r.message.bank_account) { - frm.set_value("bank_account", r.message.bank_account); + frm.set_value("party_bank_account", r.message.bank_account); } } ]); diff --git a/erpnext/accounts/doctype/purchase_invoice/test_purchase_invoice.py b/erpnext/accounts/doctype/purchase_invoice/test_purchase_invoice.py index 6deee381481..b2ad4f4d512 100644 --- a/erpnext/accounts/doctype/purchase_invoice/test_purchase_invoice.py +++ b/erpnext/accounts/doctype/purchase_invoice/test_purchase_invoice.py @@ -10,7 +10,7 @@ from erpnext.accounts.doctype.payment_entry.payment_entry import get_payment_ent from frappe.utils import cint, flt, today, nowdate, add_days import frappe.defaults from erpnext.stock.doctype.purchase_receipt.test_purchase_receipt import set_perpetual_inventory, \ - test_records as pr_test_records + test_records as pr_test_records, make_purchase_receipt, get_taxes from erpnext.controllers.accounts_controller import get_payment_terms from erpnext.exceptions import InvalidCurrency from erpnext.stock.doctype.stock_entry.test_stock_entry import get_qty_after_transaction @@ -57,16 +57,11 @@ class TestPurchaseInvoice(unittest.TestCase): self.assertEqual([d.debit, d.credit], expected_gl_entries.get(d.account)) def test_gl_entries_with_perpetual_inventory(self): - pi = frappe.copy_doc(test_records[1]) - set_perpetual_inventory(1, pi.company) + pi = make_purchase_invoice(company="_Test Company with perpetual inventory", supplier_warehouse="Work In Progress - TCP1", warehouse= "Stores - TCP1", cost_center = "Main - TCP1", expense_account ="_Test Account Cost for Goods Sold - TCP1", get_taxes_and_charges=True, qty=10) self.assertTrue(cint(erpnext.is_perpetual_inventory_enabled(pi.company)), 1) - pi.insert() - pi.submit() self.check_gle_for_pi(pi.name) - set_perpetual_inventory(0, pi.company) - def test_terms_added_after_save(self): pi = frappe.copy_doc(test_records[1]) pi.insert() @@ -196,21 +191,21 @@ class TestPurchaseInvoice(unittest.TestCase): self.assertEqual(pi.on_hold, 0) def test_gl_entries_with_perpetual_inventory_against_pr(self): - pr = frappe.copy_doc(pr_test_records[0]) - set_perpetual_inventory(1, pr.company) - self.assertTrue(cint(erpnext.is_perpetual_inventory_enabled(pr.company)), 1) - pr.submit() - pi = frappe.copy_doc(test_records[1]) - for d in pi.get("items"): + pr = make_purchase_receipt(company="_Test Company with perpetual inventory", supplier_warehouse="Work In Progress - TCP1", warehouse= "Stores - TCP1", cost_center = "Main - TCP1", get_taxes_and_charges=True,) + + self.assertTrue(cint(erpnext.is_perpetual_inventory_enabled(pr.company)), 1) + + pi = make_purchase_invoice(company="_Test Company with perpetual inventory", supplier_warehouse="Work In Progress - TCP1", warehouse= "Stores - TCP1", cost_center = "Main - TCP1", expense_account ="_Test Account Cost for Goods Sold - TCP1", get_taxes_and_charges=True, qty=10,do_not_save= "True") + + for d in pi.items: d.purchase_receipt = pr.name + pi.insert() pi.submit() self.check_gle_for_pi(pi.name) - set_perpetual_inventory(0, pr.company) - def check_gle_for_pi(self, pi): gl_entries = frappe.db.sql("""select account, debit, credit from `tabGL Entry` where voucher_type='Purchase Invoice' and voucher_no=%s @@ -218,10 +213,10 @@ class TestPurchaseInvoice(unittest.TestCase): self.assertTrue(gl_entries) expected_values = dict((d[0], d) for d in [ - ["_Test Payable - _TC", 0, 720], - ["Stock Received But Not Billed - _TC", 500.0, 0], - ["_Test Account Shipping Charges - _TC", 100.0, 0], - ["_Test Account VAT - _TC", 120.0, 0], + ["Creditors - TCP1", 0, 720], + ["Stock Received But Not Billed - TCP1", 500.0, 0], + ["_Test Account Shipping Charges - TCP1", 100.0, 0], + ["_Test Account VAT - TCP1", 120.0, 0], ]) for i, gle in enumerate(gl_entries): @@ -524,10 +519,9 @@ class TestPurchaseInvoice(unittest.TestCase): self.assertFalse(gle) def test_purchase_invoice_update_stock_gl_entry_with_perpetual_inventory(self): - set_perpetual_inventory() pi = make_purchase_invoice(update_stock=1, posting_date=frappe.utils.nowdate(), - posting_time=frappe.utils.nowtime()) + posting_time=frappe.utils.nowtime(), cash_bank_account="Cash - TCP1", company="_Test Company with perpetual inventory", supplier_warehouse="Work In Progress - TCP1", warehouse= "Stores - TCP1", cost_center = "Main - TCP1", expense_account ="_Test Account Cost for Goods Sold - TCP1") gl_entries = frappe.db.sql("""select account, account_currency, debit, credit, debit_in_account_currency, credit_in_account_currency @@ -548,9 +542,9 @@ class TestPurchaseInvoice(unittest.TestCase): self.assertEqual(expected_gl_entries[gle.account][2], gle.credit) def test_purchase_invoice_for_is_paid_and_update_stock_gl_entry_with_perpetual_inventory(self): - set_perpetual_inventory() + pi = make_purchase_invoice(update_stock=1, posting_date=frappe.utils.nowdate(), - posting_time=frappe.utils.nowtime(), cash_bank_account="Cash - _TC", is_paid=1) + posting_time=frappe.utils.nowtime(), cash_bank_account="Cash - TCP1", is_paid=1, company="_Test Company with perpetual inventory", supplier_warehouse="Work In Progress - TCP1", warehouse= "Stores - TCP1", cost_center = "Main - TCP1", expense_account ="_Test Account Cost for Goods Sold - TCP1") gl_entries = frappe.db.sql("""select account, account_currency, sum(debit) as debit, sum(credit) as credit, debit_in_account_currency, credit_in_account_currency @@ -563,7 +557,7 @@ class TestPurchaseInvoice(unittest.TestCase): expected_gl_entries = dict((d[0], d) for d in [ [pi.credit_to, 250.0, 250.0], [stock_in_hand_account, 250.0, 0.0], - ["Cash - _TC", 0.0, 250.0] + ["Cash - TCP1", 0.0, 250.0] ]) for i, gle in enumerate(gl_entries): @@ -630,6 +624,7 @@ class TestPurchaseInvoice(unittest.TestCase): self.assertEqual(pi.get("items")[0].rm_supp_cost, flt(rm_supp_cost, 2)) def test_rejected_serial_no(self): + set_perpetual_inventory(0) pi = make_purchase_invoice(item_code="_Test Serialized Item With Series", received_qty=2, qty=1, rejected_qty=1, rate=500, update_stock=1, rejected_warehouse = "_Test Rejected Warehouse - _TC") @@ -881,7 +876,7 @@ def make_purchase_invoice(**args): pi.is_return = args.is_return pi.return_against = args.return_against pi.is_subcontracted = args.is_subcontracted or "No" - pi.supplier_warehouse = "_Test Warehouse 1 - _TC" + pi.supplier_warehouse = args.supplier_warehouse or "_Test Warehouse 1 - _TC" pi.append("items", { "item_code": args.item or args.item_code or "_Test Item", @@ -890,14 +885,21 @@ def make_purchase_invoice(**args): "received_qty": args.received_qty or 0, "rejected_qty": args.rejected_qty or 0, "rate": args.rate or 50, + 'expense_account': args.expense_account or '_Test Account Cost for Goods Sold - _TC', "conversion_factor": 1.0, "serial_no": args.serial_no, "stock_uom": "_Test UOM", - "cost_center": "_Test Cost Center - _TC", + "cost_center": args.cost_center or "_Test Cost Center - _TC", "project": args.project, "rejected_warehouse": args.rejected_warehouse or "", "rejected_serial_no": args.rejected_serial_no or "" }) + + if args.get_taxes_and_charges: + taxes = get_taxes() + for tax in taxes: + pi.append("taxes", tax) + if not args.do_not_save: pi.insert() if not args.do_not_submit: diff --git a/erpnext/accounts/doctype/sales_invoice/pos.py b/erpnext/accounts/doctype/sales_invoice/pos.py index 7d4fc639554..ed45b2cc2c7 100755 --- a/erpnext/accounts/doctype/sales_invoice/pos.py +++ b/erpnext/accounts/doctype/sales_invoice/pos.py @@ -402,14 +402,21 @@ def make_invoice(doc_list={}, email_queue_list={}, customers_list={}): for docs in doc_list: for name, doc in iteritems(docs): if not frappe.db.exists('Sales Invoice', {'offline_pos_name': name}): - validate_records(doc) - si_doc = frappe.new_doc('Sales Invoice') - si_doc.offline_pos_name = name - si_doc.update(doc) - si_doc.set_posting_time = 1 - si_doc.customer = get_customer_id(doc) - si_doc.due_date = doc.get('posting_date') - name_list = submit_invoice(si_doc, name, doc, name_list) + if isinstance(doc, dict): + validate_records(doc) + si_doc = frappe.new_doc('Sales Invoice') + si_doc.offline_pos_name = name + si_doc.update(doc) + si_doc.set_posting_time = 1 + si_doc.customer = get_customer_id(doc) + si_doc.due_date = doc.get('posting_date') + name_list = submit_invoice(si_doc, name, doc, name_list) + else: + doc.due_date = doc.get('posting_date') + doc.customer = get_customer_id(doc) + doc.set_posting_time = 1 + doc.offline_pos_name = name + name_list = submit_invoice(doc, name, doc, name_list) else: name_list.append(name) diff --git a/erpnext/accounts/doctype/sales_invoice/sales_invoice.py b/erpnext/accounts/doctype/sales_invoice/sales_invoice.py index e1256a78d95..5766c9a8d14 100644 --- a/erpnext/accounts/doctype/sales_invoice/sales_invoice.py +++ b/erpnext/accounts/doctype/sales_invoice/sales_invoice.py @@ -686,7 +686,6 @@ class SalesInvoice(SellingController): def make_gl_entries(self, gl_entries=None, repost_future_gle=True, from_repost=False): auto_accounting_for_stock = erpnext.is_perpetual_inventory_enabled(self.company) - if not gl_entries: gl_entries = self.get_gl_entries() diff --git a/erpnext/accounts/doctype/sales_invoice/test_records.json b/erpnext/accounts/doctype/sales_invoice/test_records.json index 9c8de7d5a24..ebe6e3da8df 100644 --- a/erpnext/accounts/doctype/sales_invoice/test_records.json +++ b/erpnext/accounts/doctype/sales_invoice/test_records.json @@ -68,8 +68,6 @@ "selling_price_list": "_Test Price List", "territory": "_Test Territory" }, - - { "company": "_Test Company", "conversion_rate": 1.0, @@ -276,7 +274,6 @@ "uom": "_Test UOM 1", "conversion_factor": 1, "stock_uom": "_Test UOM 1" - }, { "cost_center": "_Test Cost Center - _TC", diff --git a/erpnext/accounts/doctype/sales_invoice/test_sales_invoice.py b/erpnext/accounts/doctype/sales_invoice/test_sales_invoice.py index 4f253b69f79..530bd893c0e 100644 --- a/erpnext/accounts/doctype/sales_invoice/test_sales_invoice.py +++ b/erpnext/accounts/doctype/sales_invoice/test_sales_invoice.py @@ -20,6 +20,9 @@ from erpnext.stock.doctype.item.test_item import create_item from six import iteritems from erpnext.accounts.doctype.sales_invoice.sales_invoice import make_inter_company_transaction from erpnext.regional.india.utils import get_ewb_data +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.delivery_note.delivery_note import make_sales_invoice class TestSalesInvoice(unittest.TestCase): def make(self): @@ -550,7 +553,6 @@ class TestSalesInvoice(unittest.TestCase): si.get("taxes")[6].tax_amount = 2 si.insert() - print(si.name) expected_values = [ { @@ -679,56 +681,67 @@ class TestSalesInvoice(unittest.TestCase): self.assertFalse(gle) def test_pos_gl_entry_with_perpetual_inventory(self): - set_perpetual_inventory() make_pos_profile() - self._insert_purchase_receipt() - pos = copy.deepcopy(test_records[1]) - pos["is_pos"] = 1 - pos["update_stock"] = 1 - pos["payments"] = [{'mode_of_payment': 'Bank Draft', 'account': '_Test Bank - _TC', 'amount': 300}, - {'mode_of_payment': 'Cash', 'account': 'Cash - _TC', 'amount': 300}] + pr = make_purchase_receipt(company= "_Test Company with perpetual inventory",supplier_warehouse= "Work In Progress - TCP1", item_code= "_Test FG Item",warehouse= "Stores - TCP1",cost_center= "Main - TCP1") + + pos = create_sales_invoice(company= "_Test Company with perpetual inventory", debit_to="Debtors - TCP1", item_code= "_Test FG Item", warehouse="Stores - TCP1", income_account = "Sales - TCP1", expense_account = "Cost of Goods Sold - TCP1", cost_center = "Main - TCP1", do_not_save=True) + + pos.is_pos = 1 + pos.update_stock = 1 + + pos.append("payments", {'mode_of_payment': 'Bank Draft', 'account': '_Test Bank - TCP1', 'amount': 50}) + pos.append("payments", {'mode_of_payment': 'Cash', 'account': 'Cash - TCP1', 'amount': 50}) + + taxes = get_taxes_and_charges() + pos.taxes = [] + for tax in taxes: + pos.append("taxes", tax) si = frappe.copy_doc(pos) si.insert() si.submit() + self.assertEqual(si.paid_amount, 100.0) - self.assertEqual(si.paid_amount, 600.0) - - self.pos_gl_entry(si, pos, 300) + self.pos_gl_entry(si, pos, 50) def test_pos_change_amount(self): - set_perpetual_inventory() make_pos_profile() - self._insert_purchase_receipt() - pos = copy.deepcopy(test_records[1]) - pos["is_pos"] = 1 - pos["update_stock"] = 1 - pos["payments"] = [{'mode_of_payment': 'Bank Draft', 'account': '_Test Bank - _TC', 'amount': 300}, - {'mode_of_payment': 'Cash', 'account': 'Cash - _TC', 'amount': 340}] + pr = make_purchase_receipt(company= "_Test Company with perpetual inventory",supplier_warehouse= "Work In Progress - TCP1", item_code= "_Test FG Item",warehouse= "Stores - TCP1",cost_center= "Main - TCP1") - si = frappe.copy_doc(pos) - si.change_amount = 5.0 - si.insert() - si.submit() + pos = create_sales_invoice(company= "_Test Company with perpetual inventory", debit_to="Debtors - TCP1", item_code= "_Test FG Item", warehouse="Stores - TCP1", income_account = "Sales - TCP1", expense_account = "Cost of Goods Sold - TCP1", cost_center = "Main - TCP1", do_not_save=True) - self.assertEqual(si.grand_total, 630.0) - self.assertEqual(si.write_off_amount, -5) + pos.is_pos = 1 + pos.update_stock = 1 + + pos.append("payments", {'mode_of_payment': 'Bank Draft', 'account': '_Test Bank - TCP1', 'amount': 50}) + pos.append("payments", {'mode_of_payment': 'Cash', 'account': 'Cash - TCP1', 'amount': 60}) + + pos.change_amount = 5.0 + pos.insert() + pos.submit() + + self.assertEqual(pos.grand_total, 100.0) + self.assertEqual(pos.write_off_amount, -5) def test_make_pos_invoice(self): from erpnext.accounts.doctype.sales_invoice.pos import make_invoice - set_perpetual_inventory() - make_pos_profile() - self._insert_purchase_receipt() + pr = make_purchase_receipt(company= "_Test Company with perpetual inventory",supplier_warehouse= "Work In Progress - TCP1", item_code= "_Test FG Item",warehouse= "Stores - TCP1",cost_center= "Main - TCP1") + pos = create_sales_invoice(company= "_Test Company with perpetual inventory", debit_to="Debtors - TCP1", item_code= "_Test FG Item", warehouse="Stores - TCP1", income_account = "Sales - TCP1", expense_account = "Cost of Goods Sold - TCP1", cost_center = "Main - TCP1", do_not_save=True) - pos = copy.deepcopy(test_records[1]) - pos["is_pos"] = 1 - pos["update_stock"] = 1 - pos["payments"] = [{'mode_of_payment': 'Bank Draft', 'account': '_Test Bank - _TC', 'amount': 300}, - {'mode_of_payment': 'Cash', 'account': 'Cash - _TC', 'amount': 330}] + pos.is_pos = 1 + pos.update_stock = 1 + + pos.append("payments", {'mode_of_payment': 'Bank Draft', 'account': '_Test Bank - TCP1', 'amount': 50}) + pos.append("payments", {'mode_of_payment': 'Cash', 'account': 'Cash - TCP1', 'amount': 50}) + + taxes = get_taxes_and_charges() + pos.taxes = [] + for tax in taxes: + pos.append("taxes", tax) invoice_data = [{'09052016142': pos}] si = make_invoice(invoice_data).get('invoice') @@ -736,16 +749,15 @@ class TestSalesInvoice(unittest.TestCase): sales_invoice = frappe.get_all('Sales Invoice', fields =["*"], filters = {'offline_pos_name': '09052016142', 'docstatus': 1}) si = frappe.get_doc('Sales Invoice', sales_invoice[0].name) - self.assertEqual(si.grand_total, 630.0) - self.pos_gl_entry(si, pos, 330) + self.assertEqual(si.grand_total, 100) + + self.pos_gl_entry(si, pos, 50) def test_make_pos_invoice_in_draft(self): from erpnext.accounts.doctype.sales_invoice.pos import make_invoice from erpnext.stock.doctype.item.test_item import make_item - set_perpetual_inventory() - allow_negative_stock = frappe.db.get_single_value('Stock Settings', 'allow_negative_stock') if allow_negative_stock: frappe.db.set_value('Stock Settings', None, 'allow_negative_stock', 0) @@ -789,7 +801,7 @@ class TestSalesInvoice(unittest.TestCase): si.name, as_dict=1)[0] self.assertTrue(sle) self.assertEqual([sle.item_code, sle.warehouse, sle.actual_qty], - ["_Test Item", "_Test Warehouse - _TC", -1.0]) + ['_Test FG Item', 'Stores - TCP1', -1.0]) # check gl entries gl_entries = frappe.db.sql("""select account, debit, credit @@ -797,19 +809,19 @@ class TestSalesInvoice(unittest.TestCase): order by account asc, debit asc, credit asc""", si.name, as_dict=1) self.assertTrue(gl_entries) - stock_in_hand = get_inventory_account('_Test Company') - + stock_in_hand = get_inventory_account('_Test Company with perpetual inventory') expected_gl_entries = sorted([ - [si.debit_to, 630.0, 0.0], - [pos["items"][0]["income_account"], 0.0, 500.0], - [pos["taxes"][0]["account_head"], 0.0, 80.0], - [pos["taxes"][1]["account_head"], 0.0, 50.0], + [si.debit_to, 100.0, 0.0], + [pos.items[0].income_account, 0.0, 89.09], + ['Round Off - TCP1', 0.0, 0.01], + [pos.taxes[0].account_head, 0.0, 10.69], + [pos.taxes[1].account_head, 0.0, 0.21], [stock_in_hand, 0.0, abs(sle.stock_value_difference)], - [pos["items"][0]["expense_account"], abs(sle.stock_value_difference), 0.0], - [si.debit_to, 0.0, 300.0], + [pos.items[0].expense_account, abs(sle.stock_value_difference), 0.0], + [si.debit_to, 0.0, 50.0], [si.debit_to, 0.0, cash_amount], - ["_Test Bank - _TC", 300.0, 0.0], - ["Cash - _TC", cash_amount, 0.0] + ["_Test Bank - TCP1", 50, 0.0], + ["Cash - TCP1", cash_amount, 0.0] ]) for i, gle in enumerate(sorted(gl_entries, key=lambda gle: gle.account)): @@ -823,9 +835,9 @@ class TestSalesInvoice(unittest.TestCase): self.assertFalse(gle) - set_perpetual_inventory(0) frappe.db.sql("delete from `tabPOS Profile`") + si.delete() def test_pos_si_without_payment(self): set_perpetual_inventory() @@ -1008,7 +1020,6 @@ class TestSalesInvoice(unittest.TestCase): """ from erpnext.stock.doctype.stock_entry.test_stock_entry import make_serialized_item from erpnext.stock.doctype.delivery_note.test_delivery_note import create_delivery_note - from erpnext.stock.doctype.delivery_note.delivery_note import make_sales_invoice from erpnext.stock.doctype.serial_no.serial_no import get_serial_nos se = make_serialized_item() @@ -1023,14 +1034,17 @@ class TestSalesInvoice(unittest.TestCase): self.assertEqual(si.get("items")[0].serial_no, dn.get("items")[0].serial_no) def test_return_sales_invoice(self): - set_perpetual_inventory() - make_stock_entry(item_code="_Test Item", target="_Test Warehouse - _TC", qty=50, basic_rate=100) + make_stock_entry(item_code="_Test Item", target="Stores - TCP1", qty=50, basic_rate=100) - actual_qty_0 = get_qty_after_transaction() + actual_qty_0 = get_qty_after_transaction(item_code = "_Test Item", warehouse = "Stores - TCP1") - si = create_sales_invoice(qty=5, rate=500, update_stock=1) + si = create_sales_invoice(qty = 5, rate=500, update_stock=1, company= "_Test Company with perpetual inventory", debit_to="Debtors - TCP1", item_code= "_Test Item", warehouse="Stores - TCP1", income_account = "Sales - TCP1", expense_account = "Cost of Goods Sold - TCP1", cost_center = "Main - TCP1") + + + actual_qty_1 = get_qty_after_transaction(item_code = "_Test Item", warehouse = "Stores - TCP1") + + frappe.db.commit() - actual_qty_1 = get_qty_after_transaction() self.assertEqual(actual_qty_0 - 5, actual_qty_1) # outgoing_rate @@ -1038,10 +1052,9 @@ class TestSalesInvoice(unittest.TestCase): "voucher_no": si.name}, "stock_value_difference") / 5 # return entry - si1 = create_sales_invoice(is_return=1, return_against=si.name, qty=-2, rate=500, update_stock=1) - - actual_qty_2 = get_qty_after_transaction() + si1 = create_sales_invoice(is_return=1, return_against=si.name, qty=-2, rate=500, update_stock=1, company= "_Test Company with perpetual inventory", debit_to="Debtors - TCP1", item_code= "_Test Item", warehouse="Stores - TCP1", income_account = "Sales - TCP1", expense_account = "Cost of Goods Sold - TCP1", cost_center = "Main - TCP1") + actual_qty_2 = get_qty_after_transaction(item_code = "_Test Item", warehouse = "Stores - TCP1") self.assertEqual(actual_qty_1 + 2, actual_qty_2) incoming_rate, stock_value_difference = frappe.db.get_value("Stock Ledger Entry", @@ -1049,7 +1062,7 @@ class TestSalesInvoice(unittest.TestCase): ["incoming_rate", "stock_value_difference"]) self.assertEqual(flt(incoming_rate, 3), abs(flt(outgoing_rate, 3))) - stock_in_hand_account = get_inventory_account('_Test Company', si1.items[0].warehouse) + stock_in_hand_account = get_inventory_account('_Test Company with perpetual inventory', si1.items[0].warehouse) # Check gl entry gle_warehouse_amount = frappe.db.get_value("GL Entry", {"voucher_type": "Sales Invoice", @@ -1058,7 +1071,7 @@ class TestSalesInvoice(unittest.TestCase): self.assertEqual(gle_warehouse_amount, stock_value_difference) party_credited = frappe.db.get_value("GL Entry", {"voucher_type": "Sales Invoice", - "voucher_no": si1.name, "account": "Debtors - _TC", "party": "_Test Customer"}, "credit") + "voucher_no": si1.name, "account": "Debtors - TCP1", "party": "_Test Customer"}, "credit") self.assertEqual(party_credited, 1000) @@ -1066,7 +1079,6 @@ class TestSalesInvoice(unittest.TestCase): self.assertFalse(si1.outstanding_amount) self.assertEqual(frappe.db.get_value("Sales Invoice", si.name, "outstanding_amount"), 1500) - set_perpetual_inventory(0) def test_discount_on_net_total(self): si = frappe.copy_doc(test_records[2]) @@ -1524,6 +1536,8 @@ class TestSalesInvoice(unittest.TestCase): self.assertEqual(si.total_taxes_and_charges, 577.05) self.assertEqual(si.grand_total, 1827.05) + + def test_create_invoice_without_terms(self): si = create_sales_invoice(do_not_save=1) self.assertFalse(si.get('payment_schedule')) @@ -1930,4 +1944,29 @@ def get_outstanding_amount(against_voucher_type, against_voucher, account, party if against_voucher_type == 'Purchase Invoice': bal = bal * -1 - return bal \ No newline at end of file + return bal + +def get_taxes_and_charges(): + return [{ + "account_head": "_Test Account Excise Duty - TCP1", + "charge_type": "On Net Total", + "cost_center": "Main - TCP1", + "description": "Excise Duty", + "doctype": "Sales Taxes and Charges", + "idx": 1, + "included_in_print_rate": 1, + "parentfield": "taxes", + "rate": 12 + }, + { + "account_head": "_Test Account Education Cess - TCP1", + "charge_type": "On Previous Row Amount", + "cost_center": "Main - TCP1", + "description": "Education Cess", + "doctype": "Sales Taxes and Charges", + "idx": 2, + "included_in_print_rate": 1, + "parentfield": "taxes", + "rate": 2, + "row_id": 1 + }] \ No newline at end of file diff --git a/erpnext/accounts/doctype/shipping_rule/test_shipping_rule.py b/erpnext/accounts/doctype/shipping_rule/test_shipping_rule.py index 582ecb2e165..abc6ab82d3d 100644 --- a/erpnext/accounts/doctype/shipping_rule/test_shipping_rule.py +++ b/erpnext/accounts/doctype/shipping_rule/test_shipping_rule.py @@ -14,13 +14,13 @@ class TestShippingRule(unittest.TestCase): shipping_rule.name = test_records[0].get('name') shipping_rule.get("conditions")[0].from_value = 101 self.assertRaises(FromGreaterThanToError, shipping_rule.insert) - + def test_many_zero_to_values(self): shipping_rule = frappe.copy_doc(test_records[0]) shipping_rule.name = test_records[0].get('name') shipping_rule.get("conditions")[0].to_value = 0 self.assertRaises(ManyBlankToValuesError, shipping_rule.insert) - + def test_overlapping_conditions(self): for range_a, range_b in [ ((50, 150), (0, 100)), @@ -38,6 +38,10 @@ class TestShippingRule(unittest.TestCase): self.assertRaises(OverlappingConditionError, shipping_rule.insert) def create_shipping_rule(shipping_rule_type, shipping_rule_name): + + if frappe.db.exists("Shipping Rule", shipping_rule_name): + return frappe.get_doc("Shipping Rule", shipping_rule_name) + sr = frappe.new_doc("Shipping Rule") sr.account = "_Test Account Shipping Charges - _TC" sr.calculate_based_on = "Net Total" @@ -70,4 +74,4 @@ def create_shipping_rule(shipping_rule_type, shipping_rule_name): }) sr.insert(ignore_permissions=True) sr.submit() - return sr + return sr diff --git a/erpnext/accounts/general_ledger.py b/erpnext/accounts/general_ledger.py index 5c9e93d019e..43d9ad6435a 100644 --- a/erpnext/accounts/general_ledger.py +++ b/erpnext/accounts/general_ledger.py @@ -3,8 +3,9 @@ from __future__ import unicode_literals import frappe, erpnext -from frappe.utils import flt, cstr, cint +from frappe.utils import flt, cstr, cint, comma_and from frappe import _ +from erpnext.accounts.utils import get_stock_and_account_balance from frappe.model.meta import get_field_precision from erpnext.accounts.doctype.budget.budget import validate_expense_against_budget from erpnext.accounts.doctype.accounting_dimension.accounting_dimension import get_accounting_dimensions @@ -12,6 +13,7 @@ from erpnext.accounts.doctype.accounting_dimension.accounting_dimension import g class ClosedAccountingPeriod(frappe.ValidationError): pass class StockAccountInvalidTransaction(frappe.ValidationError): pass +class StockValueAndAccountBalanceOutOfSync(frappe.ValidationError): pass def make_gl_entries(gl_map, cancel=False, adv_adj=False, merge_entries=True, update_outstanding='Yes', from_repost=False): if gl_map: @@ -115,11 +117,9 @@ def check_if_in_list(gle, gl_map, dimensions=None): def save_entries(gl_map, adv_adj, update_outstanding, from_repost=False): if not from_repost: - validate_account_for_perpetual_inventory(gl_map) validate_cwip_accounts(gl_map) round_off_debit_credit(gl_map) - for entry in gl_map: make_entry(entry, adv_adj, update_outstanding, from_repost) @@ -127,6 +127,10 @@ def save_entries(gl_map, adv_adj, update_outstanding, from_repost=False): if not from_repost: validate_expense_against_budget(entry) + if not from_repost: + validate_account_for_perpetual_inventory(gl_map) + + def make_entry(args, adv_adj, update_outstanding, from_repost=False): args.update({"doctype": "GL Entry"}) gle = frappe.get_doc(args) @@ -137,15 +141,31 @@ def make_entry(args, adv_adj, update_outstanding, from_repost=False): gle.submit() def validate_account_for_perpetual_inventory(gl_map): - if cint(erpnext.is_perpetual_inventory_enabled(gl_map[0].company)) \ - and gl_map[0].voucher_type=="Journal Entry": - aii_accounts = [d[0] for d in frappe.db.sql("""select name from tabAccount - where account_type = 'Stock' and is_group=0""")] + if cint(erpnext.is_perpetual_inventory_enabled(gl_map[0].company)): + account_list = [gl_entries.account for gl_entries in gl_map] - for entry in gl_map: - if entry.account in aii_accounts: + aii_accounts = [d.name for d in frappe.get_all("Account", + filters={'account_type': 'Stock', 'is_group': 0, 'company': gl_map[0].company})] + + for account in account_list: + if account not in aii_accounts: + continue + + account_bal, stock_bal, warehouse_list = get_stock_and_account_balance(account, + gl_map[0].posting_date, gl_map[0].company) + + if gl_map[0].voucher_type=="Journal Entry": + # In case of Journal Entry, there are no corresponding SL entries, + # hence deducting currency amount + account_bal -= flt(gl_map[0].debit) - flt(gl_map[0].credit) + if account_bal == stock_bal: frappe.throw(_("Account: {0} can only be updated via Stock Transactions") - .format(entry.account), StockAccountInvalidTransaction) + .format(account), StockAccountInvalidTransaction) + + elif account_bal != stock_bal: + frappe.throw(_("Account Balance ({0}) and Stock Value ({1}) is out of sync for account {2} and linked warehouse ({3}). Please create adjustment Journal Entry for amount {4}.") + .format(account_bal, stock_bal, account, comma_and(warehouse_list), stock_bal - account_bal), + StockValueAndAccountBalanceOutOfSync) def validate_cwip_accounts(gl_map): if not cint(frappe.db.get_value("Asset Settings", None, "disable_cwip_accounting")) \ diff --git a/erpnext/accounts/report/budget_variance_report/budget_variance_report.py b/erpnext/accounts/report/budget_variance_report/budget_variance_report.py index 146c10c222d..8d65ac87148 100644 --- a/erpnext/accounts/report/budget_variance_report/budget_variance_report.py +++ b/erpnext/accounts/report/budget_variance_report/budget_variance_report.py @@ -69,7 +69,7 @@ def get_columns(filters): for year in fiscal_year: for from_date, to_date in get_period_date_ranges(filters["period"], year[0]): if filters["period"] == "Yearly": - labels = [_("Budget") + " " + str(year[0]), _("Actual ") + " " + str(year[0]), _("Varaiance ") + " " + str(year[0])] + labels = [_("Budget") + " " + str(year[0]), _("Actual ") + " " + str(year[0]), _("Variance ") + " " + str(year[0])] for label in labels: columns.append(label+":Float:150") else: diff --git a/erpnext/accounts/utils.py b/erpnext/accounts/utils.py index ac69fd3c969..382a89b310d 100644 --- a/erpnext/accounts/utils.py +++ b/erpnext/accounts/utils.py @@ -13,6 +13,10 @@ from six import iteritems # imported to enable erpnext.accounts.utils.get_account_currency from erpnext.accounts.doctype.account.account import get_account_currency +from erpnext.stock.utils import get_stock_value_on +from erpnext.stock import get_warehouse_account_map + + class FiscalYearError(frappe.ValidationError): pass @frappe.whitelist() @@ -560,23 +564,23 @@ def fix_total_debit_credit(): (dr_or_cr, dr_or_cr, '%s', '%s', '%s', dr_or_cr), (d.diff, d.voucher_type, d.voucher_no)) -def get_stock_and_account_difference(account_list=None, posting_date=None, company=None): - from erpnext.stock.utils import get_stock_value_on - from erpnext.stock import get_warehouse_account_map - +def get_stock_and_account_balance(account=None, posting_date=None, company=None): if not posting_date: posting_date = nowdate() - difference = {} warehouse_account = get_warehouse_account_map(company) - for warehouse, account_data in iteritems(warehouse_account): - if account_data.get('account') in account_list: - account_balance = get_balance_on(account_data.get('account'), posting_date, in_account_currency=False) - stock_value = get_stock_value_on(warehouse, posting_date) - if abs(flt(stock_value) - flt(account_balance)) > 0.005: - difference.setdefault(account_data.get('account'), flt(stock_value) - flt(account_balance)) + account_balance = get_balance_on(account, posting_date, in_account_currency=False) - return difference + related_warehouses = [wh for wh, wh_details in warehouse_account.items() + if wh_details.account == account and not wh_details.is_group] + + total_stock_value = 0.0 + for warehouse in related_warehouses: + value = get_stock_value_on(warehouse, posting_date) + total_stock_value += value + + precision = frappe.get_precision("Journal Entry Account", "debit_in_account_currency") + return flt(account_balance, precision), flt(total_stock_value, precision), related_warehouses def get_currency_precision(): precision = cint(frappe.db.get_default("currency_precision")) diff --git a/erpnext/assets/report/fixed_asset_register/fixed_asset_register.json b/erpnext/assets/report/fixed_asset_register/fixed_asset_register.json index ae2aa542f54..b40243cb75a 100644 --- a/erpnext/assets/report/fixed_asset_register/fixed_asset_register.json +++ b/erpnext/assets/report/fixed_asset_register/fixed_asset_register.json @@ -1,13 +1,13 @@ { "add_total_row": 0, "creation": "2019-09-23 16:35:02.836134", - "disable_prepared_report": 0, + "disable_prepared_report": 1, "disabled": 0, "docstatus": 0, "doctype": "Report", "idx": 0, "is_standard": "Yes", - "modified": "2019-09-23 16:35:02.836134", + "modified": "2019-10-22 13:00:31.539726", "modified_by": "Administrator", "module": "Assets", "name": "Fixed Asset Register", diff --git a/erpnext/assets/report/fixed_asset_register/fixed_asset_register.py b/erpnext/assets/report/fixed_asset_register/fixed_asset_register.py index 2c9e48a06c1..f395499ad6b 100644 --- a/erpnext/assets/report/fixed_asset_register/fixed_asset_register.py +++ b/erpnext/assets/report/fixed_asset_register/fixed_asset_register.py @@ -4,6 +4,7 @@ from __future__ import unicode_literals import frappe from frappe import _ +from frappe.utils import cstr def execute(filters=None): filters = frappe._dict(filters or {}) @@ -149,12 +150,12 @@ def get_finance_book_value_map(finance_book=''): FROM `tabAsset Finance Book` WHERE parentfield='finance_books' - AND finance_book=%s''', (finance_book))) + AND ifnull(finance_book, '')=%s''', cstr(finance_book))) def get_purchase_receipt_supplier_map(): return frappe._dict(frappe.db.sql(''' Select pr.name, pr.supplier - FROM `tabPurchase Receipt` pr, `tabPurchase Receipt Item` pri + FROM `tabPurchase Receipt` pr, `tabPurchase Receipt Item` pri WHERE pri.parent = pr.name AND pri.is_fixed_asset=1 @@ -164,7 +165,7 @@ def get_purchase_receipt_supplier_map(): def get_purchase_invoice_supplier_map(): return frappe._dict(frappe.db.sql(''' Select pi.name, pi.supplier - FROM `tabPurchase Invoice` pi, `tabPurchase Invoice Item` pii + FROM `tabPurchase Invoice` pi, `tabPurchase Invoice Item` pii WHERE pii.parent = pi.name AND pii.is_fixed_asset=1 diff --git a/erpnext/config/accounting.py b/erpnext/config/accounts.py similarity index 100% rename from erpnext/config/accounting.py rename to erpnext/config/accounts.py diff --git a/erpnext/config/desktop.py b/erpnext/config/desktop.py index 8ce096499dc..95f6f7c510c 100644 --- a/erpnext/config/desktop.py +++ b/erpnext/config/desktop.py @@ -18,7 +18,7 @@ def get_data(): "onboard_present": 1 }, { - "module_name": "Accounting", + "module_name": "Accounts", "category": "Modules", "label": _("Accounting"), "color": "#3498db", diff --git a/erpnext/config/selling.py b/erpnext/config/selling.py index 928bd5fa328..5db4cc27021 100644 --- a/erpnext/config/selling.py +++ b/erpnext/config/selling.py @@ -127,7 +127,11 @@ def get_data(): "name": "Shipping Rule", "description": _("Rules for adding shipping costs."), }, - + { + "type": "doctype", + "name": "Coupon Code", + "description": _("Define coupon codes."), + } ] }, { diff --git a/erpnext/controllers/accounts_controller.py b/erpnext/controllers/accounts_controller.py index 320a618f680..67f453d2b30 100644 --- a/erpnext/controllers/accounts_controller.py +++ b/erpnext/controllers/accounts_controller.py @@ -1172,6 +1172,7 @@ def set_purchase_order_defaults(parent_doctype, parent_doctype_name, child_docna def update_child_qty_rate(parent_doctype, trans_items, parent_doctype_name, child_docname="items"): data = json.loads(trans_items) + sales_doctypes = ['Sales Order', 'Sales Invoice', 'Delivery Note', 'Quotation'] parent = frappe.get_doc(parent_doctype, parent_doctype_name) for d in data: @@ -1204,18 +1205,22 @@ def update_child_qty_rate(parent_doctype, trans_items, parent_doctype_name, chil # if rate is greater than price_list_rate, set margin # or set discount child_item.discount_percentage = 0 - child_item.margin_type = "Amount" - child_item.margin_rate_or_amount = flt(child_item.rate - child_item.price_list_rate, - child_item.precision("margin_rate_or_amount")) - child_item.rate_with_margin = child_item.rate + + if parent_doctype in sales_doctypes: + child_item.margin_type = "Amount" + child_item.margin_rate_or_amount = flt(child_item.rate - child_item.price_list_rate, + child_item.precision("margin_rate_or_amount")) + child_item.rate_with_margin = child_item.rate else: child_item.discount_percentage = flt((1 - flt(child_item.rate) / flt(child_item.price_list_rate)) * 100.0, child_item.precision("discount_percentage")) child_item.discount_amount = flt( child_item.price_list_rate) - flt(child_item.rate) - child_item.margin_type = "" - child_item.margin_rate_or_amount = 0 - child_item.rate_with_margin = 0 + + if parent_doctype in sales_doctypes: + child_item.margin_type = "" + child_item.margin_rate_or_amount = 0 + child_item.rate_with_margin = 0 child_item.flags.ignore_validate_update_after_submit = True if new_child_flag: diff --git a/erpnext/controllers/queries.py b/erpnext/controllers/queries.py index 19dea080b17..a9e50bab5a4 100644 --- a/erpnext/controllers/queries.py +++ b/erpnext/controllers/queries.py @@ -152,6 +152,20 @@ def tax_account_query(doctype, txt, searchfield, start, page_len, filters): def item_query(doctype, txt, searchfield, start, page_len, filters, as_dict=False): conditions = [] + #Get searchfields from meta and use in Item Link field query + meta = frappe.get_meta("Item", cached=True) + searchfields = meta.get_search_fields() + + if "description" in searchfields: + searchfields.remove("description") + + columns = [field for field in searchfields if not field in ["name", "item_group", "description"]] + columns = ", ".join(columns) + + searchfields = searchfields + [field for field in[searchfield or "name", "item_code", "item_group", "item_name"] + if not field in searchfields] + searchfields = " or ".join([field + " like %(txt)s" for field in searchfields]) + description_cond = '' if frappe.db.count('Item', cache=True) < 50000: # scan description only if items are less than 50000 @@ -162,17 +176,14 @@ def item_query(doctype, txt, searchfield, start, page_len, filters, as_dict=Fals concat(substr(tabItem.item_name, 1, 40), "..."), item_name) as item_name, tabItem.item_group, if(length(tabItem.description) > 40, \ - concat(substr(tabItem.description, 1, 40), "..."), description) as decription + concat(substr(tabItem.description, 1, 40), "..."), description) as description, + {columns} from tabItem where tabItem.docstatus < 2 and tabItem.has_variants=0 and tabItem.disabled=0 and (tabItem.end_of_life > %(today)s or ifnull(tabItem.end_of_life, '0000-00-00')='0000-00-00') - and (tabItem.`{key}` LIKE %(txt)s - or tabItem.item_code LIKE %(txt)s - or tabItem.item_group LIKE %(txt)s - or tabItem.item_name LIKE %(txt)s - or tabItem.item_code IN (select parent from `tabItem Barcode` where barcode LIKE %(txt)s) + and ({scond} or tabItem.item_code IN (select parent from `tabItem Barcode` where barcode LIKE %(txt)s) {description_cond}) {fcond} {mcond} order by @@ -182,6 +193,8 @@ def item_query(doctype, txt, searchfield, start, page_len, filters, as_dict=Fals name, item_name limit %(start)s, %(page_len)s """.format( key=searchfield, + columns=columns, + scond=searchfields, fcond=get_filters_cond(doctype, filters, conditions).replace('%', '%%'), mcond=get_match_cond(doctype).replace('%', '%%'), description_cond = description_cond), @@ -280,22 +293,31 @@ def get_batch_no(doctype, txt, searchfield, start, page_len, filters): "page_len": page_len } + having_clause = "having sum(sle.actual_qty) > 0" + if filters.get("is_return"): + having_clause = "" + if args.get('warehouse'): - batch_nos = frappe.db.sql("""select sle.batch_no, round(sum(sle.actual_qty),2), sle.stock_uom, concat('MFG-',batch.manufacturing_date), concat('EXP-',batch.expiry_date) - from `tabStock Ledger Entry` sle - INNER JOIN `tabBatch` batch on sle.batch_no = batch.name - where - batch.disabled = 0 - and sle.item_code = %(item_code)s - and sle.warehouse = %(warehouse)s - and (sle.batch_no like %(txt)s - or batch.manufacturing_date like %(txt)s) - and batch.docstatus < 2 - {0} - {match_conditions} - group by batch_no having sum(sle.actual_qty) > 0 - order by batch.expiry_date, sle.batch_no desc - limit %(start)s, %(page_len)s""".format(cond, match_conditions=get_match_cond(doctype)), args) + batch_nos = frappe.db.sql("""select sle.batch_no, round(sum(sle.actual_qty),2), sle.stock_uom, + concat('MFG-',batch.manufacturing_date), concat('EXP-',batch.expiry_date) + from `tabStock Ledger Entry` sle + INNER JOIN `tabBatch` batch on sle.batch_no = batch.name + where + batch.disabled = 0 + and sle.item_code = %(item_code)s + and sle.warehouse = %(warehouse)s + and (sle.batch_no like %(txt)s + or batch.manufacturing_date like %(txt)s) + and batch.docstatus < 2 + {cond} + {match_conditions} + group by batch_no {having_clause} + order by batch.expiry_date, sle.batch_no desc + limit %(start)s, %(page_len)s""".format( + cond=cond, + match_conditions=get_match_cond(doctype), + having_clause = having_clause + ), args) return batch_nos else: diff --git a/erpnext/controllers/stock_controller.py b/erpnext/controllers/stock_controller.py index 2d87a98f206..542073ebd76 100644 --- a/erpnext/controllers/stock_controller.py +++ b/erpnext/controllers/stock_controller.py @@ -207,41 +207,6 @@ class StockController(AccountsController): reference_doctype=self.doctype, reference_name=self.name)).insert().name - def make_adjustment_entry(self, expected_gle, voucher_obj): - from erpnext.accounts.utils import get_stock_and_account_difference - account_list = [d.account for d in expected_gle] - acc_diff = get_stock_and_account_difference(account_list, - expected_gle[0].posting_date, self.company) - - cost_center = self.get_company_default("cost_center") - stock_adjustment_account = self.get_company_default("stock_adjustment_account") - - gl_entries = [] - for account, diff in acc_diff.items(): - if diff: - gl_entries.append([ - # stock in hand account - voucher_obj.get_gl_dict({ - "account": account, - "against": stock_adjustment_account, - "debit": diff, - "remarks": "Adjustment Accounting Entry for Stock", - }), - - # account against stock in hand - voucher_obj.get_gl_dict({ - "account": stock_adjustment_account, - "against": account, - "credit": diff, - "cost_center": cost_center or None, - "remarks": "Adjustment Accounting Entry for Stock", - }), - ]) - - if gl_entries: - from erpnext.accounts.general_ledger import make_gl_entries - make_gl_entries(gl_entries) - def check_expense_account(self, item): if not item.get("expense_account"): frappe.throw(_("Expense or Difference account is mandatory for Item {0} as it impacts overall stock value").format(item.item_code)) diff --git a/erpnext/demo/setup/setup_data.py b/erpnext/demo/setup/setup_data.py index 48dcdbe8638..a395c7c17ae 100644 --- a/erpnext/demo/setup/setup_data.py +++ b/erpnext/demo/setup/setup_data.py @@ -10,13 +10,14 @@ from erpnext.demo.domains import data from frappe import _ def setup(domain): + frappe.flags.in_demo = 1 complete_setup(domain) setup_demo_page() setup_fiscal_year() setup_holiday_list() setup_user() setup_employee() - setup_user_roles() + setup_user_roles(domain) setup_role_permissions() setup_custom_field_for_domain() @@ -183,13 +184,19 @@ def setup_salary_structure(employees, salary_slip_based_on_timesheet=0): return ss -def setup_user_roles(): +def setup_user_roles(domain): user = frappe.get_doc('User', 'demo@erpnext.com') user.add_roles('HR User', 'HR Manager', 'Accounts User', 'Accounts Manager', 'Stock User', 'Stock Manager', 'Sales User', 'Sales Manager', 'Purchase User', 'Purchase Manager', 'Projects User', 'Manufacturing User', 'Manufacturing Manager', - 'Support Team', 'Academics User', 'Physician', 'Healthcare Administrator', 'Laboratory User', - 'Nursing User', 'Patient') + 'Support Team') + + if domain == "Healthcare": + user.add_roles('Physician', 'Healthcare Administrator', 'Laboratory User', + 'Nursing User', 'Patient') + + if domain == "Education": + user.add_roles('Academics User') if not frappe.db.get_global('demo_hr_user'): user = frappe.get_doc('User', 'CaitlinSnow@example.com') @@ -219,7 +226,7 @@ def setup_user_roles(): if not frappe.db.get_global('demo_manufacturing_user'): user = frappe.get_doc('User', 'NeptuniaAquaria@example.com') - user.add_roles('Manufacturing User', 'Stock User', 'Purchase User', 'Accounts User') + user.add_roles('Manufacturing User', 'Stock Manager', 'Stock User', 'Purchase User', 'Accounts User') update_employee_department(user.name, 'Production') frappe.db.set_global('demo_manufacturing_user', user.name) @@ -241,11 +248,12 @@ def setup_user_roles(): update_employee_department(user.name, 'Management') frappe.db.set_global('demo_projects_user', user.name) - if not frappe.db.get_global('demo_education_user'): - user = frappe.get_doc('User', 'ArthurCurry@example.com') - user.add_roles('Academics User') - update_employee_department(user.name, 'Management') - frappe.db.set_global('demo_education_user', user.name) + if domain == "Education": + if not frappe.db.get_global('demo_education_user'): + user = frappe.get_doc('User', 'ArthurCurry@example.com') + user.add_roles('Academics User') + update_employee_department(user.name, 'Management') + frappe.db.set_global('demo_education_user', user.name) #Add Expense Approver user = frappe.get_doc('User', 'ClarkKent@example.com') diff --git a/erpnext/demo/user/accounts.py b/erpnext/demo/user/accounts.py index 6206dfd2d03..7fab7722358 100644 --- a/erpnext/demo/user/accounts.py +++ b/erpnext/demo/user/accounts.py @@ -73,14 +73,16 @@ def work(): make_pos_invoice() def make_payment_entries(ref_doctype, report): - outstanding_invoices = list(set([r[3] for r in query_report.run(report, { - "report_date": frappe.flags.current_date, - "company": erpnext.get_default_company() - })["result"] if r[2]==ref_doctype])) + + outstanding_invoices = frappe.get_all(ref_doctype, fields=["name"], + filters={ + "company": erpnext.get_default_company(), + "outstanding_amount": (">", 0.0) + }) # make Payment Entry for inv in outstanding_invoices[:random.randint(1, 2)]: - pe = get_payment_entry(ref_doctype, inv) + pe = get_payment_entry(ref_doctype, inv.name) pe.posting_date = frappe.flags.current_date pe.reference_no = random_string(6) pe.reference_date = frappe.flags.current_date @@ -91,7 +93,7 @@ def make_payment_entries(ref_doctype, report): # make payment via JV for inv in outstanding_invoices[:1]: - jv = frappe.get_doc(get_payment_entry_against_invoice(ref_doctype, inv)) + jv = frappe.get_doc(get_payment_entry_against_invoice(ref_doctype, inv.name)) jv.posting_date = frappe.flags.current_date jv.cheque_no = random_string(6) jv.cheque_date = frappe.flags.current_date diff --git a/erpnext/demo/user/projects.py b/erpnext/demo/user/projects.py index 36d676f3173..044e2967fde 100644 --- a/erpnext/demo/user/projects.py +++ b/erpnext/demo/user/projects.py @@ -39,61 +39,4 @@ def make_project(current_date): "doctype": "Project", "project_name": "New Product Development " + current_date.strftime("%Y-%m-%d"), }) - project.set("tasks", [ - { - "title": "Review Requirements", - "start_date": frappe.utils.add_days(current_date, 10), - "end_date": frappe.utils.add_days(current_date, 11) - }, - { - "title": "Design Options", - "start_date": frappe.utils.add_days(current_date, 11), - "end_date": frappe.utils.add_days(current_date, 20) - }, - { - "title": "Make Prototypes", - "start_date": frappe.utils.add_days(current_date, 20), - "end_date": frappe.utils.add_days(current_date, 30) - }, - { - "title": "Customer Feedback on Prototypes", - "start_date": frappe.utils.add_days(current_date, 30), - "end_date": frappe.utils.add_days(current_date, 40) - }, - { - "title": "Freeze Feature Set", - "start_date": frappe.utils.add_days(current_date, 40), - "end_date": frappe.utils.add_days(current_date, 45) - }, - { - "title": "Testing", - "start_date": frappe.utils.add_days(current_date, 45), - "end_date": frappe.utils.add_days(current_date, 60) - }, - { - "title": "Product Engineering", - "start_date": frappe.utils.add_days(current_date, 45), - "end_date": frappe.utils.add_days(current_date, 55) - }, - { - "title": "Supplier Contracts", - "start_date": frappe.utils.add_days(current_date, 55), - "end_date": frappe.utils.add_days(current_date, 70) - }, - { - "title": "Design and Build Fixtures", - "start_date": frappe.utils.add_days(current_date, 45), - "end_date": frappe.utils.add_days(current_date, 65) - }, - { - "title": "Test Run", - "start_date": frappe.utils.add_days(current_date, 70), - "end_date": frappe.utils.add_days(current_date, 80) - }, - { - "title": "Launch", - "start_date": frappe.utils.add_days(current_date, 80), - "end_date": frappe.utils.add_days(current_date, 90) - }, - ]) project.insert() diff --git a/erpnext/demo/user/sales.py b/erpnext/demo/user/sales.py index 457e9763dc7..8d5ba28b79a 100644 --- a/erpnext/demo/user/sales.py +++ b/erpnext/demo/user/sales.py @@ -66,7 +66,7 @@ def make_opportunity(domain): b = frappe.get_doc({ "doctype": "Opportunity", "opportunity_from": "Customer", - "customer": get_random("Customer"), + "party_name": frappe.get_value("Customer", get_random("Customer"), 'name'), "opportunity_type": "Sales", "with_items": 1, "transaction_date": frappe.flags.current_date, diff --git a/erpnext/education/doctype/student/student.js b/erpnext/education/doctype/student/student.js index 2c933e28b73..b6e741c4da6 100644 --- a/erpnext/education/doctype/student/student.js +++ b/erpnext/education/doctype/student/student.js @@ -27,3 +27,16 @@ frappe.ui.form.on('Student', { } } }); + +frappe.ui.form.on('Student Guardian', { + guardians_add: function(frm){ + frm.fields_dict['guardians'].grid.get_field('guardian').get_query = function(doc){ + var guardian_list = []; + if(!doc.__islocal) guardian_list.push(doc.guardian); + $.each(doc.guardians, function(idx, val){ + if (val.guardian) guardian_list.push(val.guardian); + }); + return { filters: [['Guardian', 'name', 'not in', guardian_list]] }; + }; + } +}); diff --git a/erpnext/erpnext_integrations/connectors/shopify_connection.py b/erpnext/erpnext_integrations/connectors/shopify_connection.py index bd980374b6e..3be08a2757a 100644 --- a/erpnext/erpnext_integrations/connectors/shopify_connection.py +++ b/erpnext/erpnext_integrations/connectors/shopify_connection.py @@ -29,7 +29,8 @@ def sync_sales_order(order, request_id=None): validate_item(order, shopify_settings) create_order(order, shopify_settings) except Exception as e: - make_shopify_log(status="Error", message=e.message, exception=False) + make_shopify_log(status="Error", exception=e) + else: make_shopify_log(status="Success") @@ -42,9 +43,9 @@ def prepare_sales_invoice(order, request_id=None): sales_order = get_sales_order(cstr(order['id'])) if sales_order: create_sales_invoice(order, shopify_settings, sales_order) - make_shopify_log(status="Success") - except Exception: - make_shopify_log(status="Error", exception=True) + make_shopify_log(status="Success") + except Exception as e: + make_shopify_log(status="Error", exception=e, rollback=True) def prepare_delivery_note(order, request_id=None): frappe.set_user('Administrator') @@ -56,8 +57,8 @@ def prepare_delivery_note(order, request_id=None): if sales_order: create_delivery_note(order, shopify_settings, sales_order) make_shopify_log(status="Success") - except Exception: - make_shopify_log(status="Error", exception=True) + except Exception as e: + make_shopify_log(status="Error", exception=e, rollback=True) def get_sales_order(shopify_order_id): sales_order = frappe.db.get_value("Sales Order", filters={"shopify_order_id": shopify_order_id}) @@ -97,7 +98,7 @@ def create_sales_order(shopify_order, shopify_settings, company=None): message = 'Following items are exists in order but relevant record not found in Product master' message += "\n" + ", ".join(product_not_exists) - make_shopify_log(status="Error", message=message, exception=True) + make_shopify_log(status="Error", exception=e, rollback=True) return '' diff --git a/erpnext/erpnext_integrations/doctype/shopify_log/shopify_log.py b/erpnext/erpnext_integrations/doctype/shopify_log/shopify_log.py index 0c821e031d9..a2b6af99b21 100644 --- a/erpnext/erpnext_integrations/doctype/shopify_log/shopify_log.py +++ b/erpnext/erpnext_integrations/doctype/shopify_log/shopify_log.py @@ -12,23 +12,38 @@ class ShopifyLog(Document): pass -def make_shopify_log(status="Queued", message=None, exception=False): +def make_shopify_log(status="Queued", exception=None, rollback=False): # if name not provided by log calling method then fetch existing queued state log + make_new = False + if not frappe.flags.request_id: - return + make_new = True - log = frappe.get_doc("Shopify Log", frappe.flags.request_id) - - if exception: + if rollback: frappe.db.rollback() - log = frappe.get_doc({"doctype":"Shopify Log"}).insert(ignore_permissions=True) - log.message = message if message else '' + if make_new: + log = frappe.get_doc({"doctype":"Shopify Log"}).insert(ignore_permissions=True) + else: + log = log = frappe.get_doc("Shopify Log", frappe.flags.request_id) + + log.message = get_message(exception) log.traceback = frappe.get_traceback() log.status = status log.save(ignore_permissions=True) frappe.db.commit() +def get_message(exception): + message = None + + if hasattr(exception, 'message'): + message = exception.message + elif hasattr(exception, '__str__'): + message = exception.__str__() + else: + message = "Something went wrong while syncing" + return message + def dump_request_data(data, event="create/order"): event_mapper = { "orders/create": get_webhook_address(connector_name='shopify_connection', method="sync_sales_order", exclude_uri=True), @@ -43,11 +58,11 @@ def dump_request_data(data, event="create/order"): }).insert(ignore_permissions=True) frappe.db.commit() - frappe.enqueue(method=event_mapper[event], queue='short', timeout=300, is_async=True, + frappe.enqueue(method=event_mapper[event], queue='short', timeout=300, is_async=True, **{"order": data, "request_id": log.name}) @frappe.whitelist() def resync(method, name, request_data): frappe.db.set_value("Shopify Log", name, "status", "Queued", update_modified=False) - frappe.enqueue(method=method, queue='short', timeout=300, is_async=True, + frappe.enqueue(method=method, queue='short', timeout=300, is_async=True, **{"order": json.loads(request_data), "request_id": name}) diff --git a/erpnext/erpnext_integrations/doctype/shopify_settings/shopify_settings.py b/erpnext/erpnext_integrations/doctype/shopify_settings/shopify_settings.py index e2f6d497d23..0cad0cca72d 100644 --- a/erpnext/erpnext_integrations/doctype/shopify_settings/shopify_settings.py +++ b/erpnext/erpnext_integrations/doctype/shopify_settings/shopify_settings.py @@ -30,13 +30,9 @@ class ShopifySettings(Document): # url = get_shopify_url('admin/webhooks.json', self) created_webhooks = [d.method for d in self.webhooks] url = get_shopify_url('admin/api/2019-04/webhooks.json', self) - print('url', url) for method in webhooks: - print('method', method) session = get_request_session() - print('session', session) try: - print(get_header(self)) d = session.post(url, data=json.dumps({ "webhook": { "topic": method, @@ -44,11 +40,10 @@ class ShopifySettings(Document): "format": "json" } }), headers=get_header(self)) - print('d', d.json()) d.raise_for_status() self.update_webhook_table(method, d.json()) except Exception as e: - make_shopify_log(status="Warning", message=e, exception=False) + make_shopify_log(status="Warning", exception=e, rollback=True) def unregister_webhooks(self): session = get_request_session() @@ -67,7 +62,6 @@ class ShopifySettings(Document): self.remove(d) def update_webhook_table(self, method, res): - print('update') self.append("webhooks", { "webhook_id": res['webhook']['id'], "method": method @@ -75,7 +69,6 @@ class ShopifySettings(Document): def get_shopify_url(path, settings): if settings.app_type == "Private": - print(settings.api_key, settings.get_password('password'), settings.shopify_url, path) return 'https://{}:{}@{}/{}'.format(settings.api_key, settings.get_password('password'), settings.shopify_url, path) else: return 'https://{}/{}'.format(settings.shopify_url, path) diff --git a/erpnext/hooks.py b/erpnext/hooks.py index 5c61874f50e..b1855ec9bbb 100644 --- a/erpnext/hooks.py +++ b/erpnext/hooks.py @@ -283,7 +283,6 @@ scheduler_events = { ], "daily": [ "erpnext.stock.reorder_item.reorder_item", - "erpnext.setup.doctype.email_digest.email_digest.send", "erpnext.support.doctype.issue.issue.auto_close_tickets", "erpnext.crm.doctype.opportunity.opportunity.auto_close_opportunity", "erpnext.controllers.accounts_controller.update_invoice_status", @@ -306,6 +305,7 @@ scheduler_events = { "erpnext.crm.doctype.email_campaign.email_campaign.set_email_campaign_status" ], "daily_long": [ + "erpnext.setup.doctype.email_digest.email_digest.send", "erpnext.manufacturing.doctype.bom_update_tool.bom_update_tool.update_latest_price_in_all_boms", "erpnext.hr.doctype.leave_ledger_entry.leave_ledger_entry.process_expired_allocation", "erpnext.hr.utils.generate_leave_encashment" diff --git a/erpnext/hr/doctype/expense_claim/expense_claim_list.js b/erpnext/hr/doctype/expense_claim/expense_claim_list.js index 0e25e666878..6195ad414a1 100644 --- a/erpnext/hr/doctype/expense_claim/expense_claim_list.js +++ b/erpnext/hr/doctype/expense_claim/expense_claim_list.js @@ -2,11 +2,11 @@ frappe.listview_settings['Expense Claim'] = { add_fields: ["total_claimed_amount", "docstatus"], get_indicator: function(doc) { if(doc.status == "Paid") { - return [__("Paid"), "green", "status,=,'Paid'"]; + return [__("Paid"), "green", "status,=,Paid"]; }else if(doc.status == "Unpaid") { - return [__("Unpaid"), "orange"]; + return [__("Unpaid"), "orange", "status,=,Unpaid"]; } else if(doc.status == "Rejected") { - return [__("Rejected"), "grey"]; + return [__("Rejected"), "grey", "status,=,Rejected"]; } } }; diff --git a/erpnext/hr/doctype/leave_application/leave_application.py b/erpnext/hr/doctype/leave_application/leave_application.py index 97de40ffee3..e1e5e8001d8 100755 --- a/erpnext/hr/doctype/leave_application/leave_application.py +++ b/erpnext/hr/doctype/leave_application/leave_application.py @@ -125,7 +125,7 @@ class LeaveApplication(Document): status = "Half Day" if date == self.half_day_date else "On Leave" attendance_name = frappe.db.exists('Attendance', dict(employee = self.employee, - attenance_date = date, docstatus = ('!=', 2))) + attendance_date = date, docstatus = ('!=', 2))) if attendance_name: # update existing attendance, change absent to on leave @@ -503,14 +503,17 @@ def get_leave_allocation_records(employee, date, leave_type=None): def get_pending_leaves_for_period(employee, leave_type, from_date, to_date): ''' Returns leaves that are pending approval ''' - return frappe.db.get_value("Leave Application", + leaves = frappe.get_all("Leave Application", filters={ "employee": employee, "leave_type": leave_type, - "from_date": ("<=", from_date), - "to_date": (">=", to_date), "status": "Open" - }, fieldname=['SUM(total_leave_days)']) or flt(0) + }, + or_filters={ + "from_date": ["between", (from_date, to_date)], + "to_date": ["between", (from_date, to_date)] + }, fields=['SUM(total_leave_days) as leaves'])[0] + return leaves['leaves'] if leaves['leaves'] else 0.0 def get_remaining_leaves(allocation, leaves_taken, date, expiry): ''' Returns minimum leaves remaining after comparing with remaining days for allocation expiry ''' diff --git a/erpnext/hr/doctype/leave_application/leave_application_dashboard.py b/erpnext/hr/doctype/leave_application/leave_application_dashboard.py index 8075b7b5c57..c1d6a6665b6 100644 --- a/erpnext/hr/doctype/leave_application/leave_application_dashboard.py +++ b/erpnext/hr/doctype/leave_application/leave_application_dashboard.py @@ -5,6 +5,12 @@ from frappe import _ def get_data(): return { + 'fieldname': 'leave_application', + 'transactions': [ + { + 'items': ['Attendance'] + } + ], 'reports': [ { 'label': _('Reports'), diff --git a/erpnext/hr/doctype/leave_application/test_leave_application.py b/erpnext/hr/doctype/leave_application/test_leave_application.py index ad141a57482..38ae808f27e 100644 --- a/erpnext/hr/doctype/leave_application/test_leave_application.py +++ b/erpnext/hr/doctype/leave_application/test_leave_application.py @@ -72,7 +72,7 @@ class TestLeaveApplication(unittest.TestCase): application.to_date = "2013-01-05" return application - def test_attendance_creation(self): + def test_overwrite_attendance(self): '''check attendance is automatically created on leave approval''' make_allocation_record() application = self.get_application(_test_records[0]) @@ -82,7 +82,8 @@ class TestLeaveApplication(unittest.TestCase): application.insert() application.submit() - attendance = frappe.get_all('Attendance', ['name', 'status', 'attendance_date'], dict(leave_application = application.name)) + attendance = frappe.get_all('Attendance', ['name', 'status', 'attendance_date'], + dict(attendance_date=('between', ['2018-01-01', '2018-01-03']), docstatus=("!=", 2))) # attendance created for all 3 days self.assertEqual(len(attendance), 3) @@ -95,20 +96,6 @@ class TestLeaveApplication(unittest.TestCase): for d in ('2018-01-01', '2018-01-02', '2018-01-03'): self.assertTrue(getdate(d) in dates) - def test_overwrite_attendance(self): - # employee marked as absent - doc = frappe.new_doc("Attendance") - doc.employee = '_T-Employee-00001' - doc.attendance_date = '2018-01-01' - doc.company = '_Test Company' - doc.status = 'Absent' - doc.flags.ignore_validate = True - doc.insert(ignore_permissions=True) - doc.submit() - - # now check if the status has been updated - self.test_attendance_creation() - def test_block_list(self): self._clear_roles() diff --git a/erpnext/hr/doctype/salary_slip/salary_slip.py b/erpnext/hr/doctype/salary_slip/salary_slip.py index 27a51c30e78..46be4fe2873 100644 --- a/erpnext/hr/doctype/salary_slip/salary_slip.py +++ b/erpnext/hr/doctype/salary_slip/salary_slip.py @@ -5,7 +5,7 @@ from __future__ import unicode_literals import frappe, erpnext import datetime, math -from frappe.utils import add_days, cint, cstr, flt, getdate, rounded, date_diff, money_in_words, getdate +from frappe.utils import add_days, cint, cstr, flt, getdate, rounded, date_diff, money_in_words from frappe.model.naming import make_autoname from frappe import msgprint, _ diff --git a/erpnext/hr/report/employee_leave_balance_summary/employee_leave_balance_summary.py b/erpnext/hr/report/employee_leave_balance_summary/employee_leave_balance_summary.py index 15a5da00f83..777de022387 100644 --- a/erpnext/hr/report/employee_leave_balance_summary/employee_leave_balance_summary.py +++ b/erpnext/hr/report/employee_leave_balance_summary/employee_leave_balance_summary.py @@ -75,7 +75,7 @@ def get_data(filters): leave_approvers = department_approver_map.get(employee.department_name, []).append(employee.leave_approver) - if (len(leave_approvers) and user in leave_approvers) or (user in ["Administrator", employee.user_id]) \ + if (leave_approvers and len(leave_approvers) and user in leave_approvers) or (user in ["Administrator", employee.user_id]) \ or ("HR Manager" in frappe.get_roles(user)): row = frappe._dict({ 'employee': employee.name, @@ -111,10 +111,10 @@ def get_conditions(filters): def get_department_leave_approver_map(department=None): conditions='' if department: - conditions='and department_name = %(department)s or parent_department = %(department)s'%{'department': department} + conditions="and (department_name = '%(department)s' or parent_department = '%(department)s')"%{'department': department} # get current department and all its child - department_list = frappe.db.sql_list(''' SELECT name FROM `tabDepartment` WHERE disabled=0 {0}'''.format(conditions)) #nosec + department_list = frappe.db.sql_list(""" SELECT name FROM `tabDepartment` WHERE disabled=0 {0}""".format(conditions)) #nosec # retrieve approvers list from current department and from its subsequent child departments approver_list = frappe.get_all('Department Approver', filters={ diff --git a/erpnext/maintenance/doctype/maintenance_schedule/maintenance_schedule.js b/erpnext/maintenance/doctype/maintenance_schedule/maintenance_schedule.js index ce95db3bf5a..e940b6050c6 100644 --- a/erpnext/maintenance/doctype/maintenance_schedule/maintenance_schedule.js +++ b/erpnext/maintenance/doctype/maintenance_schedule/maintenance_schedule.js @@ -20,6 +20,12 @@ frappe.ui.form.on('Maintenance Schedule', { frm.set_value({transaction_date: frappe.datetime.get_today()}); } }, + refresh: function(frm) { + setTimeout(() => { + frm.toggle_display('generate_schedule', !(frm.is_new())); + frm.toggle_display('schedule', !(frm.is_new())); + },10); + }, customer: function(frm) { erpnext.utils.get_party_details(frm) }, diff --git a/erpnext/maintenance/doctype/maintenance_schedule/maintenance_schedule.py b/erpnext/maintenance/doctype/maintenance_schedule/maintenance_schedule.py index 3a64e1aa67a..94d85f77ef4 100644 --- a/erpnext/maintenance/doctype/maintenance_schedule/maintenance_schedule.py +++ b/erpnext/maintenance/doctype/maintenance_schedule/maintenance_schedule.py @@ -150,7 +150,7 @@ class MaintenanceSchedule(TransactionBase): elif not d.no_of_visits: throw(_("Please mention no of visits required")) elif not d.sales_person: - throw(_("Please select Incharge Person's name")) + throw(_("Please select a Sales Person for item: {0}".format(d.item_name))) if getdate(d.start_date) >= getdate(d.end_date): throw(_("Start date should be less than end date for Item {0}").format(d.item_code)) diff --git a/erpnext/manufacturing/doctype/blanket_order/blanket_order.js b/erpnext/manufacturing/doctype/blanket_order/blanket_order.js index 0bbf689d4a5..1cd9446c8ba 100644 --- a/erpnext/manufacturing/doctype/blanket_order/blanket_order.js +++ b/erpnext/manufacturing/doctype/blanket_order/blanket_order.js @@ -12,6 +12,7 @@ frappe.ui.form.on('Blanket Order', { }, refresh: function(frm) { + erpnext.hide_company(); if (frm.doc.customer && frm.doc.docstatus === 1) { frm.add_custom_button(__('View Orders'), function() { frappe.set_route('List', 'Sales Order', {blanket_order: frm.doc.name}); @@ -51,11 +52,19 @@ frappe.ui.form.on('Blanket Order', { set_tc_name_filter: function(frm) { if (frm.doc.blanket_order_type === 'Selling') { + frm.set_df_property("customer","reqd", 1); + frm.set_df_property("supplier","reqd", 0); + frm.set_value("supplier", ""); + frm.set_query("tc_name", function() { return { filters: { selling: 1 } }; }); } if (frm.doc.blanket_order_type === 'Purchasing') { + frm.set_df_property("supplier","reqd", 1); + frm.set_df_property("customer","reqd", 0); + frm.set_value("customer", ""); + frm.set_query("tc_name", function() { return { filters: { buying: 1 } }; }); diff --git a/erpnext/manufacturing/doctype/blanket_order/blanket_order.json b/erpnext/manufacturing/doctype/blanket_order/blanket_order.json index 570d435c5f0..260e0b8a736 100644 --- a/erpnext/manufacturing/doctype/blanket_order/blanket_order.json +++ b/erpnext/manufacturing/doctype/blanket_order/blanket_order.json @@ -88,7 +88,8 @@ "fieldname": "company", "fieldtype": "Link", "label": "Company", - "options": "Company" + "options": "Company", + "reqd": 1 }, { "fieldname": "section_break_12", @@ -128,7 +129,7 @@ } ], "is_submittable": 1, - "modified": "2019-06-19 11:59:09.279607", + "modified": "2019-10-16 13:38:32.302316", "modified_by": "Administrator", "module": "Manufacturing", "name": "Blanket Order", diff --git a/erpnext/manufacturing/doctype/blanket_order/blanket_order.py b/erpnext/manufacturing/doctype/blanket_order/blanket_order.py index 822c7fcc1a7..faed707d600 100644 --- a/erpnext/manufacturing/doctype/blanket_order/blanket_order.py +++ b/erpnext/manufacturing/doctype/blanket_order/blanket_order.py @@ -4,13 +4,21 @@ from __future__ import unicode_literals import frappe -from frappe.utils import flt +from frappe import _ +from frappe.utils import flt, getdate from frappe.model.document import Document from frappe.model.mapper import get_mapped_doc from erpnext.stock.doctype.item.item import get_item_defaults class BlanketOrder(Document): + def validate(self): + self.validate_dates() + + def validate_dates(self): + if getdate(self.from_date) > getdate(self.to_date): + frappe.throw(_("From date cannot be greater than To date")) + def update_ordered_qty(self): ref_doctype = "Sales Order" if self.blanket_order_type == "Selling" else "Purchase Order" item_ordered_qty = frappe._dict(frappe.db.sql(""" diff --git a/erpnext/manufacturing/doctype/bom/bom.py b/erpnext/manufacturing/doctype/bom/bom.py index eb01b8c81e0..c849f5b7f22 100644 --- a/erpnext/manufacturing/doctype/bom/bom.py +++ b/erpnext/manufacturing/doctype/bom/bom.py @@ -35,12 +35,15 @@ class BOM(WebsiteGenerator): # name can be BOM/ITEM/001, BOM/ITEM/001-1, BOM-ITEM-001, BOM-ITEM-001-1 # split by item - names = [name.split(self.item)[-1][1:] for name in names] + names = [name.split(self.item, 1) for name in names] + names = [d[-1][1:] for d in filter(lambda x: len(x) > 1 and x[-1], names)] # split by (-) if cancelled - names = [cint(name.split('-')[-1]) for name in names] - - idx = max(names) + 1 + if names: + names = [cint(name.split('-')[-1]) for name in names] + idx = max(names) + 1 + else: + idx = 1 else: idx = 1 @@ -289,7 +292,8 @@ class BOM(WebsiteGenerator): return valuation_rate def manage_default_bom(self): - """ Uncheck others if current one is selected as default, + """ Uncheck others if current one is selected as default or + check the current one as default if it the only bom for the selected item, update default bom in item master """ if self.is_default and self.is_active: @@ -298,6 +302,9 @@ class BOM(WebsiteGenerator): item = frappe.get_doc("Item", self.item) if item.default_bom != self.name: frappe.db.set_value('Item', self.item, 'default_bom', self.name) + elif not frappe.db.exists(dict(doctype='BOM', docstatus=1, item=self.item, is_default=1)) \ + and self.is_active: + frappe.db.set(self, "is_default", 1) else: frappe.db.set(self, "is_default", 0) item = frappe.get_doc("Item", self.item) diff --git a/erpnext/manufacturing/doctype/production_plan/production_plan.js b/erpnext/manufacturing/doctype/production_plan/production_plan.js index 2aeea5827db..4b654b47e67 100644 --- a/erpnext/manufacturing/doctype/production_plan/production_plan.js +++ b/erpnext/manufacturing/doctype/production_plan/production_plan.js @@ -103,7 +103,7 @@ frappe.ui.form.on('Production Plan', { ${__('Reserved Qty for Production: Raw materials quantity to make manufacturing items.')}
{{ item.website_description or item.description or 'No description' }}
- {{ _('More details') }} + {{ _('More details') }}