diff --git a/erpnext/accounts/deferred_revenue.py b/erpnext/accounts/deferred_revenue.py index 3b6a5881ca..b0210e5fd4 100644 --- a/erpnext/accounts/deferred_revenue.py +++ b/erpnext/accounts/deferred_revenue.py @@ -2,9 +2,10 @@ from __future__ import unicode_literals import frappe from frappe import _ -from frappe.utils import date_diff, add_months, today, getdate, add_days, flt, get_last_day +from frappe.utils import date_diff, add_months, today, getdate, add_days, flt, get_last_day, cint, get_link_to_form from erpnext.accounts.utils import get_account_currency from frappe.email import sendmail_to_system_managers +from frappe.utils.background_jobs import enqueue def validate_service_stop_date(doc): ''' Validates service_stop_date for Purchase Invoice and Sales Invoice ''' @@ -32,8 +33,20 @@ def validate_service_stop_date(doc): if old_stop_dates and old_stop_dates.get(item.name) and item.service_stop_date!=old_stop_dates.get(item.name): frappe.throw(_("Cannot change Service Stop Date for item in row {0}").format(item.idx)) -def convert_deferred_expense_to_expense(start_date=None, end_date=None): +def build_conditions(process_type, account, company): + conditions='' + deferred_account = "item.deferred_revenue_account" if process_type=="Income" else "item.deferred_expense_account" + + if account: + conditions += "AND %s='%s'"%(deferred_account, account) + elif company: + conditions += "AND p.company='%s'"%(company) + + return conditions + +def convert_deferred_expense_to_expense(deferred_process, start_date=None, end_date=None, conditions=''): # book the expense/income on the last day, but it will be trigger on the 1st of month at 12:00 AM + if not start_date: start_date = add_months(today(), -1) if not end_date: @@ -41,18 +54,25 @@ def convert_deferred_expense_to_expense(start_date=None, end_date=None): # check for the purchase invoice for which GL entries has to be done invoices = frappe.db.sql_list(''' - select distinct parent from `tabPurchase Invoice Item` - where service_start_date<=%s and service_end_date>=%s - and enable_deferred_expense = 1 and docstatus = 1 and ifnull(amount, 0) > 0 - ''', (end_date, start_date)) + select distinct item.parent + from `tabPurchase Invoice Item` item, `tabPurchase Invoice` p + where item.service_start_date<=%s and item.service_end_date>=%s + and item.enable_deferred_expense = 1 and item.parent=p.name + and item.docstatus = 1 and ifnull(item.amount, 0) > 0 + {0} + '''.format(conditions), (end_date, start_date)) #nosec # For each invoice, book deferred expense for invoice in invoices: doc = frappe.get_doc("Purchase Invoice", invoice) - book_deferred_income_or_expense(doc, end_date) + book_deferred_income_or_expense(doc, deferred_process, end_date) -def convert_deferred_revenue_to_income(start_date=None, end_date=None): + if frappe.flags.deferred_accounting_error: + send_mail(deferred_process) + +def convert_deferred_revenue_to_income(deferred_process, start_date=None, end_date=None, conditions=''): # book the expense/income on the last day, but it will be trigger on the 1st of month at 12:00 AM + if not start_date: start_date = add_months(today(), -1) if not end_date: @@ -60,14 +80,20 @@ def convert_deferred_revenue_to_income(start_date=None, end_date=None): # check for the sales invoice for which GL entries has to be done invoices = frappe.db.sql_list(''' - select distinct parent from `tabSales Invoice Item` - where service_start_date<=%s and service_end_date>=%s - and enable_deferred_revenue = 1 and docstatus = 1 and ifnull(amount, 0) > 0 - ''', (end_date, start_date)) + select distinct item.parent + from `tabSales Invoice Item` item, `tabSales Invoice` p + where item.service_start_date<=%s and item.service_end_date>=%s + and item.enable_deferred_revenue = 1 and item.parent=p.name + and item.docstatus = 1 and ifnull(item.amount, 0) > 0 + {0} + '''.format(conditions), (end_date, start_date)) #nosec for invoice in invoices: doc = frappe.get_doc("Sales Invoice", invoice) - book_deferred_income_or_expense(doc, end_date) + book_deferred_income_or_expense(doc, deferred_process, end_date) + + if frappe.flags.deferred_accounting_error: + send_mail(deferred_process) def get_booking_dates(doc, item, posting_date=None): if not posting_date: @@ -136,7 +162,7 @@ def calculate_amount(doc, item, last_gl_entry, total_days, total_booking_days, a return amount, base_amount -def book_deferred_income_or_expense(doc, posting_date=None): +def book_deferred_income_or_expense(doc, deferred_process, posting_date=None): enable_check = "enable_deferred_revenue" \ if doc.doctype=="Sales Invoice" else "enable_deferred_expense" @@ -159,7 +185,11 @@ def book_deferred_income_or_expense(doc, posting_date=None): total_days, total_booking_days, account_currency) make_gl_entries(doc, credit_account, debit_account, against, - amount, base_amount, end_date, project, account_currency, item.cost_center, item.name) + amount, base_amount, end_date, project, account_currency, item.cost_center, item.name, deferred_process) + + # Returned in case of any errors because it tries to submit the same record again and again in case of errors + if frappe.flags.deferred_accounting_error: + return if getdate(end_date) < getdate(posting_date) and not last_gl_entry: _book_deferred_revenue_or_expense(item) @@ -169,8 +199,30 @@ def book_deferred_income_or_expense(doc, posting_date=None): if item.get(enable_check): _book_deferred_revenue_or_expense(item) +def process_deferred_accounting(posting_date=today()): + ''' Converts deferred income/expense into income/expense + Executed via background jobs on every month end ''' + + if not cint(frappe.db.get_singles_value('Accounts Settings', 'automatically_process_deferred_accounting_entry')): + return + + start_date = add_months(today(), -1) + end_date = add_days(today(), -1) + + for record_type in ('Income', 'Expense'): + doc = frappe.get_doc(dict( + doctype='Process Deferred Accounting', + posting_date=posting_date, + start_date=start_date, + end_date=end_date, + type=record_type + )) + + doc.insert() + doc.submit() + def make_gl_entries(doc, credit_account, debit_account, against, - amount, base_amount, posting_date, project, account_currency, cost_center, voucher_detail_no): + amount, base_amount, posting_date, project, account_currency, cost_center, voucher_detail_no, deferred_process=None): # GL Entry for crediting the amount in the deferred expense from erpnext.accounts.general_ledger import make_gl_entries @@ -186,7 +238,9 @@ def make_gl_entries(doc, credit_account, debit_account, against, "cost_center": cost_center, "voucher_detail_no": voucher_detail_no, 'posting_date': posting_date, - 'project': project + 'project': project, + 'against_voucher_type': 'Process Deferred Accounting', + 'against_voucher': deferred_process }, account_currency) ) # GL Entry to debit the amount from the expense @@ -199,7 +253,9 @@ def make_gl_entries(doc, credit_account, debit_account, against, "cost_center": cost_center, "voucher_detail_no": voucher_detail_no, 'posting_date': posting_date, - 'project': project + 'project': project, + 'against_voucher_type': 'Process Deferred Accounting', + 'against_voucher': deferred_process }, account_currency) ) @@ -209,7 +265,16 @@ def make_gl_entries(doc, credit_account, debit_account, against, frappe.db.commit() except: frappe.db.rollback() - title = _("Error while processing deferred accounting for {0}").format(doc.name) traceback = frappe.get_traceback() - frappe.log_error(message=traceback , title=title) - sendmail_to_system_managers(title, traceback) \ No newline at end of file + frappe.log_error(message=traceback) + + frappe.flags.deferred_accounting_error = True + +def send_mail(deferred_process): + title = _("Error while processing deferred accounting for {0}".format(deferred_process)) + content = _(""" + Deferred accounting failed for some invoices: + Please check Process Deferred Accounting {0} + and submit manually after resolving errors + """).format(get_link_to_form('Process Deferred Accounting', deferred_process)) + sendmail_to_system_managers(title, content) diff --git a/erpnext/accounts/desk_page/accounting/accounting.json b/erpnext/accounts/desk_page/accounting/accounting.json index 8a9a7c58d2..0d6aca65b1 100644 --- a/erpnext/accounts/desk_page/accounting/accounting.json +++ b/erpnext/accounts/desk_page/accounting/accounting.json @@ -8,7 +8,7 @@ { "hidden": 0, "label": "General Ledger", - "links": "[\n {\n \"description\": \"Accounting journal entries.\",\n \"label\": \"Journal Entry\",\n \"name\": \"Journal Entry\",\n \"type\": \"doctype\"\n },\n {\n \"dependencies\": [\n \"GL Entry\"\n ],\n \"doctype\": \"GL Entry\",\n \"is_query_report\": true,\n \"label\": \"General Ledger\",\n \"name\": \"General Ledger\",\n \"type\": \"report\"\n },\n {\n \"dependencies\": [\n \"Sales Invoice\"\n ],\n \"doctype\": \"Sales Invoice\",\n \"is_query_report\": true,\n \"label\": \"Customer Ledger Summary\",\n \"name\": \"Customer Ledger Summary\",\n \"type\": \"report\"\n },\n {\n \"dependencies\": [\n \"Sales Invoice\"\n ],\n \"doctype\": \"Sales Invoice\",\n \"is_query_report\": true,\n \"label\": \"Supplier Ledger Summary\",\n \"name\": \"Supplier Ledger Summary\",\n \"type\": \"report\"\n }\n]" + "links": "[\n {\n \"description\": \"Accounting journal entries.\",\n \"label\": \"Journal Entry\",\n \"name\": \"Journal Entry\",\n \"type\": \"doctype\"\n },\n {\n \"description\": \"Make journal entries from a template.\",\n \"label\": \"Journal Entry Template\",\n \"name\": \"Journal Entry Template\",\n \"type\": \"doctype\"\n },\n \n {\n \"dependencies\": [\n \"GL Entry\"\n ],\n \"doctype\": \"GL Entry\",\n \"is_query_report\": true,\n \"label\": \"General Ledger\",\n \"name\": \"General Ledger\",\n \"type\": \"report\"\n },\n {\n \"dependencies\": [\n \"Sales Invoice\"\n ],\n \"doctype\": \"Sales Invoice\",\n \"is_query_report\": true,\n \"label\": \"Customer Ledger Summary\",\n \"name\": \"Customer Ledger Summary\",\n \"type\": \"report\"\n },\n {\n \"dependencies\": [\n \"Sales Invoice\"\n ],\n \"doctype\": \"Sales Invoice\",\n \"is_query_report\": true,\n \"label\": \"Supplier Ledger Summary\",\n \"name\": \"Supplier Ledger Summary\",\n \"type\": \"report\"\n }\n]" }, { "hidden": 0, @@ -103,7 +103,7 @@ "idx": 0, "is_standard": 1, "label": "Accounting", - "modified": "2020-04-01 11:28:50.925719", + "modified": "2020-04-29 12:17:34.844397", "modified_by": "Administrator", "module": "Accounts", "name": "Accounting", diff --git a/erpnext/accounts/doctype/accounts_settings/accounts_settings.json b/erpnext/accounts/doctype/accounts_settings/accounts_settings.json index 4ff4212920..353ff77223 100644 --- a/erpnext/accounts/doctype/accounts_settings/accounts_settings.json +++ b/erpnext/accounts/doctype/accounts_settings/accounts_settings.json @@ -1,210 +1,226 @@ { - "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 + "actions": [], + "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", + "automatically_process_deferred_accounting_entry", + "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": "0", + "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" + }, + { + "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 (%)" + }, + { + "default": "1", + "fieldname": "automatically_process_deferred_accounting_entry", + "fieldtype": "Check", + "label": "Automatically Process Deferred Accounting Entry" } + ], + "icon": "icon-cog", + "idx": 1, + "issingle": 1, + "links": [], + "modified": "2019-12-19 16:58:17.395595", + "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/gl_entry/gl_entry.json b/erpnext/accounts/doctype/gl_entry/gl_entry.json index 0d75329039..e6d97a1fb2 100644 --- a/erpnext/accounts/doctype/gl_entry/gl_entry.json +++ b/erpnext/accounts/doctype/gl_entry/gl_entry.json @@ -1,4 +1,5 @@ { + "actions": [], "autoname": "ACC-GLE-.YYYY.-.#####", "creation": "2013-01-10 16:34:06", "doctype": "DocType", diff --git a/erpnext/accounts/doctype/journal_entry/journal_entry.js b/erpnext/accounts/doctype/journal_entry/journal_entry.js index 3604b60b75..9a832e3c1f 100644 --- a/erpnext/accounts/doctype/journal_entry/journal_entry.js +++ b/erpnext/accounts/doctype/journal_entry/journal_entry.js @@ -12,7 +12,6 @@ frappe.ui.form.on("Journal Entry", { refresh: function(frm) { erpnext.toggle_naming_series(); - frm.cscript.voucher_type(frm.doc); if(frm.doc.docstatus==1) { frm.add_custom_button(__('Ledger'), function() { @@ -120,9 +119,78 @@ frappe.ui.form.on("Journal Entry", { } } }); + }, + + voucher_type: function(frm){ + + if(!frm.doc.company) return null; + + if((!(frm.doc.accounts || []).length) || ((frm.doc.accounts || []).length === 1 && !frm.doc.accounts[0].account)) { + if(in_list(["Bank Entry", "Cash Entry"], frm.doc.voucher_type)) { + return frappe.call({ + type: "GET", + method: "erpnext.accounts.doctype.journal_entry.journal_entry.get_default_bank_cash_account", + args: { + "account_type": (frm.doc.voucher_type=="Bank Entry" ? + "Bank" : (frm.doc.voucher_type=="Cash Entry" ? "Cash" : null)), + "company": frm.doc.company + }, + callback: function(r) { + if(r.message) { + // If default company bank account not set + if(!$.isEmptyObject(r.message)){ + update_jv_details(frm.doc, [r.message]); + } + } + } + }); + } + else if(frm.doc.voucher_type=="Opening Entry") { + return frappe.call({ + type:"GET", + method: "erpnext.accounts.doctype.journal_entry.journal_entry.get_opening_accounts", + args: { + "company": frm.doc.company + }, + callback: function(r) { + frappe.model.clear_table(frm.doc, "accounts"); + if(r.message) { + update_jv_details(frm.doc, r.message); + } + cur_frm.set_value("is_opening", "Yes"); + } + }); + } + } + }, + + from_template: function(frm){ + if (frm.doc.from_template){ + frappe.db.get_doc("Journal Entry Template", frm.doc.from_template) + .then((doc) => { + frappe.model.clear_table(frm.doc, "accounts"); + frm.set_value({ + "company": doc.company, + "voucher_type": doc.voucher_type, + "naming_series": doc.naming_series, + "is_opening": doc.is_opening, + "multi_currency": doc.multi_currency + }) + update_jv_details(frm.doc, doc.accounts); + }); + } } }); +var update_jv_details = function(doc, r) { + $.each(r, function(i, d) { + var row = frappe.model.add_child(doc, "Journal Entry Account", "accounts"); + row.account = d.account; + row.balance = d.balance; + }); + refresh_field("accounts"); +} + erpnext.accounts.JournalEntry = frappe.ui.form.Controller.extend({ onload: function() { this.load_defaults(); @@ -375,56 +443,6 @@ cur_frm.cscript.select_print_heading = function(doc,cdt,cdn){ cur_frm.pformat.print_heading = __("Journal Entry"); } -cur_frm.cscript.voucher_type = function(doc, cdt, cdn) { - cur_frm.set_df_property("cheque_no", "reqd", doc.voucher_type=="Bank Entry"); - cur_frm.set_df_property("cheque_date", "reqd", doc.voucher_type=="Bank Entry"); - - if(!doc.company) return; - - var update_jv_details = function(doc, r) { - $.each(r, function(i, d) { - var row = frappe.model.add_child(doc, "Journal Entry Account", "accounts"); - row.account = d.account; - row.balance = d.balance; - }); - refresh_field("accounts"); - } - - if((!(doc.accounts || []).length) || ((doc.accounts || []).length==1 && !doc.accounts[0].account)) { - if(in_list(["Bank Entry", "Cash Entry"], doc.voucher_type)) { - return frappe.call({ - type: "GET", - method: "erpnext.accounts.doctype.journal_entry.journal_entry.get_default_bank_cash_account", - args: { - "account_type": (doc.voucher_type=="Bank Entry" ? - "Bank" : (doc.voucher_type=="Cash Entry" ? "Cash" : null)), - "company": doc.company - }, - callback: function(r) { - if(r.message) { - update_jv_details(doc, [r.message]); - } - } - }) - } else if(doc.voucher_type=="Opening Entry") { - return frappe.call({ - type:"GET", - method: "erpnext.accounts.doctype.journal_entry.journal_entry.get_opening_accounts", - args: { - "company": doc.company - }, - callback: function(r) { - frappe.model.clear_table(doc, "accounts"); - if(r.message) { - update_jv_details(doc, r.message); - } - cur_frm.set_value("is_opening", "Yes") - } - }) - } - } -} - frappe.ui.form.on("Journal Entry Account", { party: function(frm, cdt, cdn) { var d = frappe.get_doc(cdt, cdn); diff --git a/erpnext/accounts/doctype/journal_entry/journal_entry.json b/erpnext/accounts/doctype/journal_entry/journal_entry.json index f5991241a7..9d5063929f 100644 --- a/erpnext/accounts/doctype/journal_entry/journal_entry.json +++ b/erpnext/accounts/doctype/journal_entry/journal_entry.json @@ -1,4 +1,5 @@ { + "actions": [], "allow_import": 1, "autoname": "naming_series:", "creation": "2013-03-25 10:53:52", @@ -10,10 +11,11 @@ "title", "voucher_type", "naming_series", - "column_break1", - "posting_date", - "company", "finance_book", + "column_break1", + "from_template", + "company", + "posting_date", "2_add_edit_gl_entries", "accounts", "section_break99", @@ -157,6 +159,7 @@ "in_global_search": 1, "in_list_view": 1, "label": "Reference Number", + "mandatory_depends_on": "eval:doc.voucher_type == \"Bank Entry\"", "no_copy": 1, "oldfieldname": "cheque_no", "oldfieldtype": "Data", @@ -166,6 +169,7 @@ "fieldname": "cheque_date", "fieldtype": "Date", "label": "Reference Date", + "mandatory_depends_on": "eval:doc.voucher_type == \"Bank Entry\"", "no_copy": 1, "oldfieldname": "cheque_date", "oldfieldtype": "Date", @@ -484,12 +488,22 @@ "options": "Journal Entry", "print_hide": 1, "read_only": 1 + }, + { + "fieldname": "from_template", + "fieldtype": "Link", + "label": "From Template", + "no_copy": 1, + "options": "Journal Entry Template", + "print_hide": 1, + "report_hide": 1 } ], "icon": "fa fa-file-text", "idx": 176, "is_submittable": 1, - "modified": "2020-01-16 13:05:30.634226", + "links": [], + "modified": "2020-04-29 10:55:28.240916", "modified_by": "Administrator", "module": "Accounts", "name": "Journal Entry", diff --git a/erpnext/accounts/doctype/journal_entry_account/journal_entry_account.json b/erpnext/accounts/doctype/journal_entry_account/journal_entry_account.json index 9552e60a85..26c84a6398 100644 --- a/erpnext/accounts/doctype/journal_entry_account/journal_entry_account.json +++ b/erpnext/accounts/doctype/journal_entry_account/journal_entry_account.json @@ -1,4 +1,5 @@ { + "actions": [], "autoname": "hash", "creation": "2013-02-22 01:27:39", "doctype": "DocType", @@ -271,7 +272,8 @@ ], "idx": 1, "istable": 1, - "modified": "2020-01-13 12:41:33.968025", + "links": [], + "modified": "2020-04-25 01:47:49.060128", "modified_by": "Administrator", "module": "Accounts", "name": "Journal Entry Account", @@ -280,4 +282,4 @@ "sort_field": "modified", "sort_order": "DESC", "track_changes": 1 -} +} \ No newline at end of file diff --git a/erpnext/accounts/doctype/journal_entry_template/__init__.py b/erpnext/accounts/doctype/journal_entry_template/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/erpnext/accounts/doctype/journal_entry_template/journal_entry_template.js b/erpnext/accounts/doctype/journal_entry_template/journal_entry_template.js new file mode 100644 index 0000000000..cbb9fc4b0f --- /dev/null +++ b/erpnext/accounts/doctype/journal_entry_template/journal_entry_template.js @@ -0,0 +1,91 @@ +// Copyright (c) 2020, Frappe Technologies Pvt. Ltd. and contributors +// For license information, please see license.txt + +frappe.ui.form.on("Journal Entry Template", { + setup: function(frm) { + frappe.model.set_default_values(frm.doc); + + frm.set_query("account" ,"accounts", function(){ + var filters = { + company: frm.doc.company, + is_group: 0 + }; + + if(!frm.doc.multi_currency) { + $.extend(filters, { + account_currency: frappe.get_doc(":Company", frm.doc.company).default_currency + }); + } + + return { filters: filters }; + }); + + frappe.call({ + type: "GET", + method: "erpnext.accounts.doctype.journal_entry_template.journal_entry_template.get_naming_series", + callback: function(r){ + if(r.message){ + frm.set_df_property("naming_series", "options", r.message.split("\n")); + frm.set_value("naming_series", r.message.split("\n")[0]); + frm.refresh_field("naming_series"); + } + } + }); + }, + voucher_type: function(frm) { + var add_accounts = function(doc, r) { + $.each(r, function(i, d) { + var row = frappe.model.add_child(doc, "Journal Entry Template Account", "accounts"); + row.account = d.account; + }); + refresh_field("accounts"); + }; + + if(!frm.doc.company) return; + + frm.trigger("clear_child"); + switch(frm.doc.voucher_type){ + case "Opening Entry": + frm.set_value("is_opening", "Yes"); + frappe.call({ + type:"GET", + method: "erpnext.accounts.doctype.journal_entry.journal_entry.get_opening_accounts", + args: { + "company": frm.doc.company + }, + callback: function(r) { + if(r.message) { + add_accounts(frm.doc, r.message); + } + } + }); + break; + case "Bank Entry": + case "Cash Entry": + frappe.call({ + type: "GET", + method: "erpnext.accounts.doctype.journal_entry.journal_entry.get_default_bank_cash_account", + args: { + "account_type": (frm.doc.voucher_type=="Bank Entry" ? + "Bank" : (frm.doc.voucher_type=="Cash Entry" ? "Cash" : null)), + "company": frm.doc.company + }, + callback: function(r) { + if(r.message) { + // If default company bank account not set + if(!$.isEmptyObject(r.message)){ + add_accounts(frm.doc, [r.message]); + } + } + } + }); + break; + default: + frm.trigger("clear_child"); + } + }, + clear_child: function(frm){ + frappe.model.clear_table(frm.doc, "accounts"); + frm.refresh_field("accounts"); + } +}); \ No newline at end of file diff --git a/erpnext/accounts/doctype/journal_entry_template/journal_entry_template.json b/erpnext/accounts/doctype/journal_entry_template/journal_entry_template.json new file mode 100644 index 0000000000..660ae85cef --- /dev/null +++ b/erpnext/accounts/doctype/journal_entry_template/journal_entry_template.json @@ -0,0 +1,134 @@ +{ + "actions": [], + "autoname": "field:template_title", + "creation": "2020-04-09 01:32:51.332301", + "doctype": "DocType", + "document_type": "Document", + "editable_grid": 1, + "engine": "InnoDB", + "field_order": [ + "section_break_1", + "template_title", + "voucher_type", + "naming_series", + "column_break_3", + "company", + "is_opening", + "multi_currency", + "section_break_3", + "accounts" + ], + "fields": [ + { + "fieldname": "section_break_1", + "fieldtype": "Section Break" + }, + { + "fieldname": "voucher_type", + "fieldtype": "Select", + "in_list_view": 1, + "label": "Journal Entry Type", + "options": "Journal Entry\nInter Company Journal Entry\nBank Entry\nCash Entry\nCredit Card Entry\nDebit Note\nCredit Note\nContra Entry\nExcise Entry\nWrite Off Entry\nOpening Entry\nDepreciation Entry\nExchange Rate Revaluation", + "reqd": 1 + }, + { + "fieldname": "company", + "fieldtype": "Link", + "in_list_view": 1, + "in_standard_filter": 1, + "label": "Company", + "options": "Company", + "remember_last_selected_value": 1, + "reqd": 1 + }, + { + "fieldname": "column_break_3", + "fieldtype": "Column Break" + }, + { + "default": "No", + "fieldname": "is_opening", + "fieldtype": "Select", + "label": "Is Opening", + "options": "No\nYes" + }, + { + "fieldname": "accounts", + "fieldtype": "Table", + "label": "Accounting Entries", + "options": "Journal Entry Template Account" + }, + { + "fieldname": "naming_series", + "fieldtype": "Select", + "label": "Series", + "no_copy": 1, + "print_hide": 1, + "reqd": 1, + "set_only_once": 1 + }, + { + "fieldname": "template_title", + "fieldtype": "Data", + "label": "Template Title", + "reqd": 1, + "unique": 1 + }, + { + "fieldname": "section_break_3", + "fieldtype": "Section Break" + }, + { + "default": "0", + "fieldname": "multi_currency", + "fieldtype": "Check", + "label": "Multi Currency" + } + ], + "links": [], + "modified": "2020-05-01 18:32:01.420488", + "modified_by": "Administrator", + "module": "Accounts", + "name": "Journal Entry Template", + "owner": "Administrator", + "permissions": [ + { + "create": 1, + "delete": 1, + "email": 1, + "export": 1, + "print": 1, + "read": 1, + "report": 1, + "role": "Accounts User", + "share": 1, + "write": 1 + }, + { + "create": 1, + "delete": 1, + "email": 1, + "export": 1, + "print": 1, + "read": 1, + "report": 1, + "role": "Accounts Manager", + "share": 1, + "write": 1 + }, + { + "email": 1, + "export": 1, + "print": 1, + "read": 1, + "report": 1, + "role": "Auditor", + "share": 1 + } + ], + "search_fields": "voucher_type, company", + "sort_field": "modified", + "sort_order": "DESC", + "title_field": "template_title", + "track_changes": 1 +} \ No newline at end of file diff --git a/erpnext/accounts/doctype/journal_entry_template/journal_entry_template.py b/erpnext/accounts/doctype/journal_entry_template/journal_entry_template.py new file mode 100644 index 0000000000..e0b9cbc919 --- /dev/null +++ b/erpnext/accounts/doctype/journal_entry_template/journal_entry_template.py @@ -0,0 +1,14 @@ +# -*- 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 JournalEntryTemplate(Document): + pass + +@frappe.whitelist() +def get_naming_series(): + return frappe.get_meta("Journal Entry").get_field("naming_series").options diff --git a/erpnext/accounts/doctype/journal_entry_template/test_journal_entry_template.py b/erpnext/accounts/doctype/journal_entry_template/test_journal_entry_template.py new file mode 100644 index 0000000000..5f74a2042f --- /dev/null +++ b/erpnext/accounts/doctype/journal_entry_template/test_journal_entry_template.py @@ -0,0 +1,10 @@ +# -*- coding: utf-8 -*- +# Copyright (c) 2020, Frappe Technologies Pvt. Ltd. and Contributors +# See license.txt +from __future__ import unicode_literals + +# import frappe +import unittest + +class TestJournalEntryTemplate(unittest.TestCase): + pass diff --git a/erpnext/accounts/doctype/journal_entry_template_account/__init__.py b/erpnext/accounts/doctype/journal_entry_template_account/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/erpnext/accounts/doctype/journal_entry_template_account/journal_entry_template_account.json b/erpnext/accounts/doctype/journal_entry_template_account/journal_entry_template_account.json new file mode 100644 index 0000000000..eecd87727d --- /dev/null +++ b/erpnext/accounts/doctype/journal_entry_template_account/journal_entry_template_account.json @@ -0,0 +1,31 @@ +{ + "actions": [], + "creation": "2020-04-09 01:48:42.783620", + "doctype": "DocType", + "editable_grid": 1, + "engine": "InnoDB", + "field_order": [ + "account" + ], + "fields": [ + { + "fieldname": "account", + "fieldtype": "Link", + "in_list_view": 1, + "label": "Account", + "options": "Account", + "reqd": 1 + } + ], + "istable": 1, + "links": [], + "modified": "2020-04-25 01:15:44.879839", + "modified_by": "Administrator", + "module": "Accounts", + "name": "Journal Entry Template Account", + "owner": "Administrator", + "permissions": [], + "sort_field": "modified", + "sort_order": "DESC", + "track_changes": 1 +} \ No newline at end of file diff --git a/erpnext/accounts/doctype/journal_entry_template_account/journal_entry_template_account.py b/erpnext/accounts/doctype/journal_entry_template_account/journal_entry_template_account.py new file mode 100644 index 0000000000..48e6abbc28 --- /dev/null +++ b/erpnext/accounts/doctype/journal_entry_template_account/journal_entry_template_account.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 JournalEntryTemplateAccount(Document): + pass diff --git a/erpnext/accounts/doctype/period_closing_voucher/period_closing_voucher.py b/erpnext/accounts/doctype/period_closing_voucher/period_closing_voucher.py index 0bd9a90b3e..bd8d8bd86d 100644 --- a/erpnext/accounts/doctype/period_closing_voucher/period_closing_voucher.py +++ b/erpnext/accounts/doctype/period_closing_voucher/period_closing_voucher.py @@ -19,7 +19,7 @@ class PeriodClosingVoucher(AccountsController): def on_cancel(self): self.ignore_linked_doctypes = ('GL Entry', 'Stock Ledger Entry') from erpnext.accounts.general_ledger import make_reverse_gl_entries - make_reverse_gl_entries(voucher_type="Period Closing Voucher", voucher_no=self.name, cancel=True) + make_reverse_gl_entries(voucher_type="Period Closing Voucher", voucher_no=self.name) def validate_account_head(self): closing_account_type = frappe.db.get_value("Account", self.closing_account_head, "root_type") diff --git a/erpnext/accounts/doctype/process_deferred_accounting/__init__.py b/erpnext/accounts/doctype/process_deferred_accounting/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/erpnext/accounts/doctype/process_deferred_accounting/process_deferred_accounting.js b/erpnext/accounts/doctype/process_deferred_accounting/process_deferred_accounting.js new file mode 100644 index 0000000000..975c60cf91 --- /dev/null +++ b/erpnext/accounts/doctype/process_deferred_accounting/process_deferred_accounting.js @@ -0,0 +1,39 @@ +// Copyright (c) 2019, Frappe Technologies Pvt. Ltd. and contributors +// For license information, please see license.txt + +frappe.ui.form.on('Process Deferred Accounting', { + setup: function(frm) { + frm.set_query("document_type", function() { + return { + filters: { + 'name': ['in', ['Sales Invoice', 'Purchase Invoice']] + } + }; + }); + }, + + validate: function() { + return new Promise((resolve) => { + return frappe.db.get_single_value('Accounts Settings', 'automatically_process_deferred_accounting_entry') + .then(value => { + if(value) { + frappe.throw(__('Manual entry cannot be created! Disable automatic entry for deferred accounting in accounts settings and try again')); + } + resolve(value); + }); + }); + }, + + end_date: function(frm) { + if (frm.doc.end_date && frm.doc.end_date < frm.doc.start_date) { + frappe.throw(__("End date cannot be before start date")); + } + }, + + onload: function(frm) { + if (frm.doc.posting_date && frm.doc.docstatus === 0) { + frm.set_value('start_date', frappe.datetime.add_months(frm.doc.posting_date, -1)); + frm.set_value('end_date', frm.doc.posting_date); + } + } +}); diff --git a/erpnext/accounts/doctype/process_deferred_accounting/process_deferred_accounting.json b/erpnext/accounts/doctype/process_deferred_accounting/process_deferred_accounting.json new file mode 100644 index 0000000000..4daafef3ec --- /dev/null +++ b/erpnext/accounts/doctype/process_deferred_accounting/process_deferred_accounting.json @@ -0,0 +1,128 @@ +{ + "actions": [], + "autoname": "ACC-PDA-.#####", + "creation": "2019-11-04 18:01:23.454775", + "doctype": "DocType", + "editable_grid": 1, + "engine": "InnoDB", + "field_order": [ + "company", + "type", + "account", + "column_break_3", + "posting_date", + "start_date", + "end_date", + "amended_from" + ], + "fields": [ + { + "fieldname": "type", + "fieldtype": "Select", + "in_list_view": 1, + "label": "Type", + "options": "\nIncome\nExpense", + "reqd": 1 + }, + { + "fieldname": "amended_from", + "fieldtype": "Link", + "label": "Amended From", + "no_copy": 1, + "options": "Process Deferred Accounting", + "print_hide": 1, + "read_only": 1 + }, + { + "fieldname": "start_date", + "fieldtype": "Date", + "in_list_view": 1, + "label": "Service Start Date", + "reqd": 1 + }, + { + "fieldname": "end_date", + "fieldtype": "Date", + "in_list_view": 1, + "label": "Service End Date", + "reqd": 1 + }, + { + "fieldname": "column_break_3", + "fieldtype": "Column Break" + }, + { + "default": "Today", + "fieldname": "posting_date", + "fieldtype": "Date", + "in_list_view": 1, + "label": "Posting Date", + "reqd": 1 + }, + { + "fieldname": "account", + "fieldtype": "Link", + "label": "Account", + "options": "Account" + }, + { + "fieldname": "company", + "fieldtype": "Link", + "label": "Company", + "options": "Company", + "reqd": 1 + } + ], + "is_submittable": 1, + "links": [], + "modified": "2020-02-06 18:18:09.852844", + "modified_by": "Administrator", + "module": "Accounts", + "name": "Process Deferred Accounting", + "owner": "Administrator", + "permissions": [ + { + "cancel": 1, + "create": 1, + "delete": 1, + "email": 1, + "export": 1, + "print": 1, + "read": 1, + "report": 1, + "role": "System Manager", + "share": 1, + "submit": 1, + "write": 1 + }, + { + "cancel": 1, + "create": 1, + "delete": 1, + "email": 1, + "export": 1, + "print": 1, + "read": 1, + "report": 1, + "role": "Accounts Manager", + "share": 1, + "submit": 1, + "write": 1 + }, + { + "create": 1, + "delete": 1, + "email": 1, + "export": 1, + "print": 1, + "read": 1, + "report": 1, + "role": "Accounts User", + "share": 1, + "submit": 1, + "write": 1 + } + ], + "sort_field": "modified", + "sort_order": "DESC" +} \ No newline at end of file diff --git a/erpnext/accounts/doctype/process_deferred_accounting/process_deferred_accounting.py b/erpnext/accounts/doctype/process_deferred_accounting/process_deferred_accounting.py new file mode 100644 index 0000000000..0eac73236e --- /dev/null +++ b/erpnext/accounts/doctype/process_deferred_accounting/process_deferred_accounting.py @@ -0,0 +1,34 @@ +# -*- coding: utf-8 -*- +# Copyright (c) 2019, Frappe Technologies Pvt. Ltd. and contributors +# For license information, please see license.txt + +from __future__ import unicode_literals +import frappe +import erpnext +from frappe import _ +from frappe.model.document import Document +from erpnext.accounts.general_ledger import make_reverse_gl_entries +from erpnext.accounts.deferred_revenue import convert_deferred_expense_to_expense, \ + convert_deferred_revenue_to_income, build_conditions + +class ProcessDeferredAccounting(Document): + def validate(self): + if self.end_date < self.start_date: + frappe.throw(_("End date cannot be before start date")) + + def on_submit(self): + conditions = build_conditions(self.type, self.account, self.company) + if self.type == 'Income': + convert_deferred_revenue_to_income(self.name, self.start_date, self.end_date, conditions) + else: + convert_deferred_expense_to_expense(self.name, self.start_date, self.end_date, conditions) + + def on_cancel(self): + self.ignore_linked_doctypes = ['GL Entry'] + gl_entries = frappe.get_all('GL Entry', fields = ['*'], + filters={ + 'against_voucher_type': self.doctype, + 'against_voucher': self.name + }) + + make_reverse_gl_entries(gl_entries=gl_entries) \ No newline at end of file diff --git a/erpnext/accounts/doctype/process_deferred_accounting/test_process_deferred_accounting.py b/erpnext/accounts/doctype/process_deferred_accounting/test_process_deferred_accounting.py new file mode 100644 index 0000000000..31356c6e8b --- /dev/null +++ b/erpnext/accounts/doctype/process_deferred_accounting/test_process_deferred_accounting.py @@ -0,0 +1,48 @@ +# -*- coding: utf-8 -*- +# Copyright (c) 2019, Frappe Technologies Pvt. Ltd. and Contributors +# See license.txt +from __future__ import unicode_literals + +import frappe +import unittest +from erpnext.accounts.doctype.account.test_account import create_account +from erpnext.stock.doctype.item.test_item import create_item +from erpnext.accounts.doctype.sales_invoice.test_sales_invoice import create_sales_invoice, check_gl_entries + +class TestProcessDeferredAccounting(unittest.TestCase): + def test_creation_of_ledger_entry_on_submit(self): + ''' test creation of gl entries on submission of document ''' + deferred_account = create_account(account_name="Deferred Revenue", + parent_account="Current Liabilities - _TC", company="_Test Company") + + item = create_item("_Test Item for Deferred Accounting") + item.enable_deferred_revenue = 1 + item.deferred_revenue_account = deferred_account + item.no_of_months = 12 + item.save() + + si = create_sales_invoice(item=item.name, posting_date="2019-01-10", do_not_submit=True) + si.items[0].enable_deferred_revenue = 1 + si.items[0].service_start_date = "2019-01-10" + si.items[0].service_end_date = "2019-03-15" + si.items[0].deferred_revenue_account = deferred_account + si.save() + si.submit() + + process_deferred_accounting = doc = frappe.get_doc(dict( + doctype='Process Deferred Accounting', + posting_date="2019-01-01", + start_date="2019-01-01", + end_date="2019-01-31", + type="Income" + )) + + process_deferred_accounting.insert() + process_deferred_accounting.submit() + + expected_gle = [ + [deferred_account, 33.85, 0.0, "2019-01-31"], + ["Sales - _TC", 0.0, 33.85, "2019-01-31"] + ] + + check_gl_entries(self, si.name, expected_gle, "2019-01-10") \ No newline at end of file diff --git a/erpnext/accounts/doctype/sales_invoice/test_sales_invoice.py b/erpnext/accounts/doctype/sales_invoice/test_sales_invoice.py index dd727a49b0..c82a249843 100644 --- a/erpnext/accounts/doctype/sales_invoice/test_sales_invoice.py +++ b/erpnext/accounts/doctype/sales_invoice/test_sales_invoice.py @@ -5,7 +5,7 @@ from __future__ import unicode_literals import frappe import unittest, copy, time -from frappe.utils import nowdate, flt, getdate, cint, add_days +from frappe.utils import nowdate, flt, getdate, cint, add_days, add_months from frappe.model.dynamic_links import get_dynamic_link_map from erpnext.stock.doctype.stock_entry.test_stock_entry import make_stock_entry, get_qty_after_transaction from erpnext.accounts.doctype.purchase_invoice.test_purchase_invoice import unlink_payment_on_cancel_of_invoice @@ -1721,37 +1721,76 @@ class TestSalesInvoice(unittest.TestCase): si.submit() from erpnext.accounts.deferred_revenue import convert_deferred_revenue_to_income - convert_deferred_revenue_to_income(start_date="2019-01-01", end_date="2019-01-31") + + pda1 = frappe.get_doc(dict( + doctype='Process Deferred Accounting', + posting_date=nowdate(), + start_date="2019-01-01", + end_date="2019-03-31", + type="Income", + company="_Test Company" + )) + + pda1.insert() + pda1.submit() expected_gle = [ [deferred_account, 33.85, 0.0, "2019-01-31"], - ["Sales - _TC", 0.0, 33.85, "2019-01-31"] - ] - - self.check_gl_entries(si.name, expected_gle, "2019-01-10") - - convert_deferred_revenue_to_income(start_date="2019-01-01", end_date="2019-03-31") - - expected_gle = [ + ["Sales - _TC", 0.0, 33.85, "2019-01-31"], [deferred_account, 43.08, 0.0, "2019-02-28"], ["Sales - _TC", 0.0, 43.08, "2019-02-28"], [deferred_account, 23.07, 0.0, "2019-03-15"], ["Sales - _TC", 0.0, 23.07, "2019-03-15"] ] - self.check_gl_entries(si.name, expected_gle, "2019-01-31") + check_gl_entries(self, si.name, expected_gle, "2019-01-30") - def check_gl_entries(self, voucher_no, expected_gle, posting_date): - gl_entries = frappe.db.sql("""select account, debit, credit, posting_date - from `tabGL Entry` - where voucher_type='Sales Invoice' and voucher_no=%s and posting_date > %s - order by posting_date asc, account asc""", (voucher_no, posting_date), as_dict=1) + def test_deferred_error_email(self): + deferred_account = create_account(account_name="Deferred Revenue", + parent_account="Current Liabilities - _TC", company="_Test Company") - for i, gle in enumerate(gl_entries): - self.assertEqual(expected_gle[i][0], gle.account) - self.assertEqual(expected_gle[i][1], gle.debit) - self.assertEqual(expected_gle[i][2], gle.credit) - self.assertEqual(getdate(expected_gle[i][3]), gle.posting_date) + item = create_item("_Test Item for Deferred Accounting") + item.enable_deferred_revenue = 1 + item.deferred_revenue_account = deferred_account + item.no_of_months = 12 + item.save() + + si = create_sales_invoice(item=item.name, posting_date="2019-01-10", do_not_submit=True) + si.items[0].enable_deferred_revenue = 1 + si.items[0].service_start_date = "2019-01-10" + si.items[0].service_end_date = "2019-03-15" + si.items[0].deferred_revenue_account = deferred_account + si.save() + si.submit() + + from erpnext.accounts.deferred_revenue import convert_deferred_revenue_to_income + + acc_settings = frappe.get_doc('Accounts Settings', 'Accounts Settings') + acc_settings.acc_frozen_upto = '2019-01-31' + acc_settings.save() + + pda = frappe.get_doc(dict( + doctype='Process Deferred Accounting', + posting_date=nowdate(), + start_date="2019-01-01", + end_date="2019-03-31", + type="Income", + company="_Test Company" + )) + + pda.insert() + pda.submit() + + email = frappe.db.sql(""" select name from `tabEmail Queue` + where message like %(txt)s """, { + 'txt': "%%%s%%" % "Error while processing deferred accounting for {0}".format(pda.name) + }) + + self.assertTrue(email) + + acc_settings.load_from_db() + acc_settings.acc_frozen_upto = None + acc_settings.save() def test_inter_company_transaction(self): @@ -1912,6 +1951,18 @@ class TestSalesInvoice(unittest.TestCase): self.assertEqual(data['billLists'][0]['vehicleNo'], 'KA12KA1234') self.assertEqual(data['billLists'][0]['itemList'][0]['taxableAmount'], 60000) +def check_gl_entries(doc, voucher_no, expected_gle, posting_date): + gl_entries = frappe.db.sql("""select account, debit, credit, posting_date + from `tabGL Entry` + where voucher_type='Sales Invoice' and voucher_no=%s and posting_date > %s + order by posting_date asc, account asc""", (voucher_no, posting_date), as_dict=1) + + for i, gle in enumerate(gl_entries): + doc.assertEqual(expected_gle[i][0], gle.account) + doc.assertEqual(expected_gle[i][1], gle.debit) + doc.assertEqual(expected_gle[i][2], gle.credit) + doc.assertEqual(getdate(expected_gle[i][3]), gle.posting_date) + def test_item_tax_validity(self): item = frappe.get_doc("Item", "_Test Item 2") diff --git a/erpnext/accounts/party.py b/erpnext/accounts/party.py index 47dfa09c61..6e5b33f07d 100644 --- a/erpnext/accounts/party.py +++ b/erpnext/accounts/party.py @@ -600,10 +600,12 @@ def get_party_shipping_address(doctype, name): else: return '' -def get_partywise_advanced_payment_amount(party_type, posting_date = None): +def get_partywise_advanced_payment_amount(party_type, posting_date = None, company=None): cond = "1=1" if posting_date: cond = "posting_date <= '{0}'".format(posting_date) + if company: + cond += "and company = '{0}'".format(company) data = frappe.db.sql(""" SELECT party, sum({0}) as amount FROM `tabGL Entry` diff --git a/erpnext/accounts/report/accounts_receivable_summary/accounts_receivable_summary.py b/erpnext/accounts/report/accounts_receivable_summary/accounts_receivable_summary.py index b607c0f702..aa6b42e89d 100644 --- a/erpnext/accounts/report/accounts_receivable_summary/accounts_receivable_summary.py +++ b/erpnext/accounts/report/accounts_receivable_summary/accounts_receivable_summary.py @@ -33,7 +33,7 @@ class AccountsReceivableSummary(ReceivablePayableReport): self.get_party_total(args) party_advance_amount = get_partywise_advanced_payment_amount(self.party_type, - self.filters.report_date) or {} + self.filters.report_date, self.filters.company) or {} for party, party_dict in iteritems(self.party_total): if party_dict.outstanding == 0: diff --git a/erpnext/config/accounts.py b/erpnext/config/accounts.py index b9b0da48c9..839c4ad84a 100644 --- a/erpnext/config/accounts.py +++ b/erpnext/config/accounts.py @@ -245,6 +245,10 @@ def get_data(): "name": "Supplier Ledger Summary", "doctype": "Sales Invoice", "is_query_report": True, + }, + { + "type": "doctype", + "name": "Process Deferred Accounting" } ] }, diff --git a/erpnext/controllers/buying_controller.py b/erpnext/controllers/buying_controller.py index 0e72ec2853..608e537e1b 100644 --- a/erpnext/controllers/buying_controller.py +++ b/erpnext/controllers/buying_controller.py @@ -101,7 +101,7 @@ class BuyingController(StockController): for d in tax_for_valuation: d.category = 'Total' msgprint(_('Tax Category has been changed to "Total" because all the Items are non-stock items')) - + def validate_asset_return(self): if self.doctype not in ['Purchase Receipt', 'Purchase Invoice'] or not self.is_return: return @@ -691,10 +691,10 @@ class BuyingController(StockController): for qty in range(cint(d.qty)): asset = self.make_asset(d) created_assets.append(asset) - + if len(created_assets) > 5: # dont show asset form links if more than 5 assets are created - messages.append(_('{} Asset{} created for {}').format(len(created_assets), is_plural, frappe.bold(d.item_code))) + messages.append(_('{} Assets created for {}').format(len(created_assets), frappe.bold(d.item_code))) else: assets_link = list(map(lambda d: frappe.utils.get_link_to_form('Asset', d), created_assets)) assets_link = frappe.bold(','.join(assets_link)) diff --git a/erpnext/education/doctype/fees/fees.py b/erpnext/education/doctype/fees/fees.py index 01f7b87249..f0d60faed6 100644 --- a/erpnext/education/doctype/fees/fees.py +++ b/erpnext/education/doctype/fees/fees.py @@ -82,7 +82,7 @@ class Fees(AccountsController): def on_cancel(self): self.ignore_linked_doctypes = ('GL Entry', 'Stock Ledger Entry') - make_reverse_gl_entries(voucher_type=self.doctype, voucher_no=self.name, cancel=True) + make_reverse_gl_entries(voucher_type=self.doctype, voucher_no=self.name) # frappe.db.set(self, 'status', 'Cancelled') diff --git a/erpnext/hr/doctype/leave_application/leave_application.json b/erpnext/hr/doctype/leave_application/leave_application.json index cdb1add391..74707a24b8 100644 --- a/erpnext/hr/doctype/leave_application/leave_application.json +++ b/erpnext/hr/doctype/leave_application/leave_application.json @@ -1,332 +1,337 @@ { - "allow_import": 1, - "autoname": "naming_series:", - "creation": "2013-02-20 11:18:11", - "description": "Apply / Approve Leaves", - "doctype": "DocType", - "document_type": "Document", - "engine": "InnoDB", - "field_order": [ - "naming_series", - "employee", - "employee_name", - "column_break_4", - "leave_type", - "department", - "leave_balance", - "section_break_5", - "from_date", - "to_date", - "half_day", - "half_day_date", - "total_leave_days", - "column_break1", - "description", - "section_break_7", - "leave_approver", - "leave_approver_name", - "column_break_18", - "status", - "salary_slip", - "sb10", - "posting_date", - "follow_via_email", - "color", - "column_break_17", - "company", - "letter_head", - "amended_from" - ], - "fields": [ - { - "fieldname": "naming_series", - "fieldtype": "Select", - "label": "Series", - "no_copy": 1, - "options": "HR-LAP-.YYYY.-", - "print_hide": 1, - "reqd": 1, - "set_only_once": 1 - }, - { - "fieldname": "employee", - "fieldtype": "Link", - "in_global_search": 1, - "in_standard_filter": 1, - "label": "Employee", - "options": "Employee", - "reqd": 1, - "search_index": 1 - }, - { - "fieldname": "employee_name", - "fieldtype": "Data", - "in_global_search": 1, - "label": "Employee Name", - "read_only": 1 - }, - { - "fieldname": "column_break_4", - "fieldtype": "Column Break" - }, - { - "fieldname": "leave_type", - "fieldtype": "Link", - "ignore_user_permissions": 1, - "in_standard_filter": 1, - "label": "Leave Type", - "options": "Leave Type", - "reqd": 1, - "search_index": 1 - }, - { - "fetch_from": "employee.department", - "fieldname": "department", - "fieldtype": "Link", - "label": "Department", - "options": "Department", - "read_only": 1 - }, - { - "fieldname": "leave_balance", - "fieldtype": "Float", - "label": "Leave Balance Before Application", - "no_copy": 1, - "read_only": 1 - }, - { - "fieldname": "section_break_5", - "fieldtype": "Section Break" - }, - { - "fieldname": "from_date", - "fieldtype": "Date", - "in_list_view": 1, - "label": "From Date", - "reqd": 1, - "search_index": 1 - }, - { - "fieldname": "to_date", - "fieldtype": "Date", - "label": "To Date", - "reqd": 1, - "search_index": 1 - }, - { - "default": "0", - "fieldname": "half_day", - "fieldtype": "Check", - "label": "Half Day" - }, - { - "depends_on": "eval:doc.half_day && (doc.from_date != doc.to_date)", - "fieldname": "half_day_date", - "fieldtype": "Date", - "label": "Half Day Date" - }, - { - "fieldname": "total_leave_days", - "fieldtype": "Float", - "in_list_view": 1, - "label": "Total Leave Days", - "no_copy": 1, - "precision": "1", - "read_only": 1 - }, - { - "fieldname": "column_break1", - "fieldtype": "Column Break", - "print_width": "50%", - "width": "50%" - }, - { - "fieldname": "description", - "fieldtype": "Small Text", - "label": "Reason" - }, - { - "fieldname": "section_break_7", - "fieldtype": "Section Break" - }, - { - "fieldname": "leave_approver", - "fieldtype": "Link", - "label": "Leave Approver", - "options": "User" - }, - { - "fieldname": "leave_approver_name", - "fieldtype": "Data", - "label": "Leave Approver Name", - "read_only": 1 - }, - { - "fieldname": "column_break_18", - "fieldtype": "Column Break" - }, - { - "default": "Open", - "fieldname": "status", - "fieldtype": "Select", - "in_standard_filter": 1, - "label": "Status", - "no_copy": 1, - "options": "Open\nApproved\nRejected\nCancelled" - }, - { - "fieldname": "sb10", - "fieldtype": "Section Break" - }, - { - "default": "Today", - "fieldname": "posting_date", - "fieldtype": "Date", - "label": "Posting Date", - "no_copy": 1, - "reqd": 1 - }, - { - "fieldname": "company", - "fieldtype": "Link", - "label": "Company", - "options": "Company", - "remember_last_selected_value": 1, - "reqd": 1 - }, - { - "allow_on_submit": 1, - "default": "1", - "fieldname": "follow_via_email", - "fieldtype": "Check", - "label": "Follow via Email", - "print_hide": 1 - }, - { - "fieldname": "column_break_17", - "fieldtype": "Column Break" - }, - { - "fieldname": "salary_slip", - "fieldtype": "Link", - "label": "Salary Slip", - "options": "Salary Slip", - "print_hide": 1 - }, - { - "allow_on_submit": 1, - "fieldname": "letter_head", - "fieldtype": "Link", - "ignore_user_permissions": 1, - "label": "Letter Head", - "options": "Letter Head", - "print_hide": 1 - }, - { - "allow_on_submit": 1, - "fieldname": "color", - "fieldtype": "Color", - "label": "Color", - "print_hide": 1 - }, - { - "fieldname": "amended_from", - "fieldtype": "Link", - "ignore_user_permissions": 1, - "label": "Amended From", - "no_copy": 1, - "options": "Leave Application", - "print_hide": 1, - "read_only": 1 - } - ], - "icon": "fa fa-calendar", - "idx": 1, - "is_submittable": 1, - "max_attachments": 3, - "modified": "2019-08-13 13:32:04.860848", - "modified_by": "Administrator", - "module": "HR", - "name": "Leave Application", - "owner": "Administrator", - "permissions": [ - { - "create": 1, - "email": 1, - "print": 1, - "read": 1, - "report": 1, - "role": "Employee", - "share": 1, - "write": 1 - }, - { - "amend": 1, - "cancel": 1, - "create": 1, - "delete": 1, - "email": 1, - "export": 1, - "print": 1, - "read": 1, - "report": 1, - "role": "HR Manager", - "set_user_permissions": 1, - "share": 1, - "submit": 1, - "write": 1 - }, - { - "permlevel": 1, - "read": 1, - "role": "All" - }, - { - "amend": 1, - "cancel": 1, - "create": 1, - "delete": 1, - "email": 1, - "print": 1, - "read": 1, - "report": 1, - "role": "HR User", - "set_user_permissions": 1, - "share": 1, - "submit": 1, - "write": 1 - }, - { - "amend": 1, - "cancel": 1, - "delete": 1, - "email": 1, - "print": 1, - "read": 1, - "report": 1, - "role": "Leave Approver", - "share": 1, - "submit": 1, - "write": 1 - }, - { - "permlevel": 1, - "read": 1, - "report": 1, - "role": "HR User", - "write": 1 - }, - { - "permlevel": 1, - "read": 1, - "report": 1, - "role": "Leave Approver", - "write": 1 - } - ], - "search_fields": "employee,employee_name,leave_type,from_date,to_date,total_leave_days", - "sort_field": "modified", - "sort_order": "DESC", - "timeline_field": "employee", - "title_field": "employee_name" - } \ No newline at end of file + "actions": [], + "allow_import": 1, + "autoname": "naming_series:", + "creation": "2013-02-20 11:18:11", + "description": "Apply / Approve Leaves", + "doctype": "DocType", + "document_type": "Document", + "engine": "InnoDB", + "field_order": [ + "naming_series", + "employee", + "employee_name", + "column_break_4", + "leave_type", + "department", + "leave_balance", + "section_break_5", + "from_date", + "to_date", + "half_day", + "half_day_date", + "total_leave_days", + "column_break1", + "description", + "section_break_7", + "leave_approver", + "leave_approver_name", + "column_break_18", + "status", + "salary_slip", + "sb10", + "posting_date", + "follow_via_email", + "color", + "column_break_17", + "company", + "letter_head", + "amended_from" + ], + "fields": [ + { + "fieldname": "naming_series", + "fieldtype": "Select", + "label": "Series", + "no_copy": 1, + "options": "HR-LAP-.YYYY.-", + "print_hide": 1, + "reqd": 1, + "set_only_once": 1 + }, + { + "fieldname": "employee", + "fieldtype": "Link", + "in_global_search": 1, + "in_standard_filter": 1, + "label": "Employee", + "options": "Employee", + "reqd": 1, + "search_index": 1 + }, + { + "fieldname": "employee_name", + "fieldtype": "Data", + "in_global_search": 1, + "label": "Employee Name", + "read_only": 1 + }, + { + "fieldname": "column_break_4", + "fieldtype": "Column Break" + }, + { + "fieldname": "leave_type", + "fieldtype": "Link", + "ignore_user_permissions": 1, + "in_standard_filter": 1, + "label": "Leave Type", + "options": "Leave Type", + "reqd": 1, + "search_index": 1 + }, + { + "fetch_from": "employee.department", + "fieldname": "department", + "fieldtype": "Link", + "label": "Department", + "options": "Department", + "read_only": 1 + }, + { + "fieldname": "leave_balance", + "fieldtype": "Float", + "label": "Leave Balance Before Application", + "no_copy": 1, + "read_only": 1 + }, + { + "fieldname": "section_break_5", + "fieldtype": "Section Break" + }, + { + "fieldname": "from_date", + "fieldtype": "Date", + "in_list_view": 1, + "label": "From Date", + "reqd": 1, + "search_index": 1 + }, + { + "fieldname": "to_date", + "fieldtype": "Date", + "label": "To Date", + "reqd": 1, + "search_index": 1 + }, + { + "default": "0", + "fieldname": "half_day", + "fieldtype": "Check", + "label": "Half Day" + }, + { + "depends_on": "eval:doc.half_day && (doc.from_date != doc.to_date)", + "fieldname": "half_day_date", + "fieldtype": "Date", + "label": "Half Day Date" + }, + { + "fieldname": "total_leave_days", + "fieldtype": "Float", + "in_list_view": 1, + "label": "Total Leave Days", + "no_copy": 1, + "precision": "1", + "read_only": 1 + }, + { + "fieldname": "column_break1", + "fieldtype": "Column Break", + "print_width": "50%", + "width": "50%" + }, + { + "fieldname": "description", + "fieldtype": "Small Text", + "label": "Reason" + }, + { + "fieldname": "section_break_7", + "fieldtype": "Section Break" + }, + { + "fieldname": "leave_approver", + "fieldtype": "Link", + "label": "Leave Approver", + "options": "User" + }, + { + "fieldname": "leave_approver_name", + "fieldtype": "Data", + "label": "Leave Approver Name", + "read_only": 1 + }, + { + "fieldname": "column_break_18", + "fieldtype": "Column Break" + }, + { + "default": "Open", + "fieldname": "status", + "fieldtype": "Select", + "in_standard_filter": 1, + "label": "Status", + "no_copy": 1, + "options": "Open\nApproved\nRejected\nCancelled", + "permlevel": 1 + }, + { + "fieldname": "sb10", + "fieldtype": "Section Break" + }, + { + "default": "Today", + "fieldname": "posting_date", + "fieldtype": "Date", + "label": "Posting Date", + "no_copy": 1, + "reqd": 1 + }, + { + "fieldname": "company", + "fieldtype": "Link", + "label": "Company", + "options": "Company", + "read_only": 1, + "remember_last_selected_value": 1, + "reqd": 1, + "fetch_from": "employee.company" + }, + { + "allow_on_submit": 1, + "default": "1", + "fieldname": "follow_via_email", + "fieldtype": "Check", + "label": "Follow via Email", + "print_hide": 1 + }, + { + "fieldname": "column_break_17", + "fieldtype": "Column Break" + }, + { + "fieldname": "salary_slip", + "fieldtype": "Link", + "label": "Salary Slip", + "options": "Salary Slip", + "print_hide": 1 + }, + { + "allow_on_submit": 1, + "fieldname": "letter_head", + "fieldtype": "Link", + "ignore_user_permissions": 1, + "label": "Letter Head", + "options": "Letter Head", + "print_hide": 1 + }, + { + "allow_on_submit": 1, + "fieldname": "color", + "fieldtype": "Color", + "label": "Color", + "print_hide": 1 + }, + { + "fieldname": "amended_from", + "fieldtype": "Link", + "ignore_user_permissions": 1, + "label": "Amended From", + "no_copy": 1, + "options": "Leave Application", + "print_hide": 1, + "read_only": 1 + } + ], + "icon": "fa fa-calendar", + "idx": 1, + "is_submittable": 1, + "links": [], + "max_attachments": 3, + "modified": "2020-03-10 22:40:43.487721", + "modified_by": "Administrator", + "module": "HR", + "name": "Leave Application", + "owner": "Administrator", + "permissions": [ + { + "create": 1, + "email": 1, + "print": 1, + "read": 1, + "report": 1, + "role": "Employee", + "share": 1, + "write": 1 + }, + { + "amend": 1, + "cancel": 1, + "create": 1, + "delete": 1, + "email": 1, + "export": 1, + "print": 1, + "read": 1, + "report": 1, + "role": "HR Manager", + "set_user_permissions": 1, + "share": 1, + "submit": 1, + "write": 1 + }, + { + "permlevel": 1, + "read": 1, + "role": "All" + }, + { + "amend": 1, + "cancel": 1, + "create": 1, + "delete": 1, + "email": 1, + "print": 1, + "read": 1, + "report": 1, + "role": "HR User", + "set_user_permissions": 1, + "share": 1, + "submit": 1, + "write": 1 + }, + { + "amend": 1, + "cancel": 1, + "delete": 1, + "email": 1, + "print": 1, + "read": 1, + "report": 1, + "role": "Leave Approver", + "share": 1, + "submit": 1, + "write": 1 + }, + { + "permlevel": 1, + "read": 1, + "report": 1, + "role": "HR User", + "write": 1 + }, + { + "permlevel": 1, + "read": 1, + "report": 1, + "role": "Leave Approver", + "write": 1 + } + ], + "search_fields": "employee,employee_name,leave_type,from_date,to_date,total_leave_days", + "sort_field": "modified", + "sort_order": "DESC", + "timeline_field": "employee", + "title_field": "employee_name" +} diff --git a/erpnext/hr/report/monthly_attendance_sheet/monthly_attendance_sheet.py b/erpnext/hr/report/monthly_attendance_sheet/monthly_attendance_sheet.py index b55b45fcdf..9a9e42e5e8 100644 --- a/erpnext/hr/report/monthly_attendance_sheet/monthly_attendance_sheet.py +++ b/erpnext/hr/report/monthly_attendance_sheet/monthly_attendance_sheet.py @@ -13,7 +13,7 @@ def execute(filters=None): conditions, filters = get_conditions(filters) columns = get_columns(filters) att_map = get_attendance_list(conditions, filters) - emp_map = get_employee_details() + emp_map = get_employee_details(filters) holiday_list = [emp_map[d]["holiday_list"] for d in emp_map if emp_map[d]["holiday_list"]] default_holiday_list = frappe.get_cached_value('Company', filters.get("company"), "default_holiday_list") @@ -140,10 +140,10 @@ def get_conditions(filters): return conditions, filters -def get_employee_details(): +def get_employee_details(filters): emp_map = frappe._dict() for d in frappe.db.sql("""select name, employee_name, designation, department, branch, company, - holiday_list from tabEmployee""", as_dict=1): + holiday_list from tabEmployee where company = %s""", (filters.get("company")), as_dict=1): emp_map.setdefault(d.name, d) return emp_map diff --git a/erpnext/manufacturing/doctype/material_request_plan_item/material_request_plan_item.json b/erpnext/manufacturing/doctype/material_request_plan_item/material_request_plan_item.json index f27197d09f..f93b244a50 100644 --- a/erpnext/manufacturing/doctype/material_request_plan_item/material_request_plan_item.json +++ b/erpnext/manufacturing/doctype/material_request_plan_item/material_request_plan_item.json @@ -1,4 +1,5 @@ { + "actions": [], "creation": "2017-12-01 12:12:55.048691", "doctype": "DocType", "editable_grid": 1, @@ -6,8 +7,9 @@ "field_order": [ "item_code", "item_name", - "warehouse", "material_request_type", + "from_warehouse", + "warehouse", "column_break_4", "quantity", "uom", @@ -46,6 +48,7 @@ { "fieldname": "material_request_type", "fieldtype": "Select", + "in_list_view": 1, "label": "Material Request Type", "options": "\nPurchase\nMaterial Transfer\nMaterial Issue\nManufacture\nCustomer Provided" }, @@ -64,11 +67,11 @@ { "fieldname": "projected_qty", "fieldtype": "Float", - "in_list_view": 1, "label": "Projected Qty", "read_only": 1 }, { + "default": "0", "fieldname": "actual_qty", "fieldtype": "Float", "in_list_view": 1, @@ -119,10 +122,18 @@ "label": "UOM", "options": "UOM", "read_only": 1 + }, + { + "depends_on": "eval:doc.material_request_type == 'Material Transfer'", + "fieldname": "from_warehouse", + "fieldtype": "Link", + "label": "From Warehouse", + "options": "Warehouse" } ], "istable": 1, - "modified": "2019-11-08 15:15:43.979360", + "links": [], + "modified": "2020-02-03 12:22:29.913302", "modified_by": "Administrator", "module": "Manufacturing", "name": "Material Request Plan Item", diff --git a/erpnext/manufacturing/doctype/production_plan/production_plan.js b/erpnext/manufacturing/doctype/production_plan/production_plan.js index b49b0ba0f7..64c952b67b 100644 --- a/erpnext/manufacturing/doctype/production_plan/production_plan.js +++ b/erpnext/manufacturing/doctype/production_plan/production_plan.js @@ -19,7 +19,8 @@ frappe.ui.form.on('Production Plan', { frm.set_query('for_warehouse', function(doc) { return { filters: { - company: doc.company + company: doc.company, + is_group: 0 } } }); @@ -188,12 +189,53 @@ frappe.ui.form.on('Production Plan', { }, get_items_for_mr: function(frm) { - const set_fields = ['actual_qty', 'item_code','item_name', 'description', 'uom', + if (!frm.doc.for_warehouse) { + frappe.throw(__("Select warehouse for material requests")); + } + + if (frm.doc.ignore_existing_ordered_qty) { + frm.events.get_items_for_material_requests(frm); + } else { + const title = __("Transfer Materials For Warehouse {0}", [frm.doc.for_warehouse]); + var dialog = new frappe.ui.Dialog({ + title: title, + fields: [ + { + "fieldtype": "Table MultiSelect", "label": __("Source Warehouses"), + "fieldname": "warehouses", "options": "Production Plan Material Request Warehouse", + "description": "System will pickup the materials from the selected warehouses", + get_query: function () { + return { + filters: { + company: frm.doc.company + } + }; + }, + }, + ] + }); + + dialog.show(); + + dialog.set_primary_action(__("Get Items"), () => { + let warehouses = dialog.get_values().warehouses; + frm.events.get_items_for_material_requests(frm, warehouses); + dialog.hide(); + }); + } + }, + + get_items_for_material_requests: function(frm, warehouses) { + const set_fields = ['actual_qty', 'item_code','item_name', 'description', 'uom', 'from_warehouse', 'min_order_qty', 'quantity', 'sales_order', 'warehouse', 'projected_qty', 'material_request_type']; + frappe.call({ method: "erpnext.manufacturing.doctype.production_plan.production_plan.get_items_for_material_requests", freeze: true, - args: {doc: frm.doc}, + args: { + doc: frm.doc, + warehouses: warehouses || [] + }, callback: function(r) { if(r.message) { frm.set_value('mr_items', []); @@ -212,14 +254,14 @@ frappe.ui.form.on('Production Plan', { }, for_warehouse: function(frm) { - if (frm.doc.mr_items) { + if (frm.doc.mr_items && frm.doc.for_warehouse) { frm.trigger("get_items_for_mr"); } }, download_materials_required: function(frm) { let get_template_url = 'erpnext.manufacturing.doctype.production_plan.production_plan.download_raw_materials'; - open_url_post(frappe.request.url, { cmd: get_template_url, production_plan: frm.doc.name }); + open_url_post(frappe.request.url, { cmd: get_template_url, doc: frm.doc }); }, show_progress: function(frm) { diff --git a/erpnext/manufacturing/doctype/production_plan/production_plan.json b/erpnext/manufacturing/doctype/production_plan/production_plan.json index 77ca6b63d3..90e8b22ed9 100644 --- a/erpnext/manufacturing/doctype/production_plan/production_plan.json +++ b/erpnext/manufacturing/doctype/production_plan/production_plan.json @@ -43,6 +43,7 @@ "total_produced_qty", "column_break_32", "status", + "warehouses", "amended_from" ], "fields": [ @@ -218,12 +219,6 @@ "fieldname": "column_break_25", "fieldtype": "Column Break" }, - { - "fieldname": "for_warehouse", - "fieldtype": "Link", - "label": "For Warehouse", - "options": "Warehouse" - }, { "depends_on": "eval:!doc.__islocal", "fieldname": "download_materials_required", @@ -292,12 +287,26 @@ "options": "Production Plan", "print_hide": 1, "read_only": 1 + }, + { + "fieldname": "for_warehouse", + "fieldtype": "Link", + "label": "Material Request Warehouse", + "options": "Warehouse" + }, + { + "fieldname": "warehouses", + "fieldtype": "Table MultiSelect", + "hidden": 1, + "label": "Warehouses", + "options": "Production Plan Material Request Warehouse", + "read_only": 1 } ], "icon": "fa fa-calendar", "is_submittable": 1, "links": [], - "modified": "2020-01-21 19:13:10.113854", + "modified": "2020-02-03 00:25:25.934202", "modified_by": "Administrator", "module": "Manufacturing", "name": "Production Plan", diff --git a/erpnext/manufacturing/doctype/production_plan/production_plan.py b/erpnext/manufacturing/doctype/production_plan/production_plan.py index c3f27cd3de..560286e68a 100644 --- a/erpnext/manufacturing/doctype/production_plan/production_plan.py +++ b/erpnext/manufacturing/doctype/production_plan/production_plan.py @@ -3,7 +3,7 @@ # For license information, please see license.txt from __future__ import unicode_literals -import frappe, json +import frappe, json, copy from frappe import msgprint, _ from six import string_types, iteritems @@ -385,6 +385,7 @@ class ProductionPlan(Document): # add item material_request.append("items", { "item_code": item.item_code, + "from_warehouse": item.from_warehouse, "qty": item.quantity, "schedule_date": schedule_date, "warehouse": item.warehouse, @@ -415,19 +416,18 @@ class ProductionPlan(Document): msgprint(_("No material request created")) @frappe.whitelist() -def download_raw_materials(production_plan): - doc = frappe.get_doc('Production Plan', production_plan) - doc.check_permission() +def download_raw_materials(doc): + if isinstance(doc, string_types): + doc = frappe._dict(json.loads(doc)) item_list = [['Item Code', 'Description', 'Stock UOM', 'Required Qty', 'Warehouse', 'projected Qty', 'Actual Qty']] - doc = doc.as_dict() - for d in get_items_for_material_requests(doc, ignore_existing_ordered_qty=True): + for d in get_items_for_material_requests(doc): item_list.append([d.get('item_code'), d.get('description'), d.get('stock_uom'), d.get('quantity'), d.get('warehouse'), d.get('projected_qty'), d.get('actual_qty')]) - if not doc.for_warehouse: + if not doc.get('for_warehouse'): row = {'item_code': d.get('item_code')} for bin_dict in get_bin_details(row, doc.company, all_warehouse=True): if d.get("warehouse") == bin_dict.get('warehouse'): @@ -610,26 +610,43 @@ def get_bin_details(row, company, for_warehouse=None, all_warehouse=False): """.format(conditions=conditions), { "item_code": row['item_code'] }, as_dict=1) @frappe.whitelist() -def get_items_for_material_requests(doc, ignore_existing_ordered_qty=None): +def get_items_for_material_requests(doc, warehouses=None): if isinstance(doc, string_types): doc = frappe._dict(json.loads(doc)) + warehouse_list = [] + if warehouses: + if isinstance(warehouses, string_types): + warehouses = json.loads(warehouses) + + for row in warehouses: + child_warehouses = frappe.db.get_descendants('Warehouse', row.get("warehouse")) + if child_warehouses: + warehouse_list.extend(child_warehouses) + else: + warehouse_list.append(row.get("warehouse")) + + if warehouse_list: + warehouses = list(set(warehouse_list)) + + if doc.get("for_warehouse") and doc.get("for_warehouse") in warehouses: + warehouses.remove(doc.get("for_warehouse")) + + warehouse_list = None + doc['mr_items'] = [] po_items = doc.get('po_items') if doc.get('po_items') else doc.get('items') if not po_items: frappe.throw(_("Items are required to pull the raw materials which is associated with it.")) company = doc.get('company') - warehouse = doc.get('for_warehouse') - - if not ignore_existing_ordered_qty: - ignore_existing_ordered_qty = doc.get('ignore_existing_ordered_qty') + ignore_existing_ordered_qty = doc.get('ignore_existing_ordered_qty') so_item_details = frappe._dict() for data in po_items: planned_qty = data.get('required_qty') or data.get('planned_qty') ignore_existing_ordered_qty = data.get('ignore_existing_ordered_qty') or ignore_existing_ordered_qty - warehouse = data.get("warehouse") or warehouse + warehouse = doc.get('for_warehouse') item_details = {} if data.get("bom") or data.get("bom_no"): @@ -700,12 +717,51 @@ def get_items_for_material_requests(doc, ignore_existing_ordered_qty=None): if items: mr_items.append(items) + if not ignore_existing_ordered_qty and warehouses: + new_mr_items = [] + for item in mr_items: + get_materials_from_other_locations(item, warehouses, new_mr_items, company) + + mr_items = new_mr_items + if not mr_items: - frappe.msgprint(_("""As raw materials projected quantity is more than required quantity, there is no need to create material request. - Still if you want to make material request, kindly enable Ignore Existing Projected Quantity checkbox""")) + frappe.msgprint(_("""As raw materials projected quantity is more than required quantity, + there is no need to create material request for the warehouse {0}. + Still if you want to make material request, + kindly enable Ignore Existing Projected Quantity checkbox""").format(doc.get('for_warehouse'))) return mr_items +def get_materials_from_other_locations(item, warehouses, new_mr_items, company): + from erpnext.stock.doctype.pick_list.pick_list import get_available_item_locations + locations = get_available_item_locations(item.get("item_code"), + warehouses, item.get("quantity"), company, ignore_validation=True) + + if not locations: + new_mr_items.append(item) + return + + required_qty = item.get("quantity") + for d in locations: + if required_qty <=0: return + + new_dict = copy.deepcopy(item) + quantity = required_qty if d.get("qty") > required_qty else d.get("qty") + + if required_qty > 0: + new_dict.update({ + "quantity": quantity, + "material_request_type": "Material Transfer", + "from_warehouse": d.get("warehouse") + }) + + required_qty -= quantity + new_mr_items.append(new_dict) + + if required_qty: + item["quantity"] = required_qty + new_mr_items.append(item) + @frappe.whitelist() def get_item_data(item_code): item_details = get_item_details(item_code) diff --git a/erpnext/manufacturing/doctype/production_plan_material_request_warehouse/__init__.py b/erpnext/manufacturing/doctype/production_plan_material_request_warehouse/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/erpnext/manufacturing/doctype/production_plan_material_request_warehouse/production_plan_material_request_warehouse.js b/erpnext/manufacturing/doctype/production_plan_material_request_warehouse/production_plan_material_request_warehouse.js new file mode 100644 index 0000000000..53f87582d1 --- /dev/null +++ b/erpnext/manufacturing/doctype/production_plan_material_request_warehouse/production_plan_material_request_warehouse.js @@ -0,0 +1,8 @@ +// Copyright (c) 2020, Frappe Technologies Pvt. Ltd. and contributors +// For license information, please see license.txt + +frappe.ui.form.on('Production Plan Material Request Warehouse', { + // refresh: function(frm) { + + // } +}); diff --git a/erpnext/manufacturing/doctype/production_plan_material_request_warehouse/production_plan_material_request_warehouse.json b/erpnext/manufacturing/doctype/production_plan_material_request_warehouse/production_plan_material_request_warehouse.json new file mode 100644 index 0000000000..53e33c0265 --- /dev/null +++ b/erpnext/manufacturing/doctype/production_plan_material_request_warehouse/production_plan_material_request_warehouse.json @@ -0,0 +1,42 @@ +{ + "actions": [], + "creation": "2020-02-02 10:37:16.650836", + "doctype": "DocType", + "editable_grid": 1, + "engine": "InnoDB", + "field_order": [ + "warehouse" + ], + "fields": [ + { + "fieldname": "warehouse", + "fieldtype": "Link", + "label": "Warehouse", + "options": "Warehouse" + } + ], + "links": [], + "modified": "2020-02-02 10:37:16.650836", + "modified_by": "Administrator", + "module": "Manufacturing", + "name": "Production Plan Material Request Warehouse", + "owner": "Administrator", + "permissions": [ + { + "create": 1, + "delete": 1, + "email": 1, + "export": 1, + "print": 1, + "read": 1, + "report": 1, + "role": "System Manager", + "share": 1, + "write": 1 + } + ], + "quick_entry": 1, + "sort_field": "modified", + "sort_order": "DESC", + "track_changes": 1 +} \ No newline at end of file diff --git a/erpnext/manufacturing/doctype/production_plan_material_request_warehouse/production_plan_material_request_warehouse.py b/erpnext/manufacturing/doctype/production_plan_material_request_warehouse/production_plan_material_request_warehouse.py new file mode 100644 index 0000000000..f605985ae9 --- /dev/null +++ b/erpnext/manufacturing/doctype/production_plan_material_request_warehouse/production_plan_material_request_warehouse.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 ProductionPlanMaterialRequestWarehouse(Document): + pass diff --git a/erpnext/manufacturing/doctype/production_plan_material_request_warehouse/test_production_plan_material_request_warehouse.py b/erpnext/manufacturing/doctype/production_plan_material_request_warehouse/test_production_plan_material_request_warehouse.py new file mode 100644 index 0000000000..ecab5fbae1 --- /dev/null +++ b/erpnext/manufacturing/doctype/production_plan_material_request_warehouse/test_production_plan_material_request_warehouse.py @@ -0,0 +1,10 @@ +# -*- coding: utf-8 -*- +# Copyright (c) 2020, Frappe Technologies Pvt. Ltd. and Contributors +# See license.txt +from __future__ import unicode_literals + +# import frappe +import unittest + +class TestProductionPlanMaterialRequestWarehouse(unittest.TestCase): + pass diff --git a/erpnext/patches.txt b/erpnext/patches.txt index 783fee1a2b..5295399695 100644 --- a/erpnext/patches.txt +++ b/erpnext/patches.txt @@ -643,6 +643,7 @@ erpnext.patches.v12_0.set_default_shopify_app_type erpnext.patches.v12_0.set_cwip_and_delete_asset_settings erpnext.patches.v12_0.set_expense_account_in_landed_cost_voucher_taxes erpnext.patches.v12_0.replace_accounting_with_accounts_in_home_settings +erpnext.patches.v12_0.set_automatically_process_deferred_accounting_in_accounts_settings erpnext.patches.v12_0.set_payment_entry_status erpnext.patches.v12_0.update_owner_fields_in_acc_dimension_custom_fields erpnext.patches.v12_0.add_export_type_field_in_party_master diff --git a/erpnext/patches/v12_0/set_automatically_process_deferred_accounting_in_accounts_settings.py b/erpnext/patches/v12_0/set_automatically_process_deferred_accounting_in_accounts_settings.py new file mode 100644 index 0000000000..5ee75be499 --- /dev/null +++ b/erpnext/patches/v12_0/set_automatically_process_deferred_accounting_in_accounts_settings.py @@ -0,0 +1,7 @@ +from __future__ import unicode_literals +import frappe + +def execute(): + frappe.reload_doc("accounts", "doctype", "accounts_settings") + + frappe.db.set_value("Accounts Settings", None, "automatically_process_deferred_accounting_entry", 1) \ No newline at end of file diff --git a/erpnext/projects/doctype/project_template/project_template.json b/erpnext/projects/doctype/project_template/project_template.json index 8352995fac..445ad9f238 100644 --- a/erpnext/projects/doctype/project_template/project_template.json +++ b/erpnext/projects/doctype/project_template/project_template.json @@ -1,130 +1,52 @@ { - "allow_copy": 0, - "allow_events_in_timeline": 0, - "allow_guest_to_view": 0, - "allow_import": 0, - "allow_rename": 0, + "actions": [], "autoname": "Prompt", - "beta": 0, "creation": "2019-02-18 17:23:11.708371", - "custom": 0, - "docstatus": 0, "doctype": "DocType", - "document_type": "", "editable_grid": 1, "engine": "InnoDB", + "field_order": [ + "project_type", + "tasks" + ], "fields": [ { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, "fieldname": "project_type", "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": "Project Type", - "length": 0, - "no_copy": 0, - "options": "Project Type", - "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 + "options": "Project Type" }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, "fieldname": "tasks", "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": "Tasks", - "length": 0, - "no_copy": 0, "options": "Project Template Task", - "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 + "reqd": 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": 0, - "max_attachments": 0, - "modified": "2019-02-18 18:01:26.519832", + "links": [], + "modified": "2020-04-26 02:23:53.990322", "modified_by": "Administrator", "module": "Projects", "name": "Project Template", - "name_case": "", "owner": "Administrator", "permissions": [ { - "amend": 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": "System Manager", - "set_user_permissions": 0, "share": 1, - "submit": 0, "write": 1 } ], "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, - "track_views": 0 + "track_changes": 1 } \ 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 eb298a60aa..db8bffda9d 100644 --- a/erpnext/stock/doctype/material_request/material_request.js +++ b/erpnext/stock/doctype/material_request/material_request.js @@ -20,6 +20,17 @@ frappe.ui.form.on('Material Request', { frm.set_indicator_formatter('item_code', function(doc) { return (doc.qty<=doc.ordered_qty) ? "green" : "orange"; }); + frm.set_query("item_code", "items", function() { + return { + query: "erpnext.controllers.queries.item_query" + }; + }); + + frm.set_query("from_warehouse", "items", function(doc) { + return { + filters: {'company': doc.company} + }; + }) }, onload: function(frm) { @@ -53,6 +64,16 @@ frappe.ui.form.on('Material Request', { frm.toggle_reqd('customer', frm.doc.material_request_type=="Customer Provided"); }, + set_from_warehouse: function(frm) { + if (frm.doc.material_request_type == "Material Transfer" + && frm.doc.set_from_warehouse) { + frm.doc.items.forEach(d => { + frappe.model.set_value(d.doctype, d.name, + "from_warehouse", frm.doc.set_from_warehouse); + }) + } + }, + make_custom_buttons: function(frm) { if (frm.doc.docstatus==0) { frm.add_custom_button(__("Bill of Materials"), @@ -159,6 +180,7 @@ frappe.ui.form.on('Material Request', { args: { args: { item_code: item.item_code, + from_warehouse: item.from_warehouse, warehouse: item.warehouse, doctype: frm.doc.doctype, buying_price_list: frappe.defaults.get_default('buying_price_list'), @@ -176,9 +198,11 @@ frappe.ui.form.on('Material Request', { }, callback: function(r) { const d = item; + const qty_fields = ['actual_qty', 'projected_qty', 'min_order_qty']; + if(!r.exc) { $.each(r.message, function(k, v) { - if(!d[k]) d[k] = v; + if(!d[k] || in_list(qty_fields, k)) d[k] = v; }); } } @@ -324,6 +348,16 @@ frappe.ui.form.on("Material Request Item", { frm.events.get_item_data(frm, item); }, + from_warehouse: function(frm, doctype, name) { + const item = locals[doctype][name]; + frm.events.get_item_data(frm, item); + }, + + warehouse: function(frm, doctype, name) { + const item = locals[doctype][name]; + frm.events.get_item_data(frm, item); + }, + rate: function(frm, doctype, name) { const item = locals[doctype][name]; frm.events.get_item_data(frm, item); diff --git a/erpnext/stock/doctype/material_request/material_request.json b/erpnext/stock/doctype/material_request/material_request.json index 536f5fa0ac..d1f29e364a 100644 --- a/erpnext/stock/doctype/material_request/material_request.json +++ b/erpnext/stock/doctype/material_request/material_request.json @@ -18,6 +18,8 @@ "amended_from", "warehouse_section", "set_warehouse", + "column_break5", + "set_from_warehouse", "items_section", "scan_barcode", "items", @@ -287,13 +289,27 @@ "fieldtype": "Link", "label": "Set Warehouse", "options": "Warehouse" + }, + { + "fieldname": "column_break5", + "fieldtype": "Column Break", + "oldfieldtype": "Column Break", + "print_width": "50%", + "width": "50%" + }, + { + "depends_on": "eval:doc.material_request_type == 'Material Transfer'", + "fieldname": "set_from_warehouse", + "fieldtype": "Link", + "label": "Set From Warehouse", + "options": "Warehouse" } ], "icon": "fa fa-ticket", "idx": 70, "is_submittable": 1, "links": [], - "modified": "2020-03-02 20:21:09.990867", + "modified": "2020-05-01 20:21:09.990867", "modified_by": "Administrator", "module": "Stock", "name": "Material Request", diff --git a/erpnext/stock/doctype/material_request/material_request.py b/erpnext/stock/doctype/material_request/material_request.py index 739d7492ca..97606f4e3a 100644 --- a/erpnext/stock/doctype/material_request/material_request.py +++ b/erpnext/stock/doctype/material_request/material_request.py @@ -456,6 +456,9 @@ def make_stock_entry(source_name, target_doc=None): if source_parent.material_request_type == "Customer Provided": target.allow_zero_valuation_rate = 1 + if source_parent.material_request_type == "Material Transfer": + target.s_warehouse = obj.from_warehouse + def set_missing_values(source, target): target.purpose = source.material_request_type if source.job_card: diff --git a/erpnext/stock/doctype/material_request_item/material_request_item.json b/erpnext/stock/doctype/material_request_item/material_request_item.json index 2bdc268c78..df140ffd75 100644 --- a/erpnext/stock/doctype/material_request_item/material_request_item.json +++ b/erpnext/stock/doctype/material_request_item/material_request_item.json @@ -1,4 +1,5 @@ { + "actions": [], "autoname": "hash", "creation": "2013-02-22 01:28:02", "doctype": "DocType", @@ -21,6 +22,7 @@ "quantity_and_warehouse", "qty", "stock_uom", + "from_warehouse", "warehouse", "col_break2", "uom", @@ -419,12 +421,19 @@ { "fieldname": "col_break4", "fieldtype": "Column Break" + }, + { + "depends_on": "eval:parent.material_request_type == \"Material Transfer\"", + "fieldname": "from_warehouse", + "fieldtype": "Link", + "label": "Source Warehouse (Material Transfer)", + "options": "Warehouse" } ], "idx": 1, "istable": 1, "links": [], - "modified": "2020-04-16 09:00:00.992835", + "modified": "2020-05-01 09:00:00.992835", "modified_by": "Administrator", "module": "Stock", "name": "Material Request Item", diff --git a/erpnext/stock/doctype/pick_list/pick_list.py b/erpnext/stock/doctype/pick_list/pick_list.py index 616de5e00a..231af1a022 100644 --- a/erpnext/stock/doctype/pick_list/pick_list.py +++ b/erpnext/stock/doctype/pick_list/pick_list.py @@ -139,7 +139,7 @@ def get_items_with_location_and_quantity(item_doc, item_location_map): item_location_map[item_doc.item_code] = available_locations return locations -def get_available_item_locations(item_code, from_warehouses, required_qty, company): +def get_available_item_locations(item_code, from_warehouses, required_qty, company, ignore_validation=False): locations = [] if frappe.get_cached_value('Item', item_code, 'has_serial_no'): locations = get_available_item_locations_for_serialized_item(item_code, from_warehouses, required_qty, company) @@ -152,7 +152,7 @@ def get_available_item_locations(item_code, from_warehouses, required_qty, compa remaining_qty = required_qty - total_qty_available - if remaining_qty > 0: + if remaining_qty > 0 and not ignore_validation: frappe.msgprint(_('{0} units of {1} is not available.') .format(remaining_qty, frappe.get_desk_link('Item', item_code))) diff --git a/erpnext/stock/doctype/stock_entry/stock_entry.py b/erpnext/stock/doctype/stock_entry/stock_entry.py index f9aae7baa8..ddf4ec0393 100644 --- a/erpnext/stock/doctype/stock_entry/stock_entry.py +++ b/erpnext/stock/doctype/stock_entry/stock_entry.py @@ -1053,9 +1053,9 @@ class StockEntry(StockController): fields=["required_qty", "consumed_qty"] ) - req_qty = flt(req_items[0].required_qty) + req_qty = flt(req_items[0].required_qty) if req_items else flt(4) req_qty_each = flt(req_qty / manufacturing_qty) - consumed_qty = flt(req_items[0].consumed_qty) + consumed_qty = flt(req_items[0].consumed_qty) if req_items else 0 if trans_qty and manufacturing_qty > (produced_qty + flt(self.fg_completed_qty)): if qty >= req_qty: diff --git a/erpnext/stock/get_item_details.py b/erpnext/stock/get_item_details.py index c5ba686f89..d50712aee7 100644 --- a/erpnext/stock/get_item_details.py +++ b/erpnext/stock/get_item_details.py @@ -77,7 +77,11 @@ def get_item_details(args, doc=None, for_validate=False, overwrite_warehouse=Tru if args.customer and cint(args.is_pos): out.update(get_pos_profile_item_details(args.company, args)) - if out.get("warehouse"): + if (args.get("doctype") == "Material Request" and + args.get("material_request_type") == "Material Transfer"): + out.update(get_bin_details(args.item_code, args.get("from_warehouse"))) + + elif out.get("warehouse"): out.update(get_bin_details(args.item_code, out.warehouse)) # update args with out, if key or value not exists