diff --git a/.github/workflows/docker-release.yml b/.github/workflows/docker-release.yml new file mode 100644 index 00000000000..4b1147e79f9 --- /dev/null +++ b/.github/workflows/docker-release.yml @@ -0,0 +1,14 @@ +name: Trigger Docker build on release +on: + release: + types: [released] +jobs: + curl: + runs-on: ubuntu-latest + container: + image: alpine:latest + steps: + - name: curl + run: | + apk add curl bash + curl -s -X POST -H "Content-Type: application/json" -H "Accept: application/json" -H "Travis-API-Version: 3" -H "Authorization: token ${{ secrets.TRAVIS_CI_TOKEN }}" -d '{"request":{"branch":"master"}}' https://api.travis-ci.com/repo/frappe%2Ffrappe_docker/requests diff --git a/erpnext/__init__.py b/erpnext/__init__.py index 457a8358b20..7f793f90196 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.10.1' +__version__ = '12.11.0' def get_default_company(user=None): '''Get default company for user''' diff --git a/erpnext/accounts/doctype/account/account.py b/erpnext/accounts/doctype/account/account.py index c6de6410ebc..164f120067f 100644 --- a/erpnext/accounts/doctype/account/account.py +++ b/erpnext/accounts/doctype/account/account.py @@ -244,6 +244,8 @@ class Account(NestedSet): super(Account, self).on_trash(True) +@frappe.whitelist() +@frappe.validate_and_sanitize_search_inputs def get_parent_account(doctype, txt, searchfield, start, page_len, filters): return frappe.db.sql("""select name from tabAccount where is_group = 1 and docstatus != 2 and company = %s diff --git a/erpnext/accounts/doctype/account/chart_of_accounts/chart_of_accounts.py b/erpnext/accounts/doctype/account/chart_of_accounts/chart_of_accounts.py index 1bf9196a4f7..0e3b24cda3d 100644 --- a/erpnext/accounts/doctype/account/chart_of_accounts/chart_of_accounts.py +++ b/erpnext/accounts/doctype/account/chart_of_accounts/chart_of_accounts.py @@ -225,7 +225,7 @@ def build_tree_from_json(chart_template, chart_data=None): account['parent_account'] = parent account['expandable'] = True if identify_is_group(child) else False - account['value'] = (child.get('account_number') + ' - ' + account_name) \ + account['value'] = (cstr(child.get('account_number')).strip() + ' - ' + account_name) \ if child.get('account_number') else account_name accounts.append(account) _import_accounts(child, account['value']) diff --git a/erpnext/accounts/doctype/accounts_settings/accounts_settings.json b/erpnext/accounts/doctype/accounts_settings/accounts_settings.json index 4ff42129205..d2f7de3be52 100644 --- a/erpnext/accounts/doctype/accounts_settings/accounts_settings.json +++ b/erpnext/accounts/doctype/accounts_settings/accounts_settings.json @@ -1,210 +1,210 @@ { - "creation": "2013-06-24 15:49:57", - "description": "Settings for Accounts", - "doctype": "DocType", - "document_type": "Other", - "editable_grid": 1, - "engine": "InnoDB", - "field_order": [ - "auto_accounting_for_stock", - "acc_frozen_upto", - "frozen_accounts_modifier", - "determine_address_tax_category_from", - "over_billing_allowance", - "column_break_4", - "credit_controller", - "check_supplier_invoice_uniqueness", - "make_payment_via_journal_entry", - "unlink_payment_on_cancellation_of_invoice", - "unlink_advance_payment_on_cancelation_of_order", - "book_asset_depreciation_entry_automatically", - "allow_cost_center_in_entry_of_bs_account", - "add_taxes_from_item_tax_template", - "automatically_fetch_payment_terms", - "print_settings", - "show_inclusive_tax_in_print", - "column_break_12", - "show_payment_schedule_in_print", - "currency_exchange_section", - "allow_stale", - "stale_days", - "report_settings_sb", - "use_custom_cash_flow" - ], - "fields": [ - { - "default": "1", - "description": "If enabled, the system will post accounting entries for inventory automatically.", - "fieldname": "auto_accounting_for_stock", - "fieldtype": "Check", - "hidden": 1, - "in_list_view": 1, - "label": "Make Accounting Entry For Every Stock Movement" - }, - { - "description": "Accounting entry frozen up to this date, nobody can do / modify entry except role specified below.", - "fieldname": "acc_frozen_upto", - "fieldtype": "Date", - "in_list_view": 1, - "label": "Accounts Frozen Upto" - }, - { - "description": "Users with this role are allowed to set frozen accounts and create / modify accounting entries against frozen accounts", - "fieldname": "frozen_accounts_modifier", - "fieldtype": "Link", - "in_list_view": 1, - "label": "Role Allowed to Set Frozen Accounts & Edit Frozen Entries", - "options": "Role" - }, - { - "default": "Billing Address", - "description": "Address used to determine Tax Category in transactions.", - "fieldname": "determine_address_tax_category_from", - "fieldtype": "Select", - "label": "Determine Address Tax Category From", - "options": "Billing Address\nShipping Address" - }, - { - "fieldname": "column_break_4", - "fieldtype": "Column Break" - }, - { - "description": "Role that is allowed to submit transactions that exceed credit limits set.", - "fieldname": "credit_controller", - "fieldtype": "Link", - "in_list_view": 1, - "label": "Credit Controller", - "options": "Role" - }, - { - "fieldname": "check_supplier_invoice_uniqueness", - "fieldtype": "Check", - "label": "Check Supplier Invoice Number Uniqueness" - }, - { - "fieldname": "make_payment_via_journal_entry", - "fieldtype": "Check", - "label": "Make Payment via Journal Entry" - }, - { - "default": "1", - "fieldname": "unlink_payment_on_cancellation_of_invoice", - "fieldtype": "Check", - "label": "Unlink Payment on Cancellation of Invoice" - }, - { - "default": "1", - "fieldname": "unlink_advance_payment_on_cancelation_of_order", - "fieldtype": "Check", - "label": "Unlink Advance Payment on Cancelation of Order" - }, - { - "default": "1", - "fieldname": "book_asset_depreciation_entry_automatically", - "fieldtype": "Check", - "label": "Book Asset Depreciation Entry Automatically" - }, - { - "fieldname": "allow_cost_center_in_entry_of_bs_account", - "fieldtype": "Check", - "label": "Allow Cost Center In Entry of Balance Sheet Account" - }, - { - "default": "1", - "fieldname": "add_taxes_from_item_tax_template", - "fieldtype": "Check", - "label": "Automatically Add Taxes and Charges from Item Tax Template" - }, - { - "fieldname": "print_settings", - "fieldtype": "Section Break", - "label": "Print Settings" - }, - { - "fieldname": "show_inclusive_tax_in_print", - "fieldtype": "Check", - "label": "Show Inclusive Tax In Print" - }, - { - "fieldname": "column_break_12", - "fieldtype": "Column Break" - }, - { - "fieldname": "show_payment_schedule_in_print", - "fieldtype": "Check", - "label": "Show Payment Schedule in Print" - }, - { - "fieldname": "currency_exchange_section", - "fieldtype": "Section Break", - "label": "Currency Exchange Settings" - }, - { - "default": "1", - "fieldname": "allow_stale", - "fieldtype": "Check", - "in_list_view": 1, - "label": "Allow Stale Exchange Rates" - }, - { - "default": "1", - "depends_on": "eval:doc.allow_stale==0", - "fieldname": "stale_days", - "fieldtype": "Int", - "label": "Stale Days" - }, - { - "fieldname": "report_settings_sb", - "fieldtype": "Section Break", - "label": "Report Settings" - }, - { - "default": "0", - "description": "Only select if you have setup Cash Flow Mapper documents", - "fieldname": "use_custom_cash_flow", - "fieldtype": "Check", - "label": "Use Custom Cash Flow Format" - }, - { - "fieldname": "automatically_fetch_payment_terms", - "fieldtype": "Check", - "label": "Automatically Fetch Payment Terms" - }, - { - "description": "Percentage you are allowed to bill more against the amount ordered. For example: If the order value is $100 for an item and tolerance is set as 10% then you are allowed to bill for $110.", - "fieldname": "over_billing_allowance", - "fieldtype": "Currency", - "label": "Over Billing Allowance (%)" - } - ], - "icon": "icon-cog", - "idx": 1, - "issingle": 1, - "modified": "2019-07-04 18:20:55.789946", - "modified_by": "Administrator", - "module": "Accounts", - "name": "Accounts Settings", - "owner": "Administrator", - "permissions": [ - { - "create": 1, - "email": 1, - "print": 1, - "read": 1, - "role": "Accounts Manager", - "share": 1, - "write": 1 - }, - { - "read": 1, - "role": "Sales User" - }, - { - "read": 1, - "role": "Purchase User" - } - ], - "quick_entry": 1, - "sort_order": "ASC", - "track_changes": 1 + "creation": "2013-06-24 15:49:57", + "description": "Settings for Accounts", + "doctype": "DocType", + "document_type": "Other", + "editable_grid": 1, + "engine": "InnoDB", + "field_order": [ + "auto_accounting_for_stock", + "acc_frozen_upto", + "frozen_accounts_modifier", + "determine_address_tax_category_from", + "over_billing_allowance", + "column_break_4", + "credit_controller", + "check_supplier_invoice_uniqueness", + "make_payment_via_journal_entry", + "unlink_payment_on_cancellation_of_invoice", + "unlink_advance_payment_on_cancelation_of_order", + "book_asset_depreciation_entry_automatically", + "add_taxes_from_item_tax_template", + "automatically_fetch_payment_terms", + "print_settings", + "show_inclusive_tax_in_print", + "column_break_12", + "show_payment_schedule_in_print", + "currency_exchange_section", + "allow_stale", + "stale_days", + "report_settings_sb", + "use_custom_cash_flow" + ], + "fields": [ + { + "default": "1", + "description": "If enabled, the system will post accounting entries for inventory automatically.", + "fieldname": "auto_accounting_for_stock", + "fieldtype": "Check", + "hidden": 1, + "in_list_view": 1, + "label": "Make Accounting Entry For Every Stock Movement" + }, + { + "description": "Accounting entry frozen up to this date, nobody can do / modify entry except role specified below.", + "fieldname": "acc_frozen_upto", + "fieldtype": "Date", + "in_list_view": 1, + "label": "Accounts Frozen Upto" + }, + { + "description": "Users with this role are allowed to set frozen accounts and create / modify accounting entries against frozen accounts", + "fieldname": "frozen_accounts_modifier", + "fieldtype": "Link", + "in_list_view": 1, + "label": "Role Allowed to Set Frozen Accounts & Edit Frozen Entries", + "options": "Role" + }, + { + "default": "Billing Address", + "description": "Address used to determine Tax Category in transactions.", + "fieldname": "determine_address_tax_category_from", + "fieldtype": "Select", + "label": "Determine Address Tax Category From", + "options": "Billing Address\nShipping Address" + }, + { + "fieldname": "column_break_4", + "fieldtype": "Column Break" + }, + { + "description": "Role that is allowed to submit transactions that exceed credit limits set.", + "fieldname": "credit_controller", + "fieldtype": "Link", + "in_list_view": 1, + "label": "Credit Controller", + "options": "Role" + }, + { + "default": "0", + "fieldname": "check_supplier_invoice_uniqueness", + "fieldtype": "Check", + "label": "Check Supplier Invoice Number Uniqueness" + }, + { + "default": "0", + "fieldname": "make_payment_via_journal_entry", + "fieldtype": "Check", + "label": "Make Payment via Journal Entry" + }, + { + "default": "1", + "fieldname": "unlink_payment_on_cancellation_of_invoice", + "fieldtype": "Check", + "label": "Unlink Payment on Cancellation of Invoice" + }, + { + "default": "1", + "fieldname": "unlink_advance_payment_on_cancelation_of_order", + "fieldtype": "Check", + "label": "Unlink Advance Payment on Cancelation of Order" + }, + { + "default": "1", + "fieldname": "book_asset_depreciation_entry_automatically", + "fieldtype": "Check", + "label": "Book Asset Depreciation Entry Automatically" + }, + { + "default": "1", + "fieldname": "add_taxes_from_item_tax_template", + "fieldtype": "Check", + "label": "Automatically Add Taxes and Charges from Item Tax Template" + }, + { + "fieldname": "print_settings", + "fieldtype": "Section Break", + "label": "Print Settings" + }, + { + "default": "0", + "fieldname": "show_inclusive_tax_in_print", + "fieldtype": "Check", + "label": "Show Inclusive Tax In Print" + }, + { + "fieldname": "column_break_12", + "fieldtype": "Column Break" + }, + { + "default": "0", + "fieldname": "show_payment_schedule_in_print", + "fieldtype": "Check", + "label": "Show Payment Schedule in Print" + }, + { + "fieldname": "currency_exchange_section", + "fieldtype": "Section Break", + "label": "Currency Exchange Settings" + }, + { + "default": "1", + "fieldname": "allow_stale", + "fieldtype": "Check", + "in_list_view": 1, + "label": "Allow Stale Exchange Rates" + }, + { + "default": "1", + "depends_on": "eval:doc.allow_stale==0", + "fieldname": "stale_days", + "fieldtype": "Int", + "label": "Stale Days" + }, + { + "fieldname": "report_settings_sb", + "fieldtype": "Section Break", + "label": "Report Settings" + }, + { + "default": "0", + "description": "Only select if you have setup Cash Flow Mapper documents", + "fieldname": "use_custom_cash_flow", + "fieldtype": "Check", + "label": "Use Custom Cash Flow Format" + }, + { + "default": "0", + "fieldname": "automatically_fetch_payment_terms", + "fieldtype": "Check", + "label": "Automatically Fetch Payment Terms" + }, + { + "description": "Percentage you are allowed to bill more against the amount ordered. For example: If the order value is $100 for an item and tolerance is set as 10% then you are allowed to bill for $110.", + "fieldname": "over_billing_allowance", + "fieldtype": "Currency", + "label": "Over Billing Allowance (%)" } + ], + "icon": "icon-cog", + "idx": 1, + "issingle": 1, + "modified": "2020-03-11 13:09:26.235848", + "modified_by": "Administrator", + "module": "Accounts", + "name": "Accounts Settings", + "owner": "Administrator", + "permissions": [ + { + "create": 1, + "email": 1, + "print": 1, + "read": 1, + "role": "Accounts Manager", + "share": 1, + "write": 1 + }, + { + "read": 1, + "role": "Sales User" + }, + { + "read": 1, + "role": "Purchase User" + } + ], + "quick_entry": 1, + "sort_field": "modified", + "sort_order": "ASC", + "track_changes": 1 +} \ No newline at end of file diff --git a/erpnext/accounts/doctype/accounts_settings/accounts_settings.py b/erpnext/accounts/doctype/accounts_settings/accounts_settings.py index 2473d715d0d..5593466fc2b 100644 --- a/erpnext/accounts/doctype/accounts_settings/accounts_settings.py +++ b/erpnext/accounts/doctype/accounts_settings/accounts_settings.py @@ -20,7 +20,6 @@ class AccountsSettings(Document): self.validate_stale_days() self.enable_payment_schedule_in_print() - self.enable_fields_for_cost_center_settings() def validate_stale_days(self): if not self.allow_stale and cint(self.stale_days) <= 0: @@ -33,8 +32,3 @@ class AccountsSettings(Document): for doctype in ("Sales Order", "Sales Invoice", "Purchase Order", "Purchase Invoice"): make_property_setter(doctype, "due_date", "print_hide", show_in_print, "Check") make_property_setter(doctype, "payment_schedule", "print_hide", 0 if show_in_print else 1, "Check") - - def enable_fields_for_cost_center_settings(self): - show_field = 0 if cint(self.allow_cost_center_in_entry_of_bs_account) else 1 - for doctype in ("Sales Invoice", "Purchase Invoice", "Payment Entry"): - make_property_setter(doctype, "cost_center", "hidden", show_field, "Check") diff --git a/erpnext/accounts/doctype/bank_reconciliation/bank_reconciliation.py b/erpnext/accounts/doctype/bank_reconciliation/bank_reconciliation.py index 48fd154a4db..0b33b2e5cd7 100644 --- a/erpnext/accounts/doctype/bank_reconciliation/bank_reconciliation.py +++ b/erpnext/accounts/doctype/bank_reconciliation/bank_reconciliation.py @@ -60,12 +60,13 @@ class BankReconciliation(Document): """.format(condition=condition), {"account": self.account, "from":self.from_date, "to": self.to_date, "bank_account": self.bank_account}, as_dict=1) - pos_entries = [] + + pos_sales_invoices, pos_purchase_invoices = [], [] if self.include_pos_transactions: - pos_entries = frappe.db.sql(""" + pos_sales_invoices = frappe.db.sql(""" select "Sales Invoice Payment" as payment_document, sip.name as payment_entry, sip.amount as debit, - si.posting_date, si.debit_to as against_account, sip.clearance_date, + si.posting_date, si.customer as against_account, sip.clearance_date, account.account_currency, 0 as credit from `tabSales Invoice Payment` sip, `tabSales Invoice` si, `tabAccount` account where @@ -75,7 +76,20 @@ class BankReconciliation(Document): si.posting_date ASC, si.name DESC """, {"account":self.account, "from":self.from_date, "to":self.to_date}, as_dict=1) - entries = sorted(list(payment_entries)+list(journal_entries+list(pos_entries)), + pos_purchase_invoices = frappe.db.sql(""" + select + "Purchase Invoice" as payment_document, pi.name as payment_entry, pi.paid_amount as credit, + pi.posting_date, pi.supplier as against_account, pi.clearance_date, + account.account_currency, 0 as debit + from `tabPurchase Invoice` pi, `tabAccount` account + where + pi.cash_bank_account=%(account)s and pi.docstatus=1 and account.name = pi.cash_bank_account + and pi.posting_date >= %(from)s and pi.posting_date <= %(to)s + order by + pi.posting_date ASC, pi.name DESC + """, {"account": self.account, "from": self.from_date, "to": self.to_date}, as_dict=1) + + entries = sorted(list(payment_entries) + list(journal_entries + list(pos_sales_invoices) + list(pos_purchase_invoices)), key=lambda k: k['posting_date'] or getdate(nowdate())) self.set('payment_entries', []) diff --git a/erpnext/accounts/doctype/cost_center/cost_center.js b/erpnext/accounts/doctype/cost_center/cost_center.js index 9e2f6eed3b6..0672bf1910e 100644 --- a/erpnext/accounts/doctype/cost_center/cost_center.js +++ b/erpnext/accounts/doctype/cost_center/cost_center.js @@ -60,8 +60,13 @@ frappe.ui.form.on('Cost Center', { "label": "Cost Center Number", "fieldname": "cost_center_number", "fieldtype": "Data", - "reqd": 1, "default": frm.doc.cost_center_number + }, + { + "label": __("Merge with existing"), + "fieldname": "merge", + "fieldtype": "Check", + "default": 0 } ], primary_action: function() { @@ -76,8 +81,9 @@ frappe.ui.form.on('Cost Center', { args: { docname: frm.doc.name, cost_center_name: data.cost_center_name, - cost_center_number: data.cost_center_number, - company: frm.doc.company + cost_center_number: cstr(data.cost_center_number), + company: frm.doc.company, + merge: data.merge }, callback: function(r) { frappe.dom.unfreeze(); diff --git a/erpnext/accounts/doctype/cost_center/cost_center.json b/erpnext/accounts/doctype/cost_center/cost_center.json index 5013c92a327..fa2fb51d061 100644 --- a/erpnext/accounts/doctype/cost_center/cost_center.json +++ b/erpnext/accounts/doctype/cost_center/cost_center.json @@ -125,7 +125,7 @@ "idx": 1, "is_tree": 1, "links": [], - "modified": "2020-04-29 16:09:30.025214", + "modified": "2020-06-12 16:09:30.025214", "modified_by": "Administrator", "module": "Accounts", "name": "Cost Center", diff --git a/erpnext/accounts/doctype/gl_entry/gl_entry.py b/erpnext/accounts/doctype/gl_entry/gl_entry.py index 7358a31469b..cf1ad6eab6f 100644 --- a/erpnext/accounts/doctype/gl_entry/gl_entry.py +++ b/erpnext/accounts/doctype/gl_entry/gl_entry.py @@ -4,7 +4,7 @@ from __future__ import unicode_literals import frappe, erpnext from frappe import _ -from frappe.utils import flt, fmt_money, getdate, formatdate +from frappe.utils import flt, fmt_money, getdate, formatdate, cint from frappe.model.document import Document from frappe.model.naming import set_name_from_naming_options from frappe.model.meta import get_field_precision @@ -75,12 +75,6 @@ class GLEntry(Document): if not self.cost_center and self.voucher_type != 'Period Closing Voucher': frappe.throw(_("{0} {1}: Cost Center is required for 'Profit and Loss' account {2}. Please set up a default Cost Center for the Company.") .format(self.voucher_type, self.voucher_no, self.account)) - else: - from erpnext.accounts.utils import get_allow_cost_center_in_entry_of_bs_account - if not get_allow_cost_center_in_entry_of_bs_account() and self.cost_center: - self.cost_center = None - if self.project: - self.project = None def validate_dimensions_for_pl_and_bs(self): @@ -137,10 +131,17 @@ class GLEntry(Document): return self.cost_center_company[self.cost_center] + def _check_is_group(): + return cint(frappe.get_cached_value('Cost Center', self.cost_center, 'is_group')) + if self.cost_center and _get_cost_center_company() != self.company: frappe.throw(_("{0} {1}: Cost Center {2} does not belong to Company {3}") .format(self.voucher_type, self.voucher_no, self.cost_center, self.company)) + if self.cost_center and _check_is_group(): + frappe.throw(_("""{0} {1}: Cost Center {2} is a group cost center and group cost centers cannot + be used in transactions""").format(self.voucher_type, self.voucher_no, frappe.bold(self.cost_center))) + def validate_party(self): validate_party_frozen_disabled(self.party_type, self.party) diff --git a/erpnext/accounts/doctype/invoice_discounting/invoice_discounting.py b/erpnext/accounts/doctype/invoice_discounting/invoice_discounting.py index 594b4d4a223..8083b21f759 100644 --- a/erpnext/accounts/doctype/invoice_discounting/invoice_discounting.py +++ b/erpnext/accounts/doctype/invoice_discounting/invoice_discounting.py @@ -3,7 +3,7 @@ # For license information, please see license.txt from __future__ import unicode_literals -import frappe, json +import frappe, json, erpnext from frappe import _ from frappe.utils import flt, getdate, nowdate, add_days from erpnext.controllers.accounts_controller import AccountsController @@ -134,16 +134,19 @@ class InvoiceDiscounting(AccountsController): je.append("accounts", { "account": self.bank_account, "debit_in_account_currency": flt(self.total_amount) - flt(self.bank_charges), + "cost_center": erpnext.get_default_cost_center(self.company) }) je.append("accounts", { "account": self.bank_charges_account, - "debit_in_account_currency": flt(self.bank_charges) + "debit_in_account_currency": flt(self.bank_charges), + "cost_center": erpnext.get_default_cost_center(self.company) }) je.append("accounts", { "account": self.short_term_loan, "credit_in_account_currency": flt(self.total_amount), + "cost_center": erpnext.get_default_cost_center(self.company), "reference_type": "Invoice Discounting", "reference_name": self.name }) @@ -151,6 +154,7 @@ class InvoiceDiscounting(AccountsController): je.append("accounts", { "account": self.accounts_receivable_discounted, "debit_in_account_currency": flt(d.outstanding_amount), + "cost_center": erpnext.get_default_cost_center(self.company), "reference_type": "Invoice Discounting", "reference_name": self.name, "party_type": "Customer", @@ -160,6 +164,7 @@ class InvoiceDiscounting(AccountsController): je.append("accounts", { "account": self.accounts_receivable_credit, "credit_in_account_currency": flt(d.outstanding_amount), + "cost_center": erpnext.get_default_cost_center(self.company), "reference_type": "Invoice Discounting", "reference_name": self.name, "party_type": "Customer", @@ -177,13 +182,15 @@ class InvoiceDiscounting(AccountsController): je.append("accounts", { "account": self.short_term_loan, "debit_in_account_currency": flt(self.total_amount), + "cost_center": erpnext.get_default_cost_center(self.company), "reference_type": "Invoice Discounting", "reference_name": self.name, }) je.append("accounts", { "account": self.bank_account, - "credit_in_account_currency": flt(self.total_amount) + "credit_in_account_currency": flt(self.total_amount), + "cost_center": erpnext.get_default_cost_center(self.company) }) if getdate(self.loan_end_date) > getdate(nowdate()): @@ -193,6 +200,7 @@ class InvoiceDiscounting(AccountsController): je.append("accounts", { "account": self.accounts_receivable_discounted, "credit_in_account_currency": flt(outstanding_amount), + "cost_center": erpnext.get_default_cost_center(self.company), "reference_type": "Invoice Discounting", "reference_name": self.name, "party_type": "Customer", @@ -202,6 +210,7 @@ class InvoiceDiscounting(AccountsController): je.append("accounts", { "account": self.accounts_receivable_unpaid, "debit_in_account_currency": flt(outstanding_amount), + "cost_center": erpnext.get_default_cost_center(self.company), "reference_type": "Invoice Discounting", "reference_name": self.name, "party_type": "Customer", diff --git a/erpnext/accounts/doctype/item_tax_template/test_records.json b/erpnext/accounts/doctype/item_tax_template/test_records.json index db540e86aac..4d9537d4b89 100644 --- a/erpnext/accounts/doctype/item_tax_template/test_records.json +++ b/erpnext/accounts/doctype/item_tax_template/test_records.json @@ -2,6 +2,7 @@ { "doctype": "Item Tax Template", "title": "_Test Account Excise Duty @ 10", + "company": "_Test Company", "taxes": [ { "doctype": "Item Tax Template Detail", @@ -14,6 +15,7 @@ { "doctype": "Item Tax Template", "title": "_Test Account Excise Duty @ 12", + "company": "_Test Company", "taxes": [ { "doctype": "Item Tax Template Detail", @@ -26,6 +28,7 @@ { "doctype": "Item Tax Template", "title": "_Test Account Excise Duty @ 15", + "company": "_Test Company", "taxes": [ { "doctype": "Item Tax Template Detail", @@ -38,6 +41,7 @@ { "doctype": "Item Tax Template", "title": "_Test Account Excise Duty @ 20", + "company": "_Test Company", "taxes": [ { "doctype": "Item Tax Template Detail", @@ -50,6 +54,7 @@ { "doctype": "Item Tax Template", "title": "_Test Item Tax Template 1", + "company": "_Test Company", "taxes": [ { "doctype": "Item Tax Template Detail", diff --git a/erpnext/accounts/doctype/journal_entry/journal_entry.py b/erpnext/accounts/doctype/journal_entry/journal_entry.py index f367f952b8a..d8a045b4db7 100644 --- a/erpnext/accounts/doctype/journal_entry/journal_entry.py +++ b/erpnext/accounts/doctype/journal_entry/journal_entry.py @@ -836,13 +836,34 @@ def get_opening_accounts(company): return [{"account": a, "balance": get_balance_on(a)} for a in accounts] +@frappe.whitelist() +@frappe.validate_and_sanitize_search_inputs def get_against_jv(doctype, txt, searchfield, start, page_len, filters): - return frappe.db.sql("""select jv.name, jv.posting_date, jv.user_remark - from `tabJournal Entry` jv, `tabJournal Entry Account` jv_detail - where jv_detail.parent = jv.name and jv_detail.account = %s and ifnull(jv_detail.party, '') = %s - and (jv_detail.reference_type is null or jv_detail.reference_type = '') - and jv.docstatus = 1 and jv.`{0}` like %s order by jv.name desc limit %s, %s""".format(searchfield), - (filters.get("account"), cstr(filters.get("party")), "%{0}%".format(txt), start, page_len)) + if not frappe.db.has_column('Journal Entry', searchfield): + return [] + + return frappe.db.sql(""" + SELECT jv.name, jv.posting_date, jv.user_remark + FROM `tabJournal Entry` jv, `tabJournal Entry Account` jv_detail + WHERE jv_detail.parent = jv.name + AND jv_detail.account = %(account)s + AND IFNULL(jv_detail.party, '') = %(party)s + AND ( + jv_detail.reference_type IS NULL + OR jv_detail.reference_type = '' + ) + AND jv.docstatus = 1 + AND jv.`{0}` LIKE %(txt)s + ORDER BY jv.name DESC + LIMIT %(offset)s, %(limit)s + """.format(searchfield), dict( + account=filters.get("account"), + party=cstr(filters.get("party")), + txt="%{0}%".format(txt), + offset=start, + limit=page_len + ) + ) @frappe.whitelist() diff --git a/erpnext/accounts/doctype/journal_entry/test_journal_entry.py b/erpnext/accounts/doctype/journal_entry/test_journal_entry.py index 6996c775b32..23ad1eef14c 100644 --- a/erpnext/accounts/doctype/journal_entry/test_journal_entry.py +++ b/erpnext/accounts/doctype/journal_entry/test_journal_entry.py @@ -204,11 +204,8 @@ class TestJournalEntry(unittest.TestCase): self.assertEqual(jv.inter_company_journal_entry_reference, "") self.assertEqual(jv1.inter_company_journal_entry_reference, "") - def test_jv_for_enable_allow_cost_center_in_entry_of_bs_account(self): + def test_jv_with_cost_centre(self): from erpnext.accounts.doctype.cost_center.test_cost_center import create_cost_center - accounts_settings = frappe.get_doc('Accounts Settings', 'Accounts Settings') - accounts_settings.allow_cost_center_in_entry_of_bs_account = 1 - accounts_settings.save() cost_center = "_Test Cost Center for BS Account - _TC" create_cost_center(cost_center_name="_Test Cost Center for BS Account", company="_Test Company") jv = make_journal_entry("_Test Cash - _TC", "_Test Bank - _TC", 100, cost_center = cost_center, save=False) @@ -237,15 +234,45 @@ class TestJournalEntry(unittest.TestCase): for gle in gl_entries: self.assertEqual(expected_values[gle.account]["cost_center"], gle.cost_center) - accounts_settings.allow_cost_center_in_entry_of_bs_account = 0 - accounts_settings.save() + def test_jv_with_project(self): + from erpnext.projects.doctype.project.test_project import make_project + project = make_project({ + 'project_name': 'Journal Entry Project', + 'project_template_name': 'Test Project Template', + 'start_date': '2020-01-01' + }) - def test_jv_account_and_party_balance_for_enable_allow_cost_center_in_entry_of_bs_account(self): + jv = make_journal_entry("_Test Cash - _TC", "_Test Bank - _TC", 100, save=False) + for d in jv.accounts: + d.project = project.project_name + jv.voucher_type = "Bank Entry" + jv.multi_currency = 0 + jv.cheque_no = "112233" + jv.cheque_date = nowdate() + jv.insert() + jv.submit() + + expected_values = { + "_Test Cash - _TC": { + "project": project.project_name + }, + "_Test Bank - _TC": { + "project": project.project_name + } + } + + gl_entries = frappe.db.sql("""select account, project, debit, credit + from `tabGL Entry` where voucher_type='Journal Entry' and voucher_no=%s + order by account asc""", jv.name, as_dict=1) + + self.assertTrue(gl_entries) + + for gle in gl_entries: + self.assertEqual(expected_values[gle.account]["project"], gle.project) + + def test_jv_account_and_party_balance_with_cost_centre(self): from erpnext.accounts.doctype.cost_center.test_cost_center import create_cost_center from erpnext.accounts.utils import get_balance_on - accounts_settings = frappe.get_doc('Accounts Settings', 'Accounts Settings') - accounts_settings.allow_cost_center_in_entry_of_bs_account = 1 - accounts_settings.save() cost_center = "_Test Cost Center for BS Account - _TC" create_cost_center(cost_center_name="_Test Cost Center for BS Account", company="_Test Company") jv = make_journal_entry("_Test Cash - _TC", "_Test Bank - _TC", 100, cost_center = cost_center, save=False) @@ -261,9 +288,6 @@ class TestJournalEntry(unittest.TestCase): account_balance = get_balance_on(account="_Test Bank - _TC", cost_center=cost_center) self.assertEqual(expected_account_balance, account_balance) - accounts_settings.allow_cost_center_in_entry_of_bs_account = 0 - accounts_settings.save() - def make_journal_entry(account1, account2, amount, cost_center=None, posting_date=None, exchange_rate=1, save=True, submit=False, project=None): if not cost_center: cost_center = "_Test Cost Center - _TC" diff --git a/erpnext/accounts/doctype/opening_invoice_creation_tool/opening_invoice_creation_tool.py b/erpnext/accounts/doctype/opening_invoice_creation_tool/opening_invoice_creation_tool.py index 54464e71c4e..a53417eedf9 100644 --- a/erpnext/accounts/doctype/opening_invoice_creation_tool/opening_invoice_creation_tool.py +++ b/erpnext/accounts/doctype/opening_invoice_creation_tool/opening_invoice_creation_tool.py @@ -68,6 +68,9 @@ class OpeningInvoiceCreationTool(Document): if not self.company: frappe.throw(_("Please select the Company")) + company_details = frappe.get_cached_value('Company', self.company, + ["default_currency", "default_letter_head"], as_dict=1) or {} + for row in self.invoices: if not row.qty: row.qty = 1.0 @@ -99,6 +102,12 @@ class OpeningInvoiceCreationTool(Document): if not args: continue + if company_details: + args.update({ + "currency": company_details.get("default_currency"), + "letter_head": company_details.get("default_letter_head") + }) + doc = frappe.get_doc(args).insert() doc.submit() names.append(doc.name) @@ -172,8 +181,7 @@ class OpeningInvoiceCreationTool(Document): "due_date": row.due_date, "posting_date": row.posting_date, frappe.scrub(party_type): row.party, - "doctype": "Sales Invoice" if self.invoice_type == "Sales" else "Purchase Invoice", - "currency": frappe.get_cached_value('Company', self.company, "default_currency") + "doctype": "Sales Invoice" if self.invoice_type == "Sales" else "Purchase Invoice" }) accounting_dimension = get_accounting_dimensions() diff --git a/erpnext/accounts/doctype/payment_entry/payment_entry.js b/erpnext/accounts/doctype/payment_entry/payment_entry.js index 74be4c4ef0f..70c485133f0 100644 --- a/erpnext/accounts/doctype/payment_entry/payment_entry.js +++ b/erpnext/accounts/doctype/payment_entry/payment_entry.js @@ -25,7 +25,7 @@ frappe.ui.form.on('Payment Entry', { }); frm.set_query("party_type", function() { return{ - "filters": { + filters: { "name": ["in", Object.keys(frappe.boot.party_account_types)], } } @@ -33,14 +33,16 @@ frappe.ui.form.on('Payment Entry', { frm.set_query("party_bank_account", function() { return { filters: { - "is_company_account":0 + is_company_account: 0, + party_type: frm.doc.party_type, + party: frm.doc.party } } }); frm.set_query("bank_account", function() { return { filters: { - "is_company_account":1 + is_company_account: 1 } } }); @@ -326,7 +328,7 @@ frappe.ui.form.on('Payment Entry', { () => { frm.set_party_account_based_on_party = false; if (r.message.bank_account) { - frm.set_value("party_bank_account", r.message.bank_account); + frm.set_value("bank_account", r.message.bank_account); } } ]); diff --git a/erpnext/accounts/doctype/payment_entry/payment_entry.py b/erpnext/accounts/doctype/payment_entry/payment_entry.py index 1c5bea6cf15..c17f775a4e7 100644 --- a/erpnext/accounts/doctype/payment_entry/payment_entry.py +++ b/erpnext/accounts/doctype/payment_entry/payment_entry.py @@ -6,7 +6,7 @@ from __future__ import unicode_literals import frappe, erpnext, json from frappe import _, scrub, ValidationError from frappe.utils import flt, comma_or, nowdate, getdate -from erpnext.accounts.utils import get_outstanding_invoices, get_account_currency, get_balance_on, get_allow_cost_center_in_entry_of_bs_account +from erpnext.accounts.utils import get_outstanding_invoices, get_account_currency, get_balance_on from erpnext.accounts.party import get_party_account from erpnext.accounts.doctype.journal_entry.journal_entry import get_default_bank_cash_account from erpnext.setup.utils import get_exchange_rate @@ -657,7 +657,7 @@ def get_outstanding_reference_documents(args): .format(frappe.db.escape(args["voucher_type"]), frappe.db.escape(args["voucher_no"])) # Add cost center condition - if args.get("cost_center") and get_allow_cost_center_in_entry_of_bs_account(): + if args.get("cost_center"): condition += " and cost_center='%s'" % args.get("cost_center") date_fields_dict = { diff --git a/erpnext/accounts/doctype/payment_entry/test_payment_entry.py b/erpnext/accounts/doctype/payment_entry/test_payment_entry.py index 756cc8ec547..9e2937ad436 100644 --- a/erpnext/accounts/doctype/payment_entry/test_payment_entry.py +++ b/erpnext/accounts/doctype/payment_entry/test_payment_entry.py @@ -462,11 +462,8 @@ class TestPaymentEntry(unittest.TestCase): outstanding_amount = flt(frappe.db.get_value("Sales Invoice", si.name, "outstanding_amount")) self.assertEqual(outstanding_amount, 0) - def test_payment_entry_against_sales_invoice_for_enable_allow_cost_center_in_entry_of_bs_account(self): + def test_payment_entry_against_sales_invoice_with_cost_centre(self): from erpnext.accounts.doctype.cost_center.test_cost_center import create_cost_center - accounts_settings = frappe.get_doc('Accounts Settings', 'Accounts Settings') - accounts_settings.allow_cost_center_in_entry_of_bs_account = 1 - accounts_settings.save() cost_center = "_Test Cost Center for BS Account - _TC" create_cost_center(cost_center_name="_Test Cost Center for BS Account", company="_Test Company") @@ -501,39 +498,8 @@ class TestPaymentEntry(unittest.TestCase): for gle in gl_entries: self.assertEqual(expected_values[gle.account]["cost_center"], gle.cost_center) - accounts_settings.allow_cost_center_in_entry_of_bs_account = 0 - accounts_settings.save() - - def test_payment_entry_against_sales_invoice_for_disable_allow_cost_center_in_entry_of_bs_account(self): - accounts_settings = frappe.get_doc('Accounts Settings', 'Accounts Settings') - accounts_settings.allow_cost_center_in_entry_of_bs_account = 0 - accounts_settings.save() - si = create_sales_invoice(debit_to="Debtors - _TC") - - pe = get_payment_entry("Sales Invoice", si.name, bank_account="_Test Bank - _TC") - - pe.reference_no = "112211-2" - pe.reference_date = nowdate() - pe.paid_to = "_Test Bank - _TC" - pe.paid_amount = si.grand_total - pe.insert() - pe.submit() - - gl_entries = frappe.db.sql("""select account, cost_center, account_currency, debit, credit, - debit_in_account_currency, credit_in_account_currency - from `tabGL Entry` where voucher_type='Payment Entry' and voucher_no=%s - order by account asc""", pe.name, as_dict=1) - - self.assertTrue(gl_entries) - - for gle in gl_entries: - self.assertEqual(gle.cost_center, None) - - def test_payment_entry_against_purchase_invoice_for_enable_allow_cost_center_in_entry_of_bs_account(self): + def test_payment_entry_against_purchase_invoice_with_cost_center(self): from erpnext.accounts.doctype.cost_center.test_cost_center import create_cost_center - accounts_settings = frappe.get_doc('Accounts Settings', 'Accounts Settings') - accounts_settings.allow_cost_center_in_entry_of_bs_account = 1 - accounts_settings.save() cost_center = "_Test Cost Center for BS Account - _TC" create_cost_center(cost_center_name="_Test Cost Center for BS Account", company="_Test Company") @@ -568,40 +534,9 @@ class TestPaymentEntry(unittest.TestCase): for gle in gl_entries: self.assertEqual(expected_values[gle.account]["cost_center"], gle.cost_center) - accounts_settings.allow_cost_center_in_entry_of_bs_account = 0 - accounts_settings.save() - - def test_payment_entry_against_purchase_invoice_for_disable_allow_cost_center_in_entry_of_bs_account(self): - accounts_settings = frappe.get_doc('Accounts Settings', 'Accounts Settings') - accounts_settings.allow_cost_center_in_entry_of_bs_account = 0 - accounts_settings.save() - pi = make_purchase_invoice(credit_to="Creditors - _TC") - - pe = get_payment_entry("Purchase Invoice", pi.name, bank_account="_Test Bank - _TC") - - pe.reference_no = "112222-2" - pe.reference_date = nowdate() - pe.paid_from = "_Test Bank - _TC" - pe.paid_amount = pi.grand_total - pe.insert() - pe.submit() - - gl_entries = frappe.db.sql("""select account, cost_center, account_currency, debit, credit, - debit_in_account_currency, credit_in_account_currency - from `tabGL Entry` where voucher_type='Payment Entry' and voucher_no=%s - order by account asc""", pe.name, as_dict=1) - - self.assertTrue(gl_entries) - - for gle in gl_entries: - self.assertEqual(gle.cost_center, None) - - def test_payment_entry_account_and_party_balance_for_enable_allow_cost_center_in_entry_of_bs_account(self): + def test_payment_entry_account_and_party_balance_with_cost_center(self): from erpnext.accounts.doctype.cost_center.test_cost_center import create_cost_center from erpnext.accounts.utils import get_balance_on - accounts_settings = frappe.get_doc('Accounts Settings', 'Accounts Settings') - accounts_settings.allow_cost_center_in_entry_of_bs_account = 1 - accounts_settings.save() cost_center = "_Test Cost Center for BS Account - _TC" create_cost_center(cost_center_name="_Test Cost Center for BS Account", company="_Test Company") @@ -632,9 +567,6 @@ class TestPaymentEntry(unittest.TestCase): self.assertEqual(expected_party_balance, party_balance) self.assertEqual(expected_party_account_balance, party_account_balance) - accounts_settings.allow_cost_center_in_entry_of_bs_account = 0 - accounts_settings.save() - def create_payment_terms_template(): create_payment_term('Basic Amount Receivable') diff --git a/erpnext/accounts/doctype/payment_order/payment_order.py b/erpnext/accounts/doctype/payment_order/payment_order.py index 7ecdc41d034..e5880aa67a8 100644 --- a/erpnext/accounts/doctype/payment_order/payment_order.py +++ b/erpnext/accounts/doctype/payment_order/payment_order.py @@ -26,6 +26,8 @@ class PaymentOrder(Document): for d in self.references: frappe.db.set_value(self.payment_order_type, d.get(frappe.scrub(self.payment_order_type)), ref_field, status) +@frappe.whitelist() +@frappe.validate_and_sanitize_search_inputs def get_mop_query(doctype, txt, searchfield, start, page_len, filters): return frappe.db.sql(""" select mode_of_payment from `tabPayment Order Reference` where parent = %(parent)s and mode_of_payment like %(txt)s @@ -36,6 +38,8 @@ def get_mop_query(doctype, txt, searchfield, start, page_len, filters): 'txt': "%%%s%%" % txt }) +@frappe.whitelist() +@frappe.validate_and_sanitize_search_inputs def get_supplier_query(doctype, txt, searchfield, start, page_len, filters): return frappe.db.sql(""" select supplier from `tabPayment Order Reference` where parent = %(parent)s and supplier like %(txt)s and @@ -86,4 +90,4 @@ def make_journal_entry(doc, supplier, mode_of_payment=None): je.flags.ignore_mandatory = True je.save() - frappe.msgprint(_("{0} {1} created").format(je.doctype, je.name)) \ No newline at end of file + frappe.msgprint(_("{0} {1} created").format(je.doctype, je.name)) diff --git a/erpnext/accounts/doctype/payment_reconciliation/payment_reconciliation.js b/erpnext/accounts/doctype/payment_reconciliation/payment_reconciliation.js index d3992d51115..355fe96c967 100644 --- a/erpnext/accounts/doctype/payment_reconciliation/payment_reconciliation.js +++ b/erpnext/accounts/doctype/payment_reconciliation/payment_reconciliation.js @@ -73,6 +73,10 @@ erpnext.accounts.PaymentReconciliationController = frappe.ui.form.Controller.ext }; } }); + + this.frm.set_value('party_type', ''); + this.frm.set_value('party', ''); + this.frm.set_value('receivable_payable_account', ''); }, refresh: function() { diff --git a/erpnext/accounts/doctype/payment_reconciliation/payment_reconciliation.py b/erpnext/accounts/doctype/payment_reconciliation/payment_reconciliation.py index 30804961861..2f8b634664c 100644 --- a/erpnext/accounts/doctype/payment_reconciliation/payment_reconciliation.py +++ b/erpnext/accounts/doctype/payment_reconciliation/payment_reconciliation.py @@ -48,7 +48,8 @@ class PaymentReconciliation(Document): select "Journal Entry" as reference_type, t1.name as reference_name, t1.posting_date, t1.remark as remarks, t2.name as reference_row, - {dr_or_cr} as amount, t2.is_advance + {dr_or_cr} as amount, t2.is_advance, + t2.account_currency as currency from `tabJournal Entry` t1, `tabJournal Entry Account` t2 where @@ -88,7 +89,8 @@ class PaymentReconciliation(Document): if self.party_type == 'Customer' else "Purchase Invoice") return frappe.db.sql(""" SELECT `tab{doc}`.name as reference_name, %(voucher_type)s as reference_type, - (sum(`tabGL Entry`.{dr_or_cr}) - sum(`tabGL Entry`.{reconciled_dr_or_cr})) as amount + (sum(`tabGL Entry`.{dr_or_cr}) - sum(`tabGL Entry`.{reconciled_dr_or_cr})) as amount, + account_currency as currency FROM `tab{doc}`, `tabGL Entry` WHERE (`tab{doc}`.name = `tabGL Entry`.against_voucher or `tab{doc}`.name = `tabGL Entry`.voucher_no) @@ -101,10 +103,10 @@ class PaymentReconciliation(Document): Having amount > 0 """.format( - doc=voucher_type, - dr_or_cr=dr_or_cr, - reconciled_dr_or_cr=reconciled_dr_or_cr, - party_type_field=frappe.scrub(self.party_type)), + doc=voucher_type, + dr_or_cr=dr_or_cr, + reconciled_dr_or_cr=reconciled_dr_or_cr, + party_type_field=frappe.scrub(self.party_type)), { 'party': self.party, 'party_type': self.party_type, @@ -141,6 +143,7 @@ class PaymentReconciliation(Document): ent.invoice_number = e.get('voucher_no') ent.invoice_date = e.get('posting_date') ent.amount = flt(e.get('invoice_amount')) + ent.currency = e.get('currency') ent.outstanding_amount = e.get('outstanding_amount') def reconcile(self, args): @@ -170,7 +173,7 @@ class PaymentReconciliation(Document): reconcile_against_document(lst) if dr_or_cr_notes: - reconcile_dr_cr_note(dr_or_cr_notes) + reconcile_dr_cr_note(dr_or_cr_notes, self.company) msgprint(_("Successfully Reconciled")) self.get_unreconciled_entries() @@ -261,7 +264,7 @@ class PaymentReconciliation(Document): return cond -def reconcile_dr_cr_note(dr_cr_notes): +def reconcile_dr_cr_note(dr_cr_notes, company): for d in dr_cr_notes: voucher_type = ('Credit Note' if d.voucher_type == 'Sales Invoice' else 'Debit Note') @@ -269,10 +272,14 @@ def reconcile_dr_cr_note(dr_cr_notes): reconcile_dr_or_cr = ('debit_in_account_currency' if d.dr_or_cr == 'credit_in_account_currency' else 'credit_in_account_currency') + company_currency = erpnext.get_company_currency(company) + jv = frappe.get_doc({ "doctype": "Journal Entry", "voucher_type": voucher_type, "posting_date": today(), + "company": company, + "multi_currency": 1 if d.currency != company_currency else 0, "accounts": [ { 'account': d.account, @@ -280,7 +287,8 @@ def reconcile_dr_cr_note(dr_cr_notes): 'party_type': d.party_type, d.dr_or_cr: abs(d.allocated_amount), 'reference_type': d.against_voucher_type, - 'reference_name': d.against_voucher + 'reference_name': d.against_voucher, + 'cost_center': erpnext.get_default_cost_center(company) }, { 'account': d.account, @@ -289,7 +297,8 @@ def reconcile_dr_cr_note(dr_cr_notes): reconcile_dr_or_cr: (abs(d.allocated_amount) if abs(d.unadjusted_amount) > abs(d.allocated_amount) else abs(d.unadjusted_amount)), 'reference_type': d.voucher_type, - 'reference_name': d.voucher_no + 'reference_name': d.voucher_no, + 'cost_center': erpnext.get_default_cost_center(company) } ] }) diff --git a/erpnext/accounts/doctype/payment_reconciliation_invoice/payment_reconciliation_invoice.json b/erpnext/accounts/doctype/payment_reconciliation_invoice/payment_reconciliation_invoice.json index ce7ce98edbe..6a79a85c348 100644 --- a/erpnext/accounts/doctype/payment_reconciliation_invoice/payment_reconciliation_invoice.json +++ b/erpnext/accounts/doctype/payment_reconciliation_invoice/payment_reconciliation_invoice.json @@ -1,183 +1,80 @@ { - "allow_copy": 0, - "allow_import": 0, - "allow_rename": 0, - "beta": 0, - "creation": "2014-07-09 16:14:23.672922", - "custom": 0, - "docstatus": 0, - "doctype": "DocType", - "document_type": "", - "editable_grid": 1, + "actions": [], + "creation": "2014-07-09 16:14:23.672922", + "doctype": "DocType", + "editable_grid": 1, + "engine": "InnoDB", + "field_order": [ + "invoice_type", + "invoice_number", + "invoice_date", + "col_break1", + "amount", + "outstanding_amount", + "currency" + ], "fields": [ { - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "fieldname": "invoice_type", - "fieldtype": "Select", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_list_view": 1, - "label": "Invoice Type", - "length": 0, - "no_copy": 0, - "options": "Sales Invoice\nPurchase Invoice\nJournal Entry", - "permlevel": 0, - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 1, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "unique": 0 - }, + "fieldname": "invoice_type", + "fieldtype": "Select", + "in_list_view": 1, + "label": "Invoice Type", + "options": "Sales Invoice\nPurchase Invoice\nJournal Entry", + "read_only": 1 + }, { - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "fieldname": "invoice_number", - "fieldtype": "Dynamic Link", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_list_view": 1, - "label": "Invoice Number", - "length": 0, - "no_copy": 0, - "options": "invoice_type", - "permlevel": 0, - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 1, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "unique": 0 - }, + "fieldname": "invoice_number", + "fieldtype": "Dynamic Link", + "in_list_view": 1, + "label": "Invoice Number", + "options": "invoice_type", + "read_only": 1 + }, { - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "fieldname": "invoice_date", - "fieldtype": "Date", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_list_view": 1, - "label": "Invoice Date", - "length": 0, - "no_copy": 0, - "permlevel": 0, - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 1, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "unique": 0 - }, + "fieldname": "invoice_date", + "fieldtype": "Date", + "in_list_view": 1, + "label": "Invoice Date", + "read_only": 1 + }, { - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "fieldname": "col_break1", - "fieldtype": "Column Break", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_list_view": 0, - "label": "", - "length": 0, - "no_copy": 0, - "permlevel": 0, - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "unique": 0 - }, + "fieldname": "col_break1", + "fieldtype": "Column Break" + }, { - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "fieldname": "amount", - "fieldtype": "Currency", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_list_view": 1, - "label": "Amount", - "length": 0, - "no_copy": 0, - "permlevel": 0, - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 1, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "unique": 0 - }, + "fieldname": "amount", + "fieldtype": "Currency", + "in_list_view": 1, + "label": "Amount", + "options": "currency", + "read_only": 1 + }, { - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "fieldname": "outstanding_amount", - "fieldtype": "Currency", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_list_view": 1, - "label": "Outstanding Amount", - "length": 0, - "no_copy": 0, - "permlevel": 0, - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 1, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "unique": 0 + "fieldname": "outstanding_amount", + "fieldtype": "Currency", + "in_list_view": 1, + "label": "Outstanding Amount", + "options": "currency", + "read_only": 1 + }, + { + "fieldname": "currency", + "fieldtype": "Link", + "hidden": 1, + "label": "Currency", + "options": "Currency" } - ], - "hide_heading": 0, - "hide_toolbar": 0, - "idx": 0, - "image_view": 0, - "in_create": 0, - - "is_submittable": 0, - "issingle": 0, - "istable": 1, - "max_attachments": 0, - "modified": "2016-07-11 03:28:03.588476", - "modified_by": "Administrator", - "module": "Accounts", - "name": "Payment Reconciliation Invoice", - "name_case": "", - "owner": "Administrator", - "permissions": [], - "quick_entry": 1, - "read_only": 0, - "read_only_onload": 0, - "sort_field": "modified", - "sort_order": "DESC", - "track_seen": 0 + ], + "istable": 1, + "links": [], + "modified": "2020-07-19 18:12:27.964073", + "modified_by": "Administrator", + "module": "Accounts", + "name": "Payment Reconciliation Invoice", + "owner": "Administrator", + "permissions": [], + "quick_entry": 1, + "sort_field": "modified", + "sort_order": "DESC", + "track_changes": 1 } \ No newline at end of file diff --git a/erpnext/accounts/doctype/payment_reconciliation_payment/payment_reconciliation_payment.json b/erpnext/accounts/doctype/payment_reconciliation_payment/payment_reconciliation_payment.json index 018bfd028a6..925a6f10a5e 100644 --- a/erpnext/accounts/doctype/payment_reconciliation_payment/payment_reconciliation_payment.json +++ b/erpnext/accounts/doctype/payment_reconciliation_payment/payment_reconciliation_payment.json @@ -1,7 +1,9 @@ { + "actions": [], "creation": "2014-07-09 16:13:35.452759", "doctype": "DocType", "editable_grid": 1, + "engine": "InnoDB", "field_order": [ "reference_type", "reference_name", @@ -16,7 +18,8 @@ "difference_account", "difference_amount", "sec_break1", - "remark" + "remark", + "currency" ], "fields": [ { @@ -73,6 +76,7 @@ "fieldtype": "Currency", "in_list_view": 1, "label": "Amount", + "options": "currency", "read_only": 1 }, { @@ -81,6 +85,7 @@ "fieldtype": "Currency", "in_list_view": 1, "label": "Allocated amount", + "options": "currency", "reqd": 1 }, { @@ -106,16 +111,25 @@ "fieldname": "difference_amount", "fieldtype": "Currency", "label": "Difference Amount", + "options": "currency", "print_hide": 1, "read_only": 1 }, { "fieldname": "section_break_10", "fieldtype": "Section Break" + }, + { + "fieldname": "currency", + "fieldtype": "Link", + "hidden": 1, + "label": "Currency", + "options": "Currency" } ], "istable": 1, - "modified": "2019-06-24 00:08:11.150796", + "links": [], + "modified": "2020-07-19 18:12:41.682347", "modified_by": "Administrator", "module": "Accounts", "name": "Payment Reconciliation Payment", diff --git a/erpnext/accounts/doctype/payment_request/payment_request.py b/erpnext/accounts/doctype/payment_request/payment_request.py index c863ce8e7f7..29d78a9726f 100644 --- a/erpnext/accounts/doctype/payment_request/payment_request.py +++ b/erpnext/accounts/doctype/payment_request/payment_request.py @@ -140,9 +140,6 @@ class PaymentRequest(Document): }) def set_as_paid(self): - if frappe.session.user == "Guest": - frappe.set_user("Administrator") - payment_entry = self.create_payment_entry() self.make_invoice() @@ -254,7 +251,7 @@ class PaymentRequest(Document): if status in ["Authorized", "Completed"]: redirect_to = None - self.run_method("set_as_paid") + self.set_as_paid() # if shopping cart enabled and in session if (shopping_cart_settings.enabled and hasattr(frappe.local, "session") diff --git a/erpnext/accounts/doctype/pos_profile/pos_profile.py b/erpnext/accounts/doctype/pos_profile/pos_profile.py index 4f17e9f9954..ed1e09e31b6 100644 --- a/erpnext/accounts/doctype/pos_profile/pos_profile.py +++ b/erpnext/accounts/doctype/pos_profile/pos_profile.py @@ -115,6 +115,8 @@ def get_item_groups(pos_profile): def get_series(): return frappe.get_meta("Sales Invoice").get_field("naming_series").options or "" +@frappe.whitelist() +@frappe.validate_and_sanitize_search_inputs def pos_profile_query(doctype, txt, searchfield, start, page_len, filters): user = frappe.session['user'] company = filters.get('company') or frappe.defaults.get_user_default('company') diff --git a/erpnext/accounts/doctype/pricing_rule/pricing_rule.py b/erpnext/accounts/doctype/pricing_rule/pricing_rule.py index 983f1ef85a0..0c2b5475cb8 100644 --- a/erpnext/accounts/doctype/pricing_rule/pricing_rule.py +++ b/erpnext/accounts/doctype/pricing_rule/pricing_rule.py @@ -17,6 +17,8 @@ from six import string_types apply_on_dict = {"Item Code": "items", "Item Group": "item_groups", "Brand": "brands"} +other_fields = ["other_item_code", "other_item_group", "other_brand"] + class PricingRule(Document): def validate(self): self.validate_mandatory() @@ -51,6 +53,13 @@ class PricingRule(Document): if tocheck and not self.get(tocheck): throw(_("{0} is required").format(self.meta.get_label(tocheck)), frappe.MandatoryError) + if self.apply_rule_on_other: + o_field = 'other_' + frappe.scrub(self.apply_rule_on_other) + if not self.get(o_field) and o_field in other_fields: + frappe.throw(_("For the 'Apply Rule On Other' condition the field {0} is mandatory") + .format(frappe.bold(self.apply_rule_on_other))) + + if self.price_or_product_discount == 'Price' and not self.rate_or_discount: throw(_("Rate or Discount is required for the price discount."), frappe.MandatoryError) @@ -84,13 +93,27 @@ class PricingRule(Document): for f in options: if not f: continue - f = frappe.scrub(f) - if f!=fieldname: - self.set(f, None) + scrubbed_f = frappe.scrub(f) + + if logic_field == 'apply_on': + apply_on_f = apply_on_dict.get(f, f) + else: + apply_on_f = scrubbed_f + + if scrubbed_f != fieldname: + self.set(apply_on_f, None) if self.mixed_conditions and self.get("same_item"): self.same_item = 0 + apply_rule_on_other = frappe.scrub(self.apply_rule_on_other or "") + + cleanup_other_fields = (other_fields if not apply_rule_on_other + else [o_field for o_field in other_fields if o_field != 'other_' + apply_rule_on_other]) + + for other_field in cleanup_other_fields: + self.set(other_field, None) + def validate_rate_or_discount(self): for field in ["Rate"]: if flt(self.get(frappe.scrub(field))) < 0: @@ -248,7 +271,7 @@ def get_pricing_rule_for_item(args, price_list_rate=0, doc=None, for_validate=Fa if pricing_rule.coupon_code_based==1 and args.coupon_code==None: return item_details - + if not pricing_rule.validate_applied_rule: if pricing_rule.price_or_product_discount == "Price": apply_price_discount_rule(pricing_rule, item_details, args) @@ -257,7 +280,7 @@ def get_pricing_rule_for_item(args, price_list_rate=0, doc=None, for_validate=Fa item_details.has_pricing_rule = 1 - item_details.pricing_rules = ','.join([d.pricing_rule for d in rules]) + item_details.pricing_rules = frappe.as_json([d.pricing_rule for d in rules]) if not doc: return item_details @@ -347,7 +370,7 @@ def set_discount_amount(rate, item_details): def remove_pricing_rule_for_item(pricing_rules, item_details, item_code=None): from erpnext.accounts.doctype.pricing_rule.utils import get_pricing_rule_items - for d in pricing_rules.split(','): + for d in json.loads(pricing_rules): if not d or not frappe.db.exists("Pricing Rule", d): continue pricing_rule = frappe.get_cached_doc('Pricing Rule', d) @@ -413,14 +436,15 @@ def make_pricing_rule(doctype, docname): return doc +@frappe.whitelist() +@frappe.validate_and_sanitize_search_inputs def get_item_uoms(doctype, txt, searchfield, start, page_len, filters): items = [filters.get('value')] if filters.get('apply_on') != 'Item Code': field = frappe.scrub(filters.get('apply_on')) + items = [d.name for d in frappe.db.get_all("Item", filters={field: filters.get('value')})] - items = frappe.db.sql_list("""select name - from `tabItem` where {0} = %s""".format(field), filters.get('value')) - - return frappe.get_all('UOM Conversion Detail', - filters = {'parent': ('in', items), 'uom': ("like", "{0}%".format(txt))}, - fields = ["distinct uom"], as_list=1) + return frappe.get_all('UOM Conversion Detail', filters={ + 'parent': ('in', items), + 'uom': ("like", "{0}%".format(txt)) + }, fields = ["distinct uom"], as_list=1) diff --git a/erpnext/accounts/doctype/pricing_rule/utils.py b/erpnext/accounts/doctype/pricing_rule/utils.py index 9876246c47b..7d20208fc79 100644 --- a/erpnext/accounts/doctype/pricing_rule/utils.py +++ b/erpnext/accounts/doctype/pricing_rule/utils.py @@ -11,6 +11,7 @@ import json from six import string_types import frappe +from erpnext.accounts.doctype.pricing_rule.pricing_rule import set_transaction_type from erpnext.setup.doctype.item_group.item_group import get_child_item_groups from erpnext.stock.doctype.warehouse.warehouse import get_child_warehouses from erpnext.stock.get_item_details import get_conversion_factor, get_default_income_account @@ -322,7 +323,9 @@ def apply_internal_priority(pricing_rules, field_set, args): filtered_rules = [] for field in field_set: if args.get(field): - filtered_rules = filter(lambda x: x[field]==args[field], pricing_rules) + # filter function always returns a filter object even if empty + # list conversion is necessary to check for an empty result + filtered_rules = list(filter(lambda x: x.get(field)==args.get(field), pricing_rules)) if filtered_rules: break return filtered_rules or pricing_rules @@ -416,9 +419,28 @@ def apply_pricing_rule_on_transaction(doc): values = {} conditions = get_other_conditions(conditions, values, doc) - pricing_rules = frappe.db.sql(""" Select `tabPricing Rule`.* from `tabPricing Rule` - where {conditions} and `tabPricing Rule`.disable = 0 - """.format(conditions = conditions), values, as_dict=1) + args = frappe._dict({ + 'doctype': doc.doctype, + 'transaction_type': None, + }) + set_transaction_type(args) + tran_type_condition = '{} = 1'.format(args.transaction_type) + + sql = """ + SELECT + `tabPricing Rule`.* + FROM + `tabPricing Rule` + WHERE + {conditions} and + {tran_type_condition} and + `tabPricing Rule`.disable = 0 + """.format( + conditions=conditions, + tran_type_condition=tran_type_condition, + ) + + pricing_rules = frappe.db.sql(sql, values, as_dict=1) if pricing_rules: pricing_rules = filter_pricing_rules_for_qty_amount(doc.total_qty, @@ -449,7 +471,7 @@ def apply_pricing_rule_on_transaction(doc): doc.set_missing_values() def get_applied_pricing_rules(item_row): - return (item_row.get("pricing_rules").split(',') + return (json.loads(item_row.get("pricing_rules")) if item_row.get("pricing_rules") else []) def get_product_discount_rule(pricing_rule, item_details, args=None, doc=None): diff --git a/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.json b/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.json index a1a20de0503..fbd4dee4d66 100644 --- a/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.json +++ b/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.json @@ -1,5 +1,4 @@ { - "actions": [], "allow_import": 1, "autoname": "naming_series:", "creation": "2013-05-21 16:16:39", @@ -26,6 +25,7 @@ "accounting_dimensions_section", "cost_center", "dimension_col_break", + "project", "sb_14", "on_hold", "release_date", @@ -963,8 +963,10 @@ { "fieldname": "clearance_date", "fieldtype": "Date", - "hidden": 1, - "label": "Clearance Date" + "label": "Clearance Date", + "no_copy": 1, + "print_hide": 1, + "read_only": 1 }, { "fieldname": "col_br_payments", @@ -1233,6 +1235,7 @@ "print_hide": 1 }, { + "collapsible": 1, "fieldname": "subscription_section", "fieldtype": "Section Break", "label": "Subscription Section", @@ -1286,6 +1289,12 @@ "fieldname": "dimension_col_break", "fieldtype": "Column Break" }, + { + "fieldname": "project", + "fieldtype": "Link", + "label": "Project", + "options": "Project" + }, { "fieldname": "tax_withholding_category", "fieldtype": "Link", @@ -1298,8 +1307,7 @@ "icon": "fa fa-file-text", "idx": 204, "is_submittable": 1, - "links": [], - "modified": "2020-04-18 13:05:25.199832", + "modified": "2020-08-20 11:08:19.611710", "modified_by": "Administrator", "module": "Accounts", "name": "Purchase Invoice", diff --git a/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py b/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py index 58c521f0ffe..c9fd889b63f 100644 --- a/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py +++ b/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py @@ -413,6 +413,8 @@ class PurchaseInvoice(BuyingController): self.make_tax_gl_entries(gl_entries) + gl_entries = make_regional_gl_entries(gl_entries, self) + gl_entries = merge_similar_entries(gl_entries) self.make_payment_gl_entries(gl_entries) @@ -451,7 +453,8 @@ class PurchaseInvoice(BuyingController): if self.party_account_currency==self.company_currency else grand_total, "against_voucher": self.return_against if cint(self.is_return) and self.return_against else self.name, "against_voucher_type": self.doctype, - "cost_center": self.cost_center + "cost_center": self.cost_center, + "project": self.project }, self.party_account_currency, item=self) ) @@ -492,7 +495,7 @@ class PurchaseInvoice(BuyingController): "debit": warehouse_debit_amount, "remarks": self.get("remarks") or _("Accounting Entry for Stock"), "cost_center": item.cost_center, - "project": item.project + "project": item.project or self.project }, account_currency, item=item) ) @@ -505,7 +508,7 @@ class PurchaseInvoice(BuyingController): "cost_center": item.cost_center, "remarks": self.get("remarks") or _("Accounting Entry for Stock"), "credit": flt(amount), - "project": item.project + "project": item.project or self.project }, item=item)) # sub-contracting warehouse @@ -518,6 +521,7 @@ class PurchaseInvoice(BuyingController): "account": supplier_warehouse_account, "against": item.expense_account, "cost_center": item.cost_center, + "project": item.project or self.project, "remarks": self.get("remarks") or _("Accounting Entry for Stock"), "credit": flt(item.rm_supp_cost) }, warehouse_account[self.supplier_warehouse]["account_currency"], item=item)) @@ -536,7 +540,7 @@ class PurchaseInvoice(BuyingController): "against": self.supplier, "debit": amount, "cost_center": item.cost_center, - "project": item.project + "project": item.project or self.project }, account_currency, item=item)) # If asset is bought through this document and not linked to PR @@ -549,7 +553,7 @@ class PurchaseInvoice(BuyingController): "cost_center": item.cost_center, "remarks": self.get("remarks") or _("Accounting Entry for Stock"), "credit": flt(item.landed_cost_voucher_amount), - "project": item.project + "project": item.project or self.project }, item=item)) gl_entries.append(self.get_gl_dict({ @@ -558,7 +562,7 @@ class PurchaseInvoice(BuyingController): "cost_center": item.cost_center, "remarks": self.get("remarks") or _("Accounting Entry for Stock"), "debit": flt(item.landed_cost_voucher_amount), - "project": item.project + "project": item.project or self.project }, item=item)) # update gross amount of asset bought through this document @@ -584,7 +588,8 @@ class PurchaseInvoice(BuyingController): "against": self.supplier, "debit": flt(item.item_tax_amount, item.precision("item_tax_amount")), "remarks": self.remarks or "Accounting Entry for Stock", - "cost_center": self.cost_center + "cost_center": self.cost_center, + "project": item.project or self.project }, item=item) ) @@ -613,7 +618,8 @@ class PurchaseInvoice(BuyingController): "debit": base_asset_amount, "debit_in_account_currency": (base_asset_amount if arbnb_currency == self.company_currency else asset_amount), - "cost_center": item.cost_center + "cost_center": item.cost_center, + "project": item.project or self.project }, item=item)) if item.item_tax_amount: @@ -623,6 +629,7 @@ class PurchaseInvoice(BuyingController): "against": self.supplier, "remarks": self.get("remarks") or _("Accounting Entry for Asset"), "cost_center": item.cost_center, + "project": item.project or self.project, "credit": item.item_tax_amount, "credit_in_account_currency": (item.item_tax_amount if asset_eiiav_currency == self.company_currency else @@ -639,7 +646,8 @@ class PurchaseInvoice(BuyingController): "debit": base_asset_amount, "debit_in_account_currency": (base_asset_amount if cwip_account_currency == self.company_currency else asset_amount), - "cost_center": self.cost_center + "cost_center": self.cost_center, + "project": item.project or self.project }, item=item)) if item.item_tax_amount and not cint(erpnext.is_perpetual_inventory_enabled(self.company)): @@ -650,6 +658,7 @@ class PurchaseInvoice(BuyingController): "remarks": self.get("remarks") or _("Accounting Entry for Asset"), "cost_center": item.cost_center, "credit": item.item_tax_amount, + "project": item.project or self.project, "credit_in_account_currency": (item.item_tax_amount if asset_eiiav_currency == self.company_currency else item.item_tax_amount / self.conversion_rate) @@ -665,7 +674,7 @@ class PurchaseInvoice(BuyingController): "cost_center": item.cost_center, "remarks": self.get("remarks") or _("Accounting Entry for Stock"), "credit": flt(item.landed_cost_voucher_amount), - "project": item.project + "project": item.project or self.project }, item=item)) gl_entries.append(self.get_gl_dict({ @@ -674,7 +683,7 @@ class PurchaseInvoice(BuyingController): "cost_center": item.cost_center, "remarks": self.get("remarks") or _("Accounting Entry for Stock"), "debit": flt(item.landed_cost_voucher_amount), - "project": item.project + "project": item.project or self.project }, item=item)) # update gross amount of assets bought through this document @@ -709,7 +718,7 @@ class PurchaseInvoice(BuyingController): "debit": stock_adjustment_amt, "remarks": self.get("remarks") or _("Stock Adjustment"), "cost_center": item.cost_center, - "project": item.project + "project": item.project or self.project }, account_currency, item=item) ) @@ -801,7 +810,8 @@ class PurchaseInvoice(BuyingController): if self.party_account_currency==self.company_currency else self.paid_amount, "against_voucher": self.return_against if cint(self.is_return) and self.return_against else self.name, "against_voucher_type": self.doctype, - "cost_center": self.cost_center + "cost_center": self.cost_center, + "project": self.project }, self.party_account_currency, item=self) ) @@ -833,7 +843,8 @@ class PurchaseInvoice(BuyingController): if self.party_account_currency==self.company_currency else self.write_off_amount, "against_voucher": self.return_against if cint(self.is_return) and self.return_against else self.name, "against_voucher_type": self.doctype, - "cost_center": self.cost_center + "cost_center": self.cost_center, + "project": self.project }, self.party_account_currency, item=self) ) gl_entries.append( @@ -980,7 +991,7 @@ class PurchaseInvoice(BuyingController): # calculate totals again after applying TDS self.calculate_taxes_and_totals() - + def set_status(self, update=False, status=None, update_modified=True): if self.is_new(): if self.get('amended_from'): @@ -1026,6 +1037,10 @@ def get_list_context(context=None): }) return list_context +@erpnext.allow_regional +def make_regional_gl_entries(gl_entries, doc): + return gl_entries + @frappe.whitelist() def make_debit_note(source_name, target_doc=None): from erpnext.controllers.sales_and_purchase_return import make_return_doc diff --git a/erpnext/accounts/doctype/purchase_invoice/purchase_invoice_list.js b/erpnext/accounts/doctype/purchase_invoice/purchase_invoice_list.js index 800ed921bdf..4f751636b69 100644 --- a/erpnext/accounts/doctype/purchase_invoice/purchase_invoice_list.js +++ b/erpnext/accounts/doctype/purchase_invoice/purchase_invoice_list.js @@ -16,7 +16,7 @@ frappe.listview_settings['Purchase Invoice'] = { } else if(frappe.datetime.get_diff(doc.due_date) < 0) { return [__("Overdue"), "red", "outstanding_amount,>,0|due_date,<,Today"]; } else { - return [__("Unpaid"), "orange", "outstanding_amount,>,0|due,>=,Today"]; + return [__("Unpaid"), "orange", "outstanding_amount,>,0|due_date,>=,Today"]; } } else if(cint(doc.is_return)) { return [__("Return"), "darkgrey", "is_return,=,Yes"]; diff --git a/erpnext/accounts/doctype/purchase_invoice/test_purchase_invoice.py b/erpnext/accounts/doctype/purchase_invoice/test_purchase_invoice.py index 61700050614..4019815e19a 100644 --- a/erpnext/accounts/doctype/purchase_invoice/test_purchase_invoice.py +++ b/erpnext/accounts/doctype/purchase_invoice/test_purchase_invoice.py @@ -15,6 +15,7 @@ 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 from erpnext.accounts.doctype.account.test_account import get_inventory_account +from erpnext.projects.doctype.project.test_project import make_project test_dependencies = ["Item", "Cost Center", "Payment Term", "Payment Terms Template"] test_ignore = ["Serial No"] @@ -434,6 +435,8 @@ class TestPurchaseInvoice(unittest.TestCase): ) def test_total_purchase_cost_for_project(self): + make_project({'project_name':'_Test Project'}) + existing_purchase_cost = frappe.db.sql("""select sum(base_net_amount) from `tabPurchase Invoice Item` where project = '_Test Project' and docstatus=1""") existing_purchase_cost = existing_purchase_cost and existing_purchase_cost[0][0] or 0 @@ -807,11 +810,8 @@ class TestPurchaseInvoice(unittest.TestCase): pi_doc = frappe.get_doc('Purchase Invoice', pi.name) self.assertEqual(pi_doc.outstanding_amount, 0) - def test_purchase_invoice_for_enable_allow_cost_center_in_entry_of_bs_account(self): + def test_purchase_invoice_with_cost_center(self): from erpnext.accounts.doctype.cost_center.test_cost_center import create_cost_center - accounts_settings = frappe.get_doc('Accounts Settings', 'Accounts Settings') - accounts_settings.allow_cost_center_in_entry_of_bs_account = 1 - accounts_settings.save() cost_center = "_Test Cost Center for BS Account - _TC" create_cost_center(cost_center_name="_Test Cost Center for BS Account", company="_Test Company") @@ -837,13 +837,7 @@ class TestPurchaseInvoice(unittest.TestCase): for gle in gl_entries: self.assertEqual(expected_values[gle.account]["cost_center"], gle.cost_center) - accounts_settings.allow_cost_center_in_entry_of_bs_account = 0 - accounts_settings.save() - - def test_purchase_invoice_for_disable_allow_cost_center_in_entry_of_bs_account(self): - accounts_settings = frappe.get_doc('Accounts Settings', 'Accounts Settings') - accounts_settings.allow_cost_center_in_entry_of_bs_account = 0 - accounts_settings.save() + def test_purchase_invoice_without_cost_center(self): cost_center = "_Test Cost Center - _TC" pi = make_purchase_invoice(credit_to="Creditors - _TC") @@ -866,6 +860,42 @@ class TestPurchaseInvoice(unittest.TestCase): for gle in gl_entries: self.assertEqual(expected_values[gle.account]["cost_center"], gle.cost_center) + def test_purchase_invoice_with_project_link(self): + project = make_project({ + 'project_name': 'Purchase Invoice Project', + 'project_template_name': 'Test Project Template', + 'start_date': '2020-01-01' + }) + item_project = make_project({ + 'project_name': 'Purchase Invoice Item Project', + 'project_template_name': 'Test Project Template', + 'start_date': '2019-06-01' + }) + + pi = make_purchase_invoice(credit_to="Creditors - _TC" ,do_not_save=1) + pi.items[0].project = item_project.project_name + pi.project = project.project_name + + pi.submit() + + expected_values = { + "Creditors - _TC": { + "project": project.project_name + }, + "_Test Account Cost for Goods Sold - _TC": { + "project": item_project.project_name + } + } + + gl_entries = frappe.db.sql("""select account, cost_center, project, account_currency, debit, credit, + debit_in_account_currency, credit_in_account_currency + from `tabGL Entry` where voucher_type='Purchase Invoice' and voucher_no=%s + order by account asc""", pi.name, as_dict=1) + + self.assertTrue(gl_entries) + + for gle in gl_entries: + self.assertEqual(expected_values[gle.account]["project"], gle.project) def unlink_payment_on_cancel_of_invoice(enable=1): accounts_settings = frappe.get_doc("Accounts Settings") diff --git a/erpnext/accounts/doctype/purchase_invoice_item/purchase_invoice_item.json b/erpnext/accounts/doctype/purchase_invoice_item/purchase_invoice_item.json index a8dfab6a54b..ff1dbedbd31 100644 --- a/erpnext/accounts/doctype/purchase_invoice_item/purchase_invoice_item.json +++ b/erpnext/accounts/doctype/purchase_invoice_item/purchase_invoice_item.json @@ -1,5 +1,4 @@ { - "actions": [], "autoname": "hash", "creation": "2013-05-22 12:43:10", "doctype": "DocType", @@ -86,6 +85,7 @@ "item_tax_rate", "bom", "include_exploded_items", + "purchase_invoice_item", "col_break6", "purchase_order", "po_detail", @@ -764,12 +764,21 @@ "label": "Asset Category", "options": "Asset Category", "read_only": 1 + }, + { + "depends_on": "eval:parent.update_stock == 1", + "fieldname": "purchase_invoice_item", + "fieldtype": "Data", + "ignore_user_permissions": 1, + "label": "Purchase Invoice Item", + "no_copy": 1, + "print_hide": 1, + "read_only": 1 } ], "idx": 1, "istable": 1, - "links": [], - "modified": "2020-04-07 18:34:35.104178", + "modified": "2020-06-30 16:48:01.398356", "modified_by": "Administrator", "module": "Accounts", "name": "Purchase Invoice Item", diff --git a/erpnext/accounts/doctype/sales_invoice/sales_invoice.json b/erpnext/accounts/doctype/sales_invoice/sales_invoice.json index 6e0a30d48e0..205d535e188 100644 --- a/erpnext/accounts/doctype/sales_invoice/sales_invoice.json +++ b/erpnext/accounts/doctype/sales_invoice/sales_invoice.json @@ -1,5 +1,4 @@ { - "actions": [], "allow_import": 1, "autoname": "naming_series:", "creation": "2013-05-24 19:29:05", @@ -1494,6 +1493,7 @@ "print_hide": 1 }, { + "collapsible": 1, "fieldname": "subscription_section", "fieldtype": "Section Break", "label": "Subscription Section" @@ -1569,8 +1569,7 @@ "icon": "fa fa-file-text", "idx": 181, "is_submittable": 1, - "links": [], - "modified": "2020-05-19 17:00:57.208696", + "modified": "2020-07-01 12:41:29.484813", "modified_by": "Administrator", "module": "Accounts", "name": "Sales Invoice", diff --git a/erpnext/accounts/doctype/sales_invoice/sales_invoice.py b/erpnext/accounts/doctype/sales_invoice/sales_invoice.py index ce26a258fb6..d4d40653e77 100644 --- a/erpnext/accounts/doctype/sales_invoice/sales_invoice.py +++ b/erpnext/accounts/doctype/sales_invoice/sales_invoice.py @@ -786,7 +786,8 @@ class SalesInvoice(SellingController): if self.party_account_currency==self.company_currency else grand_total, "against_voucher": self.return_against if cint(self.is_return) and self.return_against else self.name, "against_voucher_type": self.doctype, - "cost_center": self.cost_center + "cost_center": self.cost_center, + "project": self.project }, self.party_account_currency, item=self) ) @@ -841,7 +842,8 @@ class SalesInvoice(SellingController): "credit_in_account_currency": (flt(item.base_net_amount, item.precision("base_net_amount")) if account_currency==self.company_currency else flt(item.net_amount, item.precision("net_amount"))), - "cost_center": item.cost_center + "cost_center": item.cost_center, + "project": item.project or self.project }, account_currency, item=item) ) @@ -922,7 +924,8 @@ class SalesInvoice(SellingController): if self.party_account_currency==self.company_currency else flt(self.change_amount), "against_voucher": self.return_against if cint(self.is_return) and self.return_against else self.name, "against_voucher_type": self.doctype, - "cost_center": self.cost_center + "cost_center": self.cost_center, + "project": self.project }, self.party_account_currency, item=self) ) @@ -955,7 +958,8 @@ class SalesInvoice(SellingController): else flt(self.write_off_amount, self.precision("write_off_amount"))), "against_voucher": self.return_against if cint(self.is_return) else self.name, "against_voucher_type": self.doctype, - "cost_center": self.cost_center + "cost_center": self.cost_center, + "project": self.project }, self.party_account_currency, item=self) ) gl_entries.append( @@ -1105,7 +1109,10 @@ class SalesInvoice(SellingController): expiry_date=self.posting_date, include_expired_entry=True) if lp_details and getdate(lp_details.from_date) <= getdate(self.posting_date) and \ (not lp_details.to_date or getdate(lp_details.to_date) >= getdate(self.posting_date)): - points_earned = cint(eligible_amount/lp_details.collection_factor) + + collection_factor = lp_details.collection_factor if lp_details.collection_factor else 1.0 + points_earned = cint(eligible_amount/collection_factor) + doc = frappe.get_doc({ "doctype": "Loyalty Point Entry", "company": self.company, diff --git a/erpnext/accounts/doctype/sales_invoice/test_records.json b/erpnext/accounts/doctype/sales_invoice/test_records.json index ebe6e3da8df..11ebe6a573a 100644 --- a/erpnext/accounts/doctype/sales_invoice/test_records.json +++ b/erpnext/accounts/doctype/sales_invoice/test_records.json @@ -3,6 +3,7 @@ "company": "_Test Company", "conversion_rate": 1.0, "currency": "INR", + "cost_center": "_Test Cost Center - _TC", "customer": "_Test Customer", "customer_name": "_Test Customer", "debit_to": "_Test Receivable - _TC", @@ -37,7 +38,8 @@ "charge_type": "On Net Total", "description": "VAT", "doctype": "Sales Taxes and Charges", - "parentfield": "taxes", + "parentfield": "taxes", + "cost_center": "_Test Cost Center - _TC", "rate": 6 }, { @@ -45,7 +47,8 @@ "charge_type": "On Net Total", "description": "Service Tax", "doctype": "Sales Taxes and Charges", - "parentfield": "taxes", + "parentfield": "taxes", + "cost_center": "_Test Cost Center - _TC", "rate": 6.36 } ], @@ -76,6 +79,7 @@ "customer_name": "_Test Customer", "debit_to": "_Test Receivable - _TC", "doctype": "Sales Invoice", + "cost_center": "_Test Cost Center - _TC", "items": [ { "amount": 500.0, @@ -107,7 +111,8 @@ "charge_type": "On Net Total", "description": "VAT", "doctype": "Sales Taxes and Charges", - "parentfield": "taxes", + "parentfield": "taxes", + "cost_center": "_Test Cost Center - _TC", "rate": 16 }, { @@ -115,7 +120,8 @@ "charge_type": "On Net Total", "description": "Service Tax", "doctype": "Sales Taxes and Charges", - "parentfield": "taxes", + "parentfield": "taxes", + "cost_center": "_Test Cost Center - _TC", "rate": 10 } ], @@ -132,6 +138,7 @@ "customer_name": "_Test Customer", "debit_to": "_Test Receivable - _TC", "doctype": "Sales Invoice", + "cost_center": "_Test Cost Center - _TC", "items": [ { "cost_center": "_Test Cost Center - _TC", @@ -259,6 +266,7 @@ "customer_name": "_Test Customer", "debit_to": "_Test Receivable - _TC", "doctype": "Sales Invoice", + "cost_center": "_Test Cost Center - _TC", "items": [ { "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 0b06342f309..f1a2bf7aa0e 100644 --- a/erpnext/accounts/doctype/sales_invoice/test_sales_invoice.py +++ b/erpnext/accounts/doctype/sales_invoice/test_sales_invoice.py @@ -206,10 +206,19 @@ class TestSalesInvoice(unittest.TestCase): "rate": 14, 'included_in_print_rate': 1 }) + si.append("taxes", { + "charge_type": "On Item Quantity", + "account_head": "_Test Account Education Cess - _TC", + "cost_center": "_Test Cost Center - _TC", + "description": "CESS", + "rate": 5, + 'included_in_print_rate': 1 + }) si.insert() # with inclusive tax - self.assertEqual(si.net_total, 4385.96) + self.assertEqual(si.items[0].net_amount, 3947.368421052631) + self.assertEqual(si.net_total, 3947.37) self.assertEqual(si.grand_total, 5000) si.reload() @@ -222,8 +231,8 @@ class TestSalesInvoice(unittest.TestCase): si.save() # with inclusive tax and additional discount - self.assertEqual(si.net_total, 4285.96) - self.assertEqual(si.grand_total, 4885.99) + self.assertEqual(si.net_total, 3847.37) + self.assertEqual(si.grand_total, 4886) si.reload() @@ -235,7 +244,7 @@ class TestSalesInvoice(unittest.TestCase): si.save() # with inclusive tax and additional discount - self.assertEqual(si.net_total, 4298.25) + self.assertEqual(si.net_total, 3859.65) self.assertEqual(si.grand_total, 4900.00) def test_sales_invoice_discount_amount(self): @@ -1575,11 +1584,8 @@ class TestSalesInvoice(unittest.TestCase): si_doc = frappe.get_doc('Sales Invoice', si.name) self.assertEqual(si_doc.outstanding_amount, 0) - def test_sales_invoice_for_enable_allow_cost_center_in_entry_of_bs_account(self): + def test_sales_invoice_with_cost_center(self): from erpnext.accounts.doctype.cost_center.test_cost_center import create_cost_center - accounts_settings = frappe.get_doc('Accounts Settings', 'Accounts Settings') - accounts_settings.allow_cost_center_in_entry_of_bs_account = 1 - accounts_settings.save() cost_center = "_Test Cost Center for BS Account - _TC" create_cost_center(cost_center_name="_Test Cost Center for BS Account", company="_Test Company") @@ -1604,14 +1610,47 @@ class TestSalesInvoice(unittest.TestCase): for gle in gl_entries: self.assertEqual(expected_values[gle.account]["cost_center"], gle.cost_center) + + def test_sales_invoice_with_project_link(self): + from erpnext.projects.doctype.project.test_project import make_project - accounts_settings.allow_cost_center_in_entry_of_bs_account = 0 - accounts_settings.save() + project = make_project({ + 'project_name': 'Sales Invoice Project', + 'project_template_name': 'Test Project Template', + 'start_date': '2020-01-01' + }) + item_project = make_project({ + 'project_name': 'Sales Invoice Item Project', + 'project_template_name': 'Test Project Template', + 'start_date': '2019-06-01' + }) - def test_sales_invoice_for_disable_allow_cost_center_in_entry_of_bs_account(self): - accounts_settings = frappe.get_doc('Accounts Settings', 'Accounts Settings') - accounts_settings.allow_cost_center_in_entry_of_bs_account = 1 - accounts_settings.save() + sales_invoice = create_sales_invoice(do_not_save=1) + sales_invoice.items[0].project = item_project.project_name + sales_invoice.project = project.project_name + + sales_invoice.submit() + + expected_values = { + "Debtors - _TC": { + "project": project.project_name + }, + "Sales - _TC": { + "project": item_project.project_name + } + } + + gl_entries = frappe.db.sql("""select account, cost_center, project, account_currency, debit, credit, + debit_in_account_currency, credit_in_account_currency + from `tabGL Entry` where voucher_type='Sales Invoice' and voucher_no=%s + order by account asc""", sales_invoice.name, as_dict=1) + + self.assertTrue(gl_entries) + + for gle in gl_entries: + self.assertEqual(expected_values[gle.account]["project"], gle.project) + + def test_sales_invoice_without_cost_center(self): cost_center = "_Test Cost Center - _TC" si = create_sales_invoice(debit_to="Debtors - _TC") @@ -1634,9 +1673,6 @@ class TestSalesInvoice(unittest.TestCase): for gle in gl_entries: self.assertEqual(expected_values[gle.account]["cost_center"], gle.cost_center) - accounts_settings.allow_cost_center_in_entry_of_bs_account = 0 - accounts_settings.save() - def test_deferred_revenue(self): deferred_account = create_account(account_name="Deferred Revenue", parent_account="Current Liabilities - _TC", company="_Test Company") diff --git a/erpnext/accounts/doctype/sales_invoice_item/sales_invoice_item.json b/erpnext/accounts/doctype/sales_invoice_item/sales_invoice_item.json index b2294e4318f..7e285113b1c 100644 --- a/erpnext/accounts/doctype/sales_invoice_item/sales_invoice_item.json +++ b/erpnext/accounts/doctype/sales_invoice_item/sales_invoice_item.json @@ -1,5 +1,4 @@ { - "actions": [], "autoname": "hash", "creation": "2013-06-04 11:02:19", "doctype": "DocType", @@ -87,6 +86,7 @@ "edit_references", "sales_order", "so_detail", + "sales_invoice_item", "column_break_74", "delivery_note", "dn_detail", @@ -94,6 +94,7 @@ "accounting_dimensions_section", "cost_center", "dimension_col_break", + "project", "section_break_54", "page_break" ], @@ -783,12 +784,28 @@ "fieldtype": "Link", "label": "Finance Book", "options": "Finance Book" + }, + { + "fieldname": "project", + "fieldtype": "Link", + "label": "Project", + "options": "Project" + }, + { + "depends_on": "eval:parent.update_stock == 1", + "fieldname": "sales_invoice_item", + "fieldtype": "Data", + "ignore_user_permissions": 1, + "label": "Sales Invoice Item", + "no_copy": 1, + "print_hide": 1, + "read_only": 1 } ], "idx": 1, "istable": 1, "links": [], - "modified": "2019-12-04 12:22:38.517710", + "modified": "2020-08-20 11:24:41.749986", "modified_by": "Administrator", "module": "Accounts", "name": "Sales Invoice Item", diff --git a/erpnext/accounts/doctype/sales_invoice_payment/sales_invoice_payment.json b/erpnext/accounts/doctype/sales_invoice_payment/sales_invoice_payment.json index 52cf810ae4c..f5398579581 100644 --- a/erpnext/accounts/doctype/sales_invoice_payment/sales_invoice_payment.json +++ b/erpnext/accounts/doctype/sales_invoice_payment/sales_invoice_payment.json @@ -1,314 +1,89 @@ { - "allow_copy": 0, - "allow_events_in_timeline": 0, - "allow_guest_to_view": 0, - "allow_import": 0, - "allow_rename": 0, - "beta": 0, - "creation": "2016-05-08 23:49:38.842621", - "custom": 0, - "docstatus": 0, - "doctype": "DocType", - "document_type": "", - "editable_grid": 1, + "creation": "2016-05-08 23:49:38.842621", + "doctype": "DocType", + "editable_grid": 1, + "engine": "InnoDB", + "field_order": [ + "default", + "mode_of_payment", + "amount", + "column_break_3", + "account", + "type", + "base_amount", + "clearance_date" + ], "fields": [ { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "depends_on": "eval:parent.doctype == 'POS Profile'", - "fetch_if_empty": 0, - "fieldname": "default", - "fieldtype": "Check", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 1, - "in_standard_filter": 0, - "label": "Default", - "length": 0, - "no_copy": 0, - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 - }, + "default": "0", + "depends_on": "eval:parent.doctype == 'POS Profile'", + "fieldname": "default", + "fieldtype": "Check", + "in_list_view": 1, + "label": "Default" + }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fetch_if_empty": 0, - "fieldname": "mode_of_payment", - "fieldtype": "Link", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 1, - "in_standard_filter": 0, - "label": "Mode of Payment", - "length": 0, - "no_copy": 0, - "options": "Mode of Payment", - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 1, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 - }, + "fieldname": "mode_of_payment", + "fieldtype": "Link", + "in_list_view": 1, + "label": "Mode of Payment", + "options": "Mode of Payment", + "reqd": 1 + }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "default": "0", - "depends_on": "eval:parent.doctype == 'Sales Invoice'", - "fetch_if_empty": 0, - "fieldname": "amount", - "fieldtype": "Currency", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 1, - "in_standard_filter": 0, - "label": "Amount", - "length": 0, - "no_copy": 0, - "options": "currency", - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 1, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 - }, + "default": "0", + "depends_on": "eval:parent.doctype == 'Sales Invoice'", + "fieldname": "amount", + "fieldtype": "Currency", + "in_list_view": 1, + "label": "Amount", + "options": "currency", + "reqd": 1 + }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fetch_if_empty": 0, - "fieldname": "column_break_3", - "fieldtype": "Column Break", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "length": 0, - "no_copy": 0, - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 - }, + "fieldname": "column_break_3", + "fieldtype": "Column Break" + }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fetch_if_empty": 0, - "fieldname": "account", - "fieldtype": "Link", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "Account", - "length": 0, - "no_copy": 0, - "options": "Account", - "permlevel": 0, - "precision": "", - "print_hide": 1, - "print_hide_if_no_value": 0, - "read_only": 1, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 - }, + "fieldname": "account", + "fieldtype": "Link", + "label": "Account", + "options": "Account", + "print_hide": 1, + "read_only": 1 + }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fetch_from": "mode_of_payment.type", - "fetch_if_empty": 0, - "fieldname": "type", - "fieldtype": "Read Only", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "Type", - "length": 0, - "no_copy": 0, - "options": "", - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 - }, + "fetch_from": "mode_of_payment.type", + "fieldname": "type", + "fieldtype": "Read Only", + "label": "Type" + }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fetch_if_empty": 0, - "fieldname": "base_amount", - "fieldtype": "Currency", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "Base Amount (Company Currency)", - "length": 0, - "no_copy": 1, - "options": "Company:company:default_currency", - "permlevel": 0, - "precision": "", - "print_hide": 1, - "print_hide_if_no_value": 0, - "read_only": 1, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 - }, + "fieldname": "base_amount", + "fieldtype": "Currency", + "label": "Base Amount (Company Currency)", + "no_copy": 1, + "options": "Company:company:default_currency", + "print_hide": 1, + "read_only": 1 + }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fetch_if_empty": 0, - "fieldname": "clearance_date", - "fieldtype": "Date", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "Clearance Date", - "length": 0, - "no_copy": 0, - "permlevel": 0, - "precision": "", - "print_hide": 1, - "print_hide_if_no_value": 0, - "read_only": 1, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 + "fieldname": "clearance_date", + "fieldtype": "Date", + "label": "Clearance Date", + "no_copy": 1, + "print_hide": 1, + "read_only": 1 } - ], - "has_web_view": 0, - "hide_heading": 0, - "hide_toolbar": 0, - "idx": 0, - "image_view": 0, - "in_create": 0, - "is_submittable": 0, - "issingle": 0, - "istable": 1, - "max_attachments": 0, - "modified": "2019-03-19 14:54:56.524556", - "modified_by": "Administrator", - "module": "Accounts", - "name": "Sales Invoice Payment", - "name_case": "", - "owner": "Administrator", - "permissions": [], - "quick_entry": 1, - "read_only": 0, - "read_only_onload": 0, - "show_name_in_global_search": 0, - "sort_field": "modified", - "sort_order": "DESC", - "track_changes": 0, - "track_seen": 0, - "track_views": 0 + ], + "istable": 1, + "modified": "2020-08-03 13:07:49.260534", + "modified_by": "Administrator", + "module": "Accounts", + "name": "Sales Invoice Payment", + "owner": "Administrator", + "permissions": [], + "quick_entry": 1, + "sort_field": "modified", + "sort_order": "DESC" } \ No newline at end of file diff --git a/erpnext/accounts/general_ledger.py b/erpnext/accounts/general_ledger.py index 5ba455c1315..86504ba47fd 100644 --- a/erpnext/accounts/general_ledger.py +++ b/erpnext/accounts/general_ledger.py @@ -45,8 +45,8 @@ def validate_accounting_period(gl_map): }, as_dict=1) if accounting_periods: - frappe.throw(_("You can't create accounting entries in the closed accounting period {0}") - .format(accounting_periods[0].name), ClosedAccountingPeriod) + frappe.throw(_("You cannot create or cancel any accounting entries within in the closed Accounting Period {0}") + .format(frappe.bold(accounting_periods[0].name)), ClosedAccountingPeriod) def process_gl_map(gl_map, merge_entries=True): if merge_entries: @@ -296,6 +296,7 @@ def delete_gl_entries(gl_entries=None, voucher_type=None, voucher_no=None, where voucher_type=%s and voucher_no=%s""", (voucher_type, voucher_no), as_dict=True) if gl_entries: + validate_accounting_period(gl_entries) check_freezing_date(gl_entries[0]["posting_date"], adv_adj) frappe.db.sql("""delete from `tabGL Entry` where voucher_type=%s and voucher_no=%s""", diff --git a/erpnext/accounts/page/bank_reconciliation/bank_reconciliation.py b/erpnext/accounts/page/bank_reconciliation/bank_reconciliation.py index bd4b4d7e0b1..b4fffec7d40 100644 --- a/erpnext/accounts/page/bank_reconciliation/bank_reconciliation.py +++ b/erpnext/accounts/page/bank_reconciliation/bank_reconciliation.py @@ -285,6 +285,8 @@ def get_matching_transactions_payments(description_matching): else: return [] +@frappe.whitelist() +@frappe.validate_and_sanitize_search_inputs def payment_entry_query(doctype, txt, searchfield, start, page_len, filters): account = frappe.db.get_value("Bank Account", filters.get("bank_account"), "account") if not account: @@ -313,6 +315,8 @@ def payment_entry_query(doctype, txt, searchfield, start, page_len, filters): } ) +@frappe.whitelist() +@frappe.validate_and_sanitize_search_inputs def journal_entry_query(doctype, txt, searchfield, start, page_len, filters): account = frappe.db.get_value("Bank Account", filters.get("bank_account"), "account") @@ -348,6 +352,8 @@ def journal_entry_query(doctype, txt, searchfield, start, page_len, filters): } ) +@frappe.whitelist() +@frappe.validate_and_sanitize_search_inputs def sales_invoices_query(doctype, txt, searchfield, start, page_len, filters): return frappe.db.sql(""" SELECT @@ -373,4 +379,4 @@ def sales_invoices_query(doctype, txt, searchfield, start, page_len, filters): 'start': start, 'page_len': page_len } - ) \ No newline at end of file + ) diff --git a/erpnext/accounts/page/pos/pos.js b/erpnext/accounts/page/pos/pos.js index 28c9149c561..1ed3f2341eb 100755 --- a/erpnext/accounts/page/pos/pos.js +++ b/erpnext/accounts/page/pos/pos.js @@ -1062,7 +1062,7 @@ erpnext.pos.PointOfSale = erpnext.taxes_and_totals.extend({ } if(index < me.page_len) { $(frappe.render_template("pos_item", { - item_code: obj.name, + item_code: escape(obj.name), item_price: item_price, item_name: obj.name === obj.item_name ? "" : obj.item_name, item_image: obj.image, @@ -1098,7 +1098,8 @@ erpnext.pos.PointOfSale = erpnext.taxes_and_totals.extend({ get_items: function (item_code) { // To search item as per the key enter - + item_code = unescape(item_code); + item_code = item_code === "undefined" ? undefined : item_code; var me = this; this.item_serial_no = {}; this.item_batch_no = {}; @@ -1164,7 +1165,7 @@ erpnext.pos.PointOfSale = erpnext.taxes_and_totals.extend({ $(this).addClass('active'); me.numeric_val = ""; me.numeric_id = "" - me.item_code = $(this).attr("data-item-code"); + me.item_code = unescape($(this).attr("data-item-code")); me.render_selected_item() me.bind_qty_event() me.update_rate() @@ -1176,33 +1177,33 @@ erpnext.pos.PointOfSale = erpnext.taxes_and_totals.extend({ var me = this; $(this.wrapper).on("change", ".pos-item-qty", function () { - var item_code = $(this).parents(".pos-selected-item-action").attr("data-item-code"); + var item_code = unescape($(this).parents(".pos-selected-item-action").attr("data-item-code")); var qty = $(this).val(); me.update_qty(item_code, qty); me.update_value(); }) $(this.wrapper).on("focusout", ".pos-item-qty", function () { - var item_code = $(this).parents(".pos-selected-item-action").attr("data-item-code"); + var item_code = unescape($(this).parents(".pos-selected-item-action").attr("data-item-code")); var qty = $(this).val(); me.update_qty(item_code, qty, true); me.update_value(); }) $(this.wrapper).find("[data-action='increase-qty']").on("click", function () { - var item_code = $(this).parents(".pos-bill-item").attr("data-item-code"); + var item_code = unescape($(this).parents(".pos-bill-item").attr("data-item-code")); var qty = flt($(this).parents(".pos-bill-item").find('.pos-item-qty').val()) + 1; me.update_qty(item_code, qty); }) $(this.wrapper).find("[data-action='decrease-qty']").on("click", function () { - var item_code = $(this).parents(".pos-bill-item").attr("data-item-code"); + var item_code = unescape($(this).parents(".pos-bill-item").attr("data-item-code")); var qty = flt($(this).parents(".pos-bill-item").find('.pos-item-qty').val()) - 1; me.update_qty(item_code, qty); }) $(this.wrapper).on("change", ".pos-item-disc", function () { - var item_code = $(this).parents(".pos-selected-item-action").attr("data-item-code"); + var item_code = unescape($(this).parents(".pos-selected-item-action").attr("data-item-code")); var discount = $(this).val(); if(discount > 100){ discount = $(this).val(''); @@ -1253,7 +1254,7 @@ erpnext.pos.PointOfSale = erpnext.taxes_and_totals.extend({ update_rate: function () { var me = this; $(this.wrapper).on("change", ".pos-item-price", function () { - var item_code = $(this).parents(".pos-selected-item-action").attr("data-item-code"); + var item_code = unescape($(this).parents(".pos-selected-item-action").attr("data-item-code")); me.set_item_details(item_code, "rate", $(this).val()); me.update_value() }) @@ -1282,9 +1283,17 @@ erpnext.pos.PointOfSale = erpnext.taxes_and_totals.extend({ this.child_doc = this.get_child_item(this.item_code); $(this.wrapper).find('.selected-item').empty(); if(this.child_doc.length) { - this.child_doc[0]["allow_user_to_edit_rate"] = this.pos_profile_data["allow_user_to_edit_rate"] ? true : false, - this.child_doc[0]["allow_user_to_edit_discount"] = this.pos_profile_data["allow_user_to_edit_discount"] ? true : false; - this.selected_row = $(frappe.render_template("pos_selected_item", this.child_doc[0])) + this.selected_row = $(frappe.render_template("pos_selected_item", { + idx: this.child_doc[0].idx, + item_code: escape(this.child_doc[0].item_code), + qty: this.child_doc[0].qty, + price_list_rate: this.child_doc[0].price_list_rate, + allow_user_to_edit_rate: this.pos_profile_data["allow_user_to_edit_rate"] ? true : false, + allow_user_to_edit_discount: this.pos_profile_data["allow_user_to_edit_discount"] ? true : false, + discount_percentage: this.child_doc[0].discount_percentage, + rate: this.child_doc[0].rate, + amount: this.child_doc[0].amount + })) $(this.wrapper).find('.selected-item').html(this.selected_row) } @@ -1535,7 +1544,7 @@ erpnext.pos.PointOfSale = erpnext.taxes_and_totals.extend({ $.each(this.frm.doc.items || [], function (i, d) { $(frappe.render_template("pos_bill_item_new", { - item_code: d.item_code, + item_code: escape(d.item_code), item_name: (d.item_name === d.item_code || !d.item_name) ? "" : ("
" + d.item_name), qty: d.qty, discount_percentage: d.discount_percentage || 0.0, diff --git a/erpnext/accounts/report/account_balance/test_account_balance.py b/erpnext/accounts/report/account_balance/test_account_balance.py index 5544fc46738..b6ced312d09 100644 --- a/erpnext/accounts/report/account_balance/test_account_balance.py +++ b/erpnext/accounts/report/account_balance/test_account_balance.py @@ -61,7 +61,7 @@ def make_sales_invoice(): debit_to = 'Debtors - _TC2', income_account = 'Sales - _TC2', expense_account = 'Cost of Goods Sold - _TC2', - cost_center = '_Test Company 2 - _TC2') + cost_center = 'Main - _TC2') diff --git a/erpnext/accounts/report/accounts_receivable/accounts_receivable.py b/erpnext/accounts/report/accounts_receivable/accounts_receivable.py index 724e8b74437..04fc33220d9 100755 --- a/erpnext/accounts/report/accounts_receivable/accounts_receivable.py +++ b/erpnext/accounts/report/accounts_receivable/accounts_receivable.py @@ -643,8 +643,10 @@ class ReceivablePayableReport(object): account_type = "Receivable" if self.party_type == "Customer" else "Payable" accounts = [d.name for d in frappe.get_all("Account", filters={"account_type": account_type, "company": self.filters.company})] - conditions.append("account in (%s)" % ','.join(['%s'] *len(accounts))) - values += accounts + + if accounts: + conditions.append("account in (%s)" % ','.join(['%s'] *len(accounts))) + values += accounts def add_customer_filters(self, conditions, values): if self.filters.get("customer_group"): diff --git a/erpnext/accounts/report/accounts_receivable/test_accounts_receivable.py b/erpnext/accounts/report/accounts_receivable/test_accounts_receivable.py index f0274b44723..2ff5b531c51 100644 --- a/erpnext/accounts/report/accounts_receivable/test_accounts_receivable.py +++ b/erpnext/accounts/report/accounts_receivable/test_accounts_receivable.py @@ -63,7 +63,7 @@ def make_sales_invoice(): debit_to = 'Debtors - _TC2', income_account = 'Sales - _TC2', expense_account = 'Cost of Goods Sold - _TC2', - cost_center = '_Test Company 2 - _TC2', + cost_center = 'Main - _TC2', do_not_save=1) si.append('payment_schedule', dict(due_date=getdate(add_days(today(), 30)), invoice_portion=30.00, payment_amount=30)) @@ -83,14 +83,14 @@ def make_payment(docname): def make_credit_note(docname): create_sales_invoice(company="_Test Company 2", - customer = '_Test Customer 2', - currency = 'EUR', - qty = -1, - warehouse = 'Finished Goods - _TC2', - debit_to = 'Debtors - _TC2', - income_account = 'Sales - _TC2', - expense_account = 'Cost of Goods Sold - _TC2', - cost_center = '_Test Company 2 - _TC2', - is_return = 1, - return_against = docname) + customer = '_Test Customer 2', + currency = 'EUR', + qty = -1, + warehouse = 'Finished Goods - _TC2', + debit_to = 'Debtors - _TC2', + income_account = 'Sales - _TC2', + expense_account = 'Cost of Goods Sold - _TC2', + cost_center = 'Main - _TC2', + is_return = 1, + return_against = docname) diff --git a/erpnext/accounts/report/financial_statements.py b/erpnext/accounts/report/financial_statements.py index 7e96b9e237b..58117b68c52 100644 --- a/erpnext/accounts/report/financial_statements.py +++ b/erpnext/accounts/report/financial_statements.py @@ -8,6 +8,7 @@ from __future__ import unicode_literals import re from past.builtins import cmp import functools +import math import frappe, erpnext from erpnext.accounts.report.utils import get_currency, convert_to_presentation_currency @@ -42,7 +43,7 @@ def get_period_list(from_fiscal_year, to_fiscal_year, periodicity, accumulated_v start_date = year_start_date months = get_months(year_start_date, year_end_date) - for i in range(months // months_to_add): + for i in range(math.ceil(months / months_to_add)): period = frappe._dict({ "from_date": start_date }) diff --git a/erpnext/accounts/utils.py b/erpnext/accounts/utils.py index d1aa4011b63..f27911f7a3a 100644 --- a/erpnext/accounts/utils.py +++ b/erpnext/accounts/utils.py @@ -113,7 +113,7 @@ def get_balance_on(account=None, date=None, party_type=None, party=None, company acc = frappe.get_doc("Account", account) try: - year_start_date = get_fiscal_year(date, verbose=0)[1] + year_start_date = get_fiscal_year(date, company=company, verbose=0)[1] except FiscalYearError: if getdate(date) > getdate(nowdate()): # if fiscal year not found and the date is greater than today @@ -124,14 +124,12 @@ def get_balance_on(account=None, date=None, party_type=None, party=None, company # hence, assuming balance as 0.0 return 0.0 - allow_cost_center_in_entry_of_bs_account = get_allow_cost_center_in_entry_of_bs_account() - if account: report_type = acc.report_type else: report_type = "" - if cost_center and (allow_cost_center_in_entry_of_bs_account or report_type =='Profit and Loss'): + if cost_center and report_type == 'Profit and Loss': cc = frappe.get_doc("Cost Center", cost_center) if cc.is_group: cond.append(""" exists ( @@ -658,7 +656,8 @@ def get_outstanding_invoices(party_type, party, account, condition=None, filters invoice_list = frappe.db.sql(""" select voucher_no, voucher_type, posting_date, due_date, - ifnull(sum({dr_or_cr}), 0) as invoice_amount + ifnull(sum({dr_or_cr}), 0) as invoice_amount, + account_currency as currency from `tabGL Entry` where @@ -715,7 +714,8 @@ def get_outstanding_invoices(party_type, party, account, condition=None, filters 'invoice_amount': flt(d.invoice_amount), 'payment_amount': payment_amount, 'outstanding_amount': outstanding_amount, - 'due_date': d.due_date + 'due_date': d.due_date, + 'currency': d.currency }) ) @@ -767,10 +767,10 @@ def get_children(doctype, parent, company, is_root=False): company_currency = frappe.get_cached_value('Company', company, "default_currency") for each in acc: each["company_currency"] = company_currency - each["balance"] = flt(get_balance_on(each.get("value"), in_account_currency=False)) + each["balance"] = flt(get_balance_on(each.get("value"), in_account_currency=False, company=company)) if each.account_currency != company_currency: - each["balance_in_account_currency"] = flt(get_balance_on(each.get("value"))) + each["balance_in_account_currency"] = flt(get_balance_on(each.get("value"), company=company)) return acc @@ -817,7 +817,7 @@ def create_payment_gateway_account(gateway): pass @frappe.whitelist() -def update_cost_center(docname, cost_center_name, cost_center_number, company): +def update_cost_center(docname, cost_center_name, cost_center_number, company, merge): ''' Renames the document by adding the number as a prefix to the current name and updates all transaction where it was present. @@ -833,7 +833,7 @@ def update_cost_center(docname, cost_center_name, cost_center_number, company): new_name = get_autoname_with_number(cost_center_number, cost_center_name, docname, company) if docname != new_name: - frappe.rename_doc("Cost Center", docname, new_name, force=1) + frappe.rename_doc("Cost Center", docname, new_name, force=1, merge=merge) return new_name def validate_field_number(doctype_name, docname, number_value, company, field_name): @@ -877,11 +877,6 @@ def get_coa(doctype, parent, is_root, chart=None): return accounts -def get_allow_cost_center_in_entry_of_bs_account(): - def generator(): - return cint(frappe.db.get_value('Accounts Settings', None, 'allow_cost_center_in_entry_of_bs_account')) - return frappe.local_cache("get_allow_cost_center_in_entry_of_bs_account", (), generator, regenerate_if_none=True) - def get_stock_accounts(company): return frappe.get_all("Account", filters = { "account_type": "Stock", diff --git a/erpnext/assets/doctype/asset/asset.py b/erpnext/assets/doctype/asset/asset.py index ad7edd1ead2..bfb44e5c30a 100644 --- a/erpnext/assets/doctype/asset/asset.py +++ b/erpnext/assets/doctype/asset/asset.py @@ -407,6 +407,8 @@ class Asset(AccountsController): row.expected_value_after_useful_life = asset_value_after_full_schedule def validate_cancellation(self): + if self.status in ("In Maintenance", "Out of Order"): + frappe.throw(_("There are active maintenance or repairs against the asset. You must complete all of them before cancelling the asset.")) if self.status not in ("Submitted", "Partially Depreciated", "Fully Depreciated"): frappe.throw(_("Asset cannot be cancelled, as it is already {0}").format(self.status)) diff --git a/erpnext/assets/doctype/asset/depreciation.py b/erpnext/assets/doctype/asset/depreciation.py index ad671ba0f2c..8f0afb42b2c 100644 --- a/erpnext/assets/doctype/asset/depreciation.py +++ b/erpnext/assets/doctype/asset/depreciation.py @@ -58,7 +58,8 @@ def make_depreciation_entry(asset_name, date=None): "account": accumulated_depreciation_account, "credit_in_account_currency": d.depreciation_amount, "reference_type": "Asset", - "reference_name": asset.name + "reference_name": asset.name, + "cost_center": "" } debit_entry = { @@ -196,12 +197,14 @@ def get_gl_entries_on_asset_disposal(asset, selling_amount=0, finance_book=None) { "account": fixed_asset_account, "credit_in_account_currency": asset.gross_purchase_amount, - "credit": asset.gross_purchase_amount + "credit": asset.gross_purchase_amount, + "cost_center": depreciation_cost_center }, { "account": accumulated_depr_account, "debit_in_account_currency": accumulated_depr_amount, - "debit": accumulated_depr_amount + "debit": accumulated_depr_amount, + "cost_center": depreciation_cost_center } ] diff --git a/erpnext/assets/doctype/asset_maintenance/asset_maintenance.py b/erpnext/assets/doctype/asset_maintenance/asset_maintenance.py index d6adde6a371..8a954b94d1e 100644 --- a/erpnext/assets/doctype/asset_maintenance/asset_maintenance.py +++ b/erpnext/assets/doctype/asset_maintenance/asset_maintenance.py @@ -106,6 +106,7 @@ def update_maintenance_log(asset_maintenance, item_code, item_name, task): maintenance_log.save() @frappe.whitelist() +@frappe.validate_and_sanitize_search_inputs def get_team_members(doctype, txt, searchfield, start, page_len, filters): return frappe.db.get_values('Maintenance Team Member', { 'parent': filters.get("maintenance_team") }) diff --git a/erpnext/assets/doctype/asset_maintenance_log/asset_maintenance_log.py b/erpnext/assets/doctype/asset_maintenance_log/asset_maintenance_log.py index f169f016169..34facd8d050 100644 --- a/erpnext/assets/doctype/asset_maintenance_log/asset_maintenance_log.py +++ b/erpnext/assets/doctype/asset_maintenance_log/asset_maintenance_log.py @@ -11,7 +11,7 @@ from erpnext.assets.doctype.asset_maintenance.asset_maintenance import calculate class AssetMaintenanceLog(Document): def validate(self): - if getdate(self.due_date) < getdate(nowdate()): + if getdate(self.due_date) < getdate(nowdate()) and self.maintenance_status not in ["Completed", "Cancelled"]: self.maintenance_status = "Overdue" if self.maintenance_status == "Completed" and not self.completion_date: @@ -41,6 +41,7 @@ class AssetMaintenanceLog(Document): asset_maintenance_doc.save() @frappe.whitelist() +@frappe.validate_and_sanitize_search_inputs def get_maintenance_tasks(doctype, txt, searchfield, start, page_len, filters): asset_maintenance_tasks = frappe.db.get_values('Asset Maintenance Task', {'parent':filters.get("asset_maintenance")}, 'maintenance_task') return asset_maintenance_tasks diff --git a/erpnext/assets/doctype/asset_maintenance_log/asset_maintenance_log_list.js b/erpnext/assets/doctype/asset_maintenance_log/asset_maintenance_log_list.js index b854413310a..23000e60eff 100644 --- a/erpnext/assets/doctype/asset_maintenance_log/asset_maintenance_log_list.js +++ b/erpnext/assets/doctype/asset_maintenance_log/asset_maintenance_log_list.js @@ -1,14 +1,15 @@ frappe.listview_settings['Asset Maintenance Log'] = { add_fields: ["maintenance_status"], + has_indicator_for_draft: 1, get_indicator: function(doc) { - if(doc.maintenance_status=="Pending") { - return [__("Pending"), "orange"]; - } else if(doc.maintenance_status=="Completed") { - return [__("Completed"), "green"]; - } else if(doc.maintenance_status=="Cancelled") { - return [__("Cancelled"), "red"]; - } else if(doc.maintenance_status=="Overdue") { - return [__("Overdue"), "red"]; + if (doc.maintenance_status=="Planned") { + return [__(doc.maintenance_status), "orange", "status,=," + doc.maintenance_status]; + } else if (doc.maintenance_status=="Completed") { + return [__(doc.maintenance_status), "green", "status,=," + doc.maintenance_status]; + } else if (doc.maintenance_status=="Cancelled") { + return [__(doc.maintenance_status), "red", "status,=," + doc.maintenance_status]; + } else if (doc.maintenance_status=="Overdue") { + return [__(doc.maintenance_status), "red", "status,=," + doc.maintenance_status]; } } }; diff --git a/erpnext/buying/doctype/purchase_order/purchase_order.js b/erpnext/buying/doctype/purchase_order/purchase_order.js index fa4c76f364f..84480468dad 100644 --- a/erpnext/buying/doctype/purchase_order/purchase_order.js +++ b/erpnext/buying/doctype/purchase_order/purchase_order.js @@ -7,12 +7,6 @@ frappe.provide("erpnext.buying"); frappe.ui.form.on("Purchase Order", { setup: function(frm) { - frm.custom_make_buttons = { - 'Purchase Receipt': 'Receipt', - 'Purchase Invoice': 'Invoice', - 'Stock Entry': 'Material to Supplier', - 'Payment Entry': 'Payment' - } frm.set_query("reserve_warehouse", "supplied_items", function() { return { @@ -36,20 +30,6 @@ frappe.ui.form.on("Purchase Order", { }, - refresh: function(frm) { - if(frm.doc.docstatus === 1 && frm.doc.status !== 'Closed' - && flt(frm.doc.per_received) < 100 && flt(frm.doc.per_billed) < 100) { - frm.add_custom_button(__('Update Items'), () => { - erpnext.utils.update_child_items({ - frm: frm, - child_docname: "items", - child_doctype: "Purchase Order Detail", - cannot_add_row: false, - }) - }); - } - }, - onload: function(frm) { set_schedule_date(frm); if (!frm.doc.transaction_date){ @@ -76,6 +56,18 @@ frappe.ui.form.on("Purchase Order Item", { }); erpnext.buying.PurchaseOrderController = erpnext.buying.BuyingController.extend({ + setup: function() { + this.frm.custom_make_buttons = { + 'Purchase Receipt': 'Receipt', + 'Purchase Invoice': 'Invoice', + 'Stock Entry': 'Material to Supplier', + 'Payment Entry': 'Payment', + } + + this._super(); + + }, + refresh: function(doc, cdt, cdn) { var me = this; this._super(); @@ -99,6 +91,16 @@ erpnext.buying.PurchaseOrderController = erpnext.buying.BuyingController.extend( if(doc.docstatus == 1) { if(!in_list(["Closed", "Delivered"], doc.status)) { + if(this.frm.doc.status !== 'Closed' && flt(this.frm.doc.per_received) < 100 && flt(this.frm.doc.per_billed) < 100) { + this.frm.add_custom_button(__('Update Items'), () => { + erpnext.utils.update_child_items({ + frm: this.frm, + child_docname: "items", + child_doctype: "Purchase Order Detail", + cannot_add_row: false, + }) + }); + } if (this.frm.has_perm("submit")) { if(flt(doc.per_billed, 6) < 100 || flt(doc.per_received, 6) < 100) { if (doc.status != "On Hold") { @@ -123,14 +125,14 @@ erpnext.buying.PurchaseOrderController = erpnext.buying.BuyingController.extend( } if(doc.status != "Closed") { if (doc.status != "On Hold") { - if(flt(doc.per_received, 2) < 100 && allow_receipt) { + if(flt(doc.per_received) < 100 && allow_receipt) { cur_frm.add_custom_button(__('Receipt'), this.make_purchase_receipt, __('Create')); if(doc.is_subcontracted==="Yes" && me.has_unsupplied_items()) { cur_frm.add_custom_button(__('Material to Supplier'), function() { me.make_stock_entry(); }, __("Transfer")); } } - if(flt(doc.per_billed, 2) < 100) + if(flt(doc.per_billed) < 100) cur_frm.add_custom_button(__('Invoice'), this.make_purchase_invoice, __('Create')); diff --git a/erpnext/buying/doctype/purchase_order/purchase_order.json b/erpnext/buying/doctype/purchase_order/purchase_order.json index 2018d068edf..5dacfb0e02e 100644 --- a/erpnext/buying/doctype/purchase_order/purchase_order.json +++ b/erpnext/buying/doctype/purchase_order/purchase_order.json @@ -1,5 +1,4 @@ { - "actions": [], "allow_import": 1, "autoname": "naming_series:", "creation": "2013-05-21 16:16:39", @@ -998,6 +997,7 @@ "print_hide": 1 }, { + "collapsible": 1, "fieldname": "subscription_section", "fieldtype": "Section Break", "label": "Subscription Section" @@ -1055,8 +1055,7 @@ "icon": "fa fa-file-text", "idx": 105, "is_submittable": 1, - "links": [], - "modified": "2020-06-12 14:08:11.777120", + "modified": "2020-07-01 12:40:45.240948", "modified_by": "Administrator", "module": "Buying", "name": "Purchase Order", diff --git a/erpnext/buying/doctype/request_for_quotation/request_for_quotation.js b/erpnext/buying/doctype/request_for_quotation/request_for_quotation.js index 455bd68ecff..4a937f7f0d3 100644 --- a/erpnext/buying/doctype/request_for_quotation/request_for_quotation.js +++ b/erpnext/buying/doctype/request_for_quotation/request_for_quotation.js @@ -166,7 +166,8 @@ frappe.ui.form.on("Request for Quotation",{ { "fieldtype": "Select", "label": __("Supplier"), "fieldname": "supplier", "options": doc.suppliers.map(d => d.supplier), - "reqd": 1 }, + "reqd": 1, + "default": doc.suppliers.length === 1 ? doc.suppliers[0].supplier_name : "" }, { "fieldtype": "Button", "label": __('Create Supplier Quotation'), "fieldname": "make_supplier_quotation", "cssClass": "btn-primary" }, ] diff --git a/erpnext/buying/doctype/request_for_quotation/request_for_quotation.py b/erpnext/buying/doctype/request_for_quotation/request_for_quotation.py index 95db33b0f8f..b54a585b97f 100644 --- a/erpnext/buying/doctype/request_for_quotation/request_for_quotation.py +++ b/erpnext/buying/doctype/request_for_quotation/request_for_quotation.py @@ -25,6 +25,7 @@ class RequestforQuotation(BuyingController): self.validate_duplicate_supplier() self.validate_supplier_list() validate_for_items(self) + super(RequestforQuotation, self).set_qty_as_per_stock_uom() self.update_email_id() def validate_duplicate_supplier(self): @@ -50,7 +51,7 @@ class RequestforQuotation(BuyingController): def validate_email_id(self, args): if not args.email_id: - frappe.throw(_("Row {0}: For supplier {0} Email Address is required to send email").format(args.idx, args.supplier)) + frappe.throw(_("Row {0}: For Supplier {0}, Email Address is Required to Send Email").format(args.idx, args.supplier)) def on_submit(self): frappe.db.set(self, 'status', 'Submitted') @@ -153,7 +154,7 @@ class RequestforQuotation(BuyingController): sender=sender,attachments = attachments, send_email=True, doctype=self.doctype, name=self.name)["name"] - frappe.msgprint(_("Email sent to supplier {0}").format(data.supplier)) + frappe.msgprint(_("Email Sent to Supplier {0}").format(data.supplier)) def get_attachments(self): attachments = [d.name for d in get_attachments(self.doctype, self.name)] @@ -192,7 +193,7 @@ def send_supplier_emails(rfq_name): def check_portal_enabled(reference_doctype): if not frappe.db.get_value('Portal Menu Item', {'reference_doctype': reference_doctype}, 'enabled'): - frappe.throw(_("Request for Quotation is disabled to access from portal, for more check portal settings.")) + frappe.throw(_("The Access to Request for Quotation From Portal is Disabled. To Allow Access, Enable it in Portal Settings.")) def get_list_context(context=None): from erpnext.controllers.website_list_for_contact import get_list_context @@ -205,6 +206,8 @@ def get_list_context(context=None): }) return list_context +@frappe.whitelist() +@frappe.validate_and_sanitize_search_inputs def get_supplier_contacts(doctype, txt, searchfield, start, page_len, filters): return frappe.db.sql("""select `tabContact`.name from `tabContact`, `tabDynamic Link` where `tabDynamic Link`.link_doctype = 'Supplier' and (`tabDynamic Link`.link_name=%(name)s @@ -258,7 +261,7 @@ def create_supplier_quotation(doc): sq_doc.flags.ignore_permissions = True sq_doc.run_method("set_missing_values") sq_doc.save() - frappe.msgprint(_("Supplier Quotation {0} created").format(sq_doc.name)) + frappe.msgprint(_("Supplier Quotation {0} Created").format(sq_doc.name)) return sq_doc.name except Exception: return None @@ -278,6 +281,7 @@ def create_rfq_items(sq_doc, supplier, data): "description": data.description, "qty": data.qty, "rate": data.rate, + "conversion_factor": data.conversion_factor if data.conversion_factor else None, "supplier_part_no": frappe.db.get_value("Item Supplier", {'parent': data.item_code, 'supplier': supplier}, "supplier_part_no"), "warehouse": data.warehouse or '', "request_for_quotation_item": data.name, diff --git a/erpnext/buying/doctype/request_for_quotation/test_request_for_quotation.py b/erpnext/buying/doctype/request_for_quotation/test_request_for_quotation.py index dbd9f022789..3de9526c4f2 100644 --- a/erpnext/buying/doctype/request_for_quotation/test_request_for_quotation.py +++ b/erpnext/buying/doctype/request_for_quotation/test_request_for_quotation.py @@ -6,12 +6,14 @@ from __future__ import unicode_literals import unittest import frappe -from erpnext.templates.pages.rfq import check_supplier_has_docname_access from frappe.utils import nowdate +from erpnext.stock.doctype.item.test_item import make_item +from erpnext.templates.pages.rfq import check_supplier_has_docname_access +from erpnext.buying.doctype.request_for_quotation.request_for_quotation import make_supplier_quotation +from erpnext.buying.doctype.request_for_quotation.request_for_quotation import create_supplier_quotation class TestRequestforQuotation(unittest.TestCase): def test_quote_status(self): - from erpnext.buying.doctype.request_for_quotation.request_for_quotation import make_supplier_quotation rfq = make_request_for_quotation() self.assertEqual(rfq.get('suppliers')[0].quote_status, 'Pending') @@ -31,7 +33,6 @@ class TestRequestforQuotation(unittest.TestCase): self.assertEqual(rfq.get('suppliers')[1].quote_status, 'No Quote') def test_make_supplier_quotation(self): - from erpnext.buying.doctype.request_for_quotation.request_for_quotation import make_supplier_quotation rfq = make_request_for_quotation() sq = make_supplier_quotation(rfq.name, rfq.get('suppliers')[0].supplier) @@ -51,15 +52,13 @@ class TestRequestforQuotation(unittest.TestCase): self.assertEqual(sq1.get('items')[0].qty, 5) def test_make_supplier_quotation_with_special_characters(self): - from erpnext.buying.doctype.request_for_quotation.request_for_quotation import make_supplier_quotation - frappe.delete_doc_if_exists("Supplier", "_Test Supplier '1", force=1) supplier = frappe.new_doc("Supplier") supplier.supplier_name = "_Test Supplier '1" supplier.supplier_group = "_Test Supplier Group" supplier.insert() - rfq = make_request_for_quotation(supplier_wt_appos) + rfq = make_request_for_quotation(supplier_data=supplier_wt_appos) sq = make_supplier_quotation(rfq.name, supplier_wt_appos[0].get("supplier")) sq.submit() @@ -76,7 +75,6 @@ class TestRequestforQuotation(unittest.TestCase): frappe.form_dict.name = None def test_make_supplier_quotation_from_portal(self): - from erpnext.buying.doctype.request_for_quotation.request_for_quotation import create_supplier_quotation rfq = make_request_for_quotation() rfq.get('items')[0].rate = 100 rfq.supplier = rfq.suppliers[0].supplier @@ -90,12 +88,34 @@ class TestRequestforQuotation(unittest.TestCase): self.assertEqual(supplier_quotation_doc.get('items')[0].qty, 5) self.assertEqual(supplier_quotation_doc.get('items')[0].amount, 500) + def test_make_multi_uom_supplier_quotation(self): + item_code = "_Test Multi UOM RFQ Item" + if not frappe.db.exists('Item', item_code): + item = make_item(item_code, {'stock_uom': '_Test UOM'}) + row = item.append('uoms', { + 'uom': 'Kg', + 'conversion_factor': 2 + }) + row.db_update() -def make_request_for_quotation(supplier_data=None): + rfq = make_request_for_quotation(item_code="_Test Multi UOM RFQ Item", uom="Kg", conversion_factor=2) + rfq.get('items')[0].rate = 100 + rfq.supplier = rfq.suppliers[0].supplier + + self.assertEqual(rfq.items[0].stock_qty, 10) + + supplier_quotation_name = create_supplier_quotation(rfq) + supplier_quotation = frappe.get_doc('Supplier Quotation', supplier_quotation_name) + + self.assertEqual(supplier_quotation.items[0].qty, 5) + self.assertEqual(supplier_quotation.items[0].stock_qty, 10) + +def make_request_for_quotation(**args): """ :param supplier_data: List containing supplier data """ - supplier_data = supplier_data if supplier_data else get_supplier_data() + args = frappe._dict(args) + supplier_data = args.get("supplier_data") if args.get("supplier_data") else get_supplier_data() rfq = frappe.new_doc('Request for Quotation') rfq.transaction_date = nowdate() rfq.status = 'Draft' @@ -106,11 +126,13 @@ def make_request_for_quotation(supplier_data=None): rfq.append('suppliers', data) rfq.append("items", { - "item_code": "_Test Item", + "item_code": args.item_code or "_Test Item", "description": "_Test Item", - "uom": "_Test UOM", - "qty": 5, - "warehouse": "_Test Warehouse - _TC", + "uom": args.uom or "_Test UOM", + "stock_uom": args.stock_uom or "_Test UOM", + "qty": args.qty or 5, + "conversion_factor": args.conversion_factor or 1.0, + "warehouse": args.warehouse or "_Test Warehouse - _TC", "schedule_date": nowdate() }) diff --git a/erpnext/buying/doctype/request_for_quotation_item/request_for_quotation_item.json b/erpnext/buying/doctype/request_for_quotation_item/request_for_quotation_item.json index 0159df962ec..408f49f5233 100644 --- a/erpnext/buying/doctype/request_for_quotation_item/request_for_quotation_item.json +++ b/erpnext/buying/doctype/request_for_quotation_item/request_for_quotation_item.json @@ -1,4 +1,5 @@ { + "actions": [], "autoname": "hash", "creation": "2016-02-25 08:04:02.452958", "doctype": "DocType", @@ -9,6 +10,7 @@ "supplier_part_no", "column_break_3", "item_name", + "schedule_date", "section_break_5", "description", "item_group", @@ -18,9 +20,11 @@ "image_view", "quantity", "qty", + "stock_uom", "col_break2", - "schedule_date", "uom", + "conversion_factor", + "stock_qty", "warehouse_and_reference", "warehouse", "project_name", @@ -33,7 +37,7 @@ "fields": [ { "bold": 1, - "columns": 3, + "columns": 2, "fieldname": "item_code", "fieldtype": "Link", "in_list_view": 1, @@ -98,7 +102,7 @@ { "fieldname": "quantity", "fieldtype": "Section Break", - "label": "Quantity" + "label": "Quantity & Stock" }, { "bold": 1, @@ -129,12 +133,12 @@ { "fieldname": "uom", "fieldtype": "Link", + "in_list_view": 1, "label": "UOM", "oldfieldname": "uom", "oldfieldtype": "Link", "options": "UOM", "print_width": "100px", - "read_only": 1, "reqd": 1, "width": "100px" }, @@ -144,7 +148,7 @@ "label": "Warehouse and Reference" }, { - "columns": 3, + "columns": 2, "fieldname": "warehouse", "fieldtype": "Link", "in_list_view": 1, @@ -202,6 +206,7 @@ }, { "allow_on_submit": 1, + "default": "0", "fieldname": "page_break", "fieldtype": "Check", "label": "Page Break", @@ -219,10 +224,36 @@ { "fieldname": "section_break_23", "fieldtype": "Section Break" + }, + { + "fieldname": "stock_uom", + "fieldtype": "Link", + "label": "Stock UOM", + "options": "UOM", + "print_hide": 1, + "read_only": 1, + "reqd": 1 + }, + { + "fieldname": "conversion_factor", + "fieldtype": "Float", + "label": "UOM Conversion Factor", + "print_hide": 1, + "read_only": 1, + "reqd": 1 + }, + { + "fieldname": "stock_qty", + "fieldtype": "Float", + "label": "Qty as per Stock UOM", + "no_copy": 1, + "print_hide": 1, + "read_only": 1 } ], "istable": 1, - "modified": "2019-05-01 17:50:23.703801", + "links": [], + "modified": "2020-06-12 19:10:36.333441", "modified_by": "Administrator", "module": "Buying", "name": "Request for Quotation Item", diff --git a/erpnext/buying/doctype/supplier/supplier.json b/erpnext/buying/doctype/supplier/supplier.json index e78abd62615..7a18160e39f 100644 --- a/erpnext/buying/doctype/supplier/supplier.json +++ b/erpnext/buying/doctype/supplier/supplier.json @@ -97,7 +97,7 @@ { "fieldname": "default_bank_account", "fieldtype": "Link", - "label": "Default Bank Account", + "label": "Default Company Bank Account", "options": "Bank Account" }, { @@ -385,7 +385,7 @@ "idx": 370, "image_field": "image", "links": [], - "modified": "2020-03-17 09:48:30.578242", + "modified": "2020-06-17 23:28:30", "modified_by": "Administrator", "module": "Buying", "name": "Supplier", diff --git a/erpnext/buying/report/procurement_tracker/test_procurement_tracker.py b/erpnext/buying/report/procurement_tracker/test_procurement_tracker.py index c7204a1f341..44ab767c0a9 100644 --- a/erpnext/buying/report/procurement_tracker/test_procurement_tracker.py +++ b/erpnext/buying/report/procurement_tracker/test_procurement_tracker.py @@ -67,4 +67,5 @@ class TestProcurementTracker(unittest.TestCase): "expected_delivery_date": date_obj, "actual_delivery_date": date_obj } + return expected_data \ No newline at end of file diff --git a/erpnext/buying/utils.py b/erpnext/buying/utils.py index b5598f8d0b2..47b48665b60 100644 --- a/erpnext/buying/utils.py +++ b/erpnext/buying/utils.py @@ -12,7 +12,6 @@ from erpnext.stock.doctype.item.item import validate_end_of_life def update_last_purchase_rate(doc, is_submit): """updates last_purchase_rate in item table for each item""" - import frappe.utils this_purchase_date = frappe.utils.getdate(doc.get('posting_date') or doc.get('transaction_date')) @@ -23,7 +22,7 @@ def update_last_purchase_rate(doc, is_submit): # compare last purchase date and this transaction's date last_purchase_rate = None if last_purchase_details and \ - (last_purchase_details.purchase_date > this_purchase_date): + (doc.get('docstatus') == 2 or last_purchase_details.purchase_date > this_purchase_date): last_purchase_rate = last_purchase_details['base_net_rate'] elif is_submit == 1: # even if this transaction is the latest one, it should be submitted diff --git a/erpnext/change_log/v12/v12_11_0.md b/erpnext/change_log/v12/v12_11_0.md new file mode 100644 index 00000000000..30d93a31af8 --- /dev/null +++ b/erpnext/change_log/v12/v12_11_0.md @@ -0,0 +1,66 @@ +## ERPNext v12.11.0 Release Note + +- Enabled translation on html files in LMS [Proposal] ([#21582](https://github.com/frappe/erpnext/pull/21582)) +- Multi UOM support in Request for Quotation ([#22250](https://github.com/frappe/erpnext/pull/22250)) +- Exploded Item Rate ([#22816](https://github.com/frappe/erpnext/pull/22816)) +- asset maintenance fixes ([#21277](https://github.com/frappe/erpnext/pull/21277)) +- Error due to comma in Pricing rule name ([#22927](https://github.com/frappe/erpnext/pull/22927)) +- Set default reference Id for "On Previous Row Amount" and "On Previous Row Total" ([#22387](https://github.com/frappe/erpnext/pull/22387)) +- fix(Education): course wise assessment report labels ([#22805](https://github.com/frappe/erpnext/pull/22805)) +- ewaybill json had json dump of json dump, and other related fixes ([#22656](https://github.com/frappe/erpnext/pull/22656)) +- Unable to change link from new lead to existing customer ([#22795](https://github.com/frappe/erpnext/pull/22795)) +- Incorrect delivered qty in Supplier-Wise Sales Analytics ([#22642](https://github.com/frappe/erpnext/pull/22642)) +- Dont merge items if both exist in stock reco ([#22784](https://github.com/frappe/erpnext/pull/22784)) +- Status error in purchase invoice ([#22389](https://github.com/frappe/erpnext/pull/22389)) +- Whitelist all query functions for search widget ([#22606](https://github.com/frappe/erpnext/pull/22606)) +- SQL query in accounts receivable, payable reports ([#22891](https://github.com/frappe/erpnext/pull/22891)) +- In POS serial no popup coming two times ([#22268](https://github.com/frappe/erpnext/pull/22268)) +- Cannot cancel assets with repair pending (bp #22440) ([#22453](https://github.com/frappe/erpnext/pull/22453)) +- Take parent cost center for child if no cost center at child ([#22496](https://github.com/frappe/erpnext/pull/22496)) +- Handle nonetype issue for packed items ([#22493](https://github.com/frappe/erpnext/pull/22493)) +- Cannot cancel assets with repair pending ([#22440](https://github.com/frappe/erpnext/pull/22440)) +- Show or hide section or attributes depending on other attributes… ([#22933](https://github.com/frappe/erpnext/pull/22933)) +- Insert Supplier Group via List View (bp #22403) ([#22407](https://github.com/frappe/erpnext/pull/22407)) +- Skip Progress and Completed by fields on Task Duplication ([#22640](https://github.com/frappe/erpnext/pull/22640)) +- Incorrect variable used while adding new item in the submitted Sales Order ([#22308](https://github.com/frappe/erpnext/pull/22308)) +- Incorrect stock value in return case ([#22528](https://github.com/frappe/erpnext/pull/22528)) +- staffing Plan validation ([#22379](https://github.com/frappe/erpnext/pull/22379)) +- Cancellation of accounting transactions within closed accounting period ([#22986](https://github.com/frappe/erpnext/pull/22986)) +- Not able to submit sales invoice ([#22699](https://github.com/frappe/erpnext/pull/22699)) +- Offline pos not working for special character item ([#22391](https://github.com/frappe/erpnext/pull/22391)) +- Due to decimal issue make purchase receipt button not showing in Purchase Order ([#22643](https://github.com/frappe/erpnext/pull/22643)) +- Stock Reconciliation Invalid Quantity for Batched Item ([#22716](https://github.com/frappe/erpnext/pull/22716)) +- Quality procedure fixes ([#22287](https://github.com/frappe/erpnext/pull/22287)) +- Set label if domains is set ([#22523](https://github.com/frappe/erpnext/pull/22523)) +- Update item tax only if item code available ([#22575](https://github.com/frappe/erpnext/pull/22575)) +- Inclusive tax based on item quantity ([#23015](https://github.com/frappe/erpnext/pull/23015)) +- Quotation lost reason options fix ([#23016](https://github.com/frappe/erpnext/pull/23016)) +- GSTR 1 report for exports without payment of Tax ([#22968](https://github.com/frappe/erpnext/pull/22968)) +- Period list fixes in financial statements ([#22679](https://github.com/frappe/erpnext/pull/22679)) +- Don't set asset maintenance log status as Overdue when Completed or Cancelled ([#23012](https://github.com/frappe/erpnext/pull/23012)) +- Update state code and union territory for Daman and Diu ([#22989](https://github.com/frappe/erpnext/pull/22989)) +- Set Root as Parent if no parent in new tree view node ([#22507](https://github.com/frappe/erpnext/pull/22507)) +- Serial no / batch no Popup is coming for the non serialized items ([#22362](https://github.com/frappe/erpnext/pull/22362)) +- update shopify api version ([#22284](https://github.com/frappe/erpnext/pull/22284)) +- Unable to create batched item ([#22332](https://github.com/frappe/erpnext/pull/22332)) +- Incorrect balance qty in stock ledger report ([#22649](https://github.com/frappe/erpnext/pull/22649)) +- Setup status indicators for Job Offer and Job Applicant (v12) ([#22444](https://github.com/frappe/erpnext/pull/22444)) +- Set half day date None if half day is unchecked ([#22905](https://github.com/frappe/erpnext/pull/22905)) +- Completed qty not updated in work order ([#22372](https://github.com/frappe/erpnext/pull/22372)) +- Handling Empty tables in Production Plan ([#22469](https://github.com/frappe/erpnext/pull/22469)) +- Fetch project-related info in Timesheet (v12) ([#22422](https://github.com/frappe/erpnext/pull/22422)) +- Pricing Rule breaks if no item_code ([#22653](https://github.com/frappe/erpnext/pull/22653)) +- Made "Subscription Section", "Auto Repeat" and Hub Publishing" c… ([#22535](https://github.com/frappe/erpnext/pull/22535)) +- Update Packed Items via Update Items in SO ([#22404](https://github.com/frappe/erpnext/pull/22404)) +- Do not add tax amount in grand total for reverse charge invoices ([#22686](https://github.com/frappe/erpnext/pull/22686)) +- Other charges on income tax in salary slip ([#22798](https://github.com/frappe/erpnext/pull/22798)) +- Serial No Rename does not affect Stock Ledger Entry ([#22780](https://github.com/frappe/erpnext/pull/22780)) +- Quotation list view blank if quotation_to field not set as a standard filter ([#22659](https://github.com/frappe/erpnext/pull/22659)) +- cannot change customer fields if credit exhausted ([#22838](https://github.com/frappe/erpnext/pull/22838)) +- Project link not set in accounts other than profit and loss accounts ([#22049](https://github.com/frappe/erpnext/pull/22049)) +- Bank Clearance of POS purchase invoice ([#22884](https://github.com/frappe/erpnext/pull/22884)) +- Multiple GST fixes ([#22730](https://github.com/frappe/erpnext/pull/22730)) +- Add default cost center in payment reconciliation JV ([#22930](https://github.com/frappe/erpnext/pull/22930)) +- Do not copy Item Tax template from SO to PO ([#22324](https://github.com/frappe/erpnext/pull/22324)) +- Tax amounts in HSN Wise Outward summary ([#22755](https://github.com/frappe/erpnext/pull/22755)) +- Multi currency payment reconciliation ([#22928](https://github.com/frappe/erpnext/pull/22928)) diff --git a/erpnext/controllers/accounts_controller.py b/erpnext/controllers/accounts_controller.py index 3e6c7dc788f..56b872bcfba 100644 --- a/erpnext/controllers/accounts_controller.py +++ b/erpnext/controllers/accounts_controller.py @@ -20,6 +20,7 @@ from erpnext.exceptions import InvalidCurrency from six import text_type from erpnext.accounts.doctype.accounting_dimension.accounting_dimension import get_accounting_dimensions from erpnext.stock.get_item_details import get_item_warehouse +from erpnext.stock.doctype.packed_item.packed_item import make_packing_list force_item_fields = ("item_group", "brand", "stock_uom", "is_fixed_asset", "item_tax_rate", "pricing_rules") @@ -953,7 +954,7 @@ def validate_inclusive_tax(tax, doc): # all rows about the reffered tax should be inclusive _on_previous_row_error("1 - %d" % (tax.row_id,)) elif tax.get("category") == "Valuation": - frappe.throw(_("Valuation type charges can not marked as Inclusive")) + frappe.throw(_("Valuation type charges can not be marked as Inclusive")) def set_balance_in_account_currency(gl_dict, account_currency=None, conversion_rate=None, company_currency=None): @@ -1010,6 +1011,7 @@ def get_advance_journal_entries(party_type, party, party_account, amount_field, def get_advance_payment_entries(party_type, party, party_account, order_doctype, order_list=None, include_unallocated=True, against_all_orders=False, limit=None): party_account_field = "paid_from" if party_type == "Customer" else "paid_to" + currency_field = "paid_from_account_currency" if party_type == "Customer" else "paid_to_account_currency" payment_type = "Receive" if party_type == "Customer" else "Pay" payment_entries_against_order, unallocated_payment_entries = [], [] limit_cond = "limit %s" % limit if limit else "" @@ -1026,14 +1028,15 @@ def get_advance_payment_entries(party_type, party, party_account, order_doctype, select "Payment Entry" as reference_type, t1.name as reference_name, t1.remarks, t2.allocated_amount as amount, t2.name as reference_row, - t2.reference_name as against_order, t1.posting_date + t2.reference_name as against_order, t1.posting_date, + t1.{0} as currency from `tabPayment Entry` t1, `tabPayment Entry Reference` t2 where - t1.name = t2.parent and t1.{0} = %s and t1.payment_type = %s + t1.name = t2.parent and t1.{1} = %s and t1.payment_type = %s and t1.party_type = %s and t1.party = %s and t1.docstatus = 1 - and t2.reference_doctype = %s {1} - order by t1.posting_date {2} - """.format(party_account_field, reference_condition, limit_cond), + and t2.reference_doctype = %s {2} + order by t1.posting_date {3} + """.format(currency_field, party_account_field, reference_condition, limit_cond), [party_account, payment_type, party_type, party, order_doctype] + order_list, as_dict=1) @@ -1298,6 +1301,7 @@ def update_child_qty_rate(parent_doctype, trans_items, parent_doctype_name, chil parent.set_qty_as_per_stock_uom() parent.calculate_taxes_and_totals() if parent_doctype == "Sales Order": + make_packing_list(parent) parent.set_gross_profit() frappe.get_doc('Authorization Control').validate_approving_authority(parent.doctype, parent.company, parent.base_grand_total) diff --git a/erpnext/controllers/buying_controller.py b/erpnext/controllers/buying_controller.py index 0973f165232..c7e6bcf4d65 100644 --- a/erpnext/controllers/buying_controller.py +++ b/erpnext/controllers/buying_controller.py @@ -182,7 +182,7 @@ class BuyingController(StockController): if item.item_code and item.qty and item.item_code in stock_and_asset_items: item_proportion = flt(item.base_net_amount) / stock_and_asset_items_amount if stock_and_asset_items_amount \ else flt(item.qty) / stock_and_asset_items_qty - + if i == (last_item_idx - 1): item.item_tax_amount = flt(valuation_amount_adjustment, self.precision("item_tax_amount", item)) @@ -540,9 +540,19 @@ class BuyingController(StockController): "serial_no": cstr(d.serial_no).strip() }) if self.is_return: - original_incoming_rate = frappe.db.get_value("Stock Ledger Entry", - {"voucher_type": "Purchase Receipt", "voucher_no": self.return_against, - "item_code": d.item_code}, "incoming_rate") + filters = { + "voucher_type": self.doctype, + "voucher_no": self.return_against, + "item_code": d.item_code + } + + if (self.doctype == "Purchase Invoice" and self.update_stock + and d.get("purchase_invoice_item")): + filters["voucher_detail_no"] = d.purchase_invoice_item + elif self.doctype == "Purchase Receipt" and d.get("purchase_receipt_item"): + filters["voucher_detail_no"] = d.purchase_receipt_item + + original_incoming_rate = frappe.db.get_value("Stock Ledger Entry", filters, "incoming_rate") sle.update({ "outgoing_rate": original_incoming_rate @@ -728,7 +738,7 @@ class BuyingController(StockController): if delete_asset and is_auto_create_enabled: # need to delete movements to delete assets otherwise throws link exists error movements = frappe.db.sql( - """SELECT asm.name + """SELECT asm.name FROM `tabAsset Movement` asm, `tabAsset Movement Item` asm_item WHERE asm_item.parent=asm.name and asm_item.asset=%s""", asset.name, as_dict=1) for movement in movements: diff --git a/erpnext/controllers/queries.py b/erpnext/controllers/queries.py index 73ed4b01686..b49198579b8 100644 --- a/erpnext/controllers/queries.py +++ b/erpnext/controllers/queries.py @@ -10,7 +10,9 @@ from collections import defaultdict from erpnext.stock.get_item_details import _get_item_tax_template from frappe.utils import unique - # searches for active employees +# searches for active employees +@frappe.whitelist() +@frappe.validate_and_sanitize_search_inputs def employee_query(doctype, txt, searchfield, start, page_len, filters): conditions = [] fields = get_fields("Employee", ["name", "employee_name"]) @@ -40,6 +42,8 @@ def employee_query(doctype, txt, searchfield, start, page_len, filters): # searches for leads which are not converted +@frappe.whitelist() +@frappe.validate_and_sanitize_search_inputs def lead_query(doctype, txt, searchfield, start, page_len, filters): fields = get_fields("Lead", ["name", "lead_name", "company_name"]) @@ -68,7 +72,9 @@ def lead_query(doctype, txt, searchfield, start, page_len, filters): }) - # searches for customer +# searches for customer +@frappe.whitelist() +@frappe.validate_and_sanitize_search_inputs def customer_query(doctype, txt, searchfield, start, page_len, filters): conditions = [] cust_master_name = frappe.defaults.get_user_default("cust_master_name") @@ -106,8 +112,11 @@ def customer_query(doctype, txt, searchfield, start, page_len, filters): # searches for supplier +@frappe.whitelist() +@frappe.validate_and_sanitize_search_inputs def supplier_query(doctype, txt, searchfield, start, page_len, filters): supp_master_name = frappe.defaults.get_user_default("supp_master_name") + if supp_master_name == "Supplier Name": fields = ["name", "supplier_group"] else: @@ -137,31 +146,50 @@ def supplier_query(doctype, txt, searchfield, start, page_len, filters): }) +@frappe.whitelist() +@frappe.validate_and_sanitize_search_inputs def tax_account_query(doctype, txt, searchfield, start, page_len, filters): company_currency = erpnext.get_company_currency(filters.get('company')) - tax_accounts = frappe.db.sql("""select name, parent_account from tabAccount - where tabAccount.docstatus!=2 - and account_type in (%s) - and is_group = 0 - and company = %s - and account_currency = %s - and `%s` LIKE %s - order by idx desc, name - limit %s, %s""" % - (", ".join(['%s']*len(filters.get("account_type"))), "%s", "%s", searchfield, "%s", "%s", "%s"), - tuple(filters.get("account_type") + [filters.get("company"), company_currency, "%%%s%%" % txt, - start, page_len])) + def get_accounts(with_account_type_filter): + account_type_condition = '' + if with_account_type_filter: + account_type_condition = "AND account_type in %(account_types)s" + + accounts = frappe.db.sql(""" + SELECT name, parent_account + FROM `tabAccount` + WHERE `tabAccount`.docstatus!=2 + {account_type_condition} + AND is_group = 0 + AND company = %(company)s + AND account_currency = %(currency)s + AND `{searchfield}` LIKE %(txt)s + ORDER BY idx DESC, name + LIMIT %(offset)s, %(limit)s + """.format(account_type_condition=account_type_condition, searchfield=searchfield), + dict( + account_types=filters.get("account_type"), + company=filters.get("company"), + currency=company_currency, + txt="%{}%".format(txt), + offset=start, + limit=page_len + ) + ) + + return accounts + + tax_accounts = get_accounts(True) + if not tax_accounts: - tax_accounts = frappe.db.sql("""select name, parent_account from tabAccount - where tabAccount.docstatus!=2 and is_group = 0 - and company = %s and account_currency = %s and `%s` LIKE %s limit %s, %s""" #nosec - % ("%s", "%s", searchfield, "%s", "%s", "%s"), - (filters.get("company"), company_currency, "%%%s%%" % txt, start, page_len)) + tax_accounts = get_accounts(False) return tax_accounts +@frappe.whitelist() +@frappe.validate_and_sanitize_search_inputs def item_query(doctype, txt, searchfield, start, page_len, filters, as_dict=False): conditions = [] @@ -209,7 +237,6 @@ def item_query(doctype, txt, searchfield, start, page_len, filters, as_dict=Fals idx desc, name, item_name limit %(start)s, %(page_len)s """.format( - key=searchfield, columns=columns, scond=searchfields, fcond=get_filters_cond(doctype, filters, conditions).replace('%', '%%'), @@ -224,6 +251,8 @@ def item_query(doctype, txt, searchfield, start, page_len, filters, as_dict=Fals }, as_dict=as_dict) +@frappe.whitelist() +@frappe.validate_and_sanitize_search_inputs def bom(doctype, txt, searchfield, start, page_len, filters): conditions = [] fields = get_fields("BOM", ["name", "item"]) @@ -250,6 +279,8 @@ def bom(doctype, txt, searchfield, start, page_len, filters): }) +@frappe.whitelist() +@frappe.validate_and_sanitize_search_inputs def get_project_name(doctype, txt, searchfield, start, page_len, filters): cond = '' if filters.get('customer'): @@ -276,6 +307,8 @@ def get_project_name(doctype, txt, searchfield, start, page_len, filters): }) +@frappe.whitelist() +@frappe.validate_and_sanitize_search_inputs def get_delivery_notes_to_be_billed(doctype, txt, searchfield, start, page_len, filters, as_dict): fields = get_fields("Delivery Note", ["name", "customer", "posting_date"]) @@ -305,6 +338,8 @@ def get_delivery_notes_to_be_billed(doctype, txt, searchfield, start, page_len, }, {"txt": ("%%%s%%" % txt)}, as_dict=as_dict) +@frappe.whitelist() +@frappe.validate_and_sanitize_search_inputs def get_batch_no(doctype, txt, searchfield, start, page_len, filters): cond = "" if filters.get("posting_date"): @@ -362,6 +397,8 @@ def get_batch_no(doctype, txt, searchfield, start, page_len, filters): limit %(start)s, %(page_len)s""".format(cond, match_conditions=get_match_cond(doctype)), args) +@frappe.whitelist() +@frappe.validate_and_sanitize_search_inputs def get_account_list(doctype, txt, searchfield, start, page_len, filters): filter_list = [] @@ -384,7 +421,8 @@ def get_account_list(doctype, txt, searchfield, start, page_len, filters): fields = ["name", "parent_account"], limit_start=start, limit_page_length=page_len, as_list=True) - +@frappe.whitelist() +@frappe.validate_and_sanitize_search_inputs def get_blanket_orders(doctype, txt, searchfield, start, page_len, filters): return frappe.db.sql("""select distinct bo.name, bo.blanket_order_type, bo.to_date from `tabBlanket Order` bo, `tabBlanket Order Item` boi @@ -401,6 +439,7 @@ def get_blanket_orders(doctype, txt, searchfield, start, page_len, filters): @frappe.whitelist() +@frappe.validate_and_sanitize_search_inputs def get_income_account(doctype, txt, searchfield, start, page_len, filters): from erpnext.controllers.queries import get_match_cond @@ -427,6 +466,7 @@ def get_income_account(doctype, txt, searchfield, start, page_len, filters): @frappe.whitelist() +@frappe.validate_and_sanitize_search_inputs def get_expense_account(doctype, txt, searchfield, start, page_len, filters): from erpnext.controllers.queries import get_match_cond @@ -451,6 +491,7 @@ def get_expense_account(doctype, txt, searchfield, start, page_len, filters): @frappe.whitelist() +@frappe.validate_and_sanitize_search_inputs def warehouse_query(doctype, txt, searchfield, start, page_len, filters): # Should be used when item code is passed in filters. conditions, bin_conditions = [], [] @@ -494,6 +535,7 @@ def get_doctype_wise_filters(filters): @frappe.whitelist() +@frappe.validate_and_sanitize_search_inputs def get_batch_numbers(doctype, txt, searchfield, start, page_len, filters): query = """select batch_id from `tabBatch` where disabled = 0 @@ -507,6 +549,7 @@ def get_batch_numbers(doctype, txt, searchfield, start, page_len, filters): @frappe.whitelist() +@frappe.validate_and_sanitize_search_inputs def item_manufacturer_query(doctype, txt, searchfield, start, page_len, filters): item_filters = [ ['manufacturer', 'like', '%' + txt + '%'], @@ -525,6 +568,7 @@ def item_manufacturer_query(doctype, txt, searchfield, start, page_len, filters) @frappe.whitelist() +@frappe.validate_and_sanitize_search_inputs def get_purchase_receipts(doctype, txt, searchfield, start, page_len, filters): query = """ select pr.name @@ -539,6 +583,7 @@ def get_purchase_receipts(doctype, txt, searchfield, start, page_len, filters): @frappe.whitelist() +@frappe.validate_and_sanitize_search_inputs def get_purchase_invoices(doctype, txt, searchfield, start, page_len, filters): query = """ select pi.name @@ -553,6 +598,7 @@ def get_purchase_invoices(doctype, txt, searchfield, start, page_len, filters): @frappe.whitelist() +@frappe.validate_and_sanitize_search_inputs def get_tax_template(doctype, txt, searchfield, start, page_len, filters): item_doc = frappe.get_cached_doc('Item', filters.get('item_code')) diff --git a/erpnext/controllers/sales_and_purchase_return.py b/erpnext/controllers/sales_and_purchase_return.py index 81fdbbefc35..18b5daf128a 100644 --- a/erpnext/controllers/sales_and_purchase_return.py +++ b/erpnext/controllers/sales_and_purchase_return.py @@ -74,7 +74,7 @@ def validate_returned_items(doc): for d in doc.get("items"): if d.item_code and (flt(d.qty) < 0 or flt(d.get('received_qty')) < 0): if d.item_code not in valid_items: - frappe.throw(_("Row # {0}: Returned Item {1} does not exists in {2} {3}") + frappe.throw(_("Row # {0}: Returned Item {1} does not exist in {2} {3}") .format(d.idx, d.item_code, doc.doctype, doc.return_against)) else: ref = valid_items.get(d.item_code, frappe._dict()) @@ -266,6 +266,8 @@ def make_return_doc(doctype, source_name, target_doc=None): target_doc.purchase_order = source_doc.purchase_order target_doc.purchase_order_item = source_doc.purchase_order_item target_doc.rejected_warehouse = source_doc.rejected_warehouse + target_doc.purchase_receipt_item = source_doc.name + elif doctype == "Purchase Invoice": target_doc.received_qty = -1* source_doc.received_qty target_doc.rejected_qty = -1* source_doc.rejected_qty @@ -276,20 +278,25 @@ def make_return_doc(doctype, source_name, target_doc=None): target_doc.rejected_warehouse = source_doc.rejected_warehouse target_doc.po_detail = source_doc.po_detail target_doc.pr_detail = source_doc.pr_detail + target_doc.purchase_invoice_item = source_doc.name + elif doctype == "Delivery Note": target_doc.against_sales_order = source_doc.against_sales_order target_doc.against_sales_invoice = source_doc.against_sales_invoice target_doc.so_detail = source_doc.so_detail target_doc.si_detail = source_doc.si_detail target_doc.expense_account = source_doc.expense_account + target_doc.dn_detail = source_doc.name if default_warehouse_for_sales_return: target_doc.warehouse = default_warehouse_for_sales_return + elif doctype == "Sales Invoice": target_doc.sales_order = source_doc.sales_order target_doc.delivery_note = source_doc.delivery_note target_doc.so_detail = source_doc.so_detail target_doc.dn_detail = source_doc.dn_detail target_doc.expense_account = source_doc.expense_account + target_doc.sales_invoice_item = source_doc.name if default_warehouse_for_sales_return: target_doc.warehouse = default_warehouse_for_sales_return diff --git a/erpnext/controllers/selling_controller.py b/erpnext/controllers/selling_controller.py index c25ad060674..1399654ffd2 100644 --- a/erpnext/controllers/selling_controller.py +++ b/erpnext/controllers/selling_controller.py @@ -216,7 +216,9 @@ class SellingController(StockController): 'target_warehouse': p.target_warehouse, 'company': self.company, 'voucher_type': self.doctype, - 'allow_zero_valuation': d.allow_zero_valuation_rate + 'allow_zero_valuation': d.allow_zero_valuation_rate, + 'sales_invoice_item': d.get("sales_invoice_item"), + 'delivery_note_item': d.get("dn_detail") })) else: il.append(frappe._dict({ @@ -232,7 +234,9 @@ class SellingController(StockController): 'target_warehouse': d.target_warehouse, 'company': self.company, 'voucher_type': self.doctype, - 'allow_zero_valuation': d.allow_zero_valuation_rate + 'allow_zero_valuation': d.allow_zero_valuation_rate, + 'sales_invoice_item': d.get("sales_invoice_item"), + 'delivery_note_item': d.get("dn_detail") })) return il @@ -301,7 +305,11 @@ class SellingController(StockController): d.conversion_factor = get_conversion_factor(d.item_code, d.uom).get("conversion_factor") or 1.0 return_rate = 0 if cint(self.is_return) and self.return_against and self.docstatus==1: - return_rate = self.get_incoming_rate_for_sales_return(d.item_code, self.return_against) + against_document_no = (d.get("sales_invoice_item") + if self.doctype == "Sales Invoice" else d.get("delivery_note_item")) + + return_rate = self.get_incoming_rate_for_sales_return(d.item_code, + self.return_against, against_document_no) # On cancellation or if return entry submission, make stock ledger entry for # target warehouse first, to update serial no values properly diff --git a/erpnext/controllers/stock_controller.py b/erpnext/controllers/stock_controller.py index ff6ac420208..2f275bb3c91 100644 --- a/erpnext/controllers/stock_controller.py +++ b/erpnext/controllers/stock_controller.py @@ -94,6 +94,7 @@ class StockController(AccountsController): "account": warehouse_account[sle.warehouse]["account"], "against": item_row.expense_account, "cost_center": item_row.cost_center, + "project": item_row.get("project") or self.get("project"), "remarks": self.get("remarks") or "Accounting Entry for Stock", "debit": flt(sle.stock_value_difference, precision), "is_opening": item_row.get("is_opening") or self.get("is_opening") or "No", @@ -104,6 +105,7 @@ class StockController(AccountsController): "account": item_row.expense_account, "against": warehouse_account[sle.warehouse]["account"], "cost_center": item_row.cost_center, + "project": item_row.get("project") or self.get("project"), "remarks": self.get("remarks") or "Accounting Entry for Stock", "credit": flt(sle.stock_value_difference, precision), "project": item_row.get("project") or self.get("project"), @@ -297,14 +299,19 @@ class StockController(AccountsController): return serialized_items - def get_incoming_rate_for_sales_return(self, item_code, against_document): + def get_incoming_rate_for_sales_return(self, item_code, against_document, against_document_no=None): incoming_rate = 0.0 + cond = '' if against_document and item_code: + if against_document_no: + cond = " and voucher_detail_no = %s" %(frappe.db.escape(against_document_no)) + incoming_rate = frappe.db.sql("""select abs(stock_value_difference / actual_qty) from `tabStock Ledger Entry` where voucher_type = %s and voucher_no = %s - and item_code = %s limit 1""", + and item_code = %s {0} limit 1""".format(cond), (self.doctype, against_document, item_code)) + incoming_rate = incoming_rate[0][0] if incoming_rate else 0.0 return incoming_rate diff --git a/erpnext/controllers/taxes_and_totals.py b/erpnext/controllers/taxes_and_totals.py index 82f820c4252..d50461766b0 100644 --- a/erpnext/controllers/taxes_and_totals.py +++ b/erpnext/controllers/taxes_and_totals.py @@ -160,8 +160,9 @@ class calculate_taxes_and_totals(object): for item in self.doc.get("items"): item_tax_map = self._load_item_tax_rate(item.item_tax_rate) cumulated_tax_fraction = 0 + total_inclusive_tax_amount_per_qty = 0 for i, tax in enumerate(self.doc.get("taxes")): - tax.tax_fraction_for_current_item = self.get_current_tax_fraction(tax, item_tax_map) + tax.tax_fraction_for_current_item, inclusive_tax_amount_per_qty = self.get_current_tax_fraction(tax, item_tax_map) if i==0: tax.grand_total_fraction_for_current_item = 1 + tax.tax_fraction_for_current_item @@ -171,9 +172,12 @@ class calculate_taxes_and_totals(object): + tax.tax_fraction_for_current_item cumulated_tax_fraction += tax.tax_fraction_for_current_item + total_inclusive_tax_amount_per_qty += inclusive_tax_amount_per_qty * flt(item.qty) - if cumulated_tax_fraction and not self.discount_amount_applied and item.qty: - item.net_amount = flt(item.amount / (1 + cumulated_tax_fraction)) + if not self.discount_amount_applied and item.qty and (cumulated_tax_fraction or total_inclusive_tax_amount_per_qty): + amount = flt(item.amount) - total_inclusive_tax_amount_per_qty + + item.net_amount = flt(amount / (1 + cumulated_tax_fraction)) item.net_rate = flt(item.net_amount / item.qty, item.precision("net_rate")) item.discount_percentage = flt(item.discount_percentage, item.precision("discount_percentage")) @@ -189,6 +193,7 @@ class calculate_taxes_and_totals(object): from tax inclusive amount """ current_tax_fraction = 0 + inclusive_tax_amount_per_qty = 0 if cint(tax.included_in_print_rate): tax_rate = self._get_tax_rate(tax, item_tax_map) @@ -203,10 +208,15 @@ class calculate_taxes_and_totals(object): elif tax.charge_type == "On Previous Row Total": current_tax_fraction = (tax_rate / 100.0) * \ self.doc.get("taxes")[cint(tax.row_id) - 1].grand_total_fraction_for_current_item + + elif tax.charge_type == "On Item Quantity": + inclusive_tax_amount_per_qty = flt(tax_rate) - if getattr(tax, "add_deduct_tax", None): - current_tax_fraction *= -1.0 if (tax.add_deduct_tax == "Deduct") else 1.0 - return current_tax_fraction + if getattr(tax, "add_deduct_tax", None) and tax.add_deduct_tax == "Deduct": + current_tax_fraction *= -1.0 + inclusive_tax_amount_per_qty *= -1.0 + + return current_tax_fraction, inclusive_tax_amount_per_qty def _get_tax_rate(self, tax, item_tax_map): if tax.account_head in item_tax_map: @@ -320,7 +330,7 @@ class calculate_taxes_and_totals(object): current_tax_amount = (tax_rate / 100.0) * \ self.doc.get("taxes")[cint(tax.row_id) - 1].grand_total_for_current_item elif tax.charge_type == "On Item Quantity": - current_tax_amount = tax_rate * item.stock_qty + current_tax_amount = tax_rate * item.qty self.set_item_wise_tax(item, tax, tax_rate, current_tax_amount) @@ -471,7 +481,7 @@ class calculate_taxes_and_totals(object): actual_taxes_dict = {} for tax in self.doc.get("taxes"): - if tax.charge_type == "Actual": + if tax.charge_type in ["Actual", "On Item Quantity"]: tax_amount = self.get_tax_amount_if_for_valuation_or_deduction(tax.tax_amount, tax) actual_taxes_dict.setdefault(tax.idx, tax_amount) elif tax.row_id in actual_taxes_dict: @@ -593,7 +603,7 @@ class calculate_taxes_and_totals(object): base_rate_with_margin = 0.0 if item.price_list_rate: if item.pricing_rules and not self.doc.ignore_pricing_rule: - for d in item.pricing_rules.split(','): + for d in json.loads(item.pricing_rules): pricing_rule = frappe.get_cached_doc('Pricing Rule', d) if (pricing_rule.margin_type == 'Amount' and pricing_rule.currency == self.doc.currency)\ diff --git a/erpnext/controllers/tests/test_mapper.py b/erpnext/controllers/tests/test_mapper.py index d02308d8f21..66459fdbf8a 100644 --- a/erpnext/controllers/tests/test_mapper.py +++ b/erpnext/controllers/tests/test_mapper.py @@ -13,14 +13,12 @@ class TestMapper(unittest.TestCase): '''Test mapping of multiple source docs on a single target doc''' make_test_records("Item") - items = frappe.get_all("Item", fields = ["name", "item_code"], filters = {'is_sales_item': 1, 'has_variants': 0}) - customers = frappe.get_all("Customer") - if items and customers: - # Make source docs (quotations) and a target doc (sales order) - customer = random.choice(customers).name - qtn1, item_list_1 = self.make_quotation(items, customer) - qtn2, item_list_2 = self.make_quotation(items, customer) - so, item_list_3 = self.make_sales_order() + items = ['_Test Item', '_Test Item 2', '_Test FG Item'] + + # Make source docs (quotations) and a target doc (sales order) + qtn1, item_list_1 = self.make_quotation(items, '_Test Customer') + qtn2, item_list_2 = self.make_quotation(items, '_Test Customer') + so, item_list_3 = self.make_sales_order() # Map source docs to target with corresponding mapper method method = "erpnext.selling.doctype.quotation.quotation.make_sales_order" @@ -28,18 +26,12 @@ class TestMapper(unittest.TestCase): # Assert that all inserted items are present in updated sales order src_items = item_list_1 + item_list_2 + item_list_3 - self.assertEqual(set([d.item_code for d in src_items]), + self.assertEqual(set([d for d in src_items]), set([d.item_code for d in updated_so.items])) - def get_random_items(self, items, limit): - '''Get a number of random items from a list of given items''' - random_items = [] - for i in range(0, limit): - random_items.append(random.choice(items)) - return random_items - def make_quotation(self, items, customer): - item_list = self.get_random_items(items, 3) + def make_quotation(self, item_list, customer): + qtn = frappe.get_doc({ "doctype": "Quotation", "quotation_to": "Customer", @@ -49,7 +41,7 @@ class TestMapper(unittest.TestCase): "valid_till" : add_months(nowdate(), 1) }) for item in item_list: - qtn.append("items", {"qty": "2", "item_code": item.item_code}) + qtn.append("items", {"qty": "2", "item_code": item}) qtn.submit() return qtn, item_list @@ -60,7 +52,7 @@ class TestMapper(unittest.TestCase): "base_rate": 100.0, "description": "CPU", "doctype": "Sales Order Item", - "item_code": "_Test Item Home Desktop 100", + "item_code": "_Test Item", "item_name": "CPU", "parentfield": "items", "qty": 10.0, @@ -72,4 +64,4 @@ class TestMapper(unittest.TestCase): }) so = frappe.get_doc(frappe.get_test_records('Sales Order')[0]) so.insert(ignore_permissions=True) - return so, [item] + return so, [item.item_code] diff --git a/erpnext/controllers/tests/test_qty_based_taxes.py b/erpnext/controllers/tests/test_qty_based_taxes.py index fd9936bae99..aaeac5d9399 100644 --- a/erpnext/controllers/tests/test_qty_based_taxes.py +++ b/erpnext/controllers/tests/test_qty_based_taxes.py @@ -30,6 +30,7 @@ class TestTaxes(unittest.TestCase): self.item_tax_template = frappe.get_doc({ 'doctype': 'Item Tax Template', 'title': uuid4(), + 'company': self.company.name, 'taxes': [ { 'tax_type': self.account.name, diff --git a/erpnext/controllers/trends.py b/erpnext/controllers/trends.py index 092baa4018f..9b4b0eb9173 100644 --- a/erpnext/controllers/trends.py +++ b/erpnext/controllers/trends.py @@ -33,7 +33,7 @@ def validate_filters(filters): frappe.throw(_("{0} is mandatory").format(f)) if not frappe.db.exists("Fiscal Year", filters.get("fiscal_year")): - frappe.throw(_("Fiscal Year: {0} does not exists").format(filters.get("fiscal_year"))) + frappe.throw(_("Fiscal Year {0} Does Not Exist").format(filters.get("fiscal_year"))) if filters.get("based_on") == filters.get("group_by"): frappe.throw(_("'Based On' and 'Group By' can not be same")) diff --git a/erpnext/crm/doctype/opportunity/opportunity.js b/erpnext/crm/doctype/opportunity/opportunity.js index c9b0433fada..9ca46912fa7 100644 --- a/erpnext/crm/doctype/opportunity/opportunity.js +++ b/erpnext/crm/doctype/opportunity/opportunity.js @@ -60,12 +60,18 @@ frappe.ui.form.on("Opportunity", { opportunity_from: function(frm) { frm.toggle_reqd("party_name", frm.doc.opportunity_from); + frm.trigger("setup_opportunity_from"); + frm.set_value("party_name",""); + }, + + setup_opportunity_from: function(frm) { + frm.trigger('setup_queries'); frm.trigger("set_dynamic_field_label"); }, refresh: function(frm) { var doc = frm.doc; - frm.events.opportunity_from(frm); + frm.trigger("setup_opportunity_from"); frm.trigger('toggle_mandatory'); erpnext.toggle_naming_series(); @@ -95,6 +101,7 @@ frappe.ui.form.on("Opportunity", { }); } else { frm.add_custom_button(__("Reopen"), function() { + frm.set_value("lost_reasons",[]) frm.set_value("status", "Open"); frm.save(); }); diff --git a/erpnext/crm/doctype/opportunity/opportunity.json b/erpnext/crm/doctype/opportunity/opportunity.json index 979e4c7a67f..918acbfd885 100644 --- a/erpnext/crm/doctype/opportunity/opportunity.json +++ b/erpnext/crm/doctype/opportunity/opportunity.json @@ -1,5 +1,4 @@ { - "actions": [], "allow_import": 1, "allow_rename": 1, "autoname": "naming_series:", @@ -402,7 +401,7 @@ "fieldname": "lost_reasons", "fieldtype": "Table MultiSelect", "label": "Lost Reasons", - "options": "Lost Reason Detail", + "options": "Opportunity Lost Reason Detail", "read_only": 1 }, { @@ -414,8 +413,7 @@ ], "icon": "fa fa-info-sign", "idx": 195, - "links": [], - "modified": "2020-03-20 12:28:45.228994", + "modified": "2020-08-12 23:34:39.665513", "modified_by": "Administrator", "module": "CRM", "name": "Opportunity", diff --git a/erpnext/crm/doctype/opportunity/opportunity.py b/erpnext/crm/doctype/opportunity/opportunity.py index 12f8fb95ff4..8302978e1c1 100644 --- a/erpnext/crm/doctype/opportunity/opportunity.py +++ b/erpnext/crm/doctype/opportunity/opportunity.py @@ -321,7 +321,7 @@ def auto_close_opportunity(): doc.save() @frappe.whitelist() -def make_opportunity_from_communication(communication, ignore_communication_links=False): +def make_opportunity_from_communication(communication, company, ignore_communication_links=False): from erpnext.crm.doctype.lead.lead import make_lead_from_communication doc = frappe.get_doc("Communication", communication) @@ -333,8 +333,9 @@ def make_opportunity_from_communication(communication, ignore_communication_link opportunity = frappe.get_doc({ "doctype": "Opportunity", + "company": company, "opportunity_from": opportunity_from, - "lead": lead + "party_name": lead }).insert(ignore_permissions=True) link_communication_to_document(doc, "Opportunity", opportunity.name, ignore_communication_links) diff --git a/erpnext/crm/doctype/opportunity_lost_reason_detail/__init__.py b/erpnext/crm/doctype/opportunity_lost_reason_detail/__init__.py new file mode 100644 index 00000000000..e69de29bb2d diff --git a/erpnext/crm/doctype/opportunity_lost_reason_detail/opportunity_lost_reason_detail.json b/erpnext/crm/doctype/opportunity_lost_reason_detail/opportunity_lost_reason_detail.json new file mode 100644 index 00000000000..1bdcb92c81a --- /dev/null +++ b/erpnext/crm/doctype/opportunity_lost_reason_detail/opportunity_lost_reason_detail.json @@ -0,0 +1,29 @@ +{ + "creation": "2020-07-16 16:11:39.830389", + "doctype": "DocType", + "editable_grid": 1, + "engine": "InnoDB", + "field_order": [ + "lost_reason" + ], + "fields": [ + { + "fieldname": "lost_reason", + "fieldtype": "Link", + "in_list_view": 1, + "label": "Opportunity Lost Reason", + "options": "Opportunity Lost Reason" + } + ], + "istable": 1, + "modified": "2020-08-12 23:32:55.930406", + "modified_by": "Administrator", + "module": "CRM", + "name": "Opportunity Lost Reason Detail", + "owner": "Administrator", + "permissions": [], + "quick_entry": 1, + "sort_field": "modified", + "sort_order": "DESC", + "track_changes": 1 +} \ No newline at end of file diff --git a/erpnext/crm/doctype/opportunity_lost_reason_detail/opportunity_lost_reason_detail.py b/erpnext/crm/doctype/opportunity_lost_reason_detail/opportunity_lost_reason_detail.py new file mode 100644 index 00000000000..8723f1d0457 --- /dev/null +++ b/erpnext/crm/doctype/opportunity_lost_reason_detail/opportunity_lost_reason_detail.py @@ -0,0 +1,10 @@ +# -*- coding: utf-8 -*- +# Copyright (c) 2020, Frappe Technologies Pvt. Ltd. and contributors +# For license information, please see license.txt + +from __future__ import unicode_literals +# import frappe +from frappe.model.document import Document + +class OpportunityLostReasonDetail(Document): + pass diff --git a/erpnext/crm/report/lead_owner_efficiency/lead_owner_efficiency.py b/erpnext/crm/report/lead_owner_efficiency/lead_owner_efficiency.py index 6172a75fdd8..cb37fb4edfb 100644 --- a/erpnext/crm/report/lead_owner_efficiency/lead_owner_efficiency.py +++ b/erpnext/crm/report/lead_owner_efficiency/lead_owner_efficiency.py @@ -17,7 +17,8 @@ def get_columns(): { "fieldname": "lead_owner", "label": _("Lead Owner"), - "fieldtype": "Data", + "fieldtype": "Link", + "options": "User", "width": "130" }, { diff --git a/erpnext/education/api.py b/erpnext/education/api.py index 1a19716b508..d79a143ea20 100644 --- a/erpnext/education/api.py +++ b/erpnext/education/api.py @@ -151,7 +151,7 @@ def get_fee_components(fee_structure): :param fee_structure: Fee Structure. """ if fee_structure: - fs = frappe.get_list("Fee Component", fields=["fees_category", "amount"] , filters={"parent": fee_structure}, order_by= "idx") + fs = frappe.get_list("Fee Component", fields=["fees_category", "description", "amount"] , filters={"parent": fee_structure}, order_by= "idx") return fs @@ -363,9 +363,9 @@ def get_current_enrollment(student, academic_year=None): select name as program_enrollment, student_name, program, student_batch_name as student_batch, student_category, academic_term, academic_year - from + from `tabProgram Enrollment` - where + where student = %s and academic_year = %s order by creation''', (student, current_academic_year), as_dict=1) diff --git a/erpnext/education/doctype/fees/fees.js b/erpnext/education/doctype/fees/fees.js index 17ef44954b1..ba9dafce1e1 100644 --- a/erpnext/education/doctype/fees/fees.js +++ b/erpnext/education/doctype/fees/fees.js @@ -161,6 +161,7 @@ frappe.ui.form.on("Fees", { $.each(r.message, function(i, d) { var row = frappe.model.add_child(frm.doc, "Fee Component", "components"); row.fees_category = d.fees_category; + row.description = d.description; row.amount = d.amount; }); } diff --git a/erpnext/education/doctype/program_enrollment/program_enrollment.py b/erpnext/education/doctype/program_enrollment/program_enrollment.py index d5348ffd067..3a5a542ba96 100644 --- a/erpnext/education/doctype/program_enrollment/program_enrollment.py +++ b/erpnext/education/doctype/program_enrollment/program_enrollment.py @@ -97,6 +97,7 @@ class ProgramEnrollment(Document): return quiz_progress @frappe.whitelist() +@frappe.validate_and_sanitize_search_inputs def get_program_courses(doctype, txt, searchfield, start, page_len, filters): if filters.get('program'): return frappe.db.sql("""select course, course_name from `tabProgram Course` @@ -115,6 +116,7 @@ def get_program_courses(doctype, txt, searchfield, start, page_len, filters): }) @frappe.whitelist() +@frappe.validate_and_sanitize_search_inputs def get_students(doctype, txt, searchfield, start, page_len, filters): if not filters.get("academic_term"): filters["academic_term"] = frappe.defaults.get_defaults().academic_term diff --git a/erpnext/education/doctype/student_admission/student_admission.json b/erpnext/education/doctype/student_admission/student_admission.json index b3c10d43316..1096888d4d2 100644 --- a/erpnext/education/doctype/student_admission/student_admission.json +++ b/erpnext/education/doctype/student_admission/student_admission.json @@ -1,398 +1,119 @@ { - "allow_copy": 0, - "allow_guest_to_view": 1, - "allow_import": 0, - "allow_rename": 1, - "autoname": "", - "beta": 0, - "creation": "2016-09-13 03:05:27.154713", - "custom": 0, - "docstatus": 0, - "doctype": "DocType", - "document_type": "Document", - "editable_grid": 1, + "actions": [], + "allow_guest_to_view": 1, + "allow_rename": 1, + "creation": "2016-09-13 03:05:27.154713", + "doctype": "DocType", + "document_type": "Document", + "editable_grid": 1, + "engine": "InnoDB", + "field_order": [ + "title", + "route", + "column_break_3", + "academic_year", + "admission_start_date", + "admission_end_date", + "published", + "enable_admission_application", + "section_break_5", + "program_details", + "introduction" + ], "fields": [ { - "allow_bulk_edit": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "title", - "fieldtype": "Data", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "Title", - "length": 0, - "no_copy": 0, - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "unique": 0 - }, + "fieldname": "title", + "fieldtype": "Data", + "label": "Title" + }, { - "allow_bulk_edit": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "depends_on": "", - "fieldname": "route", - "fieldtype": "Data", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "Route", - "length": 0, - "no_copy": 1, - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, + "fieldname": "route", + "fieldtype": "Data", + "label": "Route", + "no_copy": 1, "unique": 1 - }, + }, { - "allow_bulk_edit": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "application_form_route", - "fieldtype": "Data", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "Application Form Route", - "length": 0, - "no_copy": 0, - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 1, - "search_index": 0, - "set_only_once": 0, - "unique": 0 - }, + "fieldname": "column_break_3", + "fieldtype": "Column Break" + }, { - "allow_bulk_edit": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "column_break_3", - "fieldtype": "Column Break", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "length": 0, - "no_copy": 0, - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "unique": 0 - }, + "fieldname": "academic_year", + "fieldtype": "Link", + "in_list_view": 1, + "in_standard_filter": 1, + "label": "Academic Year", + "no_copy": 1, + "options": "Academic Year", + "reqd": 1 + }, { - "allow_bulk_edit": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "academic_year", - "fieldtype": "Link", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 1, - "in_standard_filter": 1, - "label": "Academic Year", - "length": 0, - "no_copy": 1, - "options": "Academic Year", - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 1, - "search_index": 0, - "set_only_once": 0, - "unique": 0 - }, + "fieldname": "admission_start_date", + "fieldtype": "Date", + "label": "Admission Start Date", + "no_copy": 1 + }, { - "allow_bulk_edit": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "admission_start_date", - "fieldtype": "Date", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "Admission Start Date", - "length": 0, - "no_copy": 1, - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "unique": 0 - }, + "fieldname": "admission_end_date", + "fieldtype": "Date", + "label": "Admission End Date", + "no_copy": 1 + }, { - "allow_bulk_edit": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "admission_end_date", - "fieldtype": "Date", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "Admission End Date", - "length": 0, - "no_copy": 1, - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "unique": 0 - }, + "default": "0", + "fieldname": "published", + "fieldtype": "Check", + "label": "Publish on website" + }, { - "allow_bulk_edit": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "published", - "fieldtype": "Check", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "Publish on website", - "length": 0, - "no_copy": 0, - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "unique": 0 - }, + "fieldname": "section_break_5", + "fieldtype": "Section Break", + "label": "Eligibility and Details" + }, { - "allow_bulk_edit": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "section_break_5", - "fieldtype": "Section Break", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "Eligibility and Details", - "length": 0, - "no_copy": 0, - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "unique": 0 - }, + "fieldname": "program_details", + "fieldtype": "Table", + "label": "Eligibility and Details", + "options": "Student Admission Program" + }, { - "allow_bulk_edit": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "program_details", - "fieldtype": "Table", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "Eligibility and Details", - "length": 0, - "no_copy": 0, - "options": "Student Admission Program", - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "unique": 0 - }, + "fieldname": "introduction", + "fieldtype": "Text Editor", + "label": "Introduction" + }, { - "allow_bulk_edit": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "introduction", - "fieldtype": "Text Editor", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "Introduction", - "length": 0, - "no_copy": 0, - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "unique": 0 + "default": "0", + "fieldname": "enable_admission_application", + "fieldtype": "Check", + "label": "Enable Admission Application" } - ], - "has_web_view": 1, - "hide_heading": 0, - "hide_toolbar": 0, - "idx": 0, - "image_view": 0, - "in_create": 0, - "is_published_field": "published", - "is_submittable": 0, - "issingle": 0, - "istable": 0, - "max_attachments": 0, - "modified": "2017-11-10 18:57:34.570376", - "modified_by": "Administrator", - "module": "Education", - "name": "Student Admission", - "name_case": "", - "owner": "Administrator", + ], + "has_web_view": 1, + "is_published_field": "published", + "links": [], + "modified": "2020-06-15 20:18:38.591626", + "modified_by": "Administrator", + "module": "Education", + "name": "Student Admission", + "owner": "Administrator", "permissions": [ { - "amend": 0, - "apply_user_permissions": 0, - "cancel": 0, - "create": 1, - "delete": 1, - "email": 1, - "export": 1, - "if_owner": 0, - "import": 0, - "permlevel": 0, - "print": 1, - "read": 1, - "report": 1, - "role": "Academics User", - "set_user_permissions": 0, - "share": 1, - "submit": 0, + "create": 1, + "delete": 1, + "email": 1, + "export": 1, + "print": 1, + "read": 1, + "report": 1, + "role": "Academics User", + "share": 1, "write": 1 } - ], - "quick_entry": 0, - "read_only": 0, - "read_only_onload": 0, - "restrict_to_domain": "Education", - "route": "admissions", - "show_name_in_global_search": 1, - "sort_field": "modified", - "sort_order": "DESC", - "title_field": "title", - "track_changes": 0, - "track_seen": 0 + ], + "restrict_to_domain": "Education", + "route": "admissions", + "show_name_in_global_search": 1, + "sort_field": "modified", + "sort_order": "DESC", + "title_field": "title" } \ No newline at end of file diff --git a/erpnext/education/doctype/student_admission/templates/student_admission.html b/erpnext/education/doctype/student_admission/templates/student_admission.html index 25afaca84dc..e5a9ead31ed 100644 --- a/erpnext/education/doctype/student_admission/templates/student_admission.html +++ b/erpnext/education/doctype/student_admission/templates/student_admission.html @@ -43,8 +43,8 @@ Program/Std. - Minumum Age(DOB) - Maximum Age(DOB) + Minumum Age + Maximum Age Application Fee @@ -52,8 +52,8 @@ {% for row in program_details %} {{ row.program }} - {{ row.minimum_age }} - {{ row.maximum_age }} + {{ row.min_age }} + {{ row.max_age }} {{ row.application_fee }} {% endfor %} @@ -61,12 +61,11 @@ {% endif %} - - {%- if application_form_route -%} + {%- if doc.enable_admission_application -%}

+ href='/student-applicant?new=1&student_admission={{doc.name}}'> {{ _("Apply Now") }}

{% endif %} diff --git a/erpnext/education/doctype/student_admission/test_student_admission.js b/erpnext/education/doctype/student_admission/test_student_admission.js index ed794b2482e..3a0bb0b2f23 100644 --- a/erpnext/education/doctype/student_admission/test_student_admission.js +++ b/erpnext/education/doctype/student_admission/test_student_admission.js @@ -11,7 +11,7 @@ QUnit.test('Test: Student Admission', function(assert) { {admission_start_date: '2016-04-20'}, {admission_end_date: '2016-05-31'}, {title: '2016-17 Admissions'}, - {application_form_route: 'student-applicant'}, + {enable_admission_application: 1}, {introduction: 'Test intro'}, {program_details: [ [ @@ -28,7 +28,7 @@ QUnit.test('Test: Student Admission', function(assert) { assert.ok(cur_frm.doc.admission_start_date == '2016-04-20'); assert.ok(cur_frm.doc.admission_end_date == '2016-05-31'); assert.ok(cur_frm.doc.title == '2016-17 Admissions'); - assert.ok(cur_frm.doc.application_form_route == 'student-applicant'); + assert.ok(cur_frm.doc.enable_admission_application == 1); assert.ok(cur_frm.doc.introduction == 'Test intro'); assert.ok(cur_frm.doc.program_details[0].program == 'Standard Test', 'Program correctly selected'); assert.ok(cur_frm.doc.program_details[0].application_fee == 1000); diff --git a/erpnext/education/doctype/student_admission_program/student_admission_program.json b/erpnext/education/doctype/student_admission_program/student_admission_program.json index 97b1bba4217..e9f041e101f 100644 --- a/erpnext/education/doctype/student_admission_program/student_admission_program.json +++ b/erpnext/education/doctype/student_admission_program/student_admission_program.json @@ -1,237 +1,77 @@ { - "allow_copy": 0, - "allow_events_in_timeline": 0, - "allow_guest_to_view": 0, - "allow_import": 0, - "allow_rename": 0, - "autoname": "", - "beta": 0, - "creation": "2017-09-15 12:59:43.207923", - "custom": 0, - "docstatus": 0, - "doctype": "DocType", - "document_type": "", - "editable_grid": 1, - "engine": "InnoDB", + "actions": [], + "creation": "2017-09-15 12:59:43.207923", + "doctype": "DocType", + "editable_grid": 1, + "engine": "InnoDB", + "field_order": [ + "program", + "min_age", + "max_age", + "column_break_4", + "application_fee", + "applicant_naming_series" + ], "fields": [ { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "program", - "fieldtype": "Link", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 1, - "in_standard_filter": 0, - "label": "Program", - "length": 0, - "no_copy": 0, - "options": "Program", - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 - }, + "fieldname": "program", + "fieldtype": "Link", + "in_list_view": 1, + "label": "Program", + "options": "Program", + "show_days": 1, + "show_seconds": 1 + }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "minimum_age", - "fieldtype": "Date", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 1, - "in_standard_filter": 0, - "label": "Minimum Age", - "length": 0, - "no_copy": 0, - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 - }, + "fieldname": "column_break_4", + "fieldtype": "Column Break", + "show_days": 1, + "show_seconds": 1 + }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "maximum_age", - "fieldtype": "Date", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 1, - "in_standard_filter": 0, - "label": "Maximum Age", - "length": 0, - "no_copy": 0, - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 - }, + "fieldname": "application_fee", + "fieldtype": "Currency", + "in_list_view": 1, + "label": "Application Fee", + "show_days": 1, + "show_seconds": 1 + }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "column_break_4", - "fieldtype": "Column Break", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "length": 0, - "no_copy": 0, - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 - }, + "fieldname": "applicant_naming_series", + "fieldtype": "Data", + "in_list_view": 1, + "label": "Naming Series (for Student Applicant)", + "show_days": 1, + "show_seconds": 1 + }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "application_fee", - "fieldtype": "Currency", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 1, - "in_standard_filter": 0, - "label": "Application Fee", - "length": 0, - "no_copy": 0, - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 - }, + "fieldname": "min_age", + "fieldtype": "Int", + "in_list_view": 1, + "label": "Minimum Age", + "show_days": 1, + "show_seconds": 1 + }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "applicant_naming_series", - "fieldtype": "Data", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 1, - "in_standard_filter": 0, - "label": "Naming Series (for Student Applicant)", - "length": 0, - "no_copy": 0, - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 + "fieldname": "max_age", + "fieldtype": "Int", + "in_list_view": 1, + "label": "Maximum Age", + "show_days": 1, + "show_seconds": 1 } - ], - "has_web_view": 0, - "hide_heading": 0, - "hide_toolbar": 0, - "idx": 0, - "image_view": 0, - "in_create": 0, - "is_submittable": 0, - "issingle": 0, - "istable": 1, - "max_attachments": 0, - "modified": "2018-11-04 03:37:17.408427", - "modified_by": "Administrator", - "module": "Education", - "name": "Student Admission Program", - "name_case": "", - "owner": "Administrator", - "permissions": [], - "quick_entry": 1, - "read_only": 0, - "read_only_onload": 0, - "restrict_to_domain": "Education", - "show_name_in_global_search": 0, - "sort_field": "modified", - "sort_order": "DESC", - "track_changes": 1, - "track_seen": 0, - "track_views": 0 + ], + "istable": 1, + "links": [], + "modified": "2020-06-10 23:06:30.037404", + "modified_by": "Administrator", + "module": "Education", + "name": "Student Admission Program", + "owner": "Administrator", + "permissions": [], + "quick_entry": 1, + "restrict_to_domain": "Education", + "sort_field": "modified", + "sort_order": "DESC", + "track_changes": 1 } \ No newline at end of file diff --git a/erpnext/education/doctype/student_applicant/student_applicant.py b/erpnext/education/doctype/student_applicant/student_applicant.py index 6d0957c5021..8929abdc6cd 100644 --- a/erpnext/education/doctype/student_applicant/student_applicant.py +++ b/erpnext/education/doctype/student_applicant/student_applicant.py @@ -6,7 +6,7 @@ from __future__ import print_function, unicode_literals import frappe from frappe import _ from frappe.model.document import Document -from frappe.utils import getdate +from frappe.utils import getdate, add_years, nowdate, date_diff class StudentApplicant(Document): def autoname(self): @@ -30,6 +30,7 @@ class StudentApplicant(Document): def validate(self): self.title = " ".join(filter(None, [self.first_name, self.middle_name, self.last_name])) + if self.student_admission and self.program and self.date_of_birth: self.validation_from_student_admission() @@ -43,16 +44,16 @@ class StudentApplicant(Document): frappe.throw(_("Please select Student Admission which is mandatory for the paid student applicant")) def validation_from_student_admission(self): + student_admission = get_student_admission_data(self.student_admission, self.program) - # different validation for minimum and maximum age so that either min/max can also work independently. - if student_admission and student_admission.minimum_age and \ - getdate(student_admission.minimum_age) < getdate(self.date_of_birth): - frappe.throw(_("Not eligible for the admission in this program as per DOB")) + if student_admission and student_admission.min_age and \ + date_diff(nowdate(), add_years(getdate(self.date_of_birth), student_admission.min_age)) < 0: + frappe.throw(_("Not eligible for the admission in this program as per Date Of Birth")) - if student_admission and student_admission.maximum_age and \ - getdate(student_admission.maximum_age) > getdate(self.date_of_birth): - frappe.throw(_("Not eligible for the admission in this program as per DOB")) + if student_admission and student_admission.max_age and \ + date_diff(nowdate(), add_years(getdate(self.date_of_birth), student_admission.max_age)) > 0: + frappe.throw(_("Not eligible for the admission in this program as per Date Of Birth")) def on_payment_authorized(self, *args, **kwargs): @@ -60,10 +61,12 @@ class StudentApplicant(Document): def get_student_admission_data(student_admission, program): + student_admission = frappe.db.sql("""select sa.admission_start_date, sa.admission_end_date, - sap.program, sap.minimum_age, sap.maximum_age, sap.applicant_naming_series + sap.program, sap.min_age, sap.max_age, sap.applicant_naming_series from `tabStudent Admission` sa, `tabStudent Admission Program` sap where sa.name = sap.parent and sa.name = %s and sap.program = %s""", (student_admission, program), as_dict=1) + if student_admission: return student_admission[0] else: diff --git a/erpnext/education/doctype/student_attendance_tool/student_attendance_tool.js b/erpnext/education/doctype/student_attendance_tool/student_attendance_tool.js index cc9607da19f..0384505ec21 100644 --- a/erpnext/education/doctype/student_attendance_tool/student_attendance_tool.js +++ b/erpnext/education/doctype/student_attendance_tool/student_attendance_tool.js @@ -140,7 +140,7 @@ education.StudentsEditor = Class.extend({ frappe.call({ method: "erpnext.education.api.mark_attendance", freeze: true, - freeze_message: "Marking attendance", + freeze_message: __("Marking attendance"), args: { "students_present": students_present, "students_absent": students_absent, @@ -180,4 +180,4 @@ education.StudentsEditor = Class.extend({ ` ); } -}); \ No newline at end of file +}); diff --git a/erpnext/education/doctype/student_group/student_group.py b/erpnext/education/doctype/student_group/student_group.py index aba1b5ff5fd..54b32a843f8 100644 --- a/erpnext/education/doctype/student_group/student_group.py +++ b/erpnext/education/doctype/student_group/student_group.py @@ -106,6 +106,7 @@ def get_program_enrollment(academic_year, academic_term=None, program=None, batc @frappe.whitelist() +@frappe.validate_and_sanitize_search_inputs def fetch_students(doctype, txt, searchfield, start, page_len, filters): if filters.get("group_based_on") != "Activity": enrolled_students = get_program_enrollment(filters.get('academic_year'), filters.get('academic_term'), diff --git a/erpnext/education/report/course_wise_assessment_report/course_wise_assessment_report.py b/erpnext/education/report/course_wise_assessment_report/course_wise_assessment_report.py index ce581486ec3..1043e5bd45b 100644 --- a/erpnext/education/report/course_wise_assessment_report/course_wise_assessment_report.py +++ b/erpnext/education/report/course_wise_assessment_report/course_wise_assessment_report.py @@ -42,7 +42,7 @@ def execute(filters=None): # create the list of possible grades if student_row[scrub_criteria] not in grades: grades.append(student_row[scrub_criteria]) - + # create the dict of for gradewise analysis if student_row[scrub_criteria] not in grade_wise_analysis[criteria]: grade_wise_analysis[criteria][student_row[scrub_criteria]] = 1 @@ -101,7 +101,7 @@ def get_formatted_result(args, get_assessment_criteria=False, get_course=False, # create the nested dictionary structure as given below: # ..... - # "Total Score" -> assessment criteria used for totaling and args.assessment_group -> for totaling all the assesments + # "Final Grade" -> assessment criteria used for totaling and args.assessment_group -> for totaling all the assesments student_details = {} formatted_assessment_result = defaultdict(dict) @@ -123,13 +123,13 @@ def get_formatted_result(args, get_assessment_criteria=False, get_course=False, formatted_assessment_result[result.student][result.course][assessment_group]\ [assessment_criteria]["grade"] = tmp_grade - # create the assessment criteria "Total Score" with the sum of all the scores of the assessment criteria in a given assessment group + # create the assessment criteria "Final Grade" with the sum of all the scores of the assessment criteria in a given assessment group def add_total_score(result, assessment_group): - if "Total Score" not in formatted_assessment_result[result.student][result.course][assessment_group]: - formatted_assessment_result[result.student][result.course][assessment_group]["Total Score"] = frappe._dict({ - "assessment_criteria": "Total Score", "maximum_score": result.maximum_score, "score": result.score, "grade": result.grade}) + if "Final Grade" not in formatted_assessment_result[result.student][result.course][assessment_group]: + formatted_assessment_result[result.student][result.course][assessment_group]["Final Grade"] = frappe._dict({ + "assessment_criteria": "Final Grade", "maximum_score": result.maximum_score, "score": result.score, "grade": result.grade}) else: - add_score_and_recalculate_grade(result, assessment_group, "Total Score") + add_score_and_recalculate_grade(result, assessment_group, "Final Grade") for result in assessment_result: if result.student not in student_details: @@ -152,7 +152,7 @@ def get_formatted_result(args, get_assessment_criteria=False, get_course=False, elif create_total_dict: if get_all_assessment_groups: formatted_assessment_result[result.student][result.course][result.assessment_group]\ - [result.assessment_criteria] = assessment_criteria_details + [result.assessment_criteria] = assessment_criteria_details if not formatted_assessment_result[result.student][result.course][args.assessment_group]: formatted_assessment_result[result.student][result.course][args.assessment_group] = defaultdict(dict) formatted_assessment_result[result.student][result.course][args.assessment_group]\ @@ -166,7 +166,7 @@ def get_formatted_result(args, get_assessment_criteria=False, get_course=False, add_total_score(result, args.assessment_group) total_maximum_score = formatted_assessment_result[result.student][result.course][args.assessment_group]\ - ["Total Score"]["maximum_score"] + ["Final Grade"]["maximum_score"] if get_assessment_criteria: assessment_criteria_dict[result.assessment_criteria] = formatted_assessment_result[result.student][result.course]\ [args.assessment_group][result.assessment_criteria]["maximum_score"] @@ -174,7 +174,7 @@ def get_formatted_result(args, get_assessment_criteria=False, get_course=False, course_dict[result.course] = total_maximum_score if get_assessment_criteria and total_maximum_score: - assessment_criteria_dict["Total Score"] = total_maximum_score + assessment_criteria_dict["Final Grade"] = total_maximum_score return { "student_details": student_details, @@ -220,7 +220,7 @@ def get_chart_data(grades, criteria_list, kounter): datasets = [] for grade in grades: - tmp = frappe._dict({"values":[], "title": grade}) + tmp = frappe._dict({"name": grade, "values":[]}) for criteria in criteria_list: if grade in kounter[criteria]: tmp["values"].append(kounter[criteria][grade]) diff --git a/erpnext/education/web_form/student_applicant/student_applicant.json b/erpnext/education/web_form/student_applicant/student_applicant.json index b1ad754c327..1810f07a054 100644 --- a/erpnext/education/web_form/student_applicant/student_applicant.json +++ b/erpnext/education/web_form/student_applicant/student_applicant.json @@ -1,200 +1,248 @@ { - "accept_payment": 0, - "allow_comments": 0, - "allow_delete": 0, - "allow_edit": 1, - "allow_incomplete": 0, - "allow_multiple": 1, - "allow_print": 0, - "amount": 0.0, - "amount_based_on_field": 0, - "creation": "2016-09-22 13:10:10.792735", - "doc_type": "Student Applicant", - "docstatus": 0, - "doctype": "Web Form", - "idx": 0, - "is_standard": 1, - "login_required": 1, - "max_attachment_size": 0, - "modified": "2017-02-21 05:44:46.022738", - "modified_by": "Administrator", - "module": "Education", - "name": "student-applicant", - "owner": "Administrator", - "payment_button_label": "Buy Now", - "published": 1, - "route": "student-applicant", - "show_sidebar": 1, - "sidebar_items": [], - "success_url": "/student-applicant", - "title": "Student Applicant", + "accept_payment": 0, + "allow_comments": 0, + "allow_delete": 0, + "allow_edit": 1, + "allow_incomplete": 0, + "allow_multiple": 1, + "allow_print": 0, + "amount": 0.0, + "amount_based_on_field": 0, + "creation": "2016-09-22 13:10:10.792735", + "doc_type": "Student Applicant", + "docstatus": 0, + "doctype": "Web Form", + "idx": 0, + "is_standard": 1, + "login_required": 1, + "max_attachment_size": 0, + "modified": "2020-06-11 22:53:45.875310", + "modified_by": "Administrator", + "module": "Education", + "name": "student-applicant", + "owner": "Administrator", + "payment_button_label": "Buy Now", + "published": 1, + "route": "student-applicant", + "route_to_success_link": 0, + "show_attachments": 0, + "show_in_grid": 0, + "show_sidebar": 1, + "sidebar_items": [], + "success_url": "/student-applicant", + "title": "Student Applicant", "web_form_fields": [ { - "fieldname": "first_name", - "fieldtype": "Data", - "hidden": 0, - "label": "First Name", - "max_length": 0, - "max_value": 0, - "read_only": 0, - "reqd": 1 - }, + "allow_read_on_all_link_options": 0, + "fieldname": "first_name", + "fieldtype": "Data", + "hidden": 0, + "label": "First Name", + "max_length": 0, + "max_value": 0, + "read_only": 0, + "reqd": 1, + "show_in_filter": 0 + }, { - "fieldname": "middle_name", - "fieldtype": "Data", - "hidden": 0, - "label": "Middle Name", - "max_length": 0, - "max_value": 0, - "read_only": 0, - "reqd": 0 - }, + "allow_read_on_all_link_options": 0, + "fieldname": "middle_name", + "fieldtype": "Data", + "hidden": 0, + "label": "Middle Name", + "max_length": 0, + "max_value": 0, + "read_only": 0, + "reqd": 0, + "show_in_filter": 0 + }, { - "fieldname": "last_name", - "fieldtype": "Data", - "hidden": 0, - "label": "Last Name", - "max_length": 0, - "max_value": 0, - "read_only": 0, - "reqd": 0 - }, + "allow_read_on_all_link_options": 0, + "fieldname": "last_name", + "fieldtype": "Data", + "hidden": 0, + "label": "Last Name", + "max_length": 0, + "max_value": 0, + "read_only": 0, + "reqd": 0, + "show_in_filter": 0 + }, { - "fieldname": "image", - "fieldtype": "Data", - "hidden": 0, - "label": "Image", - "max_length": 0, - "max_value": 0, - "read_only": 0, - "reqd": 0 - }, + "allow_read_on_all_link_options": 0, + "fieldname": "image", + "fieldtype": "Data", + "hidden": 0, + "label": "Image", + "max_length": 0, + "max_value": 0, + "read_only": 0, + "reqd": 0, + "show_in_filter": 0 + }, { - "fieldname": "program", - "fieldtype": "Link", - "hidden": 0, - "label": "Program", - "max_length": 0, - "max_value": 0, - "options": "Program", - "read_only": 0, - "reqd": 1 - }, + "allow_read_on_all_link_options": 0, + "fieldname": "program", + "fieldtype": "Link", + "hidden": 0, + "label": "Program", + "max_length": 0, + "max_value": 0, + "options": "Program", + "read_only": 0, + "reqd": 1, + "show_in_filter": 0 + }, { - "fieldname": "academic_year", - "fieldtype": "Link", - "hidden": 0, - "label": "Academic Year", - "max_length": 0, - "max_value": 0, - "options": "Academic Year", - "read_only": 0, - "reqd": 0 - }, + "allow_read_on_all_link_options": 0, + "fieldname": "academic_year", + "fieldtype": "Link", + "hidden": 0, + "label": "Academic Year", + "max_length": 0, + "max_value": 0, + "options": "Academic Year", + "read_only": 0, + "reqd": 0, + "show_in_filter": 0 + }, { - "fieldname": "date_of_birth", - "fieldtype": "Date", - "hidden": 0, - "label": "Date of Birth", - "max_length": 0, - "max_value": 0, - "read_only": 0, - "reqd": 0 - }, + "allow_read_on_all_link_options": 0, + "fieldname": "date_of_birth", + "fieldtype": "Date", + "hidden": 0, + "label": "Date of Birth", + "max_length": 0, + "max_value": 0, + "read_only": 0, + "reqd": 0, + "show_in_filter": 0 + }, { - "fieldname": "blood_group", - "fieldtype": "Select", - "hidden": 0, - "label": "Blood Group", - "max_length": 0, - "max_value": 0, - "options": "\nA+\nA-\nB+\nB-\nO+\nO-\nAB+\nAB-", - "read_only": 0, - "reqd": 0 - }, + "allow_read_on_all_link_options": 0, + "fieldname": "blood_group", + "fieldtype": "Select", + "hidden": 0, + "label": "Blood Group", + "max_length": 0, + "max_value": 0, + "options": "\nA+\nA-\nB+\nB-\nO+\nO-\nAB+\nAB-", + "read_only": 0, + "reqd": 0, + "show_in_filter": 0 + }, { - "fieldname": "student_email_id", - "fieldtype": "Data", - "hidden": 0, - "label": "Student Email ID", - "max_length": 0, - "max_value": 0, - "read_only": 0, - "reqd": 0 - }, + "allow_read_on_all_link_options": 0, + "fieldname": "student_email_id", + "fieldtype": "Data", + "hidden": 0, + "label": "Student Email ID", + "max_length": 0, + "max_value": 0, + "read_only": 0, + "reqd": 0, + "show_in_filter": 0 + }, { - "fieldname": "student_mobile_number", - "fieldtype": "Data", - "hidden": 0, - "label": "Student Mobile Number", - "max_length": 0, - "max_value": 0, - "read_only": 0, - "reqd": 0 - }, + "allow_read_on_all_link_options": 0, + "fieldname": "student_mobile_number", + "fieldtype": "Data", + "hidden": 0, + "label": "Student Mobile Number", + "max_length": 0, + "max_value": 0, + "read_only": 0, + "reqd": 0, + "show_in_filter": 0 + }, { - "default": "INDIAN", - "fieldname": "nationality", - "fieldtype": "Data", - "hidden": 0, - "label": "Nationality", - "max_length": 0, - "max_value": 0, - "options": "", - "read_only": 0, - "reqd": 0 - }, + "allow_read_on_all_link_options": 0, + "default": "INDIAN", + "fieldname": "nationality", + "fieldtype": "Data", + "hidden": 0, + "label": "Nationality", + "max_length": 0, + "max_value": 0, + "options": "", + "read_only": 0, + "reqd": 0, + "show_in_filter": 0 + }, { - "fieldname": "address_line_1", - "fieldtype": "Data", - "hidden": 0, - "label": "Address Line 1", - "max_length": 0, - "max_value": 0, - "read_only": 0, - "reqd": 0 - }, + "allow_read_on_all_link_options": 0, + "fieldname": "address_line_1", + "fieldtype": "Data", + "hidden": 0, + "label": "Address Line 1", + "max_length": 0, + "max_value": 0, + "read_only": 0, + "reqd": 0, + "show_in_filter": 0 + }, { - "fieldname": "address_line_2", - "fieldtype": "Data", - "hidden": 0, - "label": "Address Line 2", - "max_length": 0, - "max_value": 0, - "read_only": 0, - "reqd": 0 - }, + "allow_read_on_all_link_options": 0, + "fieldname": "address_line_2", + "fieldtype": "Data", + "hidden": 0, + "label": "Address Line 2", + "max_length": 0, + "max_value": 0, + "read_only": 0, + "reqd": 0, + "show_in_filter": 0 + }, { - "fieldname": "pincode", - "fieldtype": "Data", - "hidden": 0, - "label": "Pincode", - "max_length": 0, - "max_value": 0, - "read_only": 0, - "reqd": 0 - }, + "allow_read_on_all_link_options": 0, + "fieldname": "pincode", + "fieldtype": "Data", + "hidden": 0, + "label": "Pincode", + "max_length": 0, + "max_value": 0, + "read_only": 0, + "reqd": 0, + "show_in_filter": 0 + }, { - "fieldname": "guardians", - "fieldtype": "Table", - "hidden": 0, - "label": "Guardians", - "max_length": 0, - "max_value": 0, - "options": "Student Guardian", - "read_only": 0, - "reqd": 0 - }, + "allow_read_on_all_link_options": 0, + "fieldname": "guardians", + "fieldtype": "Table", + "hidden": 0, + "label": "Guardians", + "max_length": 0, + "max_value": 0, + "options": "Student Guardian", + "read_only": 0, + "reqd": 0, + "show_in_filter": 0 + }, { - "fieldname": "siblings", - "fieldtype": "Table", - "hidden": 0, - "label": "Siblings", - "max_length": 0, - "max_value": 0, - "options": "Student Sibling", - "read_only": 0, - "reqd": 0 + "allow_read_on_all_link_options": 0, + "fieldname": "siblings", + "fieldtype": "Table", + "hidden": 0, + "label": "Siblings", + "max_length": 0, + "max_value": 0, + "options": "Student Sibling", + "read_only": 0, + "reqd": 0, + "show_in_filter": 0 + }, + { + "allow_read_on_all_link_options": 0, + "fieldname": "student_admission", + "fieldtype": "Link", + "hidden": 0, + "label": "Student Admission", + "max_length": 0, + "max_value": 0, + "options": "Student Admission", + "read_only": 0, + "reqd": 0, + "show_in_filter": 0 } ] } \ No newline at end of file diff --git a/erpnext/healthcare/doctype/healthcare_practitioner/healthcare_practitioner.py b/erpnext/healthcare/doctype/healthcare_practitioner/healthcare_practitioner.py index ad32e946312..d07c7962b2f 100644 --- a/erpnext/healthcare/doctype/healthcare_practitioner/healthcare_practitioner.py +++ b/erpnext/healthcare/doctype/healthcare_practitioner/healthcare_practitioner.py @@ -67,6 +67,8 @@ def validate_service_item(item, msg): if frappe.db.get_value("Item", item, "is_stock_item") == 1: frappe.throw(_(msg)) +@frappe.whitelist() +@frappe.validate_and_sanitize_search_inputs def get_practitioner_list(doctype, txt, searchfield, start, page_len, filters=None): fields = ["name", "first_name", "mobile_phone"] diff --git a/erpnext/healthcare/doctype/inpatient_record/inpatient_record.py b/erpnext/healthcare/doctype/inpatient_record/inpatient_record.py index c107cd73350..52c31120bb3 100644 --- a/erpnext/healthcare/doctype/inpatient_record/inpatient_record.py +++ b/erpnext/healthcare/doctype/inpatient_record/inpatient_record.py @@ -168,6 +168,7 @@ def patient_leave_service_unit(inpatient_record, check_out, leave_from): inpatient_record.save(ignore_permissions = True) @frappe.whitelist() +@frappe.validate_and_sanitize_search_inputs def get_leave_from(doctype, txt, searchfield, start, page_len, filters): docname = filters['docname'] diff --git a/erpnext/healthcare/doctype/lab_test/lab_test_list.js b/erpnext/healthcare/doctype/lab_test/lab_test_list.js index 1f6a12f935f..2e76bb8ddd8 100644 --- a/erpnext/healthcare/doctype/lab_test/lab_test_list.js +++ b/erpnext/healthcare/doctype/lab_test/lab_test_list.js @@ -52,7 +52,7 @@ var create_multiple_dialog = function(listview){ } }, freeze: true, - freeze_message: "Creating Lab Test..." + freeze_message: __("Creating Lab Test...") }); dialog.hide(); } diff --git a/erpnext/healthcare/doctype/patient_encounter/patient_encounter.js b/erpnext/healthcare/doctype/patient_encounter/patient_encounter.js index c1b0b18dba4..e6ceb5c0fc3 100644 --- a/erpnext/healthcare/doctype/patient_encounter/patient_encounter.js +++ b/erpnext/healthcare/doctype/patient_encounter/patient_encounter.js @@ -116,7 +116,7 @@ var schedule_inpatient = function(frm) { } }, freeze: true, - freeze_message: "Process Inpatient Scheduling" + freeze_message: __("Process Inpatient Scheduling") }); }; @@ -130,7 +130,7 @@ var schedule_discharge = function(frm) { } }, freeze: true, - freeze_message: "Process Discharge" + freeze_message: __("Process Discharge") }); }; diff --git a/erpnext/healthcare/setup.py b/erpnext/healthcare/setup.py index 2087f49f32f..4813d9d07c8 100644 --- a/erpnext/healthcare/setup.py +++ b/erpnext/healthcare/setup.py @@ -195,10 +195,21 @@ def create_sensitivity(): def add_healthcare_service_unit_tree_root(): record = [ - { - "doctype": "Healthcare Service Unit", - "healthcare_service_unit_name": "All Healthcare Service Units", - "is_group": 1 - } + { + "doctype": "Healthcare Service Unit", + "healthcare_service_unit_name": "All Healthcare Service Units", + "is_group": 1, + "company": get_company() + } ] insert_record(record) + +def get_company(): + company = frappe.defaults.get_defaults().company + if company: + return company + else: + company = frappe.get_list("Company", limit=1) + if company: + return company[0].name + return None \ No newline at end of file diff --git a/erpnext/hooks.py b/erpnext/hooks.py index 6712842948e..5270e7beea2 100644 --- a/erpnext/hooks.py +++ b/erpnext/hooks.py @@ -244,6 +244,9 @@ doc_events = { "on_cancel": "erpnext.regional.italy.utils.sales_invoice_on_cancel", "on_trash": "erpnext.regional.check_deletion_permission" }, + "Purchase Invoice": { + "validate": "erpnext.regional.india.utils.update_grand_total_for_rcm" + }, "Payment Entry": { "on_submit": ["erpnext.regional.create_transaction_log", "erpnext.accounts.doctype.payment_request.payment_request.update_payment_req_status"], "on_trash": "erpnext.regional.check_deletion_permission" @@ -353,7 +356,8 @@ regional_overrides = { 'erpnext.controllers.taxes_and_totals.get_itemised_tax_breakup_data': 'erpnext.regional.india.utils.get_itemised_tax_breakup_data', 'erpnext.accounts.party.get_regional_address_details': 'erpnext.regional.india.utils.get_regional_address_details', 'erpnext.hr.utils.calculate_annual_eligible_hra_exemption': 'erpnext.regional.india.utils.calculate_annual_eligible_hra_exemption', - 'erpnext.hr.utils.calculate_hra_exemption_for_period': 'erpnext.regional.india.utils.calculate_hra_exemption_for_period' + 'erpnext.hr.utils.calculate_hra_exemption_for_period': 'erpnext.regional.india.utils.calculate_hra_exemption_for_period', + 'erpnext.accounts.doctype.purchase_invoice.purchase_invoice.make_regional_gl_entries': 'erpnext.regional.india.utils.make_regional_gl_entries' }, 'United Arab Emirates': { 'erpnext.controllers.taxes_and_totals.update_itemised_tax_data': 'erpnext.regional.united_arab_emirates.utils.update_itemised_tax_data' diff --git a/erpnext/hr/doctype/department_approver/department_approver.py b/erpnext/hr/doctype/department_approver/department_approver.py index df0f75a18c3..70a0aa217f7 100644 --- a/erpnext/hr/doctype/department_approver/department_approver.py +++ b/erpnext/hr/doctype/department_approver/department_approver.py @@ -11,6 +11,7 @@ class DepartmentApprover(Document): pass @frappe.whitelist() +@frappe.validate_and_sanitize_search_inputs def get_approvers(doctype, txt, searchfield, start, page_len, filters): if not filters.get("employee"): diff --git a/erpnext/hr/doctype/employee_advance/employee_advance.py b/erpnext/hr/doctype/employee_advance/employee_advance.py index 30a2a896dc6..eeb91952ab2 100644 --- a/erpnext/hr/doctype/employee_advance/employee_advance.py +++ b/erpnext/hr/doctype/employee_advance/employee_advance.py @@ -119,12 +119,14 @@ def make_bank_entry(dt, dn): "reference_type": "Employee Advance", "reference_name": doc.name, "party_type": "Employee", + "cost_center": erpnext.get_default_cost_center(doc.company), "party": doc.employee, "is_advance": "Yes" }) je.append("accounts", { "account": payment_account.account, + "cost_center": erpnext.get_default_cost_center(doc.company), "credit_in_account_currency": flt(doc.advance_amount), "account_currency": payment_account.account_currency, "account_type": payment_account.account_type diff --git a/erpnext/hr/doctype/employee_benefit_application/employee_benefit_application.py b/erpnext/hr/doctype/employee_benefit_application/employee_benefit_application.py index feaa92590a3..1322e25063f 100644 --- a/erpnext/hr/doctype/employee_benefit_application/employee_benefit_application.py +++ b/erpnext/hr/doctype/employee_benefit_application/employee_benefit_application.py @@ -223,6 +223,8 @@ def get_benefit_amount_based_on_pro_rata(sal_struct, component_max_benefit): return benefit_amount +@frappe.whitelist() +@frappe.validate_and_sanitize_search_inputs def get_earning_components(doctype, txt, searchfield, start, page_len, filters): if len(filters) < 2: return {} @@ -238,4 +240,4 @@ def get_earning_components(doctype, txt, searchfield, start, page_len, filters): """, salary_structure) else: frappe.throw(_("Salary Structure not found for employee {0} and date {1}") - .format(filters['employee'], filters['date'])) \ No newline at end of file + .format(filters['employee'], filters['date'])) diff --git a/erpnext/hr/doctype/expense_claim/expense_claim.js b/erpnext/hr/doctype/expense_claim/expense_claim.js index 88bd689d9de..45351d858b9 100644 --- a/erpnext/hr/doctype/expense_claim/expense_claim.js +++ b/erpnext/hr/doctype/expense_claim/expense_claim.js @@ -221,7 +221,6 @@ frappe.ui.form.on("Expense Claim", { }, update_employee_advance_claimed_amount: function(frm) { - console.log("update_employee_advance_claimed_amount") let amount_to_be_allocated = frm.doc.grand_total; $.each(frm.doc.advances || [], function(i, advance){ if (amount_to_be_allocated >= advance.unclaimed_amount){ @@ -297,6 +296,21 @@ frappe.ui.form.on("Expense Claim", { frm.events.get_advances(frm); }, + cost_center: function(frm) { + frm.events.set_child_cost_center(frm); + }, + + validate: function(frm) { + frm.events.set_child_cost_center(frm); + }, + + set_child_cost_center: function(frm){ + (frm.doc.expenses || []).forEach(function(d) { + if (!d.cost_center){ + d.cost_center = frm.doc.cost_center; + } + }); + }, get_taxes: function(frm) { if(frm.doc.taxes) { frappe.call({ diff --git a/erpnext/hr/doctype/expense_claim/expense_claim.py b/erpnext/hr/doctype/expense_claim/expense_claim.py index 4d97b0d0c72..51e50b7470c 100644 --- a/erpnext/hr/doctype/expense_claim/expense_claim.py +++ b/erpnext/hr/doctype/expense_claim/expense_claim.py @@ -2,7 +2,7 @@ # License: GNU General Public License v3. See license.txt from __future__ import unicode_literals -import frappe +import frappe, erpnext from frappe import _ from frappe.utils import get_fullname, flt, cstr from frappe.model.document import Document @@ -128,7 +128,7 @@ class ExpenseClaim(AccountsController): "debit": data.sanctioned_amount, "debit_in_account_currency": data.sanctioned_amount, "against": self.employee, - "cost_center": data.cost_center + "cost_center": data.cost_center or self.cost_center }, item=data) ) @@ -285,7 +285,7 @@ def make_bank_entry(dt, dn): je = frappe.new_doc("Journal Entry") je.voucher_type = 'Bank Entry' je.company = expense_claim.company - je.remark = 'Payment against Expense Claim: ' + dn; + je.remark = 'Payment against Expense Claim: ' + dn je.append("accounts", { "account": expense_claim.payable_account, @@ -293,6 +293,7 @@ def make_bank_entry(dt, dn): "reference_type": "Expense Claim", "party_type": "Employee", "party": expense_claim.employee, + "cost_center": erpnext.get_default_cost_center(expense_claim.company), "reference_name": expense_claim.name }) @@ -303,6 +304,7 @@ def make_bank_entry(dt, dn): "reference_name": expense_claim.name, "balance": default_bank_cash_account.balance, "account_currency": default_bank_cash_account.account_currency, + "cost_center": erpnext.get_default_cost_center(expense_claim.company), "account_type": default_bank_cash_account.account_type }) diff --git a/erpnext/hr/doctype/job_applicant/job_applicant_list.js b/erpnext/hr/doctype/job_applicant/job_applicant_list.js new file mode 100644 index 00000000000..3b9141ba79c --- /dev/null +++ b/erpnext/hr/doctype/job_applicant/job_applicant_list.js @@ -0,0 +1,15 @@ +// Copyright (c) 2020, Frappe Technologies Pvt. Ltd. and Contributors +// MIT License. See license.txt + +frappe.listview_settings['Job Applicant'] = { + add_fields: ["company", "designation", "job_applicant", "status"], + get_indicator: function (doc) { + if (doc.status == "Accepted") { + return [__(doc.status), "green", "status,=," + doc.status]; + } else if (["Open", "Replied"].includes(doc.status)) { + return [__(doc.status), "orange", "status,=," + doc.status]; + } else if (["Hold", "Rejected"].includes(doc.status)) { + return [__(doc.status), "red", "status,=," + doc.status]; + } + } +}; diff --git a/erpnext/hr/doctype/job_offer/job_offer.json b/erpnext/hr/doctype/job_offer/job_offer.json index 7495c486bd2..4d18d0632e5 100644 --- a/erpnext/hr/doctype/job_offer/job_offer.json +++ b/erpnext/hr/doctype/job_offer/job_offer.json @@ -1,618 +1,232 @@ { - "allow_copy": 0, - "allow_guest_to_view": 0, - "allow_import": 1, - "allow_rename": 0, - "autoname": "HR-OFF-.YYYY.-.#####", - "beta": 0, - "creation": "2015-03-04 14:20:17.662207", - "custom": 0, - "default_print_format": "", - "docstatus": 0, - "doctype": "DocType", - "document_type": "Document", - "editable_grid": 0, + "allow_copy": 0, + "allow_guest_to_view": 0, + "allow_import": 1, + "allow_rename": 0, + "autoname": "HR-OFF-.YYYY.-.#####", + "beta": 0, + "creation": "2015-03-04 14:20:17.662207", + "custom": 0, + "default_print_format": "", + "docstatus": 0, + "doctype": "DocType", + "document_type": "Document", + "editable_grid": 0, "fields": [ { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "job_applicant", - "fieldtype": "Link", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 1, - "in_standard_filter": 0, - "label": "Job Applicant", - "length": 0, - "no_copy": 0, - "options": "Job Applicant", - "permlevel": 0, - "precision": "", - "print_hide": 1, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 1, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 - }, + "fieldname": "job_applicant", + "fieldtype": "Link", + "label": "Job Applicant", + "options": "Job Applicant", + "print_hide": 1, + "reqd": 1 + }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fetch_from": "job_applicant.applicant_name", - "fieldname": "applicant_name", - "fieldtype": "Data", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 1, - "in_list_view": 1, - "in_standard_filter": 0, - "label": "Applicant Name", - "length": 0, - "no_copy": 0, - "options": "", - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 1, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 1, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 - }, + "fetch_from": "job_applicant.applicant_name", + "fieldname": "applicant_name", + "fieldtype": "Data", + "in_global_search": 1, + "in_list_view": 1, + "label": "Applicant Name", + "read_only": 1, + "reqd": 1 + }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "column_break_3", - "fieldtype": "Column Break", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "length": 0, - "no_copy": 0, - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 - }, + "fetch_from": "job_applicant.email_id", + "fieldname": "applicant_email", + "fieldtype": "Data", + "in_global_search": 1, + "label": "Applicant Email Address", + "options": "Email", + "read_only": 1 + }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 1, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "status", - "fieldtype": "Select", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 1, - "label": "Status", - "length": 0, - "no_copy": 1, - "options": "Awaiting Response\nAccepted\nRejected", - "permlevel": 0, - "precision": "", - "print_hide": 1, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 - }, + "fieldname": "column_break_3", + "fieldtype": "Column Break" + }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "default": "", - "fieldname": "offer_date", - "fieldtype": "Date", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "Offer Date", - "length": 0, - "no_copy": 0, - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 1, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 - }, + "allow_on_submit": 1, + "fieldname": "status", + "fieldtype": "Select", + "in_standard_filter": 1, + "label": "Status", + "no_copy": 1, + "options": "Awaiting Response\nAccepted\nRejected", + "print_hide": 1 + }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "designation", - "fieldtype": "Link", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 1, - "in_standard_filter": 0, - "label": "Designation", - "length": 0, - "no_copy": 0, - "options": "Designation", - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 1, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 - }, + "fieldname": "offer_date", + "fieldtype": "Date", + "label": "Offer Date", + "reqd": 1 + }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "company", - "fieldtype": "Link", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 1, - "in_standard_filter": 0, - "label": "Company", - "length": 0, - "no_copy": 0, - "options": "Company", - "permlevel": 0, - "precision": "", - "print_hide": 1, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 1, - "report_hide": 0, - "reqd": 1, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 - }, + "fieldname": "designation", + "fieldtype": "Link", + "in_list_view": 1, + "label": "Designation", + "options": "Designation", + "reqd": 1 + }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "section_break_4", - "fieldtype": "Section Break", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "length": 0, - "no_copy": 0, - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 - }, + "fieldname": "company", + "fieldtype": "Link", + "in_list_view": 1, + "label": "Company", + "options": "Company", + "print_hide": 1, + "remember_last_selected_value": 1, + "reqd": 1 + }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "offer_terms", - "fieldtype": "Table", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "Job Offer Terms", - "length": 0, - "no_copy": 0, - "options": "Job Offer Term", - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 - }, + "fieldname": "section_break_4", + "fieldtype": "Section Break" + }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "section_break_14", - "fieldtype": "Section Break", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "length": 0, - "no_copy": 0, - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 - }, + "fieldname": "offer_terms", + "fieldtype": "Table", + "label": "Job Offer Terms", + "options": "Job Offer Term" + }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "select_terms", - "fieldtype": "Link", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "Select Terms and Conditions", - "length": 0, - "no_copy": 0, - "options": "Terms and Conditions", - "permlevel": 0, - "precision": "", - "print_hide": 1, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 - }, + "fieldname": "section_break_14", + "fieldtype": "Section Break" + }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "terms", - "fieldtype": "Text Editor", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "Terms and Conditions", - "length": 0, - "no_copy": 0, - "options": "", - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 - }, + "fieldname": "select_terms", + "fieldtype": "Link", + "label": "Select Terms and Conditions", + "options": "Terms and Conditions", + "print_hide": 1 + }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 1, - "columns": 0, - "fieldname": "printing_details", - "fieldtype": "Section Break", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "Printing Details", - "length": 0, - "no_copy": 0, - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 - }, + "fieldname": "terms", + "fieldtype": "Text Editor", + "label": "Terms and Conditions" + }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 1, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fetch_from": "company.default_letter_head", - "fieldname": "letter_head", - "fieldtype": "Link", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "Letter Head", - "length": 0, - "no_copy": 0, - "options": "Letter Head", - "permlevel": 0, - "precision": "", - "print_hide": 1, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 - }, + "collapsible": 1, + "fieldname": "printing_details", + "fieldtype": "Section Break", + "label": "Printing Details" + }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "column_break_16", - "fieldtype": "Column Break", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "length": 0, - "no_copy": 0, - "permlevel": 0, - "precision": "", - "print_hide": 1, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0, + "allow_on_submit": 1, + "fetch_from": "company.default_letter_head", + "fieldname": "letter_head", + "fieldtype": "Link", + "label": "Letter Head", + "options": "Letter Head", + "print_hide": 1 + }, + { + "fieldname": "column_break_16", + "fieldtype": "Column Break", + "print_hide": 1, "width": "50%" - }, + }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 1, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "select_print_heading", - "fieldtype": "Link", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "Print Heading", - "length": 0, - "no_copy": 0, - "options": "Print Heading", - "permlevel": 0, - "precision": "", - "print_hide": 1, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 1, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, + "allow_bulk_edit": 0, + "allow_in_quick_entry": 0, + "allow_on_submit": 1, + "bold": 0, + "collapsible": 0, + "columns": 0, + "fieldname": "select_print_heading", + "fieldtype": "Link", + "hidden": 0, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 0, + "in_global_search": 0, + "in_list_view": 0, + "in_standard_filter": 0, + "label": "Print Heading", + "length": 0, + "no_copy": 0, + "options": "Print Heading", + "permlevel": 0, + "precision": "", + "print_hide": 1, + "print_hide_if_no_value": 0, + "read_only": 0, + "remember_last_selected_value": 0, + "report_hide": 1, + "reqd": 0, + "search_index": 0, + "set_only_once": 0, + "translatable": 0, "unique": 0 - }, + }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "amended_from", - "fieldtype": "Link", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "Amended From", - "length": 0, - "no_copy": 1, - "options": "Job Offer", - "permlevel": 0, - "print_hide": 1, - "print_hide_if_no_value": 0, - "read_only": 1, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, + "allow_bulk_edit": 0, + "allow_in_quick_entry": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "fieldname": "amended_from", + "fieldtype": "Link", + "hidden": 0, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 0, + "in_global_search": 0, + "in_list_view": 0, + "in_standard_filter": 0, + "label": "Amended From", + "length": 0, + "no_copy": 1, + "options": "Job Offer", + "permlevel": 0, + "print_hide": 1, + "print_hide_if_no_value": 0, + "read_only": 1, + "remember_last_selected_value": 0, + "report_hide": 0, + "reqd": 0, + "search_index": 0, + "set_only_once": 0, + "translatable": 0, "unique": 0 } - ], - "has_web_view": 0, - "hide_heading": 0, - "hide_toolbar": 0, - "idx": 0, - "image_view": 0, - "in_create": 0, - "is_submittable": 1, - "issingle": 0, - "istable": 0, - "max_attachments": 0, - "modified": "2018-08-21 16:15:35.616910", - "modified_by": "Administrator", - "module": "HR", - "name": "Job Offer", - "name_case": "", - "owner": "Administrator", + ], + "is_submittable": 1, + "links": [], + "modified": "2020-06-25 00:56:24.756395", + "modified_by": "Administrator", + "module": "HR", + "name": "Job Offer", + "owner": "Administrator", "permissions": [ { - "amend": 1, - "cancel": 1, - "create": 1, - "delete": 1, - "email": 1, - "export": 1, - "if_owner": 0, - "import": 1, - "permlevel": 0, - "print": 1, - "read": 1, - "report": 1, - "role": "HR User", - "set_user_permissions": 0, - "share": 1, - "submit": 1, + "amend": 1, + "cancel": 1, + "create": 1, + "delete": 1, + "email": 1, + "export": 1, + "if_owner": 0, + "import": 1, + "permlevel": 0, + "print": 1, + "read": 1, + "report": 1, + "role": "HR User", + "set_user_permissions": 0, + "share": 1, + "submit": 1, "write": 1 } - ], - "quick_entry": 0, - "read_only": 0, - "read_only_onload": 0, - "show_name_in_global_search": 1, - "sort_field": "modified", - "sort_order": "DESC", - "title_field": "applicant_name", - "track_changes": 0, - "track_seen": 0, + ], + "quick_entry": 0, + "read_only": 0, + "read_only_onload": 0, + "show_name_in_global_search": 1, + "sort_field": "modified", + "sort_order": "DESC", + "title_field": "applicant_name", + "track_changes": 0, + "track_seen": 0, "track_views": 0 } \ No newline at end of file diff --git a/erpnext/hr/doctype/job_offer/job_offer.py b/erpnext/hr/doctype/job_offer/job_offer.py index ef8004eedb7..fc6400d6447 100644 --- a/erpnext/hr/doctype/job_offer/job_offer.py +++ b/erpnext/hr/doctype/job_offer/job_offer.py @@ -3,6 +3,7 @@ from __future__ import unicode_literals import frappe +from frappe.utils import cint from frappe.model.document import Document from frappe.model.mapper import get_mapped_doc from frappe import _ @@ -20,10 +21,13 @@ class JobOffer(Document): staffing_plan = get_staffing_plan_detail(self.designation, self.company, self.offer_date) check_vacancies = frappe.get_single("HR Settings").check_vacancies if staffing_plan and check_vacancies: - vacancies = frappe.db.get_value("Staffing Plan Detail", filters={"name": staffing_plan.name}, fieldname=['vacancies']) - job_offers = len(self.get_job_offer(staffing_plan.from_date, staffing_plan.to_date)) - if vacancies - job_offers <= 0: - frappe.throw(_("There are no vacancies under staffing plan {0}").format(get_link_to_form("Staffing Plan", staffing_plan.parent))) + job_offers = self.get_job_offer(staffing_plan.from_date, staffing_plan.to_date) + if not staffing_plan.get("vacancies") or cint(staffing_plan.vacancies) - len(job_offers) <= 0: + error_variable = 'for ' + frappe.bold(self.designation) + if staffing_plan.get("parent"): + error_variable = frappe.bold(get_link_to_form("Staffing Plan", staffing_plan.parent)) + + frappe.throw(_("There are no vacancies under staffing plan {0}").format(error_variable)) def on_change(self): update_job_applicant(self.status, self.job_applicant) @@ -42,18 +46,22 @@ def update_job_applicant(status, job_applicant): def get_staffing_plan_detail(designation, company, offer_date): detail = frappe.db.sql(""" - SELECT spd.name as name, + SELECT DISTINCT spd.parent, sp.from_date as from_date, sp.to_date as to_date, - sp.name as parent + sp.name, + sum(spd.vacancies) as vacancies, + spd.designation FROM `tabStaffing Plan Detail` spd, `tabStaffing Plan` sp WHERE sp.docstatus=1 AND spd.designation=%s AND sp.company=%s + AND spd.parent = sp.name AND %s between sp.from_date and sp.to_date """, (designation, company, offer_date), as_dict=1) - return detail[0] if detail else None + + return frappe._dict(detail[0]) if (detail and detail[0].parent) else None @frappe.whitelist() def make_employee(source_name, target_doc=None): diff --git a/erpnext/hr/doctype/job_offer/job_offer_list.js b/erpnext/hr/doctype/job_offer/job_offer_list.js new file mode 100644 index 00000000000..4fa5be7cc84 --- /dev/null +++ b/erpnext/hr/doctype/job_offer/job_offer_list.js @@ -0,0 +1,15 @@ +// Copyright (c) 2020, Frappe Technologies Pvt. Ltd. and Contributors +// MIT License. See license.txt + +frappe.listview_settings['Job Offer'] = { + add_fields: ["company", "designation", "job_applicant", "status"], + get_indicator: function (doc) { + if (doc.status == "Accepted") { + return [__(doc.status), "green", "status,=," + doc.status]; + } else if (doc.status == "Awaiting Response") { + return [__(doc.status), "orange", "status,=," + doc.status]; + } else if (doc.status == "Rejected") { + return [__(doc.status), "red", "status,=," + doc.status]; + } + } +}; diff --git a/erpnext/hr/doctype/leave_application/leave_application.js b/erpnext/hr/doctype/leave_application/leave_application.js index df4cf71b776..7b274119244 100755 --- a/erpnext/hr/doctype/leave_application/leave_application.js +++ b/erpnext/hr/doctype/leave_application/leave_application.js @@ -39,6 +39,9 @@ frappe.ui.form.on("Leave Application", { validate: function(frm) { frm.toggle_reqd("half_day_date", frm.doc.half_day == 1); + if (frm.doc.half_day == 0){ + frm.doc.half_day_date = ""; + } }, make_dashboard: function(frm) { diff --git a/erpnext/hr/doctype/leave_policy/leave_policy_dashboard.py b/erpnext/hr/doctype/leave_policy/leave_policy_dashboard.py index 48a204596c3..71819e76ed8 100644 --- a/erpnext/hr/doctype/leave_policy/leave_policy_dashboard.py +++ b/erpnext/hr/doctype/leave_policy/leave_policy_dashboard.py @@ -1,4 +1,5 @@ from __future__ import unicode_literals +from frappe import _ def get_data(): return { @@ -8,13 +9,12 @@ def get_data(): }, 'transactions': [ { - 'items': ['Employee'] - }, - { - 'items': ['Employee Grade'] + 'label': _('Employees'), + 'items': ['Employee', 'Employee Grade'] }, { + 'label': _('Leaves'), 'items': ['Leave Allocation'] }, ] - } \ No newline at end of file + } diff --git a/erpnext/hr/doctype/payroll_entry/payroll_entry.js b/erpnext/hr/doctype/payroll_entry/payroll_entry.js index da25d7574e0..ab4b6e7d0db 100644 --- a/erpnext/hr/doctype/payroll_entry/payroll_entry.js +++ b/erpnext/hr/doctype/payroll_entry/payroll_entry.js @@ -211,7 +211,7 @@ frappe.ui.form.on('Payroll Entry', { }, doc: frm.doc, freeze: true, - freeze_message: 'Validating Employee Attendance...' + freeze_message: __('Validating Employee Attendance...') }); }else{ frm.fields_dict.attendance_detail_html.html(""); @@ -235,7 +235,7 @@ const submit_salary_slip = function (frm) { callback: function() {frm.events.refresh(frm);}, doc: frm.doc, freeze: true, - freeze_message: 'Submitting Salary Slips and creating Journal Entry...' + freeze_message: __('Submitting Salary Slips and creating Journal Entry...') }); }, function() { diff --git a/erpnext/hr/doctype/payroll_entry/payroll_entry.py b/erpnext/hr/doctype/payroll_entry/payroll_entry.py index 3b85c4da139..2a81b32aa9a 100644 --- a/erpnext/hr/doctype/payroll_entry/payroll_entry.py +++ b/erpnext/hr/doctype/payroll_entry/payroll_entry.py @@ -573,6 +573,8 @@ def submit_salary_slips_for_employees(payroll_entry, salary_slips, publish_progr if not_submitted_ss: frappe.msgprint(_("Could not submit some Salary Slips")) +@frappe.whitelist() +@frappe.validate_and_sanitize_search_inputs def get_payroll_entries_for_jv(doctype, txt, searchfield, start, page_len, filters): return frappe.db.sql(""" select name from `tabPayroll Entry` diff --git a/erpnext/hr/doctype/salary_slip/salary_slip.py b/erpnext/hr/doctype/salary_slip/salary_slip.py index 452aa74281e..774aa51daa1 100644 --- a/erpnext/hr/doctype/salary_slip/salary_slip.py +++ b/erpnext/hr/doctype/salary_slip/salary_slip.py @@ -776,10 +776,10 @@ class SalarySlip(TransactionBase): # other taxes and charges on income tax for d in tax_slab.other_taxes_and_charges: - if flt(d.min_taxable_income) and flt(d.min_taxable_income) > tax_amount: + if flt(d.min_taxable_income) and flt(d.min_taxable_income) > annual_taxable_earning: continue - if flt(d.max_taxable_income) and flt(d.max_taxable_income) < tax_amount: + if flt(d.max_taxable_income) and flt(d.max_taxable_income) < annual_taxable_earning: continue tax_amount += tax_amount * flt(d.percent) / 100 diff --git a/erpnext/hr/doctype/taxable_salary_slab/taxable_salary_slab.json b/erpnext/hr/doctype/taxable_salary_slab/taxable_salary_slab.json index a094f8a1971..2a56013e78d 100644 --- a/erpnext/hr/doctype/taxable_salary_slab/taxable_salary_slab.json +++ b/erpnext/hr/doctype/taxable_salary_slab/taxable_salary_slab.json @@ -1,232 +1,234 @@ { - "allow_copy": 0, - "allow_guest_to_view": 0, - "allow_import": 0, - "allow_rename": 0, - "beta": 0, - "creation": "2018-04-13 17:42:13.516032", - "custom": 0, - "docstatus": 0, - "doctype": "DocType", - "document_type": "", - "editable_grid": 1, - "engine": "InnoDB", + "allow_copy": 0, + "allow_guest_to_view": 0, + "allow_import": 0, + "allow_rename": 0, + "beta": 0, + "creation": "2018-04-13 17:42:13.516032", + "custom": 0, + "docstatus": 0, + "doctype": "DocType", + "document_type": "", + "editable_grid": 1, + "engine": "InnoDB", "fields": [ { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "from_amount", - "fieldtype": "Currency", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 1, - "in_standard_filter": 0, - "label": "From Amount", - "length": 0, - "no_copy": 0, - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 1, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, + "allow_bulk_edit": 0, + "allow_in_quick_entry": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "default": "0", + "fieldname": "from_amount", + "fieldtype": "Currency", + "hidden": 0, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 0, + "in_global_search": 0, + "in_list_view": 1, + "in_standard_filter": 0, + "label": "From Amount", + "length": 0, + "no_copy": 0, + "permlevel": 0, + "precision": "", + "print_hide": 0, + "print_hide_if_no_value": 0, + "read_only": 0, + "remember_last_selected_value": 0, + "report_hide": 0, + "reqd": 1, + "search_index": 0, + "set_only_once": 0, + "translatable": 0, "unique": 0 - }, + }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "to_amount", - "fieldtype": "Currency", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 1, - "in_standard_filter": 0, - "label": "To Amount", - "length": 0, - "no_copy": 0, - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, + "allow_bulk_edit": 0, + "allow_in_quick_entry": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "fieldname": "to_amount", + "fieldtype": "Currency", + "hidden": 0, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 0, + "in_global_search": 0, + "in_list_view": 1, + "in_standard_filter": 0, + "label": "To Amount", + "length": 0, + "no_copy": 0, + "permlevel": 0, + "precision": "", + "print_hide": 0, + "print_hide_if_no_value": 0, + "read_only": 0, + "remember_last_selected_value": 0, + "report_hide": 0, + "reqd": 0, + "search_index": 0, + "set_only_once": 0, + "translatable": 0, "unique": 0 - }, + }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "percent_deduction", - "fieldtype": "Percent", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 1, - "in_standard_filter": 0, - "label": "Percent Deduction", - "length": 0, - "no_copy": 0, - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 1, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, + "allow_bulk_edit": 0, + "allow_in_quick_entry": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "default": "0", + "fieldname": "percent_deduction", + "fieldtype": "Percent", + "hidden": 0, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 0, + "in_global_search": 0, + "in_list_view": 1, + "in_standard_filter": 0, + "label": "Percent Deduction", + "length": 0, + "no_copy": 0, + "permlevel": 0, + "precision": "", + "print_hide": 0, + "print_hide_if_no_value": 0, + "read_only": 0, + "remember_last_selected_value": 0, + "report_hide": 0, + "reqd": 1, + "search_index": 0, + "set_only_once": 0, + "translatable": 0, "unique": 0 - }, + }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "condition", - "fieldtype": "Code", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 1, - "in_standard_filter": 0, - "label": "Condition", - "length": 0, - "no_copy": 0, - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, + "allow_bulk_edit": 0, + "allow_in_quick_entry": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "fieldname": "condition", + "fieldtype": "Code", + "hidden": 0, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 0, + "in_global_search": 0, + "in_list_view": 1, + "in_standard_filter": 0, + "label": "Condition", + "length": 0, + "no_copy": 0, + "permlevel": 0, + "precision": "", + "print_hide": 0, + "print_hide_if_no_value": 0, + "read_only": 0, + "remember_last_selected_value": 0, + "report_hide": 0, + "reqd": 0, + "search_index": 0, + "set_only_once": 0, + "translatable": 0, "unique": 0 - }, + }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "column_break_5", - "fieldtype": "Column Break", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "length": 0, - "no_copy": 0, - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, + "allow_bulk_edit": 0, + "allow_in_quick_entry": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "fieldname": "column_break_5", + "fieldtype": "Column Break", + "hidden": 0, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 0, + "in_global_search": 0, + "in_list_view": 0, + "in_standard_filter": 0, + "length": 0, + "no_copy": 0, + "permlevel": 0, + "precision": "", + "print_hide": 0, + "print_hide_if_no_value": 0, + "read_only": 0, + "remember_last_selected_value": 0, + "report_hide": 0, + "reqd": 0, + "search_index": 0, + "set_only_once": 0, + "translatable": 0, "unique": 0 - }, + }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "html_6", - "fieldtype": "HTML", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "length": 0, - "no_copy": 0, - "options": "

Condition Examples

\n
    \n
  1. Applying tax if employee born between 31-12-1937 and 01-01-1958 (Employees aged 60 to 80)
    \nCondition: date_of_birth>date(1937, 12, 31) and date_of_birth<date(1958, 01, 01)

  2. Applying tax by employee gender
    \nCondition: gender==\"Male\"

  3. \n
  4. Applying tax by Salary Component
    \nCondition: base > 10000
", - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, + "allow_bulk_edit": 0, + "allow_in_quick_entry": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "fieldname": "html_6", + "fieldtype": "HTML", + "hidden": 0, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 0, + "in_global_search": 0, + "in_list_view": 0, + "in_standard_filter": 0, + "length": 0, + "no_copy": 0, + "options": "

Condition Examples

\n
    \n
  1. Applying tax if employee born between 31-12-1937 and 01-01-1958 (Employees aged 60 to 80)
    \nCondition: date_of_birth>date(1937, 12, 31) and date_of_birth<date(1958, 01, 01)

  2. Applying tax by employee gender
    \nCondition: gender==\"Male\"

  3. \n
  4. Applying tax by Salary Component
    \nCondition: base > 10000
", + "permlevel": 0, + "precision": "", + "print_hide": 0, + "print_hide_if_no_value": 0, + "read_only": 0, + "remember_last_selected_value": 0, + "report_hide": 0, + "reqd": 0, + "search_index": 0, + "set_only_once": 0, + "translatable": 0, "unique": 0 } - ], - "has_web_view": 0, - "hide_heading": 0, - "hide_toolbar": 0, - "idx": 0, - "image_view": 0, - "in_create": 0, - "is_submittable": 0, - "issingle": 0, - "istable": 1, - "max_attachments": 0, - "modified": "2018-06-19 10:10:23.732132", - "modified_by": "Administrator", - "module": "HR", - "name": "Taxable Salary Slab", - "name_case": "", - "owner": "Administrator", - "permissions": [], - "quick_entry": 1, - "read_only": 0, - "read_only_onload": 0, - "show_name_in_global_search": 0, - "sort_field": "modified", - "sort_order": "DESC", - "track_changes": 1, + ], + "has_web_view": 0, + "hide_heading": 0, + "hide_toolbar": 0, + "idx": 0, + "image_view": 0, + "in_create": 0, + "is_submittable": 0, + "issingle": 0, + "istable": 1, + "max_attachments": 0, + "modified": "2020-06-22 18:16:07.596493", + "modified_by": "Administrator", + "module": "HR", + "name": "Taxable Salary Slab", + "name_case": "", + "owner": "Administrator", + "permissions": [], + "quick_entry": 1, + "read_only": 0, + "read_only_onload": 0, + "show_name_in_global_search": 0, + "sort_field": "modified", + "sort_order": "DESC", + "track_changes": 1, "track_seen": 0 } diff --git a/erpnext/maintenance/doctype/maintenance_visit/maintenance_visit.json b/erpnext/maintenance/doctype/maintenance_visit/maintenance_visit.json index c797b7ea77c..11925681dfd 100644 --- a/erpnext/maintenance/doctype/maintenance_visit/maintenance_visit.json +++ b/erpnext/maintenance/doctype/maintenance_visit/maintenance_visit.json @@ -701,7 +701,7 @@ "columns": 0, "default": "Draft", "fieldname": "status", - "fieldtype": "Data", + "fieldtype": "Select", "hidden": 0, "ignore_user_permissions": 0, "ignore_xss_filter": 0, @@ -1001,7 +1001,7 @@ "issingle": 0, "istable": 0, "max_attachments": 0, - "modified": "2018-08-21 14:44:44.911402", + "modified": "2020-07-15 14:44:44.911402", "modified_by": "Administrator", "module": "Maintenance", "name": "Maintenance Visit", diff --git a/erpnext/manufacturing/doctype/bom/bom.py b/erpnext/manufacturing/doctype/bom/bom.py index 067a6f7674d..68f796ba1bc 100644 --- a/erpnext/manufacturing/doctype/bom/bom.py +++ b/erpnext/manufacturing/doctype/bom/bom.py @@ -539,7 +539,7 @@ class BOM(WebsiteGenerator): 'image' : d.image, 'stock_uom' : d.stock_uom, 'stock_qty' : flt(d.stock_qty), - 'rate' : d.base_rate, + 'rate' : flt(d.base_rate) / flt(d.conversion_factor), 'include_item_in_manufacturing': d.include_item_in_manufacturing })) diff --git a/erpnext/manufacturing/doctype/job_card/job_card.py b/erpnext/manufacturing/doctype/job_card/job_card.py index 8fce3161375..88b20eb6942 100644 --- a/erpnext/manufacturing/doctype/job_card/job_card.py +++ b/erpnext/manufacturing/doctype/job_card/job_card.py @@ -131,7 +131,7 @@ class JobCard(Document): work_order_field = "name" if field == "operation_id" else field for data in wo.operations: - if data.get(work_order_field) == self.get(field) and data.workstation == self.workstation: + if data.get(work_order_field) == self.get(field): data.completed_qty = for_quantity data.actual_operation_time = time_in_mins data.actual_start_time = time_data[0].start_time if time_data else None diff --git a/erpnext/manufacturing/doctype/production_plan/production_plan.py b/erpnext/manufacturing/doctype/production_plan/production_plan.py index 61b4ca838b3..8ff691cc300 100644 --- a/erpnext/manufacturing/doctype/production_plan/production_plan.py +++ b/erpnext/manufacturing/doctype/production_plan/production_plan.py @@ -98,11 +98,17 @@ class ProductionPlan(Document): elif self.get_items_from == "Material Request": self.get_mr_items() + def get_so_mr_list(self, field, table): + """Returns a list of Sales Orders or Material Requests from the respective tables""" + so_mr_list = [d.get(field) for d in self.get(table) if d.get(field)] + return so_mr_list + def get_so_items(self): - so_list = [d.sales_order for d in self.sales_orders if d.sales_order] - if not so_list: - msgprint(_("Please enter Sales Orders in the above table")) - return [] + # Check for empty table or empty rows + if not self.get("sales_orders") or not self.get_so_mr_list("sales_order", "sales_orders"): + frappe.throw(_("Please fill the Sales Orders table"), title=_("Sales Orders Required")) + + so_list = self.get_so_mr_list("sales_order", "sales_orders") item_condition = "" if self.item_code: @@ -134,10 +140,11 @@ class ProductionPlan(Document): self.calculate_total_planned_qty() def get_mr_items(self): - mr_list = [d.material_request for d in self.material_requests if d.material_request] - if not mr_list: - msgprint(_("Please enter Material Requests in the above table")) - return [] + # Check for empty table or empty rows + if not self.get("material_requests") or not self.get_so_mr_list("material_request", "material_requests"): + frappe.throw(_("Please fill the Material Requests table"), title=_("Material Requests Required")) + + mr_list = self.get_so_mr_list("material_request", "material_requests") item_condition = "" if self.item_code: @@ -614,7 +621,13 @@ def get_items_for_material_requests(doc, ignore_existing_ordered_qty=None): doc = frappe._dict(json.loads(doc)) doc['mr_items'] = [] + po_items = doc.get('po_items') if doc.get('po_items') else doc.get('items') + # Check for empty table or empty rows + if not po_items or not [row.get('item_code') for row in po_items if row.get('item_code')]: + frappe.throw(_("Items to Manufacture are required to pull the Raw Materials associated with it."), + title=_("Items Required")) + company = doc.get('company') warehouse = doc.get('for_warehouse') diff --git a/erpnext/manufacturing/doctype/work_order/work_order.py b/erpnext/manufacturing/doctype/work_order/work_order.py index a7e1acc524c..b0585e5d734 100644 --- a/erpnext/manufacturing/doctype/work_order/work_order.py +++ b/erpnext/manufacturing/doctype/work_order/work_order.py @@ -562,6 +562,8 @@ class WorkOrder(Document): bom.set_bom_material_details() return bom +@frappe.whitelist() +@frappe.validate_and_sanitize_search_inputs def get_bom_operations(doctype, txt, searchfield, start, page_len, filters): if txt: filters['operation'] = ('like', '%%%s%%' % txt) diff --git a/erpnext/manufacturing/report/bom_variance_report/bom_variance_report.py b/erpnext/manufacturing/report/bom_variance_report/bom_variance_report.py index c5627e0c087..982266c3ebd 100644 --- a/erpnext/manufacturing/report/bom_variance_report/bom_variance_report.py +++ b/erpnext/manufacturing/report/bom_variance_report/bom_variance_report.py @@ -30,7 +30,7 @@ def get_columns(filters): "width": 180 } ]) - + columns.extend([ { "label": _("Finished Good"), @@ -73,7 +73,7 @@ def get_columns(filters): ]) return columns - + def get_data(filters): cond = "1=1" @@ -95,6 +95,7 @@ def get_data(filters): return results @frappe.whitelist() +@frappe.validate_and_sanitize_search_inputs def get_work_orders(doctype, txt, searchfield, start, page_len, filters): cond = "1=1" if filters.get('bom_no'): diff --git a/erpnext/patches.txt b/erpnext/patches.txt index 34faea36eb2..51d18e83e99 100644 --- a/erpnext/patches.txt +++ b/erpnext/patches.txt @@ -652,6 +652,7 @@ erpnext.patches.v12_0.update_price_or_product_discount erpnext.patches.v12_0.add_export_type_field_in_party_master erpnext.patches.v12_0.rename_bank_reconciliation_fields # 2020-01-22 erpnext.patches.v12_0.create_irs_1099_field_united_states +erpnext.patches.v12_0.set_purchase_receipt_delivery_note_detail erpnext.patches.v12_0.add_permission_in_lower_deduction erpnext.patches.v12_0.set_permission_einvoicing erpnext.patches.v12_0.set_received_qty_in_material_request_as_per_stock_uom @@ -669,5 +670,9 @@ erpnext.patches.v12_0.set_serial_no_status #2020-05-21 erpnext.patches.v12_0.update_price_list_currency_in_bom erpnext.patches.v12_0.update_uom_conversion_factor erpnext.patches.v12_0.set_italian_import_supplier_invoice_permissions +erpnext.patches.v12_0.unhide_cost_center_field execute:frappe.reload_doc("HR", "doctype", "Employee Advance") erpnext.patches.v12_0.move_due_advance_amount_to_pending_amount +erpnext.patches.v12_0.set_multi_uom_in_rfq +erpnext.patches.v12_0.update_state_code_for_daman_and_diu +erpnext.patches.v12_0.rename_lost_reason_detail diff --git a/erpnext/patches/v12_0/rename_lost_reason_detail.py b/erpnext/patches/v12_0/rename_lost_reason_detail.py new file mode 100644 index 00000000000..044d0232e09 --- /dev/null +++ b/erpnext/patches/v12_0/rename_lost_reason_detail.py @@ -0,0 +1,17 @@ +from __future__ import unicode_literals +import frappe + +def execute(): + if frappe.db.exists("DocType", "Lost Reason Detail"): + frappe.reload_doc("crm", "doctype", "opportunity_lost_reason_detail") + frappe.reload_doc("setup", "doctype", "quotation_lost_reason_detail") + + frappe.db.sql("""INSERT INTO `tabOpportunity Lost Reason Detail` SELECT * FROM `tabLost Reason Detail` WHERE `parenttype` = 'Opportunity'""") + + frappe.db.sql("""INSERT INTO `tabQuotation Lost Reason Detail` SELECT * FROM `tabLost Reason Detail` WHERE `parenttype` = 'Quotation'""") + + frappe.db.sql("""INSERT INTO `tabQuotation Lost Reason` (`name`, `creation`, `modified`, `modified_by`, `owner`, `docstatus`, `parent`, `parentfield`, `parenttype`, `idx`, `_comments`, `_assign`, `_user_tags`, `_liked_by`, `order_lost_reason`) + SELECT o.`name`, o.`creation`, o.`modified`, o.`modified_by`, o.`owner`, o.`docstatus`, o.`parent`, o.`parentfield`, o.`parenttype`, o.`idx`, o.`_comments`, o.`_assign`, o.`_user_tags`, o.`_liked_by`, o.`lost_reason` + FROM `tabOpportunity Lost Reason` o LEFT JOIN `tabQuotation Lost Reason` q ON q.name = o.name WHERE q.name IS NULL""") + + frappe.delete_doc("DocType", "Lost Reason Detail") \ No newline at end of file diff --git a/erpnext/patches/v12_0/set_multi_uom_in_rfq.py b/erpnext/patches/v12_0/set_multi_uom_in_rfq.py new file mode 100644 index 00000000000..70ca6b222e9 --- /dev/null +++ b/erpnext/patches/v12_0/set_multi_uom_in_rfq.py @@ -0,0 +1,16 @@ +# Copyright (c) 2017, Frappe and Contributors +# License: GNU General Public License v3. See license.txt + +from __future__ import unicode_literals +import frappe +from frappe.utils import flt +from erpnext.stock.get_item_details import get_conversion_factor + +def execute(): + frappe.reload_doc('buying', 'doctype', 'request_for_quotation_item') + + frappe.db.sql("""UPDATE `tabRequest for Quotation Item` + SET + stock_uom = uom, + conversion_factor = 1, + stock_qty = qty""") \ No newline at end of file diff --git a/erpnext/patches/v12_0/set_purchase_receipt_delivery_note_detail.py b/erpnext/patches/v12_0/set_purchase_receipt_delivery_note_detail.py new file mode 100644 index 00000000000..52c9a2d7b3c --- /dev/null +++ b/erpnext/patches/v12_0/set_purchase_receipt_delivery_note_detail.py @@ -0,0 +1,92 @@ +from __future__ import unicode_literals +import frappe +from collections import defaultdict + +def execute(): + + frappe.reload_doc('stock', 'doctype', 'delivery_note_item', force=True) + frappe.reload_doc('stock', 'doctype', 'purchase_receipt_item', force=True) + + def map_rows(doc_row, return_doc_row, detail_field, doctype): + """Map rows after identifying similar ones.""" + + frappe.db.sql(""" UPDATE `tab{doctype} Item` set {detail_field} = '{doc_row_name}' + where name = '{return_doc_row_name}'""" \ + .format(doctype=doctype, + detail_field=detail_field, + doc_row_name=doc_row.get('name'), + return_doc_row_name=return_doc_row.get('name'))) #nosec + + def row_is_mappable(doc_row, return_doc_row, detail_field): + """Checks if two rows are similar enough to be mapped.""" + + if doc_row.item_code == return_doc_row.item_code and not return_doc_row.get(detail_field): + if doc_row.get('batch_no') and return_doc_row.get('batch_no') and doc_row.batch_no == return_doc_row.batch_no: + return True + + elif doc_row.get('serial_no') and return_doc_row.get('serial_no'): + doc_sn = doc_row.serial_no.split('\n') + return_doc_sn = return_doc_row.serial_no.split('\n') + + if set(doc_sn) & set(return_doc_sn): + # if two rows have serial nos in common, map them + return True + + elif doc_row.rate == return_doc_row.rate: + return True + else: + return False + + def make_return_document_map(doctype, return_document_map): + """Returns a map of documents and it's return documents. + Format => { 'document' : ['return_document_1','return_document_2'] }""" + + return_against_documents = frappe.db.sql(""" + SELECT + return_against as document, name as return_document + FROM `tab{doctype}` + WHERE + is_return = 1 and docstatus = 1""".format(doctype=doctype),as_dict=1) #nosec + + for entry in return_against_documents: + return_document_map[entry.document].append(entry.return_document) + + return return_document_map + + def set_document_detail_in_return_document(doctype): + """Map each row of the original document in the return document.""" + mapped = [] + return_document_map = defaultdict(list) + detail_field = "purchase_receipt_item" if doctype=="Purchase Receipt" else "dn_detail" + + child_doc = frappe.scrub("{0} Item".format(doctype)) + frappe.reload_doc("stock", "doctype", child_doc) + + return_document_map = make_return_document_map(doctype, return_document_map) + + count = 0 + + #iterate through original documents and its return documents + for docname in return_document_map: + doc_items = frappe.get_cached_doc(doctype, docname).get("items") + for return_doc in return_document_map[docname]: + return_doc_items = frappe.get_cached_doc(doctype, return_doc).get("items") + + #iterate through return document items and original document items for mapping + for return_item in return_doc_items: + for doc_item in doc_items: + if row_is_mappable(doc_item, return_item, detail_field) and doc_item.get('name') not in mapped: + map_rows(doc_item, return_item, detail_field, doctype) + mapped.append(doc_item.get('name')) + break + else: + continue + + # commit after every 100 sql updates + count += 1 + if count%100 == 0: + frappe.db.commit() + + set_document_detail_in_return_document("Purchase Receipt") + set_document_detail_in_return_document("Delivery Note") + frappe.db.commit() diff --git a/erpnext/patches/v12_0/unhide_cost_center_field.py b/erpnext/patches/v12_0/unhide_cost_center_field.py new file mode 100644 index 00000000000..6005ab70726 --- /dev/null +++ b/erpnext/patches/v12_0/unhide_cost_center_field.py @@ -0,0 +1,13 @@ +# Copyright (c) 2017, Frappe and Contributors +# License: GNU General Public License v3. See license.txt + +from __future__ import unicode_literals +import frappe + +def execute(): + frappe.db.sql(""" + DELETE FROM `tabProperty Setter` + WHERE doc_type in ('Sales Invoice', 'Purchase Invoice', 'Payment Entry') + AND field_name = 'cost_center' + AND property = 'hidden' + """) \ No newline at end of file diff --git a/erpnext/patches/v12_0/update_state_code_for_daman_and_diu.py b/erpnext/patches/v12_0/update_state_code_for_daman_and_diu.py new file mode 100644 index 00000000000..7450e9cd8c0 --- /dev/null +++ b/erpnext/patches/v12_0/update_state_code_for_daman_and_diu.py @@ -0,0 +1,22 @@ +import frappe +from erpnext.regional.india import states + +def execute(): + + company = frappe.get_all('Company', filters = {'country': 'India'}) + if not company: + return + + # Update options in gst_state custom field + gst_state = frappe.get_doc('Custom Field', 'Address-gst_state') + gst_state.options = '\n'.join(states) + gst_state.save() + + # Update gst_state and state code in existing address + frappe.db.sql(""" + UPDATE `tabAddress` + SET + gst_state = 'Dadra and Nagar Haveli and Daman and Diu', + gst_state_number = 26 + WHERE gst_state = 'Daman and Diu' + """) \ No newline at end of file diff --git a/erpnext/portal/product_configurator/utils.py b/erpnext/portal/product_configurator/utils.py index 6f9a627d534..c8d3203cb70 100644 --- a/erpnext/portal/product_configurator/utils.py +++ b/erpnext/portal/product_configurator/utils.py @@ -261,15 +261,15 @@ def get_next_attribute_and_values(item_code, selected_attributes): if exact_match: data = get_product_info_for_website(exact_match[0]) product_info = data.product_info + if product_info: product_info["allow_items_not_in_stock"] = cint(data.cart_settings.allow_items_not_in_stock) + if not data.cart_settings.show_price: product_info = None else: product_info = None - - return { 'next_attribute': next_attribute, 'valid_options_for_attributes': valid_options_for_attributes, diff --git a/erpnext/projects/doctype/project/project.js b/erpnext/projects/doctype/project/project.js index 9ba9ccfb4e8..cafa1bbb911 100644 --- a/erpnext/projects/doctype/project/project.js +++ b/erpnext/projects/doctype/project/project.js @@ -119,9 +119,7 @@ frappe.ui.form.on("Project", { }, collect_progress: function(frm) { - if (frm.doc.collect_progress) { - frm.set_df_property("message", "reqd", 1); - } + frm.set_df_property("message", "reqd", frm.doc.collect_progress); } }); @@ -140,4 +138,4 @@ function open_form(frm, doctype, child_doctype, parentfield) { frappe.ui.form.make_quick_entry(doctype, null, null, new_doc); }); -} \ No newline at end of file +} diff --git a/erpnext/projects/doctype/project/project.py b/erpnext/projects/doctype/project/project.py index bf6e21aa4d8..cf5c0fb400f 100644 --- a/erpnext/projects/doctype/project/project.py +++ b/erpnext/projects/doctype/project/project.py @@ -238,6 +238,8 @@ def get_list_context(context=None): "row_template": "templates/includes/projects/project_row.html" } +@frappe.whitelist() +@frappe.validate_and_sanitize_search_inputs def get_users_for_project(doctype, txt, searchfield, start, page_len, filters): conditions = [] return frappe.db.sql("""select name, concat_ws(' ', first_name, middle_name, last_name) diff --git a/erpnext/projects/doctype/project/test_project.py b/erpnext/projects/doctype/project/test_project.py index 06c62b62d2f..0c4f6f1bdfe 100644 --- a/erpnext/projects/doctype/project/test_project.py +++ b/erpnext/projects/doctype/project/test_project.py @@ -7,7 +7,7 @@ import frappe, unittest test_records = frappe.get_test_records('Project') test_ignore = ["Sales Order"] -from erpnext.projects.doctype.project_template.test_project_template import get_project_template +from erpnext.projects.doctype.project_template.test_project_template import get_project_template, make_project_template from erpnext.projects.doctype.project.project import set_project_status from frappe.utils import getdate @@ -43,4 +43,24 @@ def get_project(name): expected_start_date = '2019-01-01' )).insert() + return project + +def make_project(args): + args = frappe._dict(args) + if args.project_template_name: + template = make_project_template(args.project_template_name) + else: + template = get_project_template() + + project = frappe.get_doc(dict( + doctype = 'Project', + project_name = args.project_name, + status = 'Open', + project_template = template.name, + expected_start_date = args.start_date + )) + + if not frappe.db.exists("Project", args.project_name): + project.insert() + return project \ No newline at end of file diff --git a/erpnext/projects/doctype/project_template/test_project_template.py b/erpnext/projects/doctype/project_template/test_project_template.py index efcb2eab68b..2c5831a5dc9 100644 --- a/erpnext/projects/doctype/project_template/test_project_template.py +++ b/erpnext/projects/doctype/project_template/test_project_template.py @@ -26,4 +26,23 @@ def get_project_template(): ] )).insert() - return frappe.get_doc('Project Template', 'Test Project Template') \ No newline at end of file + return frappe.get_doc('Project Template', 'Test Project Template') + +def make_project_template(project_template_name, project_tasks=[]): + if not frappe.db.exists('Project Template', project_template_name): + frappe.get_doc(dict( + doctype = 'Project Template', + name = project_template_name, + tasks = project_tasks or [ + dict(subject='Task 1', description='Task 1 description', + start=0, duration=3), + dict(subject='Task 2', description='Task 2 description', + start=0, duration=2), + dict(subject='Task 3', description='Task 3 description', + start=2, duration=4), + dict(subject='Task 4', description='Task 4 description', + start=3, duration=2), + ] + )).insert() + + return frappe.get_doc('Project Template', project_template_name) \ No newline at end of file diff --git a/erpnext/projects/doctype/task/task.js b/erpnext/projects/doctype/task/task.js index 5719276669a..a044e1dca89 100644 --- a/erpnext/projects/doctype/task/task.js +++ b/erpnext/projects/doctype/task/task.js @@ -3,55 +3,36 @@ frappe.provide("erpnext.projects"); -cur_frm.add_fetch("project", "company", "company"); - frappe.ui.form.on("Task", { - onload: function(frm) { - frm.set_query("task", "depends_on", function() { - var filters = { + setup: function (frm) { + frm.set_query("project", function () { + return { + query: "erpnext.projects.doctype.task.task.get_project" + } + }); + + frm.make_methods = { + 'Timesheet': () => frappe.model.open_mapped_doc({ + method: 'erpnext.projects.doctype.task.task.make_timesheet', + frm: frm + }) + } + }, + + onload: function (frm) { + frm.set_query("task", "depends_on", function () { + let filters = { name: ["!=", frm.doc.name] }; - if(frm.doc.project) filters["project"] = frm.doc.project; + if (frm.doc.project) filters["project"] = frm.doc.project; return { filters: filters }; }) }, - refresh: function(frm) { - frm.fields_dict['parent_task'].get_query = function () { - return { - filters: { - "is_group": 1, - } - } - } - - if (!frm.doc.is_group) { - if (!frm.is_new()) { - if (frappe.model.can_read("Timesheet")) { - frm.add_custom_button(__("Timesheet"), () => { - frappe.route_options = { "project": frm.doc.project, "task": frm.doc.name } - frappe.set_route("List", "Timesheet"); - }, __("View"), true); - } - - if (frappe.model.can_read("Expense Claim")) { - frm.add_custom_button(__("Expense Claims"), () => { - frappe.route_options = { "project": frm.doc.project, "task": frm.doc.name }; - frappe.set_route("List", "Expense Claim"); - }, __("View"), true); - } - } - } - }, - - setup: function(frm) { - frm.fields_dict.project.get_query = function() { - return { - query: "erpnext.projects.doctype.task.task.get_project" - } - }; + refresh: function (frm) { + frm.set_query("parent_task", { "is_group": 1 }); }, is_group: function (frm) { @@ -69,12 +50,8 @@ frappe.ui.form.on("Task", { }) }, - validate: function(frm) { + validate: function (frm) { frm.doc.project && frappe.model.remove_from_locals("Project", frm.doc.project); - }, - + } }); - -cur_frm.add_fetch('task', 'subject', 'subject'); -cur_frm.add_fetch('task', 'project', 'project'); diff --git a/erpnext/projects/doctype/task/task.json b/erpnext/projects/doctype/task/task.json index ce0dbd179ce..8d4552af518 100644 --- a/erpnext/projects/doctype/task/task.json +++ b/erpnext/projects/doctype/task/task.json @@ -183,7 +183,8 @@ { "fieldname": "progress", "fieldtype": "Percent", - "label": "% Progress" + "label": "% Progress", + "no_copy": 1 }, { "default": "0", @@ -325,6 +326,7 @@ "options": "Department" }, { + "fetch_from": "project.company", "fieldname": "company", "fieldtype": "Link", "label": "Company", @@ -357,6 +359,7 @@ "fieldname": "completed_by", "fieldtype": "Link", "label": "Completed By", + "no_copy": 1, "options": "User" } ], @@ -365,7 +368,7 @@ "is_tree": 1, "links": [], "max_attachments": 5, - "modified": "2020-03-18 18:26:04.788061", + "modified": "2020-07-03 12:36:04.960457", "modified_by": "Administrator", "module": "Projects", "name": "Task", diff --git a/erpnext/projects/doctype/task/task.py b/erpnext/projects/doctype/task/task.py index 7cf4150298c..0d403b193f3 100755 --- a/erpnext/projects/doctype/task/task.py +++ b/erpnext/projects/doctype/task/task.py @@ -7,10 +7,11 @@ import json import frappe from frappe import _, throw +from frappe.desk.form.assign_to import clear, close_all_assignments +from frappe.model.mapper import get_mapped_doc from frappe.utils import add_days, cstr, date_diff, get_link_to_form, getdate, today from frappe.utils.nestedset import NestedSet -from frappe.desk.form.assign_to import close_all_assignments, clear -from frappe.utils import date_diff + class CircularReferenceError(frappe.ValidationError): pass class EndDateCannotBeGreaterThanProjectEndDateError(frappe.ValidationError): pass @@ -188,6 +189,8 @@ def check_if_child_exists(name): return child_tasks +@frappe.whitelist() +@frappe.validate_and_sanitize_search_inputs def get_project(doctype, txt, searchfield, start, page_len, filters): from erpnext.controllers.queries import get_match_cond return frappe.db.sql(""" select name from `tabProject` @@ -219,6 +222,26 @@ def set_tasks_as_overdue(): continue frappe.get_doc("Task", task.name).update_status() + +@frappe.whitelist() +def make_timesheet(source_name, target_doc=None, ignore_permissions=False): + def set_missing_values(source, target): + target.append("time_logs", { + "hours": source.actual_time, + "completed": source.status == "Completed", + "project": source.project, + "task": source.name + }) + + doclist = get_mapped_doc("Task", source_name, { + "Task": { + "doctype": "Timesheet" + } + }, target_doc, postprocess=set_missing_values, ignore_permissions=ignore_permissions) + + return doclist + + @frappe.whitelist() def get_children(doctype, parent, task=None, project=None, is_root=False): diff --git a/erpnext/projects/doctype/timesheet/timesheet.py b/erpnext/projects/doctype/timesheet/timesheet.py index e90821689bd..2ffec339d7f 100644 --- a/erpnext/projects/doctype/timesheet/timesheet.py +++ b/erpnext/projects/doctype/timesheet/timesheet.py @@ -226,6 +226,7 @@ def get_projectwise_timesheet_data(project, parent=None): and sales_invoice is null""".format(cond), {'project': project, 'parent': parent}, as_dict=1) @frappe.whitelist() +@frappe.validate_and_sanitize_search_inputs def get_timesheet(doctype, txt, searchfield, start, page_len, filters): if not filters: filters = {} diff --git a/erpnext/projects/utils.py b/erpnext/projects/utils.py index d0d88ebdf06..c39f908e43e 100644 --- a/erpnext/projects/utils.py +++ b/erpnext/projects/utils.py @@ -7,6 +7,7 @@ from __future__ import unicode_literals import frappe @frappe.whitelist() +@frappe.validate_and_sanitize_search_inputs def query_task(doctype, txt, searchfield, start, page_len, filters): from frappe.desk.reportview import build_match_conditions diff --git a/erpnext/public/js/communication.js b/erpnext/public/js/communication.js index 5316eb45b5b..26e5ab8b322 100644 --- a/erpnext/public/js/communication.js +++ b/erpnext/public/js/communication.js @@ -7,13 +7,13 @@ frappe.ui.form.on("Communication", { }, setup_custom_buttons: (frm) => { - let confirm_msg = "Are you sure you want to create {0} from this email"; + let confirm_msg = "Are you sure you want to create {0} from this email?"; if(frm.doc.reference_doctype !== "Issue") { frm.add_custom_button(__("Issue"), () => { frappe.confirm(__(confirm_msg, [__("Issue")]), () => { frm.trigger('make_issue_from_communication'); }) - }, "Make"); + }, "Create"); } if(!in_list(["Lead", "Opportunity"], frm.doc.reference_doctype)) { @@ -62,17 +62,36 @@ frappe.ui.form.on("Communication", { }, make_opportunity_from_communication: (frm) => { - return frappe.call({ - method: "erpnext.crm.doctype.opportunity.opportunity.make_opportunity_from_communication", - args: { - communication: frm.doc.name - }, - freeze: true, - callback: (r) => { - if(r.message) { - frm.reload_doc() + const fields = [{ + fieldtype: 'Link', + label: __('Select a Company'), + fieldname: 'company', + options: 'Company', + reqd: 1, + default: frappe.defaults.get_user_default("Company") + }]; + + frappe.prompt(fields, data => { + frappe.call({ + method: "erpnext.crm.doctype.opportunity.opportunity.make_opportunity_from_communication", + args: { + communication: frm.doc.name, + company: data.company + }, + freeze: true, + callback: (r) => { + if(r.message) { + frm.reload_doc(); + frappe.show_alert({ + message: __("Opportunity {0} created", + ['' + r.message + '']), + indicator: 'green' + }); + } } - } - }) + }); + }, + 'Create an Opportunity', + 'Create'); } -}); \ No newline at end of file +}); diff --git a/erpnext/public/js/controllers/taxes_and_totals.js b/erpnext/public/js/controllers/taxes_and_totals.js index 28fb6490254..2947e880311 100644 --- a/erpnext/public/js/controllers/taxes_and_totals.js +++ b/erpnext/public/js/controllers/taxes_and_totals.js @@ -157,9 +157,11 @@ erpnext.taxes_and_totals = erpnext.payments.extend({ $.each(me.frm.doc["items"] || [], function(n, item) { var item_tax_map = me._load_item_tax_rate(item.item_tax_rate); var cumulated_tax_fraction = 0.0; - + var total_inclusive_tax_amount_per_qty = 0; $.each(me.frm.doc["taxes"] || [], function(i, tax) { - tax.tax_fraction_for_current_item = me.get_current_tax_fraction(tax, item_tax_map); + var current_tax_fraction = me.get_current_tax_fraction(tax, item_tax_map); + tax.tax_fraction_for_current_item = current_tax_fraction[0]; + var inclusive_tax_amount_per_qty = current_tax_fraction[1]; if(i==0) { tax.grand_total_fraction_for_current_item = 1 + tax.tax_fraction_for_current_item; @@ -170,10 +172,12 @@ erpnext.taxes_and_totals = erpnext.payments.extend({ } cumulated_tax_fraction += tax.tax_fraction_for_current_item; + total_inclusive_tax_amount_per_qty += inclusive_tax_amount_per_qty * flt(item.qty); }); - if(cumulated_tax_fraction && !me.discount_amount_applied) { - item.net_amount = flt(item.amount / (1 + cumulated_tax_fraction)); + if(!me.discount_amount_applied && item.qty && (total_inclusive_tax_amount_per_qty || cumulated_tax_fraction)) { + var amount = flt(item.amount) - total_inclusive_tax_amount_per_qty; + item.net_amount = flt(amount / (1 + cumulated_tax_fraction)); item.net_rate = item.qty ? flt(item.net_amount / item.qty, precision("net_rate", item)) : 0; me.set_in_company_currency(item, ["net_rate", "net_amount"]); @@ -185,6 +189,7 @@ erpnext.taxes_and_totals = erpnext.payments.extend({ // Get tax fraction for calculating tax exclusive amount // from tax inclusive amount var current_tax_fraction = 0.0; + var inclusive_tax_amount_per_qty = 0; if(cint(tax.included_in_print_rate)) { var tax_rate = this._get_tax_rate(tax, item_tax_map); @@ -199,13 +204,16 @@ erpnext.taxes_and_totals = erpnext.payments.extend({ } else if(tax.charge_type == "On Previous Row Total") { current_tax_fraction = (tax_rate / 100.0) * this.frm.doc["taxes"][cint(tax.row_id) - 1].grand_total_fraction_for_current_item; + } else if (tax.charge_type == "On Item Quantity") { + inclusive_tax_amount_per_qty = flt(tax_rate); } } - if(tax.add_deduct_tax) { - current_tax_fraction *= (tax.add_deduct_tax == "Deduct") ? -1.0 : 1.0; + if(tax.add_deduct_tax && tax.add_deduct_tax == "Deduct") { + current_tax_fraction *= -1; + inclusive_tax_amount_per_qty *= -1; } - return current_tax_fraction; + return [current_tax_fraction, inclusive_tax_amount_per_qty]; }, _get_tax_rate: function(tax, item_tax_map) { @@ -329,6 +337,16 @@ erpnext.taxes_and_totals = erpnext.payments.extend({ var tax_rate = this._get_tax_rate(tax, item_tax_map); var current_tax_amount = 0.0; + // To set row_id by default as previous row. + if(["On Previous Row Amount", "On Previous Row Total"].includes(tax.charge_type)) { + if (tax.idx === 1) { + frappe.throw( + __("Cannot select charge type as 'On Previous Row Amount' or 'On Previous Row Total' for first row")); + } + if (!tax.row_id) { + tax.row_id = tax.idx - 1; + } + } if(tax.charge_type == "Actual") { // distribute the tax amount proportionally to each item row var actual = flt(tax.tax_amount, precision("tax_amount", tax)); @@ -344,8 +362,9 @@ erpnext.taxes_and_totals = erpnext.payments.extend({ } else if(tax.charge_type == "On Previous Row Total") { current_tax_amount = (tax_rate / 100.0) * this.frm.doc["taxes"][cint(tax.row_id) - 1].grand_total_for_current_item; + } else if (tax.charge_type == "On Item Quantity") { + current_tax_amount = tax_rate * item.qty; } - this.set_item_wise_tax(item, tax, tax_rate, current_tax_amount); return current_tax_amount; @@ -557,7 +576,7 @@ erpnext.taxes_and_totals = erpnext.payments.extend({ var actual_taxes_dict = {}; $.each(this.frm.doc["taxes"] || [], function(i, tax) { - if (tax.charge_type == "Actual") { + if (in_list(["Actual", "On Item Quantity"], tax.charge_type)) { var tax_amount = (tax.category == "Valuation") ? 0.0 : tax.tax_amount; tax_amount *= (tax.add_deduct_tax == "Deduct") ? -1.0 : 1.0; actual_taxes_dict[tax.idx] = tax_amount; @@ -570,7 +589,7 @@ erpnext.taxes_and_totals = erpnext.payments.extend({ $.each(actual_taxes_dict, function(key, value) { if (value) total_actual_tax += value; }); - + return flt(this.frm.doc.grand_total - total_actual_tax, precision("grand_total")); } }, diff --git a/erpnext/public/js/controllers/transaction.js b/erpnext/public/js/controllers/transaction.js index 538bbb9bb2c..53c70936c6d 100644 --- a/erpnext/public/js/controllers/transaction.js +++ b/erpnext/public/js/controllers/transaction.js @@ -159,6 +159,26 @@ erpnext.TransactionController = erpnext.taxes_and_totals.extend({ }; }); } + if (this.frm.fields_dict["items"].grid.get_field("cost_center")) { + this.frm.set_query("cost_center", "items", function(doc) { + return { + filters: { + "company": doc.company, + "is_group": 0 + } + }; + }); + } + + if (this.frm.fields_dict["items"].grid.get_field("expense_account")) { + this.frm.set_query("expense_account", "items", function(doc) { + return { + filters: { + "company": doc.company + } + }; + }); + } if(frappe.meta.get_docfield(this.frm.doc.doctype, "pricing_rules")) { this.frm.set_indicator_formatter('pricing_rule', function(doc) { @@ -535,6 +555,12 @@ erpnext.TransactionController = erpnext.taxes_and_totals.extend({ }, () => me.frm.script_manager.trigger("price_list_rate", cdt, cdn), () => me.toggle_conversion_factor(item), + () => { + if (show_batch_dialog && !item.has_serial_no + && !item.has_batch_no) { + show_batch_dialog = false; + } + }, () => { if (show_batch_dialog) return frappe.db.get_value("Item", item.item_code, ["has_batch_no", "has_serial_no"]) diff --git a/erpnext/public/js/financial_statements.js b/erpnext/public/js/financial_statements.js index d5f83d60298..59edcf1d7e6 100644 --- a/erpnext/public/js/financial_statements.js +++ b/erpnext/public/js/financial_statements.js @@ -3,7 +3,7 @@ frappe.provide("erpnext.financial_statements"); erpnext.financial_statements = { "filters": get_filters(), "formatter": function(value, row, column, data, default_formatter) { - if (column.fieldname=="account") { + if (data && column.fieldname=="account") { value = data.account_name || value; column.link_onclick = @@ -13,7 +13,7 @@ erpnext.financial_statements = { value = default_formatter(value, row, column, data); - if (!data.parent_account) { + if (data && !data.parent_account) { value = $(`${value}`); var $value = $(value).css("font-weight", "bold"); diff --git a/erpnext/public/js/shopping_cart.js b/erpnext/public/js/shopping_cart.js index 44a8cd0067b..9986bf4ccd0 100644 --- a/erpnext/public/js/shopping_cart.js +++ b/erpnext/public/js/shopping_cart.js @@ -51,10 +51,12 @@ frappe.ready(function() { if (referral_sales_partner) { $(".txtreferral_sales_partner").val(referral_sales_partner); } + // update login shopping_cart.show_shoppingcart_dropdown(); shopping_cart.set_cart_count(); shopping_cart.bind_dropdown_cart_buttons(); + shopping_cart.show_cart_navbar(); }); $.extend(shopping_cart, { @@ -177,4 +179,12 @@ $.extend(shopping_cart, { }, + show_cart_navbar: function () { + frappe.call({ + method: "erpnext.shopping_cart.doctype.shopping_cart_settings.shopping_cart_settings.is_cart_enabled", + callback: function(r) { + $(".shopping-cart").toggleClass('hidden', r.message ? false : true); + } + }); + } }); diff --git a/erpnext/public/js/utils/serial_no_batch_selector.js b/erpnext/public/js/utils/serial_no_batch_selector.js index e78ab9fb690..7bd0a72e311 100644 --- a/erpnext/public/js/utils/serial_no_batch_selector.js +++ b/erpnext/public/js/utils/serial_no_batch_selector.js @@ -333,8 +333,8 @@ erpnext.SerialNoBatchSelector = Class.extend({ }; }, change: function () { - let val = this.get_value(); - if (val.length === 0) { + const batch_no = this.get_value(); + if (!batch_no) { this.grid_row.on_grid_fields_dict .available_qty.set_value(0); return; @@ -348,20 +348,17 @@ erpnext.SerialNoBatchSelector = Class.extend({ return row.on_grid_fields_dict.batch_no.get_value(); } }); - if (selected_batches.includes(val)) { + if (selected_batches.includes(batch_no)) { this.set_value(""); - frappe.throw(__(`Batch ${val} already selected.`)); + frappe.throw(__(`Batch ${batch_no} already selected.`)); return; } - let batch_number = me.item.batch_no || - this.grid_row.on_grid_fields_dict.batch_no.get_value(); - if (me.warehouse_details.name) { frappe.call({ method: 'erpnext.stock.doctype.batch.batch.get_batch_qty', args: { - batch_no: batch_number, + batch_no, warehouse: me.warehouse_details.name, item_code: me.item_code }, diff --git a/erpnext/regional/doctype/gstr_3b_report/gstr_3b_report.html b/erpnext/regional/doctype/gstr_3b_report/gstr_3b_report.html index 35f9cf674ce..888b2da48eb 100644 --- a/erpnext/regional/doctype/gstr_3b_report/gstr_3b_report.html +++ b/erpnext/regional/doctype/gstr_3b_report/gstr_3b_report.html @@ -52,7 +52,7 @@ - (d) {{__("Inward Supplies(liable to reverse charge")}} + (d) {{__("Inward Supplies(liable to reverse charge)")}} {{ flt(data.sup_details.isup_rev.txval, 2) }} {{ flt(data.sup_details.isup_rev.iamt, 2) }} {{ flt(data.sup_details.isup_rev.camt, 2) }} diff --git a/erpnext/regional/doctype/gstr_3b_report/gstr_3b_report.js b/erpnext/regional/doctype/gstr_3b_report/gstr_3b_report.js index a1cea8f6092..c7442667c22 100644 --- a/erpnext/regional/doctype/gstr_3b_report/gstr_3b_report.js +++ b/erpnext/regional/doctype/gstr_3b_report/gstr_3b_report.js @@ -3,6 +3,7 @@ frappe.ui.form.on('GSTR 3B Report', { refresh : function(frm) { + frm.doc.__unsaved = 1; if(!frm.is_new()) { frm.set_intro(__("Please save the report again to rebuild or update")); frm.add_custom_button(__('Download JSON'), function() { 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 15dcbd5e154..b02c4bc7333 100644 --- a/erpnext/regional/doctype/gstr_3b_report/gstr_3b_report.py +++ b/erpnext/regional/doctype/gstr_3b_report/gstr_3b_report.py @@ -8,7 +8,7 @@ from frappe import _ from frappe.model.document import Document import json from six import iteritems -from frappe.utils import flt, getdate +from frappe.utils import flt, getdate, cstr from erpnext.regional.india import state_numbers class GSTR3BReport(Document): @@ -158,7 +158,7 @@ class GSTR3BReport(Document): self.prepare_data("Sales Invoice", outward_supply_tax_amounts, "sup_details", "osup_det", ["Registered Regular"]) self.prepare_data("Sales Invoice", outward_supply_tax_amounts, "sup_details", "osup_zero", ["SEZ", "Deemed Export", "Overseas"]) - self.prepare_data("Purchase Invoice", inward_supply_tax_amounts, "sup_details", "isup_rev", ["Registered Regular"], reverse_charge="Y") + self.prepare_data("Purchase Invoice", inward_supply_tax_amounts, "sup_details", "isup_rev", ["Unregistered", "Overseas"], reverse_charge="Y") self.report_dict["sup_details"]["osup_nil_exmp"]["txval"] = flt(self.get_nil_rated_supply_value(), 2) self.set_itc_details(itc_details) @@ -192,31 +192,27 @@ class GSTR3BReport(Document): for d in self.report_dict["itc_elg"]["itc_avl"]: itc_type = itc_type_map.get(d["ty"]) - gst_category = "Registered Regular" + gst_category = ["Registered Regular"] if d["ty"] == 'ISRC': reverse_charge = "Y" + itc_type = 'All Other ITC' + gst_category = ['Unregistered', 'Overseas'] else: reverse_charge = "N" for account_head in self.account_heads: + for category in gst_category: + for key in [['iamt', 'igst_account'], ['camt', 'cgst_account'], ['samt', 'sgst_account'], ['csamt', 'cess_account']]: + d[key[0]] += flt(itc_details.get((category, itc_type, reverse_charge, account_head.get(key[1])), {}).get("amount"), 2) - d["iamt"] += flt(itc_details.get((gst_category, itc_type, reverse_charge, account_head.get('igst_account')), {}).get("amount"), 2) - d["camt"] += flt(itc_details.get((gst_category, itc_type, reverse_charge, account_head.get('cgst_account')), {}).get("amount"), 2) - d["samt"] += flt(itc_details.get((gst_category, itc_type, reverse_charge, account_head.get('sgst_account')), {}).get("amount"), 2) - d["csamt"] += flt(itc_details.get((gst_category, itc_type, reverse_charge, account_head.get('cess_account')), {}).get("amount"), 2) - - net_itc["iamt"] += flt(d["iamt"], 2) - net_itc["camt"] += flt(d["camt"], 2) - net_itc["samt"] += flt(d["samt"], 2) - net_itc["csamt"] += flt(d["csamt"], 2) + for key in ['iamt', 'camt', 'samt', 'csamt']: + net_itc[key] += flt(d[key], 2) for account_head in self.account_heads: itc_inelg = self.report_dict["itc_elg"]["itc_inelg"][1] - itc_inelg["iamt"] = flt(itc_details.get(("Ineligible", "N", account_head.get("igst_account")), {}).get("amount"), 2) - itc_inelg["camt"] = flt(itc_details.get(("Ineligible", "N", account_head.get("cgst_account")), {}).get("amount"), 2) - itc_inelg["samt"] = flt(itc_details.get(("Ineligible", "N", account_head.get("sgst_account")), {}).get("amount"), 2) - itc_inelg["csamt"] = flt(itc_details.get(("Ineligible", "N", account_head.get("cess_account")), {}).get("amount"), 2) + for key in [['iamt', 'igst_account'], ['camt', 'cgst_account'], ['samt', 'sgst_account'], ['csamt', 'cess_account']]: + itc_inelg[key[0]] = flt(itc_details.get(("Ineligible", "N", account_head.get(key[1])), {}).get("amount"), 2) def prepare_data(self, doctype, tax_details, supply_type, supply_category, gst_category_list, reverse_charge="N"): @@ -238,34 +234,26 @@ class GSTR3BReport(Document): self.report_dict[supply_type][supply_category][account_map.get(account_type)] += \ flt(tax_details.get((account_name, gst_category), {}).get("amount"), 2) - for k, v in iteritems(account_map): - txval -= self.report_dict.get(supply_type, {}).get(supply_category, {}).get(v, 0) - self.report_dict[supply_type][supply_category]["txval"] += flt(txval, 2) def set_inter_state_supply(self, inter_state_supply): osup_det = self.report_dict["sup_details"]["osup_det"] - for d in inter_state_supply.get("Unregistered", []): - self.report_dict["inter_sup"]["unreg_details"].append(d) - osup_det["txval"] = flt(osup_det["txval"] + d["txval"], 2) - osup_det["iamt"] = flt(osup_det["iamt"] + d["iamt"], 2) + for key, value in iteritems(inter_state_supply): + if key[0] == "Unregistered": + self.report_dict["inter_sup"]["unreg_details"].append(value) - for d in inter_state_supply.get("Registered Composition", []): - self.report_dict["inter_sup"]["comp_details"].append(d) - osup_det["txval"] = flt(osup_det["txval"] + d["txval"], 2) - osup_det["iamt"] = flt(osup_det["iamt"] + d["iamt"], 2) + if key[0] == "Registered Composition": + self.report_dict["inter_sup"]["comp_details"].append(value) - for d in inter_state_supply.get("UIN Holders", []): - self.report_dict["inter_sup"]["uin_details"].append(d) - osup_det["txval"] = flt(osup_det["txval"] + d["txval"], 2) - osup_det["iamt"] = flt(osup_det["iamt"] + d["iamt"], 2) + if key[0] == "UIN Holders": + self.report_dict["inter_sup"]["uin_details"].append(value) def get_total_taxable_value(self, doctype, reverse_charge): return frappe._dict(frappe.db.sql(""" - select gst_category, sum(base_grand_total) as total + select gst_category, sum(net_total) as total from `tab{doctype}` where docstatus = 1 and month(posting_date) = %s and year(posting_date) = %s and reverse_charge = %s @@ -274,17 +262,16 @@ class GSTR3BReport(Document): """ #nosec .format(doctype = doctype), (self.month_no, self.year, reverse_charge, self.company, self.gst_details.get("gstin")))) - def get_itc_details(self, reverse_charge='N'): - + def get_itc_details(self): itc_amount = frappe.db.sql(""" select s.gst_category, sum(t.tax_amount_after_discount_amount) as tax_amount, t.account_head, s.eligibility_for_itc, s.reverse_charge from `tabPurchase Invoice` s , `tabPurchase Taxes and Charges` t - where s.docstatus = 1 and t.parent = s.name and s.reverse_charge = %s + where s.docstatus = 1 and t.parent = s.name and month(s.posting_date) = %s and year(s.posting_date) = %s and s.company = %s and s.company_gstin = %s group by t.account_head, s.gst_category, s.eligibility_for_itc """, - (reverse_charge, self.month_no, self.year, self.company, self.gst_details.get("gstin")), as_dict=1) + (self.month_no, self.year, self.company, self.gst_details.get("gstin")), as_dict=1) itc_details = {} @@ -306,41 +293,57 @@ class GSTR3BReport(Document): (self.month_no, self.year, self.company, self.gst_details.get("gstin")), as_dict=1)[0].total def get_inter_state_supplies(self, state_number): - - inter_state_supply_taxable_value = frappe.db.sql(""" select sum(s.net_total) as total, s.place_of_supply, s.gst_category - from `tabSales Invoice` s where s.docstatus = 1 and month(s.posting_date) = %s and year(s.posting_date) = %s - and s.company = %s and s.company_gstin = %s and s.gst_category in ('Unregistered', 'Registered Composition', 'UIN Holders') - group by s.gst_category, s.place_of_supply""", (self.month_no, self.year, self.company, self.gst_details.get("gstin")), as_dict=1) - - inter_state_supply_tax = frappe.db.sql(""" select sum(t.tax_amount_after_discount_amount) as tax_amount, s.place_of_supply, s.gst_category - from `tabSales Invoice` s, `tabSales Taxes and Charges` t + inter_state_supply_tax = frappe.db.sql(""" select t.account_head, t.tax_amount_after_discount_amount as tax_amount, + s.name, s.net_total, s.place_of_supply, s.gst_category from `tabSales Invoice` s, `tabSales Taxes and Charges` t where t.parent = s.name and s.docstatus = 1 and month(s.posting_date) = %s and year(s.posting_date) = %s and s.company = %s and s.company_gstin = %s and s.gst_category in ('Unregistered', 'Registered Composition', 'UIN Holders') - group by s.gst_category, s.place_of_supply""", (self.month_no, self.year, self.company, self.gst_details.get("gstin")), as_dict=1) + and ifnull(s.name, '') != '' + """, (self.month_no, self.year, self.company, self.gst_details.get("gstin")), as_dict=1) - inter_state_supply_tax_mapping={} + inter_state_supply_tax_mapping = {} inter_state_supply_details = {} for d in inter_state_supply_tax: - inter_state_supply_tax_mapping.setdefault(d.place_of_supply, d.tax_amount) + inter_state_supply_tax_mapping.setdefault(cstr(d.name), { + 'place_of_supply': d.place_of_supply, + 'taxable_value': d.net_total, + 'gst_category': d.gst_category, + 'camt': 0.0, + 'samt': 0.0, + 'iamt': 0.0, + 'csamt': 0.0 + }) - for d in inter_state_supply_taxable_value: - inter_state_supply_details.setdefault( - d.gst_category, [] - ) + if d.account_head in [a.cgst_account for a in self.account_heads]: + inter_state_supply_tax_mapping[cstr(d.name)]['camt'] += d.tax_amount - if d.place_of_supply: - if state_number != d.place_of_supply.split("-")[0]: - inter_state_supply_details[d.gst_category].append({ - "pos": d.place_of_supply.split("-")[0], - "txval": flt(d.total, 2), - "iamt": flt(inter_state_supply_tax_mapping.get(d.place_of_supply), 2) + if d.account_head in [a.sgst_account for a in self.account_heads]: + inter_state_supply_tax_mapping[cstr(d.name)]['samt'] += d.tax_amount + + if d.account_head in [a.igst_account for a in self.account_heads]: + inter_state_supply_tax_mapping[cstr(d.name)]['iamt'] += d.tax_amount + + if d.account_head in [a.cess_account for a in self.account_heads]: + inter_state_supply_tax_mapping[cstr(d.name)]['csamt'] += d.tax_amount + + for key, value in iteritems(inter_state_supply_tax_mapping): + if value.get('place_of_supply'): + osup_det = self.report_dict["sup_details"]["osup_det"] + osup_det["txval"] = flt(osup_det["txval"] + value['taxable_value'], 2) + osup_det["iamt"] = flt(osup_det["iamt"] + value['iamt'], 2) + osup_det["camt"] = flt(osup_det["camt"] + value['camt'], 2) + osup_det["samt"] = flt(osup_det["samt"] + value['samt'], 2) + osup_det["csamt"] = flt(osup_det["csamt"] + value['csamt'], 2) + + if state_number != value.get('place_of_supply').split("-")[0]: + inter_state_supply_details.setdefault((value.get('gst_category'), value.get('place_of_supply')), { + "txval": 0.0, + "pos": value.get('place_of_supply').split("-")[0], + "iamt": 0.0 }) - else: - osup_det = self.report_dict["sup_details"]["osup_det"] - osup_det["txval"] = flt(osup_det["txval"] + d.total, 2) - osup_det["camt"] = flt(osup_det["camt"] + inter_state_supply_tax_mapping.get(d.place_of_supply)/2, 2) - osup_det["samt"] = flt(osup_det["samt"] + inter_state_supply_tax_mapping.get(d.place_of_supply)/2, 2) + + inter_state_supply_details[(value.get('gst_category'), value.get('place_of_supply'))]['txval'] += value['taxable_value'] + inter_state_supply_details[(value.get('gst_category'), value.get('place_of_supply'))]['iamt'] += value['iamt'] return inter_state_supply_details diff --git a/erpnext/regional/doctype/gstr_3b_report/test_gstr_3b_report.py b/erpnext/regional/doctype/gstr_3b_report/test_gstr_3b_report.py index fa6fb706e9b..bf31a6b959a 100644 --- a/erpnext/regional/doctype/gstr_3b_report/test_gstr_3b_report.py +++ b/erpnext/regional/doctype/gstr_3b_report/test_gstr_3b_report.py @@ -387,6 +387,10 @@ def make_company(): def set_account_heads(): + from erpnext.accounts.doctype.account.test_account import create_account + + create_account(account_name="Cess", parent_account = "Duties and Taxes - _GST", company="_Test Company GST") + gst_settings = frappe.get_doc("GST Settings") gst_account = frappe.get_all( @@ -400,6 +404,7 @@ def set_account_heads(): "cgst_account": "CGST - _GST", "sgst_account": "SGST - _GST", "igst_account": "IGST - _GST", + "cess_account": "Cess - _GST" }) gst_settings.save() diff --git a/erpnext/regional/india/__init__.py b/erpnext/regional/india/__init__.py index 0ed98b74eef..d6221a80aa2 100644 --- a/erpnext/regional/india/__init__.py +++ b/erpnext/regional/india/__init__.py @@ -10,8 +10,7 @@ states = [ 'Bihar', 'Chandigarh', 'Chhattisgarh', - 'Dadra and Nagar Haveli', - 'Daman and Diu', + 'Dadra and Nagar Haveli and Daman and Diu', 'Delhi', 'Goa', 'Gujarat', @@ -50,8 +49,7 @@ state_numbers = { "Bihar": "10", "Chandigarh": "04", "Chhattisgarh": "22", - "Dadra and Nagar Haveli": "26", - "Daman and Diu": "25", + "Dadra and Nagar Haveli and Daman and Diu": "26", "Delhi": "07", "Goa": "30", "Gujarat": "24", diff --git a/erpnext/regional/india/gst_state_code_data.json b/erpnext/regional/india/gst_state_code_data.json index 6dab81d6688..ff88e0f9d6c 100644 --- a/erpnext/regional/india/gst_state_code_data.json +++ b/erpnext/regional/india/gst_state_code_data.json @@ -134,15 +134,10 @@ "state_code": "DL", "state_name": "Delhi" }, - { - "state_number": "25", - "state_code": "DD", - "state_name": "Daman and Diu" - }, { "state_number": "26", "state_code": "DN", - "state_name": "Dadra and Nagar Haveli" + "state_name": "Dadra and Nagar Haveli and Daman and Diu" }, { "state_number": "22", diff --git a/erpnext/regional/india/taxes.js b/erpnext/regional/india/taxes.js index 1e59032db10..44891a76a0b 100644 --- a/erpnext/regional/india/taxes.js +++ b/erpnext/regional/india/taxes.js @@ -10,6 +10,8 @@ erpnext.setup_auto_gst_taxation = (doctype) => { frm.trigger('get_tax_template'); }, get_tax_template: function(frm) { + if (!frm.doc.company) return; + let party_details = { 'shipping_address': frm.doc.shipping_address || '', 'shipping_address_name': frm.doc.shipping_address_name || '', diff --git a/erpnext/regional/india/utils.py b/erpnext/regional/india/utils.py index abcc953277a..88637bb4ec5 100644 --- a/erpnext/regional/india/utils.py +++ b/erpnext/regional/india/utils.py @@ -1,7 +1,8 @@ from __future__ import unicode_literals import frappe, re, json from frappe import _ -from frappe.utils import cstr, flt, date_diff, nowdate +import erpnext +from frappe.utils import cstr, flt, date_diff, nowdate, round_based_on_smallest_currency_fraction, money_in_words from erpnext.regional.india import states, state_numbers from erpnext.controllers.taxes_and_totals import get_itemised_tax, get_itemised_taxable_amount from erpnext.controllers.accounts_controller import get_taxes_and_charges @@ -9,6 +10,8 @@ from erpnext.hr.utils import get_salary_assignment from erpnext.hr.doctype.salary_structure.salary_structure import make_salary_slip from erpnext.regional.india import number_state_mapping from six import string_types +from erpnext.accounts.general_ledger import make_gl_entries +from erpnext.accounts.utils import get_account_currency def validate_gstin_for_india(doc, method): if hasattr(doc, 'gst_state') and doc.gst_state: @@ -441,19 +444,23 @@ def generate_ewb_json(dt, dn): @frappe.whitelist() def download_ewb_json(): - data = frappe._dict(frappe.local.form_dict) - - frappe.local.response.filecontent = json.dumps(data['data'], indent=4, sort_keys=True) + data = json.loads(frappe.local.form_dict.data) + frappe.local.response.filecontent = json.dumps(data, indent=4, sort_keys=True) frappe.local.response.type = 'download' - billList = json.loads(data['data'])['billLists'] + filename_prefix = 'Bulk' + docname = frappe.local.form_dict.docname + if docname: + if docname.startswith('['): + docname = json.loads(docname) + if len(docname) == 1: + docname = docname[0] - if len(billList) > 1: - doc_name = 'Bulk' - else: - doc_name = data['docname'] + if not isinstance(docname, list): + # removes characters not allowed in a filename (https://stackoverflow.com/a/38766141/4767738) + filename_prefix = re.sub('[^\w_.)( -]', '', docname) - frappe.local.response.filename = '{0}_e-WayBill_Data_{1}.json'.format(doc_name, frappe.utils.random_string(5)) + frappe.local.response.filename = '{0}_e-WayBill_Data_{1}.json'.format(filename_prefix, frappe.utils.random_string(5)) @frappe.whitelist() def get_gstins_for_company(company): @@ -627,6 +634,7 @@ def validate_state_code(state_code, address): else: return int(state_code) +@frappe.whitelist() def get_gst_accounts(company, account_wise=False): gst_accounts = frappe._dict() gst_settings_accounts = frappe.get_all("GST Account", @@ -643,5 +651,90 @@ def get_gst_accounts(company, account_wise=False): elif val: gst_accounts[val] = acc - return gst_accounts + +def update_grand_total_for_rcm(doc, method): + country = frappe.get_cached_value('Company', doc.company, 'country') + + if country != 'India': + return + + if not doc.total_taxes_and_charges: + return + + if doc.reverse_charge == 'Y': + gst_accounts = get_gst_accounts(doc.company) + gst_account_list = gst_accounts.get('cgst_account') + gst_accounts.get('sgst_account') \ + + gst_accounts.get('igst_account') + + base_gst_tax = 0 + gst_tax = 0 + + for tax in doc.get('taxes'): + if tax.category not in ("Total", "Valuation and Total"): + continue + + if flt(tax.base_tax_amount_after_discount_amount) and tax.account_head in gst_account_list: + base_gst_tax += tax.base_tax_amount_after_discount_amount + gst_tax += tax.tax_amount_after_discount_amount + + doc.taxes_and_charges_added -= gst_tax + doc.total_taxes_and_charges -= gst_tax + doc.base_taxes_and_charges_added -= base_gst_tax + doc.base_total_taxes_and_charges -= base_gst_tax + + update_totals(gst_tax, base_gst_tax, doc) + +def update_totals(gst_tax, base_gst_tax, doc): + doc.base_grand_total -= base_gst_tax + doc.grand_total -= gst_tax + + if doc.meta.get_field("rounded_total"): + if doc.is_rounded_total_disabled(): + doc.outstanding_amount = doc.grand_total + else: + doc.rounded_total = round_based_on_smallest_currency_fraction(doc.grand_total, + doc.currency, doc.precision("rounded_total")) + + doc.rounding_adjustment += flt(doc.rounded_total - doc.grand_total, + doc.precision("rounding_adjustment")) + + doc.outstanding_amount = doc.rounded_total or doc.grand_total + + doc.in_words = money_in_words(doc.grand_total, doc.currency) + doc.base_in_words = money_in_words(doc.base_grand_total, erpnext.get_company_currency(doc.company)) + doc.set_payment_schedule() + +def make_regional_gl_entries(gl_entries, doc): + country = frappe.get_cached_value('Company', doc.company, 'country') + + if country != 'India': + return gl_entries + + if doc.reverse_charge == 'Y': + gst_accounts = get_gst_accounts(doc.company) + gst_account_list = gst_accounts.get('cgst_account') + gst_accounts.get('sgst_account') \ + + gst_accounts.get('igst_account') + + for tax in doc.get('taxes'): + if tax.category not in ("Total", "Valuation and Total"): + continue + + dr_or_cr = "credit" if tax.add_deduct_tax == "Add" else "debit" + if flt(tax.base_tax_amount_after_discount_amount) and tax.account_head in gst_account_list: + account_currency = get_account_currency(tax.account_head) + + gl_entries.append(doc.get_gl_dict( + { + "account": tax.account_head, + "cost_center": tax.cost_center, + "posting_date": doc.posting_date, + "against": doc.supplier, + dr_or_cr: tax.base_tax_amount_after_discount_amount, + dr_or_cr + "_in_account_currency": tax.base_tax_amount_after_discount_amount \ + if account_currency==doc.company_currency \ + else tax.tax_amount_after_discount_amount + }, account_currency, item=tax) + ) + + return gl_entries \ No newline at end of file diff --git a/erpnext/regional/report/gstr_1/gstr_1.py b/erpnext/regional/report/gstr_1/gstr_1.py index fae9dc6e9d8..9368d8fa9ac 100644 --- a/erpnext/regional/report/gstr_1/gstr_1.py +++ b/erpnext/regional/report/gstr_1/gstr_1.py @@ -118,7 +118,7 @@ class Gstr1Report(object): row.append(invoice_details.get(fieldname)) taxable_value = 0 - if invoice in self.cgst_igst_invoices: + if invoice in self.cgst_sgst_invoices: division_factor = 2 else: division_factor = 1 @@ -129,6 +129,11 @@ class Gstr1Report(object): taxable_value += abs(net_amount) elif not self.item_tax_rate.get(invoice): taxable_value += abs(net_amount) + elif tax_rate: + taxable_value += abs(net_amount) + elif not tax_rate and self.filters.get('type_of_business') == 'EXPORT' \ + and invoice_details.get('export_type') == "Without Payment of Tax": + taxable_value += abs(net_amount) row += [tax_rate or 0, taxable_value] @@ -227,7 +232,7 @@ class Gstr1Report(object): self.items_based_on_tax_rate = {} self.invoice_cess = frappe._dict() - self.cgst_igst_invoices = [] + self.cgst_sgst_invoices = [] unidentified_gst_accounts = [] for parent, account, item_wise_tax_detail, tax_amount in self.tax_details: @@ -251,8 +256,8 @@ class Gstr1Report(object): tax_rate = tax_amounts[0] if cgst_or_sgst: tax_rate *= 2 - if parent not in self.cgst_igst_invoices: - self.cgst_igst_invoices.append(parent) + if parent not in self.cgst_sgst_invoices: + self.cgst_sgst_invoices.append(parent) rate_based_dict = self.items_based_on_tax_rate\ .setdefault(parent, {}).setdefault(tax_rate, []) diff --git a/erpnext/regional/report/gstr_2/gstr_2.py b/erpnext/regional/report/gstr_2/gstr_2.py index f326fe07cac..f899349ccc0 100644 --- a/erpnext/regional/report/gstr_2/gstr_2.py +++ b/erpnext/regional/report/gstr_2/gstr_2.py @@ -44,30 +44,30 @@ class Gstr2Report(Gstr1Report): for inv, items_based_on_rate in self.items_based_on_tax_rate.items(): invoice_details = self.invoices.get(inv) for rate, items in items_based_on_rate.items(): - if inv not in self.igst_invoices: - rate = rate / 2 - row, taxable_value = self.get_row_data_for_invoice(inv, invoice_details, rate, items) - tax_amount = taxable_value * rate / 100 - row += [0, tax_amount, tax_amount] - else: - row, taxable_value = self.get_row_data_for_invoice(inv, invoice_details, rate, items) - tax_amount = taxable_value * rate / 100 - row += [tax_amount, 0, 0] + if rate: + if inv not in self.igst_invoices: + rate = rate / 2 + row, taxable_value = self.get_row_data_for_invoice(inv, invoice_details, rate, items) + tax_amount = taxable_value * rate / 100 + row += [0, tax_amount, tax_amount] + else: + row, taxable_value = self.get_row_data_for_invoice(inv, invoice_details, rate, items) + tax_amount = taxable_value * rate / 100 + row += [tax_amount, 0, 0] + row += [ + self.invoice_cess.get(inv), + invoice_details.get('eligibility_for_itc'), + invoice_details.get('itc_integrated_tax'), + invoice_details.get('itc_central_tax'), + invoice_details.get('itc_state_tax'), + invoice_details.get('itc_cess_amount') + ] + if self.filters.get("type_of_business") == "CDNR": + row.append("Y" if invoice_details.posting_date <= date(2017, 7, 1) else "N") + row.append("C" if invoice_details.return_against else "R") - row += [ - self.invoice_cess.get(inv), - invoice_details.get('eligibility_for_itc'), - invoice_details.get('itc_integrated_tax'), - invoice_details.get('itc_central_tax'), - invoice_details.get('itc_state_tax'), - invoice_details.get('itc_cess_amount') - ] - if self.filters.get("type_of_business") == "CDNR": - row.append("Y" if invoice_details.posting_date <= date(2017, 7, 1) else "N") - row.append("C" if invoice_details.return_against else "R") - - self.data.append(row) + self.data.append(row) def get_igst_invoices(self): self.igst_invoices = [] @@ -86,7 +86,7 @@ class Gstr2Report(Gstr1Report): conditions += opts[1] if self.filters.get("type_of_business") == "B2B": - conditions += "and ifnull(gst_category, '') != 'Overseas' and is_return != 1 " + conditions += "and ifnull(gst_category, '') in ('Registered Regular', 'Deemed Export', 'SEZ') and is_return != 1 " elif self.filters.get("type_of_business") == "CDNR": conditions += """ and is_return = 1 """ diff --git a/erpnext/regional/report/hsn_wise_summary_of_outward_supplies/hsn_wise_summary_of_outward_supplies.py b/erpnext/regional/report/hsn_wise_summary_of_outward_supplies/hsn_wise_summary_of_outward_supplies.py index 222dfa1eb78..a3ed4cebb12 100644 --- a/erpnext/regional/report/hsn_wise_summary_of_outward_supplies/hsn_wise_summary_of_outward_supplies.py +++ b/erpnext/regional/report/hsn_wise_summary_of_outward_supplies/hsn_wise_summary_of_outward_supplies.py @@ -7,6 +7,8 @@ from frappe import _ from frappe.utils import flt from frappe.model.meta import get_field_precision from frappe.utils.xlsxutils import handle_html +from six import iteritems +import json def execute(filters=None): return _execute(filters) @@ -21,21 +23,24 @@ def _execute(filters=None): itemised_tax, tax_columns = get_tax_accounts(item_list, columns, company_currency) data = [] + added_item = [] for d in item_list: - row = [d.gst_hsn_code, d.description, d.stock_uom, d.stock_qty] - total_tax = 0 - for tax in tax_columns: - item_tax = itemised_tax.get(d.name, {}).get(tax, {}) - total_tax += flt(item_tax.get("tax_amount")) + if (d.parent, d.item_code) not in added_item: + row = [d.gst_hsn_code, d.description, d.stock_uom, d.stock_qty] + total_tax = 0 + for tax in tax_columns: + item_tax = itemised_tax.get((d.parent, d.item_code), {}).get(tax, {}) + total_tax += flt(item_tax.get("tax_amount", 0)) - row += [d.base_net_amount + total_tax] - row += [d.base_net_amount] + row += [d.base_net_amount + total_tax] + row += [d.base_net_amount] - for tax in tax_columns: - item_tax = itemised_tax.get(d.name, {}).get(tax, {}) - row += [item_tax.get("tax_amount", 0)] + for tax in tax_columns: + item_tax = itemised_tax.get((d.parent, d.item_code), {}).get(tax, {}) + row += [item_tax.get("tax_amount", 0)] - data.append(row) + data.append(row) + added_item.append((d.parent, d.item_code)) if data: data = get_merged_data(columns, data) # merge same hsn code data return columns, data @@ -103,7 +108,7 @@ def get_items(filters): match_conditions = " and {0} ".format(match_conditions) - return frappe.db.sql(""" + items = frappe.db.sql(""" select `tabSales Invoice Item`.name, `tabSales Invoice Item`.base_price_list_rate, `tabSales Invoice Item`.gst_hsn_code, `tabSales Invoice Item`.stock_qty, @@ -118,10 +123,9 @@ def get_items(filters): """ % (conditions, match_conditions), filters, as_dict=1) + return items -def get_tax_accounts(item_list, columns, company_currency, - doctype="Sales Invoice", tax_doctype="Sales Taxes and Charges"): - import json +def get_tax_accounts(item_list, columns, company_currency, doctype="Sales Invoice", tax_doctype="Sales Taxes and Charges"): item_row_map = {} tax_columns = [] invoice_item_row = {} @@ -171,7 +175,7 @@ def get_tax_accounts(item_list, columns, company_currency, for d in item_row_map.get(parent, {}).get(item_code, []): item_tax_amount = tax_amount if item_tax_amount: - itemised_tax.setdefault(d.name, {})[description] = frappe._dict({ + itemised_tax.setdefault((parent, item_code), {})[description] = frappe._dict({ "tax_amount": flt(item_tax_amount, tax_amount_precision) }) except ValueError: @@ -179,42 +183,32 @@ def get_tax_accounts(item_list, columns, company_currency, tax_columns.sort() for desc in tax_columns: - columns.append(desc + " Amount:Currency/currency:160") + columns.append({ + "label": desc, + "fieldname": frappe.scrub(desc), + "fieldtype": "Float", + "width": 110 + }) - # columns += ["Total Amount:Currency/currency:110"] return itemised_tax, tax_columns def get_merged_data(columns, data): merged_hsn_dict = {} # to group same hsn under one key and perform row addition - add_column_index = [] # store index of columns that needs to be added - tax_col = len(get_columns()) - fields_to_merge = ["stock_qty", "total_amount", "taxable_amount"] # columns for which index needs to be found - - for i,d in enumerate(columns): - # check if fieldname in to_merge list and ignore tax-columns - if i < tax_col and d["fieldname"] in fields_to_merge: - add_column_index.append(i) + result = [] for row in data: - if row[0] in merged_hsn_dict: - to_add_row = merged_hsn_dict.get(row[0]) + merged_hsn_dict.setdefault(row[0], {}) + for i, d in enumerate(columns): + if d['fieldtype'] not in ('Int', 'Float', 'Currency'): + merged_hsn_dict[row[0]][d['fieldname']] = row[i] + else: + if merged_hsn_dict.get(row[0], {}).get(d['fieldname'], ''): + merged_hsn_dict[row[0]][d['fieldname']] += row[i] + else: + merged_hsn_dict[row[0]][d['fieldname']] = row[i] - # add columns from the add_column_index table - for k in add_column_index: - to_add_row[k] += row[k] + for key, value in iteritems(merged_hsn_dict): + result.append(value) - # add tax columns - for k in range(len(columns)): - if tax_col <= k < len(columns): - to_add_row[k] += row[k] - - # update hsn dict with the newly added data - merged_hsn_dict[row[0]] = to_add_row - else: - merged_hsn_dict[row[0]] = row - - # extract data rows to be displayed in report - data = [merged_hsn_dict[d] for d in merged_hsn_dict] - - return data + return result diff --git a/erpnext/regional/united_arab_emirates/utils.py b/erpnext/regional/united_arab_emirates/utils.py index 772bbf5914b..a0425f6b1c2 100644 --- a/erpnext/regional/united_arab_emirates/utils.py +++ b/erpnext/regional/united_arab_emirates/utils.py @@ -11,14 +11,17 @@ def update_itemised_tax_data(doc): for row in doc.items: tax_rate = 0.0 - item_tax_rate = frappe.parse_json(row.item_tax_rate) + item_tax_rate = 0.0 + + if row.item_tax_rate: + item_tax_rate = frappe.parse_json(row.item_tax_rate) # First check if tax rate is present # If not then look up in item_wise_tax_detail if item_tax_rate: for account, rate in iteritems(item_tax_rate): tax_rate += rate - elif itemised_tax.get(row.item_code): + elif row.item_code and itemised_tax.get(row.item_code): tax_rate = sum([tax.get('tax_rate', 0) for d, tax in itemised_tax.get(row.item_code).items()]) row.tax_rate = flt(tax_rate, row.precision("tax_rate")) diff --git a/erpnext/restaurant/doctype/restaurant_order_entry/restaurant_order_entry.py b/erpnext/restaurant/doctype/restaurant_order_entry/restaurant_order_entry.py index a748f9a0075..357deaac007 100644 --- a/erpnext/restaurant/doctype/restaurant_order_entry/restaurant_order_entry.py +++ b/erpnext/restaurant/doctype/restaurant_order_entry/restaurant_order_entry.py @@ -65,6 +65,7 @@ def make_invoice(table, customer, mode_of_payment): return invoice.name +@frappe.whitelist() def item_query_restaurant(doctype='Item', txt='', searchfield='name', start=0, page_len=20, filters=None, as_dict=False): '''Return items that are selected in active menu of the restaurant''' restaurant, menu = get_restaurant_and_menu_name(filters['table']) @@ -84,4 +85,4 @@ def get_restaurant_and_menu_name(table): if not menu: frappe.throw(_('Please set an active menu for Restaurant {0}').format(restaurant)) - return restaurant, menu \ No newline at end of file + return restaurant, menu diff --git a/erpnext/selling/doctype/customer/customer.py b/erpnext/selling/doctype/customer/customer.py index 9a5d2ebd910..1484f6b2290 100644 --- a/erpnext/selling/doctype/customer/customer.py +++ b/erpnext/selling/doctype/customer/customer.py @@ -181,6 +181,14 @@ class Customer(TransactionBase): def validate_credit_limit_on_change(self): if self.get("__islocal") or not self.credit_limits: return + + past_credit_limits = [d.credit_limit + for d in frappe.db.get_all("Customer Credit Limit", filters={'parent': self.name}, fields=["credit_limit"], order_by="company")] + + current_credit_limits = [d.credit_limit for d in sorted(self.credit_limits, key=lambda k: k.company)] + + if past_credit_limits == current_credit_limits: + return company_record = [] for limit in self.credit_limits: @@ -299,6 +307,8 @@ def get_loyalty_programs(doc): return lp_details +@frappe.whitelist() +@frappe.validate_and_sanitize_search_inputs def get_customer_list(doctype, txt, searchfield, start, page_len, filters=None): from erpnext.controllers.queries import get_fields @@ -467,6 +477,8 @@ def make_address(args, is_primary_address=1): return address +@frappe.whitelist() +@frappe.validate_and_sanitize_search_inputs def get_customer_primary_contact(doctype, txt, searchfield, start, page_len, filters): customer = filters.get('customer') return frappe.db.sql(""" diff --git a/erpnext/selling/doctype/product_bundle/product_bundle.py b/erpnext/selling/doctype/product_bundle/product_bundle.py index c8a71677f93..273bf784fad 100644 --- a/erpnext/selling/doctype/product_bundle/product_bundle.py +++ b/erpnext/selling/doctype/product_bundle/product_bundle.py @@ -22,12 +22,15 @@ class ProductBundle(Document): """Validates, main Item is not a stock item""" if frappe.db.get_value("Item", self.new_item_code, "is_stock_item"): frappe.throw(_("Parent Item {0} must not be a Stock Item").format(self.new_item_code)) - + def validate_child_items(self): for item in self.items: if frappe.db.exists("Product Bundle", item.item_code): - frappe.throw(_("Child Item should not be a Product Bundle. Please remove item `{0}` and save").format(item.item_code)) - + frappe.throw(_("Row #{0}: Child Item should not be a Product Bundle. Please remove Item {1} and Save").format(item.idx, frappe.bold(item.item_code))) + + +@frappe.whitelist() +@frappe.validate_and_sanitize_search_inputs def get_new_item_code(doctype, txt, searchfield, start, page_len, filters): from erpnext.controllers.queries import get_match_cond diff --git a/erpnext/selling/doctype/product_bundle/test_product_bundle.py b/erpnext/selling/doctype/product_bundle/test_product_bundle.py index 85a2b209f64..7d1d372b111 100644 --- a/erpnext/selling/doctype/product_bundle/test_product_bundle.py +++ b/erpnext/selling/doctype/product_bundle/test_product_bundle.py @@ -7,7 +7,7 @@ from __future__ import unicode_literals import frappe test_records = frappe.get_test_records('Product Bundle') -def make_product_bundle(parent, items): +def make_product_bundle(parent, items, qty=None): if frappe.db.exists("Product Bundle", parent): return frappe.get_doc("Product Bundle", parent) @@ -17,7 +17,7 @@ def make_product_bundle(parent, items): }) for item in items: - product_bundle.append("items", {"item_code": item, "qty": 1}) + product_bundle.append("items", {"item_code": item, "qty": qty or 1}) product_bundle.insert() diff --git a/erpnext/selling/doctype/quotation/quotation.json b/erpnext/selling/doctype/quotation/quotation.json index 8e21927fa54..fefd30d6859 100644 --- a/erpnext/selling/doctype/quotation/quotation.json +++ b/erpnext/selling/doctype/quotation/quotation.json @@ -1,5 +1,4 @@ { - "actions": [], "allow_import": 1, "autoname": "naming_series:", "creation": "2013-05-24 19:29:08", @@ -921,16 +920,15 @@ "fieldname": "lost_reasons", "fieldtype": "Table MultiSelect", "label": "Lost Reasons", - "options": "Lost Reason Detail", + "options": "Quotation Lost Reason Detail", "read_only": 1 } ], "icon": "fa fa-shopping-cart", "idx": 82, "is_submittable": 1, - "links": [], "max_attachments": 1, - "modified": "2019-12-30 19:14:56.630270", + "modified": "2020-08-12 23:35:13.621823", "modified_by": "Administrator", "module": "Selling", "name": "Quotation", diff --git a/erpnext/selling/doctype/quotation/quotation.py b/erpnext/selling/doctype/quotation/quotation.py index 1571c734b7f..3c8ba467680 100644 --- a/erpnext/selling/doctype/quotation/quotation.py +++ b/erpnext/selling/doctype/quotation/quotation.py @@ -92,6 +92,8 @@ class Quotation(SellingController): self.update_lead() def on_cancel(self): + if self.lost_reasons: + self.lost_reasons = [] super(Quotation, self).on_cancel() #update enquiry status diff --git a/erpnext/selling/doctype/quotation/quotation_list.js b/erpnext/selling/doctype/quotation/quotation_list.js index 802c0ba641d..f425acf180a 100644 --- a/erpnext/selling/doctype/quotation/quotation_list.js +++ b/erpnext/selling/doctype/quotation/quotation_list.js @@ -3,13 +3,15 @@ frappe.listview_settings['Quotation'] = { "company", "currency", 'valid_till'], onload: function(listview) { - listview.page.fields_dict.quotation_to.get_query = function() { - return { - "filters": { - "name": ["in", ["Customer", "Lead"]], - } + if (listview.page.fields_dict.quotation_to) { + listview.page.fields_dict.quotation_to.get_query = function() { + return { + "filters": { + "name": ["in", ["Customer", "Lead"]], + } + }; }; - }; + } }, get_indicator: function(doc) { diff --git a/erpnext/selling/doctype/sales_order/sales_order.json b/erpnext/selling/doctype/sales_order/sales_order.json index b03b6a6fa7e..c6a3205f1e7 100644 --- a/erpnext/selling/doctype/sales_order/sales_order.json +++ b/erpnext/selling/doctype/sales_order/sales_order.json @@ -1,5 +1,4 @@ { - "actions": [], "allow_import": 1, "autoname": "naming_series:", "creation": "2013-06-18 12:39:59", @@ -1140,6 +1139,7 @@ }, { "allow_on_submit": 1, + "collapsible": 1, "fieldname": "subscription_section", "fieldtype": "Section Break", "label": "Auto Repeat Section", @@ -1196,8 +1196,7 @@ "icon": "fa fa-file-text", "idx": 105, "is_submittable": 1, - "links": [], - "modified": "2020-05-19 21:36:57.437325", + "modified": "2020-07-01 12:39:57.698621", "modified_by": "Administrator", "module": "Selling", "name": "Sales Order", diff --git a/erpnext/selling/doctype/sales_order/sales_order.py b/erpnext/selling/doctype/sales_order/sales_order.py index ffb66354fa0..f88289871e9 100755 --- a/erpnext/selling/doctype/sales_order/sales_order.py +++ b/erpnext/selling/doctype/sales_order/sales_order.py @@ -888,6 +888,7 @@ def make_purchase_order(source_name, for_supplier=None, selected_items=[], targe @frappe.whitelist() +@frappe.validate_and_sanitize_search_inputs def get_supplier(doctype, txt, searchfield, start, page_len, filters): supp_master_name = frappe.defaults.get_user_default("supp_master_name") if supp_master_name == "Supplier Name": diff --git a/erpnext/selling/doctype/sales_order/test_records.json b/erpnext/selling/doctype/sales_order/test_records.json index 6cbd6c2fc17..8a090e6d3d3 100644 --- a/erpnext/selling/doctype/sales_order/test_records.json +++ b/erpnext/selling/doctype/sales_order/test_records.json @@ -1,39 +1,39 @@ [ { "advance_paid": 0.0, - "company": "_Test Company", - "conversion_rate": 1.0, - "currency": "INR", - "customer": "_Test Customer", - "customer_group": "_Test Customer Group", - "customer_name": "_Test Customer", - "doctype": "Sales Order", - "base_grand_total": 1000.0, - "grand_total": 1000.0, - "naming_series": "_T-Sales Order-", - "order_type": "Sales", - "plc_conversion_rate": 1.0, - "price_list_currency": "INR", + "company": "_Test Company", + "conversion_rate": 1.0, + "currency": "INR", + "customer": "_Test Customer", + "customer_group": "_Test Customer Group", + "customer_name": "_Test Customer", + "doctype": "Sales Order", + "base_grand_total": 1000.0, + "grand_total": 1000.0, + "naming_series": "_T-Sales Order-", + "order_type": "Sales", + "plc_conversion_rate": 1.0, + "price_list_currency": "INR", "items": [ { - "base_amount": 1000.0, - "base_rate": 100.0, - "description": "CPU", - "doctype": "Sales Order Item", - "item_code": "_Test Item Home Desktop 100", - "item_name": "CPU", - "delivery_date": "2013-02-23", - "parentfield": "items", - "qty": 10.0, - "rate": 100.0, + "base_amount": 1000.0, + "base_rate": 100.0, + "description": "CPU", + "doctype": "Sales Order Item", + "item_code": "_Test Item", + "item_name": "_Test Item 1", + "delivery_date": "2013-02-23", + "parentfield": "items", + "qty": 10.0, + "rate": 100.0, "warehouse": "_Test Warehouse - _TC", "stock_uom": "_Test UOM", "conversion_factor": 1.0, "uom": "_Test UOM" } - ], - "selling_price_list": "_Test Price List", - "territory": "_Test Territory", + ], + "selling_price_list": "_Test Price List", + "territory": "_Test Territory", "transaction_date": "2013-02-21" } ] \ No newline at end of file diff --git a/erpnext/selling/doctype/sales_order/test_sales_order.py b/erpnext/selling/doctype/sales_order/test_sales_order.py index 6a6d7c96a22..5e360902e83 100644 --- a/erpnext/selling/doctype/sales_order/test_sales_order.py +++ b/erpnext/selling/doctype/sales_order/test_sales_order.py @@ -2,6 +2,7 @@ # 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 @@ -10,8 +11,9 @@ from erpnext.selling.doctype.sales_order.sales_order \ from erpnext.stock.doctype.stock_entry.stock_entry_utils import make_stock_entry from erpnext.selling.doctype.sales_order.sales_order import make_work_orders from erpnext.controllers.accounts_controller import update_child_qty_rate -import json from erpnext.selling.doctype.sales_order.sales_order import make_raw_material_request +from erpnext.selling.doctype.product_bundle.test_product_bundle import make_product_bundle +from erpnext.stock.doctype.item.test_item import make_item class TestSalesOrder(unittest.TestCase): def tearDown(self): @@ -416,6 +418,26 @@ class TestSalesOrder(unittest.TestCase): self.assertRaises(frappe.ValidationError, update_child_qty_rate,'Sales Order', trans_item, so.name) frappe.set_user("Administrator") + def test_update_child_qty_rate_product_bundle(self): + # test Update Items with product bundle + if not frappe.db.exists("Item", "_Product Bundle Item"): + bundle_item = make_item("_Product Bundle Item", {"is_stock_item": 0}) + bundle_item.append("item_defaults", { + "company": "_Test Company", + "default_warehouse": "_Test Warehouse - _TC"}) + bundle_item.save(ignore_permissions=True) + + make_item("_Packed Item", {"is_stock_item": 1}) + make_product_bundle("_Product Bundle Item", ["_Packed Item"], 2) + + so = make_sales_order(item_code = "_Test Item", warehouse=None) + + added_item = json.dumps([{"item_code" : "_Product Bundle Item", "rate" : 200, 'qty' : 2}]) + update_child_qty_rate('Sales Order', added_item, so.name) + + so.reload() + self.assertEqual(so.packed_items[0].qty, 4) + 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") @@ -456,8 +478,6 @@ class TestSalesOrder(unittest.TestCase): self.assertRaises(frappe.CancelledLinkError, dn.submit) def test_service_type_product_bundle(self): - from erpnext.selling.doctype.product_bundle.test_product_bundle import make_product_bundle - from erpnext.stock.doctype.item.test_item import make_item make_item("_Test Service Product Bundle", {"is_stock_item": 0}) make_item("_Test Service Product Bundle Item 1", {"is_stock_item": 0}) make_item("_Test Service Product Bundle Item 2", {"is_stock_item": 0}) @@ -471,8 +491,6 @@ class TestSalesOrder(unittest.TestCase): self.assertTrue("_Test Service Product Bundle Item 2" in [d.item_code for d in so.packed_items]) def test_mix_type_product_bundle(self): - from erpnext.selling.doctype.product_bundle.test_product_bundle import make_product_bundle - from erpnext.stock.doctype.item.test_item import make_item make_item("_Test Mix Product Bundle", {"is_stock_item": 0}) make_item("_Test Mix Product Bundle Item 1", {"is_stock_item": 1}) make_item("_Test Mix Product Bundle Item 2", {"is_stock_item": 0}) @@ -483,7 +501,6 @@ class TestSalesOrder(unittest.TestCase): self.assertRaises(WarehouseRequired, make_sales_order, item_code = "_Test Mix Product Bundle", warehouse="") def test_auto_insert_price(self): - from erpnext.stock.doctype.item.test_item import make_item make_item("_Test Item for Auto Price List", {"is_stock_item": 0}) frappe.db.set_value("Stock Settings", None, "auto_insert_price_list_rate_if_missing", 1) @@ -518,7 +535,6 @@ class TestSalesOrder(unittest.TestCase): from erpnext.buying.doctype.purchase_order.purchase_order import update_status make_stock_entry(target="_Test Warehouse - _TC", qty=10, rate=100) - from erpnext.stock.doctype.item.test_item import make_item po_item = make_item("_Test Item for Drop Shipping", {"is_stock_item": 1, "delivered_by_supplier": 1}) dn_item = make_item("_Test Regular Item", {"is_stock_item": 1}) @@ -713,7 +729,6 @@ class TestSalesOrder(unittest.TestCase): def test_serial_no_based_delivery(self): frappe.set_value("Stock Settings", None, "automatically_set_serial_nos_based_on_fifo", 1) - from erpnext.stock.doctype.item.test_item import make_item item = make_item("_Reserved_Serialized_Item", {"is_stock_item": 1, "maintain_stock": 1, "has_serial_no": 1, @@ -835,7 +850,6 @@ class TestSalesOrder(unittest.TestCase): self.assertRaises(frappe.LinkExistsError, so_doc.cancel) def test_request_for_raw_materials(self): - from erpnext.stock.doctype.item.test_item import make_item item = make_item("_Test Finished Item", {"is_stock_item": 1, "maintain_stock": 1, "valuation_rate": 500, diff --git a/erpnext/selling/page/point_of_sale/point_of_sale.py b/erpnext/selling/page/point_of_sale/point_of_sale.py index 5eb35d642b9..8e130ba4246 100644 --- a/erpnext/selling/page/point_of_sale/point_of_sale.py +++ b/erpnext/selling/page/point_of_sale/point_of_sale.py @@ -167,6 +167,8 @@ def get_item_group_condition(pos_profile): return cond % tuple(item_groups) +@frappe.whitelist() +@frappe.validate_and_sanitize_search_inputs def item_group_query(doctype, txt, searchfield, start, page_len, filters): item_groups = [] cond = "1=1" diff --git a/erpnext/selling/report/item_wise_sales_history/item_wise_sales_history.py b/erpnext/selling/report/item_wise_sales_history/item_wise_sales_history.py index 08a98ba6c07..f1b8bc34efb 100644 --- a/erpnext/selling/report/item_wise_sales_history/item_wise_sales_history.py +++ b/erpnext/selling/report/item_wise_sales_history/item_wise_sales_history.py @@ -9,6 +9,9 @@ from frappe.utils.nestedset import get_descendants_of def execute(filters=None): filters = frappe._dict(filters or {}) + if filters.from_date > filters.to_date: + frappe.throw(_('From Date cannot be greater than To Date')) + columns = get_columns(filters) data = get_data(filters) return columns, data diff --git a/erpnext/selling/report/pending_so_items_for_purchase_request/pending_so_items_for_purchase_request.py b/erpnext/selling/report/pending_so_items_for_purchase_request/pending_so_items_for_purchase_request.py index 14d80315823..e89c45182fd 100644 --- a/erpnext/selling/report/pending_so_items_for_purchase_request/pending_so_items_for_purchase_request.py +++ b/erpnext/selling/report/pending_so_items_for_purchase_request/pending_so_items_for_purchase_request.py @@ -158,7 +158,7 @@ def get_data(): } pending_so.append(so_record) else: - for item in bundled_item_map.get((so.name, so.item_code)): + for item in bundled_item_map.get((so.name, so.item_code), []): material_requests_against_so = materials_request_dict.get((so.name, item.item_code)) or {} if flt(item.qty) > flt(material_requests_against_so.get('qty')): so_record = { diff --git a/erpnext/selling/report/sales_analytics/sales_analytics.json b/erpnext/selling/report/sales_analytics/sales_analytics.json index bf9edd6cd49..de5c3a6b2a7 100644 --- a/erpnext/selling/report/sales_analytics/sales_analytics.json +++ b/erpnext/selling/report/sales_analytics/sales_analytics.json @@ -1,5 +1,5 @@ { - "add_total_row": 0, + "add_total_row": 1, "creation": "2018-09-21 12:46:29.451048", "disable_prepared_report": 0, "disabled": 0, @@ -7,7 +7,7 @@ "doctype": "Report", "idx": 0, "is_standard": "Yes", - "modified": "2020-04-30 19:49:02.303320", + "modified": "2020-06-19 17:41:03.132101", "modified_by": "Administrator", "module": "Selling", "name": "Sales Analytics", diff --git a/erpnext/selling/report/sales_analytics/sales_analytics.py b/erpnext/selling/report/sales_analytics/sales_analytics.py index 97d9322918d..4d113c8e9e9 100644 --- a/erpnext/selling/report/sales_analytics/sales_analytics.py +++ b/erpnext/selling/report/sales_analytics/sales_analytics.py @@ -23,7 +23,14 @@ class Analytics(object): self.get_columns() self.get_data() self.get_chart_data() - return self.columns, self.data, None, self.chart + + # Skipping total row for tree-view reports + skip_total_row = 0 + + if self.filters.tree_type in ["Supplier Group", "Item Group", "Customer Group", "Territory"]: + skip_total_row = 1 + + return self.columns, self.data, None, self.chart, None, skip_total_row def get_columns(self): self.columns = [{ @@ -194,9 +201,6 @@ class Analytics(object): def get_rows(self): self.data = [] self.get_periodic_data() - total_row = { - "entity": "Total", - } for entity, period_data in iteritems(self.entity_periodic_data): row = { @@ -210,9 +214,6 @@ class Analytics(object): row[scrub(period)] = amount total += amount - if not total_row.get(scrub(period)): total_row[scrub(period)] = 0 - total_row[scrub(period)] += amount - row["total"] = total if self.filters.tree_type == "Item": @@ -220,8 +221,6 @@ class Analytics(object): self.data.append(row) - self.data.append(total_row) - def get_rows_by_group(self): self.get_periodic_data() out = [] diff --git a/erpnext/selling/report/sales_analytics/test_analytics.py b/erpnext/selling/report/sales_analytics/test_analytics.py index 7e8501dcc15..4d81a1e4dda 100644 --- a/erpnext/selling/report/sales_analytics/test_analytics.py +++ b/erpnext/selling/report/sales_analytics/test_analytics.py @@ -33,21 +33,6 @@ class TestAnalytics(unittest.TestCase): report = execute(filters) expected_data = [ - { - 'entity': 'Total', - 'apr_2017': 0.0, - 'may_2017': 0.0, - 'jun_2017': 2000.0, - 'jul_2017': 1000.0, - 'aug_2017': 0.0, - 'sep_2017': 1500.0, - 'oct_2017': 1000.0, - 'nov_2017': 0.0, - 'dec_2017': 0.0, - 'jan_2018': 0.0, - 'feb_2018': 2000.0, - 'mar_2018': 0.0 - }, { "entity": "_Test Customer 1", "entity_name": "_Test Customer 1", @@ -149,21 +134,6 @@ class TestAnalytics(unittest.TestCase): report = execute(filters) expected_data = [ - { - 'entity': 'Total', - 'apr_2017': 0.0, - 'may_2017': 0.0, - 'jun_2017': 20.0, - 'jul_2017': 10.0, - 'aug_2017': 0.0, - 'sep_2017': 15.0, - 'oct_2017': 10.0, - 'nov_2017': 0.0, - 'dec_2017': 0.0, - 'jan_2018': 0.0, - 'feb_2018': 20.0, - 'mar_2018': 0.0 - }, { "entity": "_Test Customer 1", "entity_name": "_Test Customer 1", diff --git a/erpnext/selling/sales_common.js b/erpnext/selling/sales_common.js index 99c4e62fdbe..15a9a1ab882 100644 --- a/erpnext/selling/sales_common.js +++ b/erpnext/selling/sales_common.js @@ -492,13 +492,18 @@ frappe.ui.form.on(cur_frm.doctype, { var dialog = new frappe.ui.Dialog({ title: __("Set as Lost"), fields: [ - {"fieldtype": "Table MultiSelect", - "label": __("Lost Reasons"), - "fieldname": "lost_reason", - "options": "Lost Reason Detail", - "reqd": 1}, - - {"fieldtype": "Text", "label": __("Detailed Reason"), "fieldname": "detailed_reason"}, + { + "fieldtype": "Table MultiSelect", + "label": __("Lost Reasons"), + "fieldname": "lost_reason", + "options": frm.doctype === 'Opportunity' ? 'Opportunity Lost Reason Detail': 'Quotation Lost Reason Detail', + "reqd": 1 + }, + { + "fieldtype": "Text", + "label": __("Detailed Reason"), + "fieldname": "detailed_reason" + }, ], primary_action: function() { var values = dialog.get_values(); diff --git a/erpnext/setup/doctype/company/company.js b/erpnext/setup/doctype/company/company.js index be736d2d9d1..14cacf37bb6 100644 --- a/erpnext/setup/doctype/company/company.js +++ b/erpnext/setup/doctype/company/company.js @@ -202,8 +202,6 @@ cur_frm.cscript.change_abbr = function() { if(r.exc) { frappe.msgprint(__("There were errors.")); return; - } else { - cur_frm.set_value("abbr", args.new_abbr); } dialog.hide(); cur_frm.refresh(); diff --git a/erpnext/setup/doctype/company/company.py b/erpnext/setup/doctype/company/company.py index bc27f40f560..335cad3598b 100644 --- a/erpnext/setup/doctype/company/company.py +++ b/erpnext/setup/doctype/company/company.py @@ -400,8 +400,6 @@ def replace_abbr(company, old, new): for dt in ["Warehouse", "Account", "Cost Center", "Department", "Sales Taxes and Charges Template", "Purchase Taxes and Charges Template"]: _rename_records(dt) - frappe.db.commit() - def get_name_with_abbr(name, company): company_abbr = frappe.get_cached_value('Company', company, "abbr") diff --git a/erpnext/setup/doctype/customer_group/customer_group.js b/erpnext/setup/doctype/customer_group/customer_group.js index c199a8e57f1..44a50191209 100644 --- a/erpnext/setup/doctype/customer_group/customer_group.js +++ b/erpnext/setup/doctype/customer_group/customer_group.js @@ -8,7 +8,7 @@ cur_frm.cscript.refresh = function(doc, cdt, cdn) { cur_frm.cscript.set_root_readonly = function(doc) { // read-only for root customer group - if(!doc.parent_customer_group) { + if(!doc.parent_customer_group && !doc.__islocal) { cur_frm.set_read_only(); cur_frm.set_intro(__("This is a root customer group and cannot be edited.")); } else { @@ -20,7 +20,8 @@ cur_frm.cscript.set_root_readonly = function(doc) { cur_frm.fields_dict['parent_customer_group'].get_query = function(doc,cdt,cdn) { return { filters: { - 'is_group': 1 + 'is_group': 1, + 'name': ['!=', cur_frm.doc.customer_group_name] } } } diff --git a/erpnext/setup/doctype/customer_group/customer_group.py b/erpnext/setup/doctype/customer_group/customer_group.py index f62613ea1bb..68e1ccb6356 100644 --- a/erpnext/setup/doctype/customer_group/customer_group.py +++ b/erpnext/setup/doctype/customer_group/customer_group.py @@ -6,9 +6,12 @@ import frappe from frappe import _ -from frappe.utils.nestedset import NestedSet +from frappe.utils.nestedset import NestedSet, get_root_of class CustomerGroup(NestedSet): nsm_parent_field = 'parent_customer_group' + def validate(self): + if not self.parent_customer_group: + self.parent_customer_group = get_root_of("Customer Group") def on_update(self): self.validate_name_with_customer() diff --git a/erpnext/setup/doctype/email_digest/email_digest.py b/erpnext/setup/doctype/email_digest/email_digest.py index 0bcddc21517..662cda26bfb 100644 --- a/erpnext/setup/doctype/email_digest/email_digest.py +++ b/erpnext/setup/doctype/email_digest/email_digest.py @@ -101,8 +101,7 @@ class EmailDigest(Document): if not context.purchase_order_list: frappe.throw(_("No items to be received are overdue")) - if not (context.events or context.todo_list or context.notifications or context.cards - or context.purchase_orders_items_overdue_list): + if not context: return None frappe.flags.ignore_account_permission = False diff --git a/erpnext/setup/doctype/item_group/item_group.js b/erpnext/setup/doctype/item_group/item_group.js index df2223192bd..9892dc3dcc0 100644 --- a/erpnext/setup/doctype/item_group/item_group.js +++ b/erpnext/setup/doctype/item_group/item_group.js @@ -66,7 +66,7 @@ frappe.ui.form.on("Item Group", { set_root_readonly: function(frm) { // read-only for root item group frm.set_intro(""); - if(!frm.doc.parent_item_group) { + if(!frm.doc.parent_item_group && !frm.doc.__islocal) { frm.set_read_only(); frm.set_intro(__("This is a root item group and cannot be edited."), true); } diff --git a/erpnext/setup/doctype/party_type/party_type.py b/erpnext/setup/doctype/party_type/party_type.py index b29c305ee7f..96e60936a4b 100644 --- a/erpnext/setup/doctype/party_type/party_type.py +++ b/erpnext/setup/doctype/party_type/party_type.py @@ -10,6 +10,7 @@ class PartyType(Document): pass @frappe.whitelist() +@frappe.validate_and_sanitize_search_inputs def get_party_type(doctype, txt, searchfield, start, page_len, filters): cond = '' if filters and filters.get('account'): diff --git a/erpnext/setup/doctype/quotation_lost_reason_detail/__init__.py b/erpnext/setup/doctype/quotation_lost_reason_detail/__init__.py new file mode 100644 index 00000000000..e69de29bb2d diff --git a/erpnext/setup/doctype/quotation_lost_reason_detail/quotation_lost_reason_detail.json b/erpnext/setup/doctype/quotation_lost_reason_detail/quotation_lost_reason_detail.json new file mode 100644 index 00000000000..898878f6bdb --- /dev/null +++ b/erpnext/setup/doctype/quotation_lost_reason_detail/quotation_lost_reason_detail.json @@ -0,0 +1,29 @@ +{ + "creation": "2020-07-14 09:21:44.057724", + "doctype": "DocType", + "editable_grid": 1, + "engine": "InnoDB", + "field_order": [ + "lost_reason" + ], + "fields": [ + { + "fieldname": "lost_reason", + "fieldtype": "Link", + "in_list_view": 1, + "label": "Quotation Lost Reason", + "options": "Quotation Lost Reason" + } + ], + "istable": 1, + "modified": "2020-08-12 23:33:14.490491", + "modified_by": "Administrator", + "module": "Setup", + "name": "Quotation Lost Reason Detail", + "owner": "Administrator", + "permissions": [], + "quick_entry": 1, + "sort_field": "modified", + "sort_order": "DESC", + "track_changes": 1 +} \ No newline at end of file diff --git a/erpnext/setup/doctype/quotation_lost_reason_detail/quotation_lost_reason_detail.py b/erpnext/setup/doctype/quotation_lost_reason_detail/quotation_lost_reason_detail.py new file mode 100644 index 00000000000..7bb8d02670e --- /dev/null +++ b/erpnext/setup/doctype/quotation_lost_reason_detail/quotation_lost_reason_detail.py @@ -0,0 +1,10 @@ +# -*- coding: utf-8 -*- +# Copyright (c) 2020, Frappe Technologies Pvt. Ltd. and contributors +# For license information, please see license.txt + +from __future__ import unicode_literals +# import frappe +from frappe.model.document import Document + +class QuotationLostReasonDetail(Document): + pass diff --git a/erpnext/setup/doctype/sales_person/sales_person.js b/erpnext/setup/doctype/sales_person/sales_person.js index 89ca4a9dd74..8f7593d6eef 100644 --- a/erpnext/setup/doctype/sales_person/sales_person.js +++ b/erpnext/setup/doctype/sales_person/sales_person.js @@ -19,7 +19,7 @@ frappe.ui.form.on('Sales Person', { } } }; - + frm.make_methods = { 'Sales Order': () => frappe.new_doc("Sales Order") .then(() => frm.add_child("sales_team", {"sales_person": frm.doc.name})) @@ -33,7 +33,7 @@ cur_frm.cscript.refresh = function(doc, cdt, cdn) { cur_frm.cscript.set_root_readonly = function(doc) { // read-only for root - if(!doc.parent_sales_person) { + if(!doc.parent_sales_person && !doc.__islocal) { cur_frm.set_read_only(); cur_frm.set_intro(__("This is a root sales person and cannot be edited.")); } else { diff --git a/erpnext/setup/doctype/sales_person/sales_person.py b/erpnext/setup/doctype/sales_person/sales_person.py index 3379534cf82..19c2e5b9543 100644 --- a/erpnext/setup/doctype/sales_person/sales_person.py +++ b/erpnext/setup/doctype/sales_person/sales_person.py @@ -5,13 +5,16 @@ from __future__ import unicode_literals import frappe from frappe import _ from frappe.utils import flt -from frappe.utils.nestedset import NestedSet +from frappe.utils.nestedset import NestedSet, get_root_of from erpnext import get_default_currency class SalesPerson(NestedSet): nsm_parent_field = 'parent_sales_person' def validate(self): + if not self.parent_sales_person: + self.parent_sales_person = get_root_of("Sales Person") + for d in self.get('targets') or []: if not flt(d.target_qty) and not flt(d.target_amount): frappe.throw(_("Either target qty or target amount is mandatory.")) diff --git a/erpnext/setup/doctype/supplier_group/supplier_group.js b/erpnext/setup/doctype/supplier_group/supplier_group.js index ac5bda6e2c6..e75030d4414 100644 --- a/erpnext/setup/doctype/supplier_group/supplier_group.js +++ b/erpnext/setup/doctype/supplier_group/supplier_group.js @@ -8,7 +8,7 @@ cur_frm.cscript.refresh = function(doc) { cur_frm.cscript.set_root_readonly = function(doc) { // read-only for root customer group - if(!doc.parent_supplier_group) { + if(!doc.parent_supplier_group && !doc.__islocal) { cur_frm.set_read_only(); cur_frm.set_intro(__("This is a root supplier group and cannot be edited.")); } else { @@ -20,7 +20,8 @@ cur_frm.cscript.set_root_readonly = function(doc) { cur_frm.fields_dict['parent_supplier_group'].get_query = function() { return { filters: { - 'is_group': 1 + 'is_group': 1, + 'name': ['!=', cur_frm.doc.supplier_group_name] } }; }; diff --git a/erpnext/setup/doctype/terms_and_conditions/terms_and_conditions.json b/erpnext/setup/doctype/terms_and_conditions/terms_and_conditions.json index aba6a791a4e..28d1d16a051 100644 --- a/erpnext/setup/doctype/terms_and_conditions/terms_and_conditions.json +++ b/erpnext/setup/doctype/terms_and_conditions/terms_and_conditions.json @@ -1,4 +1,5 @@ { + "actions": [], "allow_import": 1, "allow_rename": 1, "autoname": "field:title", @@ -49,7 +50,7 @@ "fieldname": "terms_and_conditions_help", "fieldtype": "HTML", "label": "Terms and Conditions Help", - "options": "

Standard Terms and Conditions Example

\n\n
Delivery Terms for Order number {{ name }}\n\n-Order Date : {{ transaction_date }} \n-Expected Delivery Date : {{ delivery_date }}\n
\n\n

How to get fieldnames

\n\n

The fieldnames you can use in your email template are the fields in the document from which you are sending the email. You can find out the fields of any documents via Setup > Customize Form View and selecting the document type (e.g. Sales Invoice)

\n\n

Templating

\n\n

Templates are compiled using the Jinja Templating Langauge. To learn more about Jinja, read this documentation.

" + "options": "

Standard Terms and Conditions Example

\n\n
Delivery Terms for Order number {{ name }}\n\n-Order Date : {{ transaction_date }} \n-Expected Delivery Date : {{ delivery_date }}\n
\n\n

How to get fieldnames

\n\n

The fieldnames you can use in your email template are the fields in the document from which you are sending the email. You can find out the fields of any documents via Setup > Customize Form View and selecting the document type (e.g. Sales Invoice)

\n\n

Templating

\n\n

Templates are compiled using the Jinja Templating Language. To learn more about Jinja, read this documentation.

" }, { "fieldname": "applicable_modules_section", @@ -81,7 +82,8 @@ ], "icon": "icon-legal", "idx": 1, - "modified": "2019-07-04 13:31:30.393425", + "links": [], + "modified": "2020-06-16 22:54:38.094844", "modified_by": "Administrator", "module": "Setup", "name": "Terms and Conditions", diff --git a/erpnext/setup/doctype/territory/territory.js b/erpnext/setup/doctype/territory/territory.js index 1eb9958ce70..ceec47ae8c6 100644 --- a/erpnext/setup/doctype/territory/territory.js +++ b/erpnext/setup/doctype/territory/territory.js @@ -20,7 +20,7 @@ cur_frm.cscript.refresh = function(doc, cdt, cdn) { cur_frm.cscript.set_root_readonly = function(doc) { // read-only for root territory - if(!doc.parent_territory) { + if(!doc.parent_territory && !doc.__islocal) { cur_frm.set_read_only(); cur_frm.set_intro(__("This is a root territory and cannot be edited.")); } else { diff --git a/erpnext/setup/doctype/territory/territory.py b/erpnext/setup/doctype/territory/territory.py index 095bd1c179c..89423b5a693 100644 --- a/erpnext/setup/doctype/territory/territory.py +++ b/erpnext/setup/doctype/territory/territory.py @@ -8,12 +8,15 @@ import frappe from frappe.utils import flt from frappe import _ -from frappe.utils.nestedset import NestedSet +from frappe.utils.nestedset import NestedSet, get_root_of class Territory(NestedSet): nsm_parent_field = 'parent_territory' def validate(self): + if not self.parent_territory: + self.parent_territory = get_root_of("Territory") + for d in self.get('targets') or []: if not flt(d.target_qty) and not flt(d.target_amount): frappe.throw(_("Either target qty or target amount is mandatory")) diff --git a/erpnext/shopping_cart/doctype/shopping_cart_settings/shopping_cart_settings.js b/erpnext/shopping_cart/doctype/shopping_cart_settings/shopping_cart_settings.js index e1510f53354..23814f2aabf 100644 --- a/erpnext/shopping_cart/doctype/shopping_cart_settings/shopping_cart_settings.js +++ b/erpnext/shopping_cart/doctype/shopping_cart_settings/shopping_cart_settings.js @@ -1,25 +1,32 @@ // Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors // License: GNU General Public License v3. See license.txt -$.extend(cur_frm.cscript, { - onload: function() { - if(cur_frm.doc.__onload && cur_frm.doc.__onload.quotation_series) { - cur_frm.fields_dict.quotation_series.df.options = cur_frm.doc.__onload.quotation_series; - cur_frm.refresh_field("quotation_series"); +frappe.ui.form.on("Shopping Cart Settings", { + onload: function(frm) { + if(frm.doc.__onload && frm.doc.__onload.quotation_series) { + frm.fields_dict.quotation_series.df.options = frm.doc.__onload.quotation_series; + frm.refresh_field("quotation_series"); } }, - refresh: function(){ - toggle_mandatory(cur_frm) + refresh: function(frm){ + toggle_mandatory(frm) }, - enable_checkout: function(){ - toggle_mandatory(cur_frm) + enable_checkout: function(frm){ + toggle_mandatory(frm) + }, + enabled: function(frm) { + if (frm.doc.enabled === 1) { + frm.set_value('enable_variants', 1); + } + let is_required = frm.doc.enabled ? 1 : 0; + frm.toggle_reqd(["company", "default_customer_group", "quotation_series"], is_required); } }); -function toggle_mandatory (cur_frm){ - cur_frm.toggle_reqd("payment_gateway_account", false); - if(cur_frm.doc.enabled && cur_frm.doc.enable_checkout) { - cur_frm.toggle_reqd("payment_gateway_account", true); +function toggle_mandatory (frm){ + frm.toggle_reqd("payment_gateway_account", false); + if(frm.doc.enabled && frm.doc.enable_checkout) { + frm.toggle_reqd("payment_gateway_account", true); } } diff --git a/erpnext/shopping_cart/doctype/shopping_cart_settings/shopping_cart_settings.json b/erpnext/shopping_cart/doctype/shopping_cart_settings/shopping_cart_settings.json index e828f54878b..e22af9a601d 100644 --- a/erpnext/shopping_cart/doctype/shopping_cart_settings/shopping_cart_settings.json +++ b/erpnext/shopping_cart/doctype/shopping_cart_settings/shopping_cart_settings.json @@ -1,750 +1,182 @@ { - "allow_copy": 0, - "allow_events_in_timeline": 0, - "allow_guest_to_view": 0, - "allow_import": 0, - "allow_rename": 0, - "beta": 0, - "creation": "2013-06-19 15:57:32", - "custom": 0, - "description": "Default settings for Shopping Cart", - "docstatus": 0, - "doctype": "DocType", - "document_type": "System", - "editable_grid": 0, - "fields": [ - { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "enabled", - "fieldtype": "Check", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 1, - "in_standard_filter": 0, - "label": "Enable Shopping Cart", - "length": 0, - "no_copy": 0, - "permlevel": 0, - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 - }, - { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "depends_on": "", - "description": "", - "fieldname": "display_settings", - "fieldtype": "Section Break", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "Display Settings", - "length": 0, - "no_copy": 0, - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 - }, - { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "depends_on": "", - "description": "", - "fieldname": "show_attachments", - "fieldtype": "Check", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "Show Public Attachments", - "length": 0, - "no_copy": 0, - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 - }, - { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "depends_on": "", - "description": "", - "fieldname": "show_price", - "fieldtype": "Check", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "Show Price", - "length": 0, - "no_copy": 0, - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 - }, - { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "show_stock_availability", - "fieldtype": "Check", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "Show Stock Availability", - "length": 0, - "no_copy": 0, - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 - }, - { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "show_configure_button", - "fieldtype": "Check", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "Show Configure Button", - "length": 0, - "no_copy": 0, - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 - }, - { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "show_contact_us_button", - "fieldtype": "Check", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "Show Contact Us Button", - "length": 0, - "no_copy": 0, - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 - }, - { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "depends_on": "show_stock_availability", - "fieldname": "show_quantity_in_website", - "fieldtype": "Check", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "Show Stock Quantity", - "length": 0, - "no_copy": 0, - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 - }, - { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "depends_on": "", - "fetch_if_empty": 0, - "fieldname": "show_apply_coupon_code_in_website", - "fieldtype": "Check", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "Show Apply Coupon Code", - "length": 0, - "no_copy": 0, - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 - }, - { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fetch_if_empty": 0, - "fieldname": "allow_items_not_in_stock", - "fieldtype": "Check", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "Allow items not in stock to be added to cart", - "length": 0, - "no_copy": 0, - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 - }, - { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "depends_on": "enabled", - "fieldname": "section_break_2", - "fieldtype": "Section Break", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "length": 0, - "no_copy": 0, - "permlevel": 0, - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 - }, - { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "depends_on": "", - "fieldname": "company", - "fieldtype": "Link", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 1, - "in_standard_filter": 0, - "label": "Company", - "length": 0, - "no_copy": 0, - "options": "Company", - "permlevel": 0, - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 1, - "report_hide": 0, - "reqd": 1, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 - }, - { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "description": "Prices will not be shown if Price List is not set", - "fieldname": "price_list", - "fieldtype": "Link", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "Price List", - "length": 0, - "no_copy": 0, - "options": "Price List", - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 - }, - { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "column_break_4", - "fieldtype": "Column Break", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "length": 0, - "no_copy": 0, - "permlevel": 0, - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 - }, - { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "description": "", - "fieldname": "default_customer_group", - "fieldtype": "Link", - "hidden": 0, - "ignore_user_permissions": 1, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "Default Customer Group", - "length": 0, - "no_copy": 0, - "options": "Customer Group", - "permlevel": 0, - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 1, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 - }, - { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "quotation_series", - "fieldtype": "Select", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "Quotation Series", - "length": 0, - "no_copy": 0, - "permlevel": 0, - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 1, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 - }, - { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 1, - "collapsible_depends_on": "eval:doc.enable_checkout", - "columns": 0, - "depends_on": "enabled", - "fieldname": "section_break_8", - "fieldtype": "Section Break", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "Checkout Settings", - "length": 0, - "no_copy": 0, - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 - }, - { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "collapsible_depends_on": "", - "columns": 0, - "depends_on": "", - "fieldname": "enable_checkout", - "fieldtype": "Check", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "Enable Checkout", - "length": 0, - "no_copy": 0, - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 - }, - { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "default": "Orders", - "description": "After payment completion redirect user to selected page.", - "fieldname": "payment_success_url", - "fieldtype": "Select", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "Payment Success Url", - "length": 0, - "no_copy": 0, - "options": "\nOrders\nInvoices\nMy Account", - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 - }, - { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "column_break_11", - "fieldtype": "Column Break", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "length": 0, - "no_copy": 0, - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 - }, - { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "payment_gateway_account", - "fieldtype": "Link", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "Payment Gateway Account", - "length": 0, - "no_copy": 0, - "options": "Payment Gateway Account", - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 - } - ], - "has_web_view": 0, - "hide_heading": 0, - "hide_toolbar": 0, - "icon": "fa fa-shopping-cart", - "idx": 1, - "image_view": 0, - "in_create": 0, - "is_submittable": 0, - "issingle": 1, - "istable": 0, - "max_attachments": 0, - "modified": "2019-10-14 13:54:24.575322", - "modified_by": "Administrator", - "module": "Shopping Cart", - "name": "Shopping Cart Settings", - "owner": "Administrator", - "permissions": [ - { - "amend": 0, - "cancel": 0, - "create": 1, - "delete": 0, - "email": 1, - "export": 0, - "if_owner": 0, - "import": 0, - "permlevel": 0, - "print": 1, - "read": 1, - "report": 0, - "role": "Website Manager", - "set_user_permissions": 0, - "share": 1, - "submit": 0, - "write": 1 - } - ], - "quick_entry": 0, - "read_only": 0, - "read_only_onload": 0, - "show_name_in_global_search": 0, - "sort_order": "ASC", - "track_changes": 0, - "track_seen": 0, - "track_views": 0 - } \ No newline at end of file + "creation": "2013-06-19 15:57:32", + "description": "Default settings for Shopping Cart", + "doctype": "DocType", + "document_type": "System", + "engine": "InnoDB", + "field_order": [ + "enabled", + "display_settings", + "show_attachments", + "show_price", + "show_stock_availability", + "enable_variants", + "show_contact_us_button", + "show_quantity_in_website", + "show_apply_coupon_code_in_website", + "allow_items_not_in_stock", + "section_break_2", + "company", + "price_list", + "column_break_4", + "default_customer_group", + "quotation_series", + "section_break_8", + "enable_checkout", + "payment_success_url", + "column_break_11", + "payment_gateway_account" + ], + "fields": [ + { + "default": "0", + "fieldname": "enabled", + "fieldtype": "Check", + "in_list_view": 1, + "label": "Enable Shopping Cart" + }, + { + "fieldname": "display_settings", + "fieldtype": "Section Break", + "label": "Display Settings" + }, + { + "default": "0", + "fieldname": "show_attachments", + "fieldtype": "Check", + "label": "Show Public Attachments" + }, + { + "default": "0", + "fieldname": "show_price", + "fieldtype": "Check", + "label": "Show Price" + }, + { + "default": "0", + "fieldname": "show_stock_availability", + "fieldtype": "Check", + "label": "Show Stock Availability" + }, + { + "default": "0", + "fieldname": "show_contact_us_button", + "fieldtype": "Check", + "label": "Show Contact Us Button" + }, + { + "default": "0", + "depends_on": "show_stock_availability", + "fieldname": "show_quantity_in_website", + "fieldtype": "Check", + "label": "Show Stock Quantity" + }, + { + "default": "0", + "fieldname": "show_apply_coupon_code_in_website", + "fieldtype": "Check", + "label": "Show Apply Coupon Code" + }, + { + "default": "0", + "fieldname": "allow_items_not_in_stock", + "fieldtype": "Check", + "label": "Allow items not in stock to be added to cart" + }, + { + "depends_on": "enabled", + "fieldname": "section_break_2", + "fieldtype": "Section Break" + }, + { + "fieldname": "company", + "fieldtype": "Link", + "in_list_view": 1, + "label": "Company", + "options": "Company", + "remember_last_selected_value": 1 + }, + { + "description": "Prices will not be shown if Price List is not set", + "fieldname": "price_list", + "fieldtype": "Link", + "label": "Price List", + "options": "Price List" + }, + { + "fieldname": "column_break_4", + "fieldtype": "Column Break" + }, + { + "fieldname": "default_customer_group", + "fieldtype": "Link", + "ignore_user_permissions": 1, + "label": "Default Customer Group", + "options": "Customer Group" + }, + { + "fieldname": "quotation_series", + "fieldtype": "Select", + "label": "Quotation Series" + }, + { + "collapsible": 1, + "collapsible_depends_on": "eval:doc.enable_checkout", + "depends_on": "enabled", + "fieldname": "section_break_8", + "fieldtype": "Section Break", + "label": "Checkout Settings" + }, + { + "default": "0", + "fieldname": "enable_checkout", + "fieldtype": "Check", + "label": "Enable Checkout" + }, + { + "default": "Orders", + "description": "After payment completion redirect user to selected page.", + "fieldname": "payment_success_url", + "fieldtype": "Select", + "label": "Payment Success Url", + "options": "\nOrders\nInvoices\nMy Account" + }, + { + "fieldname": "column_break_11", + "fieldtype": "Column Break" + }, + { + "fieldname": "payment_gateway_account", + "fieldtype": "Link", + "label": "Payment Gateway Account", + "options": "Payment Gateway Account" + }, + { + "default": "0", + "fieldname": "enable_variants", + "fieldtype": "Check", + "label": "Enable Variants" + } + ], + "icon": "fa fa-shopping-cart", + "idx": 1, + "issingle": 1, + "modified": "2020-07-07 02:13:23.175604", + "modified_by": "Administrator", + "module": "Shopping Cart", + "name": "Shopping Cart Settings", + "owner": "Administrator", + "permissions": [ + { + "create": 1, + "email": 1, + "print": 1, + "read": 1, + "role": "Website Manager", + "share": 1, + "write": 1 + } + ], + "sort_field": "modified", + "sort_order": "ASC" +} \ No newline at end of file diff --git a/erpnext/shopping_cart/doctype/shopping_cart_settings/shopping_cart_settings.py b/erpnext/shopping_cart/doctype/shopping_cart_settings/shopping_cart_settings.py index 3098190383b..c069b90e986 100644 --- a/erpnext/shopping_cart/doctype/shopping_cart_settings/shopping_cart_settings.py +++ b/erpnext/shopping_cart/doctype/shopping_cart_settings/shopping_cart_settings.py @@ -80,6 +80,7 @@ def get_shopping_cart_settings(): return frappe.local.shopping_cart_settings +@frappe.whitelist(allow_guest=True) def is_cart_enabled(): return get_shopping_cart_settings().enabled diff --git a/erpnext/shopping_cart/product_info.py b/erpnext/shopping_cart/product_info.py index 7c08f5b5b24..29617a87485 100644 --- a/erpnext/shopping_cart/product_info.py +++ b/erpnext/shopping_cart/product_info.py @@ -55,7 +55,7 @@ def get_product_info_for_website(item_code, skip_quotation_creation=False): def set_product_info_for_website(item): """set product price uom for website""" - product_info = get_product_info_for_website(item.item_code, skip_quotation_creation=True) + product_info = get_product_info_for_website(item.item_code, skip_quotation_creation=True).get("product_info") if product_info: item.update(product_info) diff --git a/erpnext/stock/doctype/batch/batch.py b/erpnext/stock/doctype/batch/batch.py index a091ac7fae9..c8424f13e12 100644 --- a/erpnext/stock/doctype/batch/batch.py +++ b/erpnext/stock/doctype/batch/batch.py @@ -143,7 +143,7 @@ class Batch(Document): @frappe.whitelist() -def get_batch_qty(batch_no=None, warehouse=None, item_code=None): +def get_batch_qty(batch_no=None, warehouse=None, item_code=None, posting_date=None, posting_time=None): """Returns batch actual qty if warehouse is passed, or returns dict of qty by warehouse if warehouse is None @@ -155,9 +155,14 @@ def get_batch_qty(batch_no=None, warehouse=None, item_code=None): out = 0 if batch_no and warehouse: + cond = "" + if posting_date and posting_time: + cond = " and timestamp(posting_date, posting_time) <= timestamp('{0}', '{1}')".format(posting_date, + posting_time) + out = float(frappe.db.sql("""select sum(actual_qty) from `tabStock Ledger Entry` - where warehouse=%s and batch_no=%s""", + where warehouse=%s and batch_no=%s {0}""".format(cond), (warehouse, batch_no))[0][0] or 0) if batch_no and not warehouse: diff --git a/erpnext/stock/doctype/delivery_note/delivery_note.py b/erpnext/stock/doctype/delivery_note/delivery_note.py index c72bb893fff..f4334c4980a 100644 --- a/erpnext/stock/doctype/delivery_note/delivery_note.py +++ b/erpnext/stock/doctype/delivery_note/delivery_note.py @@ -386,13 +386,12 @@ def get_invoiced_qty_map(delivery_note): def get_returned_qty_map(delivery_note): """returns a map: {so_detail: returned_qty}""" - returned_qty_map = frappe._dict(frappe.db.sql("""select dn_item.item_code, sum(abs(dn_item.qty)) as qty + returned_qty_map = frappe._dict(frappe.db.sql("""select dn_item.dn_detail, abs(dn_item.qty) as qty from `tabDelivery Note Item` dn_item, `tabDelivery Note` dn where dn.name = dn_item.parent and dn.docstatus = 1 and dn.is_return = 1 and dn.return_against = %s - group by dn_item.item_code """, delivery_note)) return returned_qty_map @@ -411,7 +410,7 @@ def make_sales_invoice(source_name, target_doc=None): target.run_method("set_po_nos") if len(target.get("items")) == 0: - frappe.throw(_("All these items have already been invoiced")) + frappe.throw(_("All these items have already been Invoiced/Returned")) target.run_method("calculate_taxes_and_totals") @@ -436,9 +435,9 @@ def make_sales_invoice(source_name, target_doc=None): pending_qty = item_row.qty - invoiced_qty_map.get(item_row.name, 0) returned_qty = 0 - if returned_qty_map.get(item_row.item_code, 0) > 0: - returned_qty = flt(returned_qty_map.get(item_row.item_code, 0)) - returned_qty_map[item_row.item_code] -= pending_qty + if returned_qty_map.get(item_row.name, 0) > 0: + returned_qty = flt(returned_qty_map.get(item_row.name, 0)) + returned_qty_map[item_row.name] -= pending_qty if returned_qty: if returned_qty >= pending_qty: diff --git a/erpnext/stock/doctype/delivery_note/test_delivery_note.py b/erpnext/stock/doctype/delivery_note/test_delivery_note.py index d7a93fb6917..9d92d43ec2f 100644 --- a/erpnext/stock/doctype/delivery_note/test_delivery_note.py +++ b/erpnext/stock/doctype/delivery_note/test_delivery_note.py @@ -88,7 +88,7 @@ class TestDeliveryNote(unittest.TestCase): # check stock in hand balance bal = get_balance_on(stock_in_hand_account) - self.assertEqual(bal, prev_bal - stock_value_difference) + self.assertEqual(flt(bal, 2), flt(prev_bal - stock_value_difference, 2)) # back dated incoming entry make_stock_entry(posting_date=add_days(nowdate(), -2), target="Stores - TCP1", @@ -548,11 +548,8 @@ class TestDeliveryNote(unittest.TestCase): dt = make_delivery_trip(dn.name) self.assertEqual(dn.name, dt.delivery_stops[0].delivery_note) - def test_delivery_note_for_enable_allow_cost_center_in_entry_of_bs_account(self): + def test_delivery_note_with_cost_center(self): from erpnext.accounts.doctype.cost_center.test_cost_center import create_cost_center - accounts_settings = frappe.get_doc('Accounts Settings', 'Accounts Settings') - accounts_settings.allow_cost_center_in_entry_of_bs_account = 1 - accounts_settings.save() cost_center = "_Test Cost Center for BS Account - TCP1" create_cost_center(cost_center_name="_Test Cost Center for BS Account", company="_Test Company with perpetual inventory") @@ -578,13 +575,8 @@ class TestDeliveryNote(unittest.TestCase): } for i, gle in enumerate(gl_entries): self.assertEqual(expected_values[gle.account]["cost_center"], gle.cost_center) - accounts_settings.allow_cost_center_in_entry_of_bs_account = 0 - accounts_settings.save() - def test_delivery_note_for_disable_allow_cost_center_in_entry_of_bs_account(self): - accounts_settings = frappe.get_doc('Accounts Settings', 'Accounts Settings') - accounts_settings.allow_cost_center_in_entry_of_bs_account = 0 - accounts_settings.save() + def test_delivery_note_cost_center_with_balance_sheet_account(self): cost_center = "Main - TCP1" company = frappe.db.get_value('Warehouse', 'Stores - TCP1', 'company') @@ -594,7 +586,11 @@ class TestDeliveryNote(unittest.TestCase): make_stock_entry(target="Stores - TCP1", qty=5, basic_rate=100) stock_in_hand_account = get_inventory_account('_Test Company with perpetual inventory') - dn = create_delivery_note(company='_Test Company with perpetual inventory', warehouse='Stores - TCP1', cost_center = 'Main - TCP1', expense_account = "Cost of Goods Sold - TCP1") + dn = create_delivery_note(company='_Test Company with perpetual inventory', warehouse='Stores - TCP1', cost_center = 'Main - TCP1', expense_account = "Cost of Goods Sold - TCP1", + do_not_submit=1) + + dn.get('items')[0].cost_center = None + dn.submit() gl_entries = get_gl_entries("Delivery Note", dn.name) @@ -604,7 +600,7 @@ class TestDeliveryNote(unittest.TestCase): "cost_center": cost_center }, stock_in_hand_account: { - "cost_center": None + "cost_center": cost_center } } for i, gle in enumerate(gl_entries): @@ -623,6 +619,7 @@ class TestDeliveryNote(unittest.TestCase): dn1 = create_delivery_note(is_return=1, return_against=dn.name, qty=-1, do_not_submit=True) dn1.items[0].against_sales_order = so.name dn1.items[0].so_detail = so.items[0].name + dn1.items[0].dn_detail = dn.items[0].name dn1.submit() si = make_sales_invoice(dn.name) @@ -649,7 +646,9 @@ class TestDeliveryNote(unittest.TestCase): si1.save() si1.submit() - create_delivery_note(is_return=1, return_against=dn.name, qty=-2) + dn1 = create_delivery_note(is_return=1, return_against=dn.name, qty=-2, do_not_submit=True) + dn1.items[0].dn_detail = dn.items[0].name + dn1.submit() si2 = make_sales_invoice(dn.name) self.assertEquals(si2.items[0].qty, 2) diff --git a/erpnext/stock/doctype/delivery_note_item/delivery_note_item.json b/erpnext/stock/doctype/delivery_note_item/delivery_note_item.json index a3386fce193..542d198c946 100644 --- a/erpnext/stock/doctype/delivery_note_item/delivery_note_item.json +++ b/erpnext/stock/doctype/delivery_note_item/delivery_note_item.json @@ -1,5 +1,4 @@ { - "actions": [], "autoname": "hash", "creation": "2013-04-22 13:15:44", "doctype": "DocType", @@ -67,6 +66,7 @@ "so_detail", "against_sales_invoice", "si_detail", + "dn_detail", "section_break_40", "batch_no", "serial_no", @@ -81,6 +81,7 @@ "accounting_dimensions_section", "cost_center", "dimension_col_break", + "project", "reason_for_return_section_break", "reason_for_return", "section_break_72", @@ -702,6 +703,12 @@ "fieldname": "dimension_col_break", "fieldtype": "Column Break" }, + { + "fieldname": "project", + "fieldtype": "Link", + "label": "Project", + "options": "Project" + }, { "depends_on": "eval:parent.is_return==1", "fieldname": "reason_for_return", @@ -715,12 +722,21 @@ "fieldname": "reason_for_return_section_break", "fieldtype": "Section Break", "label": "Reason For Return" + }, + { + "fieldname": "dn_detail", + "fieldtype": "Data", + "hidden": 1, + "label": "Against Delivery Note Item", + "no_copy": 1, + "print_hide": 1, + "read_only": 1 } ], "idx": 1, "istable": 1, "links": [], - "modified": "2020-03-06 14:18:33.131672", + "modified": "2020-08-20 11:18:33.131672", "modified_by": "Administrator", "module": "Stock", "name": "Delivery Note Item", diff --git a/erpnext/stock/doctype/item/item.js b/erpnext/stock/doctype/item/item.js index c10e62973bb..34476273c28 100644 --- a/erpnext/stock/doctype/item/item.js +++ b/erpnext/stock/doctype/item/item.js @@ -117,7 +117,7 @@ frappe.ui.form.on("Item", { const stock_exists = (frm.doc.__onload && frm.doc.__onload.stock_exists) ? 1 : 0; - ['is_stock_item', 'has_serial_no', 'has_batch_no'].forEach((fieldname) => { + ['is_stock_item', 'has_serial_no', 'has_batch_no', 'has_variants'].forEach((fieldname) => { frm.set_df_property(fieldname, 'read_only', stock_exists); }); diff --git a/erpnext/stock/doctype/item/item.json b/erpnext/stock/doctype/item/item.json index 7d2e3112fb3..11be6588957 100644 --- a/erpnext/stock/doctype/item/item.json +++ b/erpnext/stock/doctype/item/item.json @@ -1,5 +1,4 @@ { - "actions": [], "allow_guest_to_view": 1, "allow_import": 1, "allow_rename": 1, @@ -473,6 +472,7 @@ }, { "default": "0", + "depends_on": "has_batch_no", "fieldname": "retain_sample", "fieldtype": "Check", "label": "Retain Sample" @@ -499,7 +499,7 @@ "oldfieldtype": "Select" }, { - "depends_on": "eval:doc.is_stock_item || doc.is_fixed_asset", + "depends_on": "has_serial_no", "description": "Example: ABCD.#####\nIf series is set and Serial No is not mentioned in transactions, then automatic serial number will be created based on this series. If you always want to explicitly mention Serial Nos for this item. leave this blank.", "fieldname": "serial_no_series", "fieldtype": "Data", @@ -987,6 +987,7 @@ "read_only": 1 }, { + "collapsible": 1, "depends_on": "eval:(!doc.is_item_from_hub)", "fieldname": "hub_publishing_sb", "fieldtype": "Section Break", @@ -1058,9 +1059,8 @@ "icon": "fa fa-tag", "idx": 2, "image_field": "image", - "links": [], "max_attachments": 1, - "modified": "2020-04-07 15:56:06.195722", + "modified": "2020-08-06 17:03:26.594319", "modified_by": "Administrator", "module": "Stock", "name": "Item", diff --git a/erpnext/stock/doctype/item/item.py b/erpnext/stock/doctype/item/item.py index f5ffe242a1f..deace33f343 100644 --- a/erpnext/stock/doctype/item/item.py +++ b/erpnext/stock/doctype/item/item.py @@ -13,7 +13,7 @@ from erpnext.controllers.item_variant import (ItemVariantExistsError, from erpnext.setup.doctype.item_group.item_group import (get_parent_item_groups, invalidate_cache_for) from frappe import _, msgprint from frappe.utils import (cint, cstr, flt, formatdate, get_timestamp, getdate, - now_datetime, random_string, strip) + now_datetime, random_string, strip, get_link_to_form) from frappe.utils.html_utils import clean_html from frappe.website.doctype.website_slideshow.website_slideshow import \ get_slideshow @@ -634,6 +634,9 @@ class Item(WebsiteGenerator): + ": \n" + ", ".join([self.meta.get_label(fld) for fld in field_list])) def after_rename(self, old_name, new_name, merge): + if merge: + self.validate_duplicate_item_in_stock_reconciliation(old_name, new_name) + if self.route: invalidate_cache_for_item(self) clear_cache(self.route) @@ -656,6 +659,27 @@ class Item(WebsiteGenerator): frappe.db.set_value(dt, d.name, "item_wise_tax_detail", json.dumps(item_wise_tax_detail), update_modified=False) + def validate_duplicate_item_in_stock_reconciliation(self, old_name, new_name): + records = frappe.db.sql(""" SELECT parent, COUNT(*) as records + FROM `tabStock Reconciliation Item` + WHERE item_code = %s and docstatus = 1 + GROUP By item_code, warehouse, parent + HAVING records > 1 + """, new_name, as_dict=1) + + if not records: return + document = _("Stock Reconciliation") if len(records) == 1 else _("Stock Reconciliations") + + msg = _("The items {0} and {1} are present in the following {2} :
" + .format(frappe.bold(old_name), frappe.bold(new_name), document)) + + msg += ', '.join([get_link_to_form("Stock Reconciliation", d.parent) for d in records]) + "

" + + msg += _("Note: To merge the items, create a separate Stock Reconciliation for the old item {0}" + .format(frappe.bold(old_name))) + + frappe.throw(_(msg), title=_("Merge not allowed")) + def set_last_purchase_rate(self, new_name): last_purchase_rate = get_last_purchase_details(new_name).get("base_net_rate", 0) frappe.db.set_value("Item", new_name, "last_purchase_rate", last_purchase_rate) @@ -883,7 +907,12 @@ class Item(WebsiteGenerator): linked_doctypes += ["Sales Order Item", "Purchase Order Item", "Material Request Item"] for doctype in linked_doctypes: - if frappe.db.get_value(doctype, filters={"item_code": self.name, "docstatus": 1}) or \ + if doctype in ("Purchase Invoice Item", "Sales Invoice Item",): + # If Invoice has Stock impact, only then consider it. + if self.stock_ledger_created(): + return True + + elif frappe.db.get_value(doctype, filters={"item_code": self.name, "docstatus": 1}) or \ frappe.db.get_value("Production Order", filters={"production_item": self.name, "docstatus": 1}): return True @@ -949,6 +978,7 @@ def _msgprint(msg, verbose): def get_last_purchase_details(item_code, doc_name=None, conversion_rate=1.0): """returns last purchase details in stock uom""" # get last purchase order item details + last_purchase_order = frappe.db.sql("""\ select po.name, po.transaction_date, po.conversion_rate, po_item.conversion_factor, po_item.base_price_list_rate, @@ -959,6 +989,7 @@ def get_last_purchase_details(item_code, doc_name=None, conversion_rate=1.0): order by po.transaction_date desc, po.name desc limit 1""", (item_code, cstr(doc_name)), as_dict=1) + # get last purchase receipt item details last_purchase_receipt = frappe.db.sql("""\ select pr.name, pr.posting_date, pr.posting_time, pr.conversion_rate, @@ -970,19 +1001,20 @@ def get_last_purchase_details(item_code, doc_name=None, conversion_rate=1.0): order by pr.posting_date desc, pr.posting_time desc, pr.name desc limit 1""", (item_code, cstr(doc_name)), as_dict=1) + + purchase_order_date = getdate(last_purchase_order and last_purchase_order[0].transaction_date or "1900-01-01") purchase_receipt_date = getdate(last_purchase_receipt and last_purchase_receipt[0].posting_date or "1900-01-01") - if (purchase_order_date > purchase_receipt_date) or \ - (last_purchase_order and not last_purchase_receipt): + if last_purchase_order and (purchase_order_date >= purchase_receipt_date or not last_purchase_receipt): # use purchase order + last_purchase = last_purchase_order[0] purchase_date = purchase_order_date - elif (purchase_receipt_date > purchase_order_date) or \ - (last_purchase_receipt and not last_purchase_order): + elif last_purchase_receipt and (purchase_receipt_date > purchase_order_date or not last_purchase_order): # use purchase receipt last_purchase = last_purchase_receipt[0] purchase_date = purchase_receipt_date @@ -994,10 +1026,11 @@ def get_last_purchase_details(item_code, doc_name=None, conversion_rate=1.0): out = frappe._dict({ "base_price_list_rate": flt(last_purchase.base_price_list_rate) / conversion_factor, "base_rate": flt(last_purchase.base_rate) / conversion_factor, - "base_net_rate": flt(last_purchase.net_rate) / conversion_factor, + "base_net_rate": flt(last_purchase.base_net_rate) / conversion_factor, "discount_percentage": flt(last_purchase.discount_percentage), "purchase_date": purchase_date }) + conversion_rate = flt(conversion_rate) or 1.0 out.update({ diff --git a/erpnext/stock/doctype/item/test_records.json b/erpnext/stock/doctype/item/test_records.json index 6c1a55945c8..9ca887c77e3 100644 --- a/erpnext/stock/doctype/item/test_records.json +++ b/erpnext/stock/doctype/item/test_records.json @@ -92,8 +92,7 @@ { "doctype": "Item Tax", "parentfield": "taxes", - "item_tax_template": "_Test Account Excise Duty @ 10", - "tax_category": "" + "item_tax_template": "_Test Account Excise Duty @ 10" } ], "stock_uom": "_Test UOM 1" @@ -371,8 +370,7 @@ { "doctype": "Item Tax", "parentfield": "taxes", - "item_tax_template": "_Test Account Excise Duty @ 10", - "tax_category": "" + "item_tax_template": "_Test Account Excise Duty @ 10" }, { "doctype": "Item Tax", @@ -451,14 +449,13 @@ { "doctype": "Item Tax", "parentfield": "taxes", - "item_tax_template": "_Test Account Excise Duty @ 20", - "tax_category": "" + "item_tax_template": "_Test Account Excise Duty @ 20" }, { "doctype": "Item Tax", "parentfield": "taxes", - "item_tax_template": "_Test Item Tax Template 1", - "tax_category": "_Test Tax Category 1" + "tax_category": "_Test Tax Category 1", + "item_tax_template": "_Test Item Tax Template 1" } ] } diff --git a/erpnext/stock/doctype/item_alternative/item_alternative.py b/erpnext/stock/doctype/item_alternative/item_alternative.py index 0c0d5b3271f..204f71a2bb4 100644 --- a/erpnext/stock/doctype/item_alternative/item_alternative.py +++ b/erpnext/stock/doctype/item_alternative/item_alternative.py @@ -42,6 +42,8 @@ class ItemAlternative(Document): 'alternative_item_code': self.alternative_item_code, 'name': ('!=', self.name)}): frappe.throw(_("Already record exists for the item {0}".format(self.item_code))) +@frappe.whitelist() +@frappe.validate_and_sanitize_search_inputs def get_alternative_items(doctype, txt, searchfield, start, page_len, filters): return frappe.db.sql(""" (select alternative_item_code from `tabItem Alternative` where item_code = %(item_code)s and alternative_item_code like %(txt)s) @@ -52,4 +54,4 @@ def get_alternative_items(doctype, txt, searchfield, start, page_len, filters): """.format(start, page_len), { "item_code": filters.get('item_code'), "txt": '%' + txt + '%' - }) \ No newline at end of file + }) diff --git a/erpnext/stock/doctype/material_request/material_request.js b/erpnext/stock/doctype/material_request/material_request.js index b113781def3..1ccd8cf31a0 100644 --- a/erpnext/stock/doctype/material_request/material_request.js +++ b/erpnext/stock/doctype/material_request/material_request.js @@ -19,6 +19,12 @@ frappe.ui.form.on('Material Request', { frm.set_indicator_formatter('item_code', function(doc) { return (doc.stock_qty<=doc.ordered_qty) ? "green" : "orange"; }); + frm.set_query("from_warehouse", "items", function(doc) { + return { + filters: {'company': doc.company} + }; + }); + }, onload: function(frm) { @@ -27,11 +33,24 @@ frappe.ui.form.on('Material Request', { // set schedule_date set_schedule_date(frm); - frm.fields_dict["items"].grid.get_field("warehouse").get_query = function(doc) { + + frm.set_query("warehouse", "items", function(doc) { return { filters: {'company': doc.company} }; - }; + }); + + frm.set_query("set_warehouse", function(doc){ + return { + filters: {'company': doc.company} + }; + }); + + frm.set_query("set_from_warehouse", function(doc){ + return { + filters: {'company': doc.company} + }; + }); }, onload_post_render: function(frm) { diff --git a/erpnext/stock/doctype/material_request/material_request.py b/erpnext/stock/doctype/material_request/material_request.py index 5c6718bb4d5..1c7cdad48bc 100644 --- a/erpnext/stock/doctype/material_request/material_request.py +++ b/erpnext/stock/doctype/material_request/material_request.py @@ -385,6 +385,8 @@ def get_material_requests_based_on_supplier(supplier): return material_requests, supplier_items +@frappe.whitelist() +@frappe.validate_and_sanitize_search_inputs def get_default_supplier_query(doctype, txt, searchfield, start, page_len, filters): doc = frappe.get_doc("Material Request", filters.get("doc")) item_list = [] @@ -540,4 +542,4 @@ def create_pick_list(source_name, target_doc=None): doc.set_item_locations() - return doc \ No newline at end of file + return doc diff --git a/erpnext/stock/doctype/packing_slip/packing_slip.py b/erpnext/stock/doctype/packing_slip/packing_slip.py index 7a5ae317c2b..a7a29cca7f8 100644 --- a/erpnext/stock/doctype/packing_slip/packing_slip.py +++ b/erpnext/stock/doctype/packing_slip/packing_slip.py @@ -175,6 +175,8 @@ class PackingSlip(Document): self.update_item_details() +@frappe.whitelist() +@frappe.validate_and_sanitize_search_inputs def item_details(doctype, txt, searchfield, start, page_len, filters): from erpnext.controllers.queries import get_match_cond return frappe.db.sql("""select name, item_name, description from `tabItem` diff --git a/erpnext/stock/doctype/purchase_receipt/purchase_receipt.json b/erpnext/stock/doctype/purchase_receipt/purchase_receipt.json index 889d7326e90..6f657ab3d01 100755 --- a/erpnext/stock/doctype/purchase_receipt/purchase_receipt.json +++ b/erpnext/stock/doctype/purchase_receipt/purchase_receipt.json @@ -1,5 +1,4 @@ { - "actions": [], "allow_import": 1, "autoname": "naming_series:", "creation": "2013-05-21 16:16:39", @@ -102,6 +101,7 @@ "bill_no", "bill_date", "more_info", + "project", "status", "amended_from", "range", @@ -1059,13 +1059,20 @@ "fieldname": "scan_barcode", "fieldtype": "Data", "label": "Scan Barcode" + }, + { + "description": "Track this Purchase Receipt against any Project", + "fieldname": "project", + "fieldtype": "Link", + "label": "Project", + "options": "Project" } ], "icon": "fa fa-truck", "idx": 261, "is_submittable": 1, "links": [], - "modified": "2020-04-17 13:06:26.970288", + "modified": "2020-07-15 12:49:42.095297", "modified_by": "Administrator", "module": "Stock", "name": "Purchase Receipt", diff --git a/erpnext/stock/doctype/purchase_receipt/purchase_receipt.py b/erpnext/stock/doctype/purchase_receipt/purchase_receipt.py index f21c4ef8593..a150e097d42 100644 --- a/erpnext/stock/doctype/purchase_receipt/purchase_receipt.py +++ b/erpnext/stock/doctype/purchase_receipt/purchase_receipt.py @@ -495,7 +495,7 @@ def make_purchase_invoice(source_name, target_doc=None): def set_missing_values(source, target): if len(target.get("items")) == 0: - frappe.throw(_("All items have already been invoiced")) + frappe.throw(_("All items have already been Invoiced/Returned")) doc = frappe.get_doc(target) doc.ignore_pricing_rule = 1 @@ -505,11 +505,11 @@ def make_purchase_invoice(source_name, target_doc=None): def update_item(source_doc, target_doc, source_parent): target_doc.qty, returned_qty = get_pending_qty(source_doc) - returned_qty_map[source_doc.item_code] = returned_qty + returned_qty_map[source_doc.name] = returned_qty def get_pending_qty(item_row): pending_qty = item_row.qty - invoiced_qty_map.get(item_row.name, 0) - returned_qty = flt(returned_qty_map.get(item_row.item_code, 0)) + returned_qty = flt(returned_qty_map.get(item_row.name, 0)) if returned_qty: if returned_qty >= pending_qty: pending_qty = 0 @@ -567,13 +567,12 @@ def get_invoiced_qty_map(purchase_receipt): def get_returned_qty_map(purchase_receipt): """returns a map: {so_detail: returned_qty}""" - returned_qty_map = frappe._dict(frappe.db.sql("""select pr_item.item_code, sum(abs(pr_item.qty)) as qty + returned_qty_map = frappe._dict(frappe.db.sql("""select pr_item.purchase_receipt_item, abs(pr_item.qty) as qty from `tabPurchase Receipt Item` pr_item, `tabPurchase Receipt` pr where pr.name = pr_item.parent and pr.docstatus = 1 and pr.is_return = 1 and pr.return_against = %s - group by pr_item.item_code """, purchase_receipt)) return returned_qty_map diff --git a/erpnext/stock/doctype/purchase_receipt/test_purchase_receipt.py b/erpnext/stock/doctype/purchase_receipt/test_purchase_receipt.py index 09271cebfa8..74019c7b3cb 100644 --- a/erpnext/stock/doctype/purchase_receipt/test_purchase_receipt.py +++ b/erpnext/stock/doctype/purchase_receipt/test_purchase_receipt.py @@ -51,7 +51,7 @@ class TestPurchaseReceipt(unittest.TestCase): self.assertEqual(current_bin_stock_value, existing_bin_stock_value + 250) self.assertFalse(get_gl_entries("Purchase Receipt", pr.name)) - + def test_batched_serial_no_purchase(self): item = frappe.db.exists("Item", {'item_name': 'Batched Serialized Item'}) if not item: @@ -68,7 +68,7 @@ class TestPurchaseReceipt(unittest.TestCase): pr = make_purchase_receipt(item_code=item.name, qty=5, rate=500) self.assertTrue(frappe.db.get_value('Batch', {'item': item.name, 'reference_name': pr.name})) - + pr.load_from_db() batch_no = pr.items[0].batch_no pr.cancel() @@ -374,7 +374,7 @@ class TestPurchaseReceipt(unittest.TestCase): location = frappe.db.get_value('Asset', assets[0].name, 'location') self.assertEquals(location, "Test Location") - + def test_purchase_return_with_submitted_asset(self): from erpnext.stock.doctype.purchase_receipt.purchase_receipt import make_purchase_return @@ -396,17 +396,14 @@ class TestPurchaseReceipt(unittest.TestCase): pr_return = make_purchase_return(pr.name) self.assertRaises(frappe.exceptions.ValidationError, pr_return.submit) - + asset.load_from_db() asset.cancel() - + pr_return.submit() - def test_purchase_receipt_for_enable_allow_cost_center_in_entry_of_bs_account(self): + def test_purchase_receipt_cost_center(self): from erpnext.accounts.doctype.cost_center.test_cost_center import create_cost_center - accounts_settings = frappe.get_doc('Accounts Settings', 'Accounts Settings') - accounts_settings.allow_cost_center_in_entry_of_bs_account = 1 - accounts_settings.save() cost_center = "_Test Cost Center for BS Account - TCP1" create_cost_center(cost_center_name="_Test Cost Center for BS Account", company="_Test Company with perpetual inventory") @@ -434,14 +431,7 @@ class TestPurchaseReceipt(unittest.TestCase): for i, gle in enumerate(gl_entries): self.assertEqual(expected_values[gle.account]["cost_center"], gle.cost_center) - accounts_settings.allow_cost_center_in_entry_of_bs_account = 0 - accounts_settings.save() - - def test_purchase_receipt_for_disable_allow_cost_center_in_entry_of_bs_account(self): - accounts_settings = frappe.get_doc('Accounts Settings', 'Accounts Settings') - accounts_settings.allow_cost_center_in_entry_of_bs_account = 0 - accounts_settings.save() - + def test_purchase_receipt_cost_center_with_balance_sheet_account(self): if not frappe.db.exists('Location', 'Test Location'): frappe.get_doc({ 'doctype': 'Location', @@ -453,13 +443,14 @@ class TestPurchaseReceipt(unittest.TestCase): gl_entries = get_gl_entries("Purchase Receipt", pr.name) self.assertTrue(gl_entries) + cost_center = pr.get('items')[0].cost_center expected_values = { "Stock Received But Not Billed - TCP1": { - "cost_center": None + "cost_center": cost_center }, stock_in_hand_account: { - "cost_center": None + "cost_center": cost_center } } for i, gle in enumerate(gl_entries): @@ -474,6 +465,7 @@ class TestPurchaseReceipt(unittest.TestCase): pr1 = make_purchase_receipt(is_return=1, return_against=pr.name, qty=-1, do_not_submit=True) pr1.items[0].purchase_order = po.name pr1.items[0].purchase_order_item = po.items[0].name + pr1.items[0].purchase_receipt_item = pr.items[0].name pr1.submit() pi = make_purchase_invoice(pr.name) @@ -497,7 +489,9 @@ class TestPurchaseReceipt(unittest.TestCase): pi1.save() pi1.submit() - make_purchase_receipt(is_return=1, return_against=pr1.name, qty=-2) + pr2 = make_purchase_receipt(is_return=1, return_against=pr1.name, qty=-2, do_not_submit=True) + pr2.items[0].purchase_receipt_item = pr1.items[0].name + pr2.submit() pi2 = make_purchase_invoice(pr1.name) self.assertEquals(pi2.items[0].qty, 2) diff --git a/erpnext/stock/doctype/purchase_receipt_item/purchase_receipt_item.json b/erpnext/stock/doctype/purchase_receipt_item/purchase_receipt_item.json index e1dc95be481..3d46289d570 100644 --- a/erpnext/stock/doctype/purchase_receipt_item/purchase_receipt_item.json +++ b/erpnext/stock/doctype/purchase_receipt_item/purchase_receipt_item.json @@ -78,6 +78,7 @@ "stock_qty", "purchase_order_item", "material_request_item", + "purchase_receipt_item", "section_break_45", "allow_zero_valuation_rate", "bom", @@ -551,7 +552,7 @@ "fieldname": "batch_no", "fieldtype": "Link", "in_list_view": 1, - "label": "Batch No!", + "label": "Batch No", "no_copy": 1, "oldfieldname": "batch_no", "oldfieldtype": "Link", @@ -817,12 +818,21 @@ "label": "Asset Category", "options": "Asset Category", "read_only": 1 + }, + { + "fieldname": "purchase_receipt_item", + "fieldtype": "Data", + "hidden": 1, + "label": "Purchase Receipt Item", + "no_copy": 1, + "print_hide": 1, + "read_only": 1 } ], "idx": 1, "istable": 1, "links": [], - "modified": "2020-04-07 18:38:21.141558", + "modified": "2020-06-20 18:49:16.824489", "modified_by": "Administrator", "module": "Stock", "name": "Purchase Receipt Item", diff --git a/erpnext/stock/doctype/quality_inspection/quality_inspection.py b/erpnext/stock/doctype/quality_inspection/quality_inspection.py index 37ab807cb7b..c3bb5141849 100644 --- a/erpnext/stock/doctype/quality_inspection/quality_inspection.py +++ b/erpnext/stock/doctype/quality_inspection/quality_inspection.py @@ -58,6 +58,8 @@ class QualityInspection(Document): .format(parent_doc=self.reference_type, child_doc=doctype), (quality_inspection, self.modified, self.reference_name, self.item_code)) +@frappe.whitelist() +@frappe.validate_and_sanitize_search_inputs def item_query(doctype, txt, searchfield, start, page_len, filters): if filters.get("from"): from frappe.desk.reportview import get_match_cond @@ -86,6 +88,8 @@ def item_query(doctype, txt, searchfield, start, page_len, filters): page_len = page_len, qi_condition = qi_condition), {'parent': filters.get('parent'), 'txt': "%%%s%%" % txt}) +@frappe.whitelist() +@frappe.validate_and_sanitize_search_inputs def quality_inspection_query(doctype, txt, searchfield, start, page_len, filters): return frappe.get_all('Quality Inspection', limit_start=start, @@ -118,4 +122,4 @@ def make_quality_inspection(source_name, target_doc=None): } }, target_doc, postprocess) - return doc \ No newline at end of file + return doc diff --git a/erpnext/stock/doctype/serial_no/serial_no.json b/erpnext/stock/doctype/serial_no/serial_no.json index d9f8b627545..b9427289449 100644 --- a/erpnext/stock/doctype/serial_no/serial_no.json +++ b/erpnext/stock/doctype/serial_no/serial_no.json @@ -427,7 +427,7 @@ "icon": "fa fa-barcode", "idx": 1, "links": [], - "modified": "2020-05-21 19:29:58.517772", + "modified": "2020-07-22 15:53:50.900855", "modified_by": "Administrator", "module": "Stock", "name": "Serial No", diff --git a/erpnext/stock/doctype/serial_no/serial_no.py b/erpnext/stock/doctype/serial_no/serial_no.py index 2cb43ae2db3..bbdac992b58 100644 --- a/erpnext/stock/doctype/serial_no/serial_no.py +++ b/erpnext/stock/doctype/serial_no/serial_no.py @@ -193,7 +193,7 @@ class SerialNo(StockController): def after_rename(self, old, new, merge=False): """rename serial_no text fields""" for dt in frappe.db.sql("""select parent from tabDocField - where fieldname='serial_no' and fieldtype in ('Text', 'Small Text')"""): + where fieldname='serial_no' and fieldtype in ('Text', 'Small Text', 'Long Text')"""): for item in frappe.db.sql("""select name, serial_no from `tab%s` where serial_no like %s""" % (dt[0], frappe.db.escape('%' + old + '%'))): diff --git a/erpnext/stock/doctype/stock_entry/stock_entry.py b/erpnext/stock/doctype/stock_entry/stock_entry.py index 9eeccac3652..daa71bc6b2e 100644 --- a/erpnext/stock/doctype/stock_entry/stock_entry.py +++ b/erpnext/stock/doctype/stock_entry/stock_entry.py @@ -196,7 +196,8 @@ class StockEntry(StockController): item.set(f, item_details.get(f)) if not item.transfer_qty and item.qty: - item.transfer_qty = item.qty * item.conversion_factor + item.transfer_qty = flt(flt(item.qty) * flt(item.conversion_factor), + self.precision("transfer_qty", item)) if (self.purpose in ("Material Transfer", "Material Transfer for Manufacture") and not item.serial_no @@ -571,9 +572,7 @@ class StockEntry(StockController): {"parent": self.purchase_order, "item_code": se_item.subcontracted_item}, "bom") - allow_alternative_item = frappe.get_value("BOM", bom_no, "allow_alternative_item") - - if allow_alternative_item: + if se_item.allow_alternative_item: original_item_code = frappe.get_value("Item Alternative", {"alternative_item_code": item_code}, "item_code") required_qty = sum([flt(d.required_qty) for d in purchase_order.supplied_items \ @@ -736,7 +735,7 @@ class StockEntry(StockController): def get_item_details(self, args=None, for_update=False): item = frappe.db.sql("""select i.name, i.stock_uom, i.description, i.image, i.item_name, i.item_group, - i.has_batch_no, i.sample_quantity, i.has_serial_no, + i.has_batch_no, i.sample_quantity, i.has_serial_no, i.allow_alternative_item, id.expense_account, id.buying_cost_center from `tabItem` i LEFT JOIN `tabItem Default` id ON i.name=id.parent and id.company=%s where i.name=%s @@ -770,6 +769,9 @@ class StockEntry(StockController): 'sample_quantity' : item.sample_quantity }) + if self.purpose == 'Send to Subcontractor': + ret["allow_alternative_item"] = item.allow_alternative_item + # update uom if args.get("uom") and for_update: ret.update(get_uom_details(args.get('item_code'), args.get('uom'), args.get('qty'))) diff --git a/erpnext/stock/doctype/stock_reconciliation/stock_reconciliation.js b/erpnext/stock/doctype/stock_reconciliation/stock_reconciliation.js index 1791978a068..0475ea7a2ec 100644 --- a/erpnext/stock/doctype/stock_reconciliation/stock_reconciliation.js +++ b/erpnext/stock/doctype/stock_reconciliation/stock_reconciliation.js @@ -74,6 +74,20 @@ frappe.ui.form.on("Stock Reconciliation", { , __("Get Items"), __("Update")); }, + posting_date: function(frm) { + frm.trigger("set_valuation_rate_and_qty_for_all_items"); + }, + + posting_time: function(frm) { + frm.trigger("set_valuation_rate_and_qty_for_all_items"); + }, + + set_valuation_rate_and_qty_for_all_items: function(frm) { + frm.doc.items.forEach(row => { + frm.events.set_valuation_rate_and_qty(frm, row.doctype, row.name); + }); + }, + set_valuation_rate_and_qty: function(frm, cdt, cdn) { var d = frappe.model.get_doc(cdt, cdn); diff --git a/erpnext/stock/doctype/stock_reconciliation/stock_reconciliation.py b/erpnext/stock/doctype/stock_reconciliation/stock_reconciliation.py index 0a49c26b629..0dc87767dde 100644 --- a/erpnext/stock/doctype/stock_reconciliation/stock_reconciliation.py +++ b/erpnext/stock/doctype/stock_reconciliation/stock_reconciliation.py @@ -184,8 +184,12 @@ class StockReconciliation(StockController): sl_entries = [] has_serial_no = False + has_batch_no = False for row in self.items: item = frappe.get_doc("Item", row.item_code) + if item.has_batch_no: + has_batch_no = True + if item.has_serial_no or item.has_batch_no: has_serial_no = True self.get_sle_for_serialized_items(row, sl_entries) @@ -221,7 +225,11 @@ class StockReconciliation(StockController): if has_serial_no: sl_entries = self.merge_similar_item_serial_nos(sl_entries) - self.make_sl_entries(sl_entries) + allow_negative_stock = False + if has_batch_no: + allow_negative_stock = True + + self.make_sl_entries(sl_entries, allow_negative_stock=allow_negative_stock) if has_serial_no and sl_entries: self.update_valuation_rate_for_serial_no() @@ -249,6 +257,7 @@ class StockReconciliation(StockController): sl_entries.append(args) + qty_after_transaction = 0 for serial_no in serial_nos: args = self.get_sle_for_items(row, [serial_no]) @@ -262,11 +271,19 @@ class StockReconciliation(StockController): if previous_sle and row.warehouse != previous_sle.get("warehouse"): # If serial no exists in different warehouse + warehouse = previous_sle.get("warehouse", '') or row.warehouse + + if not qty_after_transaction: + qty_after_transaction = get_stock_balance(row.item_code, + warehouse, self.posting_date, self.posting_time) + + qty_after_transaction -= 1 + new_args = args.copy() new_args.update({ 'actual_qty': -1, - 'qty_after_transaction': cint(previous_sle.get('qty_after_transaction')) - 1, - 'warehouse': previous_sle.get("warehouse", '') or row.warehouse, + 'qty_after_transaction': qty_after_transaction, + 'warehouse': warehouse, 'valuation_rate': previous_sle.get("valuation_rate") }) @@ -498,7 +515,7 @@ def get_stock_balance_for(item_code, warehouse, qty, rate = data if item_dict.get("has_batch_no"): - qty = get_batch_qty(batch_no, warehouse) or 0 + qty = get_batch_qty(batch_no, warehouse, posting_date=posting_date, posting_time=posting_time) or 0 return { 'qty': qty, diff --git a/erpnext/stock/doctype/warehouse/warehouse.json b/erpnext/stock/doctype/warehouse/warehouse.json index 0606d0bdc64..6e40bc53d07 100644 --- a/erpnext/stock/doctype/warehouse/warehouse.json +++ b/erpnext/stock/doctype/warehouse/warehouse.json @@ -1,5 +1,4 @@ { - "actions": [], "allow_import": 1, "allow_rename": 1, "creation": "2013-03-07 18:50:32", @@ -10,12 +9,14 @@ "field_order": [ "warehouse_detail", "warehouse_name", + "section_break_3", + "warehouse_type", + "parent_warehouse", "is_group", - "company", - "disabled", "column_break_4", "account", - "warehouse_type", + "company", + "disabled", "address_and_contact", "address_html", "column_break_10", @@ -31,7 +32,6 @@ "state", "pin", "tree_details", - "parent_warehouse", "lft", "rgt", "old_parent" @@ -44,7 +44,6 @@ "oldfieldtype": "Section Break" }, { - "description": "If blank, parent Warehouse Account or company default will be considered", "fieldname": "warehouse_name", "fieldtype": "Data", "label": "Warehouse Name", @@ -85,12 +84,14 @@ "fieldtype": "Column Break" }, { + "description": "If blank, parent Warehouse Account or company default will be considered in transactions", "fieldname": "account", "fieldtype": "Link", "label": "Account", "options": "Account" }, { + "depends_on": "eval:!doc.__islocal", "fieldname": "address_and_contact", "fieldtype": "Section Break", "label": "Address and Contact" @@ -224,13 +225,17 @@ "fieldtype": "Link", "label": "Warehouse Type", "options": "Warehouse Type" + }, + { + "fieldname": "section_break_3", + "fieldtype": "Section Break" } ], "icon": "fa fa-building", "idx": 1, "is_tree": 1, "links": [], - "modified": "2020-03-18 18:26:00.479541", + "modified": "2020-08-03 18:41:52.442502", "modified_by": "Administrator", "module": "Stock", "name": "Warehouse", diff --git a/erpnext/stock/report/stock_ageing/stock_ageing.py b/erpnext/stock/report/stock_ageing/stock_ageing.py index 803a5c81a3b..99d816c4a24 100644 --- a/erpnext/stock/report/stock_ageing/stock_ageing.py +++ b/erpnext/stock/report/stock_ageing/stock_ageing.py @@ -178,14 +178,14 @@ def get_fifo_queue(filters, sle=None): qty_to_pop = abs(d.actual_qty) while qty_to_pop: batch = fifo_queue[0] if fifo_queue else [0, None] - if 0 < batch[0] <= qty_to_pop: + if 0 < flt(batch[0]) <= qty_to_pop: # if batch qty > 0 # not enough or exactly same qty in current batch, clear batch - qty_to_pop -= batch[0] + qty_to_pop -= flt(batch[0]) transferred_item_details[(d.voucher_no, d.name)].append(fifo_queue.pop(0)) else: # all from current batch - batch[0] -= qty_to_pop + batch[0] = flt(batch[0]) - qty_to_pop transferred_item_details[(d.voucher_no, d.name)].append([qty_to_pop, batch[1]]) qty_to_pop = 0 diff --git a/erpnext/stock/report/stock_balance/stock_balance.py b/erpnext/stock/report/stock_balance/stock_balance.py index 74a4f6ef142..042087a4a77 100644 --- a/erpnext/stock/report/stock_balance/stock_balance.py +++ b/erpnext/stock/report/stock_balance/stock_balance.py @@ -2,7 +2,7 @@ # License: GNU General Public License v3. See license.txt from __future__ import unicode_literals -import frappe +import frappe, erpnext from frappe import _ from frappe.utils import flt, cint, getdate, now, date_diff from erpnext.stock.utils import add_additional_uom_columns @@ -20,6 +20,11 @@ def execute(filters=None): from_date = filters.get('from_date') to_date = filters.get('to_date') + if filters.get("company"): + company_currency = erpnext.get_company_currency(filters.get("company")) + else: + company_currency = frappe.db.get_single_value("Global Defaults", "default_currency") + include_uom = filters.get("include_uom") columns = get_columns(filters) items = get_items(filters) @@ -52,6 +57,7 @@ def execute(filters=None): item_reorder_qty = item_reorder_detail_map[item + warehouse]["warehouse_reorder_qty"] report_data = { + 'currency': company_currency, 'item_code': item, 'warehouse': warehouse, 'company': company, @@ -89,7 +95,6 @@ def execute(filters=None): def get_columns(filters): """return columns""" - columns = [ {"label": _("Item"), "fieldname": "item_code", "fieldtype": "Link", "options": "Item", "width": 100}, {"label": _("Item Name"), "fieldname": "item_name", "width": 150}, @@ -97,14 +102,14 @@ def get_columns(filters): {"label": _("Warehouse"), "fieldname": "warehouse", "fieldtype": "Link", "options": "Warehouse", "width": 100}, {"label": _("Stock UOM"), "fieldname": "stock_uom", "fieldtype": "Link", "options": "UOM", "width": 90}, {"label": _("Balance Qty"), "fieldname": "bal_qty", "fieldtype": "Float", "width": 100, "convertible": "qty"}, - {"label": _("Balance Value"), "fieldname": "bal_val", "fieldtype": "Currency", "width": 100}, + {"label": _("Balance Value"), "fieldname": "bal_val", "fieldtype": "Currency", "width": 100, "options": "currency"}, {"label": _("Opening Qty"), "fieldname": "opening_qty", "fieldtype": "Float", "width": 100, "convertible": "qty"}, - {"label": _("Opening Value"), "fieldname": "opening_val", "fieldtype": "Float", "width": 110}, + {"label": _("Opening Value"), "fieldname": "opening_val", "fieldtype": "Currency", "width": 110, "options": "currency"}, {"label": _("In Qty"), "fieldname": "in_qty", "fieldtype": "Float", "width": 80, "convertible": "qty"}, {"label": _("In Value"), "fieldname": "in_val", "fieldtype": "Float", "width": 80}, {"label": _("Out Qty"), "fieldname": "out_qty", "fieldtype": "Float", "width": 80, "convertible": "qty"}, {"label": _("Out Value"), "fieldname": "out_val", "fieldtype": "Float", "width": 80}, - {"label": _("Valuation Rate"), "fieldname": "val_rate", "fieldtype": "Currency", "width": 90, "convertible": "rate"}, + {"label": _("Valuation Rate"), "fieldname": "val_rate", "fieldtype": "Currency", "width": 90, "convertible": "rate", "options": "currency"}, {"label": _("Reorder Level"), "fieldname": "reorder_level", "fieldtype": "Float", "width": 80, "convertible": "qty"}, {"label": _("Reorder Qty"), "fieldname": "reorder_qty", "fieldtype": "Float", "width": 80, "convertible": "qty"}, {"label": _("Company"), "fieldname": "company", "fieldtype": "Link", "options": "Company", "width": 100} diff --git a/erpnext/stock/report/stock_ledger/stock_ledger.py b/erpnext/stock/report/stock_ledger/stock_ledger.py index d757ecb293d..6a265ec4cc5 100644 --- a/erpnext/stock/report/stock_ledger/stock_ledger.py +++ b/erpnext/stock/report/stock_ledger/stock_ledger.py @@ -4,6 +4,7 @@ from __future__ import unicode_literals import frappe from frappe import _ +from frappe.utils import cint, flt from erpnext.stock.utils import update_included_uom_in_report def execute(filters=None): @@ -13,6 +14,7 @@ def execute(filters=None): sl_entries = get_stock_ledger_entries(filters, items) item_details = get_item_details(items, sl_entries, include_uom) opening_row = get_opening_balance(filters, columns) + precision = cint(frappe.db.get_single_value("System Settings", "float_precision")) data = [] conversion_factors = [] @@ -27,10 +29,10 @@ def execute(filters=None): sle.update(item_detail) if filters.get("batch_no"): - actual_qty += sle.actual_qty + actual_qty += flt(sle.actual_qty, precision) stock_value += sle.stock_value_difference - if sle.voucher_type == 'Stock Reconciliation': + if sle.voucher_type == 'Stock Reconciliation' and not sle.actual_qty: actual_qty = sle.qty_after_transaction stock_value = sle.stock_value diff --git a/erpnext/stock/report/supplier_wise_sales_analytics/supplier_wise_sales_analytics.py b/erpnext/stock/report/supplier_wise_sales_analytics/supplier_wise_sales_analytics.py index 6a86889aa3d..5873a7a3008 100644 --- a/erpnext/stock/report/supplier_wise_sales_analytics/supplier_wise_sales_analytics.py +++ b/erpnext/stock/report/supplier_wise_sales_analytics/supplier_wise_sales_analytics.py @@ -21,7 +21,7 @@ def execute(filters=None): for cd in consumed_details.get(item_code): if (cd.voucher_no not in material_transfer_vouchers): - if cd.voucher_type=="Delivery Note": + if cd.voucher_type in ["Delivery Note", "Sales Invoice"]: delivered_qty += abs(flt(cd.actual_qty)) delivered_amount += abs(flt(cd.stock_value_difference)) elif cd.voucher_type!="Delivery Note": diff --git a/erpnext/templates/generators/item/item_configure.html b/erpnext/templates/generators/item/item_configure.html index 04f89eca9db..b8b0d98bdc2 100644 --- a/erpnext/templates/generators/item/item_configure.html +++ b/erpnext/templates/generators/item/item_configure.html @@ -2,7 +2,7 @@ {% set cart_settings = shopping_cart.cart_settings %}
- {% if cart_settings.show_configure_button | int %} + {% if cart_settings.enable_variants | int %}
-{% endif %} +{% endif %} -{%- if application_form_route -%} +{%- if doc.enable_admission_application -%}

+ href='/student-applicant'> {{ _("Apply Now") }}

{% endif %} diff --git a/erpnext/templates/includes/footer/footer_powered.html b/erpnext/templates/includes/footer/footer_powered.html index cf7661ee3fa..f0f65a37618 100644 --- a/erpnext/templates/includes/footer/footer_powered.html +++ b/erpnext/templates/includes/footer/footer_powered.html @@ -10,9 +10,17 @@ 'Agriculture': '/agriculture', 'Hospitality': '' } %} + {% set link = '' %} {% if domains %} - {% set link = links[domains[0].domain] %} + {% set label = domains[0].domain %} + {% set link = links[label] %} {% endif %} -Powered by ERPNext - {{ '' if domains else 'Open Source' }} ERP Software {{ ('for ' + domains[0].domain + ' Companies') if domains else '' }} +{% if label == "Services" %} + {% set label = "Service" %} +{% endif %} + + + +Powered by ERPNext - {{ '' if domains else 'Open Source' }} ERP Software {{ ('for ' + label + ' Companies') if domains else '' }} diff --git a/erpnext/utilities/transaction_base.py b/erpnext/utilities/transaction_base.py index ef7bd989042..44fdc2f0a49 100644 --- a/erpnext/utilities/transaction_base.py +++ b/erpnext/utilities/transaction_base.py @@ -117,6 +117,15 @@ class TransactionBase(StatusUpdater): def validate_rate_with_reference_doc(self, ref_details): + buying_doctypes = ["Purchase Order", "Purchase Invoice", "Purchase Receipt"] + + if self.doctype in buying_doctypes: + to_disable = "Maintain same rate throughout Purchase cycle" + settings_page = "Buying Settings" + else: + to_disable = "Maintain same rate throughout Sales cycle" + settings_page = "Selling Settings" + for ref_dt, ref_dn_field, ref_link_field in ref_details: for d in self.get("items"): if d.get(ref_link_field): @@ -126,8 +135,8 @@ class TransactionBase(StatusUpdater): frappe.msgprint(_("Row #{0}: Rate must be same as {1}: {2} ({3} / {4}) ") .format(d.idx, ref_dt, d.get(ref_dn_field), d.rate, ref_rate)) frappe.throw(_("To allow different rates, disable the {0} checkbox in {1}.") - .format(frappe.bold("Maintain Same Rate Throughout Sales Cycle"), - get_link_to_form("Selling Settings", "Selling Settings", frappe.bold("Selling Settings")))) + .format(frappe.bold(_(to_disable)), + get_link_to_form(settings_page, settings_page, frappe.bold(settings_page)))) def get_link_filters(self, for_doctype): if hasattr(self, "prev_link_mapper") and self.prev_link_mapper.get(for_doctype): diff --git a/erpnext/www/lms/content.html b/erpnext/www/lms/content.html index cdc71412c48..dc9b6d80fb5 100644 --- a/erpnext/www/lms/content.html +++ b/erpnext/www/lms/content.html @@ -59,7 +59,7 @@ {% macro title() %}
@@ -69,15 +69,15 @@ {% macro navigation() %} {% if previous %} - Previous + {{_('Previous')}} {% else %} - Back to Course + {{ _('Back to Course') }} {% endif %} {% if next %} - + {% else %} - + {% endif %} {% endmacro %} @@ -86,7 +86,7 @@ {{ title() }}
{% if content.duration %} - {{ content.duration }} Mins + {{ content.duration }} {{_('Mins')}} {% endif %} {% if content.publish_date and content.duration%} @@ -94,7 +94,7 @@ {% endif %} {% if content.publish_date %} - Published on {{ content.publish_date.strftime('%d, %b %Y') }} + {{_('Published on')}} {{ content.publish_date.strftime('%d, %b %Y') }} {% endif %}
@@ -109,13 +109,13 @@ {{ title() }}
{% if content.author or content.publish_date %} - Published + {{_('Published')}} {% endif %} {% if content.author %} - by {{ content.author }} + {{_('by')}} {{ content.author }} {% endif %} {% if content.publish_date %} - on {{ content.publish_date.strftime('%d, %b %Y') }} + {{_('on')}} {{ content.publish_date.strftime('%d, %b %Y') }} {% endif %}
@@ -205,4 +205,4 @@ {% endif %} -{% endblock %} \ No newline at end of file +{% endblock %} diff --git a/erpnext/www/lms/course.html b/erpnext/www/lms/course.html index f2fd9363e8c..0d70ed5cefd 100644 --- a/erpnext/www/lms/course.html +++ b/erpnext/www/lms/course.html @@ -72,11 +72,11 @@ {% if has_access %} diff --git a/erpnext/www/lms/index.html b/erpnext/www/lms/index.html index ffb4419f367..7ce3521273f 100644 --- a/erpnext/www/lms/index.html +++ b/erpnext/www/lms/index.html @@ -45,7 +45,7 @@

{{ education_settings.description }}

{% if frappe.session.user == 'Guest' %} - Sign Up + {{_('Sign Up')}} {% endif %}

diff --git a/erpnext/www/lms/macros/card.html b/erpnext/www/lms/macros/card.html index 076061d41b3..dc8fc5c72c7 100644 --- a/erpnext/www/lms/macros/card.html +++ b/erpnext/www/lms/macros/card.html @@ -15,8 +15,8 @@ {% if has_access or program.intro_video%} {% endif %} diff --git a/erpnext/www/lms/macros/hero.html b/erpnext/www/lms/macros/hero.html index 66bb861c467..94f239eb8ed 100644 --- a/erpnext/www/lms/macros/hero.html +++ b/erpnext/www/lms/macros/hero.html @@ -2,16 +2,16 @@

{{ title }}

{{ description or ''}}

{% if frappe.session.user == 'Guest' %} - Sign Up + {{_('Sign Up')}} {% elif not has_access %} - + {% endif %}

@@ -28,7 +28,7 @@ let btn = document.getElementById('enroll'); btn.disbaled = true; - btn.innerText = 'Enrolling...' + btn.innerText = __('Enrolling...') let opts = { method: 'erpnext.education.utils.enroll_in_program', @@ -44,7 +44,7 @@ window.location.reload() } }) - success_dialog.set_message('You have successfully enrolled for the program '); + success_dialog.set_message(__('You have successfully enrolled for the program ')); success_dialog.$message.show() success_dialog.show(); btn.disbaled = false; diff --git a/erpnext/www/lms/profile.html b/erpnext/www/lms/profile.html index 9508daedb71..5755dfe6d8e 100644 --- a/erpnext/www/lms/profile.html +++ b/erpnext/www/lms/profile.html @@ -30,7 +30,7 @@ @@ -43,11 +43,11 @@

{{ student.first_name }} {{ student.last_name or '' }}

@@ -61,4 +61,4 @@ -{% endblock %} \ No newline at end of file +{% endblock %} diff --git a/erpnext/www/lms/program.html b/erpnext/www/lms/program.html index 271b7813bb4..7ad618630a4 100644 --- a/erpnext/www/lms/program.html +++ b/erpnext/www/lms/program.html @@ -55,11 +55,11 @@ {% if has_access and progress[course.name] %} {% endif %} diff --git a/erpnext/www/lms/topic.html b/erpnext/www/lms/topic.html index 1f0d1876646..cd24616cd45 100644 --- a/erpnext/www/lms/topic.html +++ b/erpnext/www/lms/topic.html @@ -23,13 +23,13 @@ {% if has_access %}