diff --git a/erpnext/accounts/doctype/account/account_tree.js b/erpnext/accounts/doctype/account/account_tree.js index b803984183c..20d760670f5 100644 --- a/erpnext/accounts/doctype/account/account_tree.js +++ b/erpnext/accounts/doctype/account/account_tree.js @@ -41,6 +41,41 @@ frappe.treeview_settings["Account"] = { description: __("Optional. Sets company's default currency, if not specified.")} ], ignore_fields:["parent_account"], + onload: function(treeview) { + function get_company() { + return treeview.page.fields_dict.company.get_value(); + } + + // tools + treeview.page.add_inner_button(__("Chart of Cost Centers"), function() { + frappe.set_route('Tree', 'Cost Center', {company: get_company()}); + }, __('View')); + + treeview.page.add_inner_button(__("Opening Invoice Creation Tool"), function() { + frappe.set_route('Form', 'Opening Invoice Creation Tool', {company: get_company()}); + }, __('View')); + + treeview.page.add_inner_button(__("Period Closing Voucher"), function() { + frappe.set_route('List', 'Period Closing Voucher', {company: get_company()}); + }, __('View')); + + // make + treeview.page.add_inner_button(__("Journal Entry"), function() { + frappe.new_doc('Journal Entry', {company: get_company()}); + }, __('Make')); + treeview.page.add_inner_button(__("New Company"), function() { + frappe.new_doc('Company'); + }, __('Make')); + + // financial statements + for (let report of ['Trial Balance', 'General Ledger', 'Balance Sheet', + 'Profit and Loss', 'Cash Flow Statement', 'Accounts Payable', 'Accounts Receivable']) { + treeview.page.add_inner_button(__(report), function() { + frappe.set_route('query-report', report, {company: get_company()}); + }, __('Financial Statements')); + } + + }, onrender: function(node) { var dr_or_cr = node.data.balance < 0 ? "Cr" : "Dr"; if (node.data && node.data.balance!==undefined) { diff --git a/erpnext/accounts/doctype/cost_center/cost_center_tree.js b/erpnext/accounts/doctype/cost_center/cost_center_tree.js index 6eab34f9c31..f409d648fee 100644 --- a/erpnext/accounts/doctype/cost_center/cost_center_tree.js +++ b/erpnext/accounts/doctype/cost_center/cost_center_tree.js @@ -23,5 +23,30 @@ frappe.treeview_settings["Cost Center"] = { {fieldtype:'Check', fieldname:'is_group', label:__('Is Group'), description:__('Further cost centers can be made under Groups but entries can be made against non-Groups')} ], - ignore_fields:["parent_cost_center"] + ignore_fields:["parent_cost_center"], + onload: function(treeview) { + function get_company() { + return treeview.page.fields_dict.company.get_value(); + } + + // tools + treeview.page.add_inner_button(__("Chart of Accounts"), function() { + frappe.set_route('Tree', 'Account', {company: get_company()}); + }, __('View')); + + // make + treeview.page.add_inner_button(__("Budget List"), function() { + frappe.set_route('List', 'Budget', {company: get_company()}); + }, __('Budget')); + + treeview.page.add_inner_button(__("Monthly Distribution"), function() { + frappe.set_route('List', 'Monthly Distribution', {company: get_company()}); + }, __('Budget')); + + treeview.page.add_inner_button(__("Budget Variance Report"), function() { + frappe.set_route('query-report', 'Budget Variance Report', {company: get_company()}); + }, __('Budget')); + + }, + } \ No newline at end of file diff --git a/erpnext/accounts/doctype/opening_invoice_creation_tool/__init__.py b/erpnext/accounts/doctype/opening_invoice_creation_tool/__init__.py new file mode 100644 index 00000000000..e69de29bb2d diff --git a/erpnext/accounts/doctype/opening_invoice_creation_tool/opening_invoice_creation_tool.js b/erpnext/accounts/doctype/opening_invoice_creation_tool/opening_invoice_creation_tool.js new file mode 100644 index 00000000000..7c7d3a46001 --- /dev/null +++ b/erpnext/accounts/doctype/opening_invoice_creation_tool/opening_invoice_creation_tool.js @@ -0,0 +1,96 @@ +// Copyright (c) 2017, Frappe Technologies Pvt. Ltd. and contributors +// For license information, please see license.txt + +frappe.ui.form.on('Opening Invoice Creation Tool', { + setup: function(frm) { + frm.set_query('party_type', 'invoices', function(doc, cdt, cdn) { + return { + filters: { + 'name': ['in', 'Customer,Supplier'] + } + }; + }); + }, + + refresh: function(frm) { + frm.disable_save(); + frm.trigger("make_dashboard"); + frm.page.set_primary_action(__("Make Invoices"), () => { + let btn_primary = frm.page.btn_primary.get(0); + return frm.call({ + doc: frm.doc, + freeze: true, + btn: $(btn_primary), + method: "make_invoices", + freeze_message: __("Creating {0} Invoice", [frm.doc.invoice_type]), + callback: (r) => { + if(!r.exc){ + frappe.msgprint(__("Opening {0} Invoice created", [frm.doc.invoice_type])); + frm.clear_table("invoices"); + frm.refresh_fields(); + frm.reload_doc(); + } + } + }); + }); + }, + + company: function(frm) { + frappe.call({ + method: 'erpnext.accounts.doctype.opening_invoice_creation_tool.opening_invoice_creation_tool.get_temporary_opening_account', + args: { + company: frm.doc.company + }, + callback: (r) => { + if (r.message) { + frm.doc.__onload.temporary_opening_account = r.message; + frm.trigger('update_invoice_table'); + } + } + }) + }, + + invoice_type: function(frm) { + $.each(frm.doc.invoices, (idx, row) => { + row.party_type = frm.doc.invoice_type == "Sales"? "Customer": "Supplier"; + row.party = ""; + }); + frm.refresh_fields(); + }, + + make_dashboard: function(frm) { + let max_count = frm.doc.__onload.max_count; + let opening_invoices_summary = frm.doc.__onload.opening_invoices_summary; + if(!$.isEmptyObject(opening_invoices_summary)) { + let section = frm.dashboard.add_section( + frappe.render_template('opening_invoice_creation_tool_dashboard', { + data: opening_invoices_summary, + max_count: max_count + }) + ); + + section.on('click', '.invoice-link', function() { + let doctype = $(this).attr('data-type'); + let company = $(this).attr('data-company'); + frappe.set_route('List', doctype, + {'is_opening': 'Yes', 'company': company, 'docstatus': 1}); + }); + frm.dashboard.show(); + } + }, + + update_invoice_table: function(frm) { + $.each(frm.doc.invoices, (idx, row) => { + if (!row.temporary_opening_account) { + row.temporary_opening_account = frm.doc.__onload.temporary_opening_account; + } + row.party_type = frm.doc.invoice_type == "Sales"? "Customer": "Supplier"; + }); + } +}); + +frappe.ui.form.on('Opening Invoice Creation Tool Item', { + invoices_add: (frm) => { + frm.trigger('update_invoice_table'); + } +}); \ No newline at end of file diff --git a/erpnext/accounts/doctype/opening_invoice_creation_tool/opening_invoice_creation_tool.json b/erpnext/accounts/doctype/opening_invoice_creation_tool/opening_invoice_creation_tool.json new file mode 100644 index 00000000000..cde4c995010 --- /dev/null +++ b/erpnext/accounts/doctype/opening_invoice_creation_tool/opening_invoice_creation_tool.json @@ -0,0 +1,184 @@ +{ + "allow_copy": 1, + "allow_guest_to_view": 0, + "allow_import": 0, + "allow_rename": 0, + "beta": 1, + "creation": "2017-08-29 02:22:54.947711", + "custom": 0, + "docstatus": 0, + "doctype": "DocType", + "document_type": "", + "editable_grid": 1, + "engine": "InnoDB", + "fields": [ + { + "allow_bulk_edit": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "fieldname": "company", + "fieldtype": "Link", + "hidden": 0, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 0, + "in_global_search": 0, + "in_list_view": 1, + "in_standard_filter": 0, + "label": "Company", + "length": 0, + "no_copy": 0, + "options": "Company", + "permlevel": 0, + "precision": "", + "print_hide": 0, + "print_hide_if_no_value": 0, + "read_only": 0, + "remember_last_selected_value": 0, + "report_hide": 0, + "reqd": 1, + "search_index": 0, + "set_only_once": 0, + "unique": 0 + }, + { + "allow_bulk_edit": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "fieldname": "invoice_type", + "fieldtype": "Select", + "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": "Invoice Type", + "length": 0, + "no_copy": 0, + "options": "Sales\nPurchase", + "permlevel": 0, + "precision": "", + "print_hide": 0, + "print_hide_if_no_value": 0, + "read_only": 0, + "remember_last_selected_value": 0, + "report_hide": 0, + "reqd": 1, + "search_index": 0, + "set_only_once": 0, + "unique": 0 + }, + { + "allow_bulk_edit": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "fieldname": "section_break_4", + "fieldtype": "Section Break", + "hidden": 0, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 0, + "in_global_search": 0, + "in_list_view": 0, + "in_standard_filter": 0, + "label": "Invoices", + "length": 0, + "no_copy": 0, + "permlevel": 0, + "precision": "", + "print_hide": 0, + "print_hide_if_no_value": 0, + "read_only": 0, + "remember_last_selected_value": 0, + "report_hide": 0, + "reqd": 0, + "search_index": 0, + "set_only_once": 0, + "unique": 0 + }, + { + "allow_bulk_edit": 1, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "fieldname": "invoices", + "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, + "length": 0, + "no_copy": 0, + "options": "Opening Invoice Creation Tool Item", + "permlevel": 0, + "precision": "", + "print_hide": 0, + "print_hide_if_no_value": 0, + "read_only": 0, + "remember_last_selected_value": 0, + "report_hide": 0, + "reqd": 1, + "search_index": 0, + "set_only_once": 0, + "unique": 0 + } + ], + "has_web_view": 0, + "hide_heading": 0, + "hide_toolbar": 1, + "idx": 0, + "image_view": 0, + "in_create": 0, + "is_submittable": 0, + "issingle": 1, + "istable": 0, + "max_attachments": 0, + "modified": "2017-09-05 01:30:33.235664", + "modified_by": "Administrator", + "module": "Accounts", + "name": "Opening Invoice Creation Tool", + "name_case": "", + "owner": "Administrator", + "permissions": [ + { + "amend": 0, + "apply_user_permissions": 0, + "cancel": 0, + "create": 1, + "delete": 0, + "email": 1, + "export": 0, + "if_owner": 0, + "import": 0, + "permlevel": 0, + "print": 1, + "read": 1, + "report": 0, + "role": "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 +} \ No newline at end of file diff --git a/erpnext/accounts/doctype/opening_invoice_creation_tool/opening_invoice_creation_tool.py b/erpnext/accounts/doctype/opening_invoice_creation_tool/opening_invoice_creation_tool.py new file mode 100644 index 00000000000..8bd155492a8 --- /dev/null +++ b/erpnext/accounts/doctype/opening_invoice_creation_tool/opening_invoice_creation_tool.py @@ -0,0 +1,163 @@ +# -*- coding: utf-8 -*- +# Copyright (c) 2017, Frappe Technologies Pvt. Ltd. and contributors +# For license information, please see license.txt + +from __future__ import unicode_literals +import frappe +from frappe import _ +from frappe.utils import flt +from frappe.model.document import Document + +class OpeningInvoiceCreationTool(Document): + def onload(self): + """Load the Opening Invoice summary""" + summary, max_count = self.get_opening_invoice_summary() + self.set_onload('opening_invoices_summary', summary) + self.set_onload('max_count', max_count) + self.set_onload('temporary_opening_account', get_temporary_opening_account(self.company)) + + def get_opening_invoice_summary(self): + def prepare_invoice_summary(doctype, invoices): + # add company wise sales / purchase invoice summary + paid_amount = [] + outstanding_amount = [] + for invoice in invoices: + company = invoice.pop("company") + _summary = invoices_summary.get(company, {}) + _summary.update({ + "currency": company_wise_currency.get(company), + doctype: invoice + }) + invoices_summary.update({company: _summary}) + + paid_amount.append(invoice.paid_amount) + outstanding_amount.append(invoice.outstanding_amount) + + if paid_amount or outstanding_amount: + max_count.update({ + doctype: { + "max_paid": max(paid_amount) if paid_amount else 0.0, + "max_due": max(outstanding_amount) if outstanding_amount else 0.0 + } + }) + + invoices_summary = {} + max_count = {} + fields = [ + "company", "count(name) as total_invoices", "sum(outstanding_amount) as outstanding_amount" + ] + companies = frappe.get_all("Company", fields=["name as company", "default_currency as currency"]) + if not companies: + return None, None + + company_wise_currency = {row.company: row.currency for row in companies} + for doctype in ["Sales Invoice", "Purchase Invoice"]: + invoices = frappe.get_all(doctype, filters=dict(is_opening="Yes", docstatus=1), + fields=fields, group_by="company") + prepare_invoice_summary(doctype, invoices) + + return invoices_summary, max_count + + def make_invoices(self): + names = [] + mandatory_error_msg = _("Row {idx}: {field} is required to create the Opening {invoice_type} Invoices") + if not self.company: + frappe.throw(_("Please select the Company")) + + for row in self.invoices: + if not row.qty: + row.qty = 1.0 + if not row.party: + frappe.throw(mandatory_error_msg.format( + idx=row.idx, + field= _("Party"), + invoice_type=self.invoice_type + )) + # set party type if not available + if not row.party_type: + row.party_type = "Customer" if self.invoice_type == "Sales" else "Supplier" + + if not row.posting_date: + frappe.throw(mandatory_error_msg.format( + idx=row.idx, + field= _("Party"), + invoice_type=self.invoice_type + )) + + if not row.outstanding_amount: + frappe.throw(mandatory_error_msg.format( + idx=row.idx, + field= _("Outstanding Amount"), + invoice_type=self.invoice_type + )) + + args = self.get_invoice_dict(row=row) + if not args: + continue + + doc = frappe.get_doc(args).insert() + doc.submit() + names.append(doc.name) + + if(len(self.invoices) > 5): + frappe.publish_realtime("progress", + dict(progress=[row.idx, len(self.invoices)], title=_('Creating {0}').format(doc.doctype)), + user=frappe.session.user) + + return names + + def get_invoice_dict(self, row=None): + def get_item_dict(): + default_uom = frappe.db.get_single_value("Stock Settings", "stock_uom") or _("Nos") + cost_center = frappe.db.get_value("Company", self.company, "cost_center") + if not cost_center: + frappe.throw(_("Please set the Default Cost Center in {0} company").format(frappe.bold(self.company))) + rate = flt(row.outstanding_amount) / row.qty + + return frappe._dict({ + "uom": default_uom, + "rate": rate or 0.0, + "qty": row.qty, + "conversion_factor": 1.0, + "item_name": row.item_name or "Opening Invoice Item", + "description": row.item_name or "Opening Invoice Item", + income_expense_account_field: row.temporary_opening_account, + "cost_center": cost_center + }) + + if not row: + return None + + party_type = "Customer" + income_expense_account_field = "income_account" + if self.invoice_type == "Purchase": + party_type = "Supplier" + income_expense_account_field = "expense_account" + + item = get_item_dict() + return frappe._dict({ + "items": [item], + "is_opening": "Yes", + "set_posting_time": 1, + "company": self.company, + "due_date": row.due_date, + "posting_date": row.posting_date, + frappe.scrub(party_type): row.party, + "doctype": "Sales Invoice" if self.invoice_type == "Sales" \ + else "Purchase Invoice", + "currency": frappe.db.get_value("Company", self.company, "default_currency") + }) + +@frappe.whitelist() +def get_temporary_opening_account(company=None): + if not company: + return + + accounts = frappe.get_all("Account", filters={ + 'company': company, + 'account_type': 'Temporary' + }) + if not accounts: + frappe.throw(_("Please add a Temporary Opening account in Chart of Accounts")) + + return accounts[0].name \ No newline at end of file diff --git a/erpnext/accounts/doctype/opening_invoice_creation_tool/opening_invoice_creation_tool_dashboard.html b/erpnext/accounts/doctype/opening_invoice_creation_tool/opening_invoice_creation_tool_dashboard.html new file mode 100644 index 00000000000..5b136d4f666 --- /dev/null +++ b/erpnext/accounts/doctype/opening_invoice_creation_tool/opening_invoice_creation_tool_dashboard.html @@ -0,0 +1,32 @@ +
{{ __("Opening Invoices Summary") }}
+{% $.each(data, (company, summary) => { %} +
{{ company }}
+ + + + + + + + + + + {% $.each(["Sales Invoice", "Purchase Invoice"], (idx, doctype) => { %} + {% if summary[doctype] %} + + + + + + {% endif %} + {% }); %} + +
{{ __("Invoice Type") }}{{ __("Opening Invoices") }}{{ __("Total Outstanding") }}
+ + {{ __(doctype) }} + + {{ summary[doctype].total_invoices }} + + {{ format_currency(summary[doctype].outstanding_amount, summary.currency, 2) }} +
+{% }); %} diff --git a/erpnext/accounts/doctype/opening_invoice_creation_tool/test_opening_invoice_creation_tool.js b/erpnext/accounts/doctype/opening_invoice_creation_tool/test_opening_invoice_creation_tool.js new file mode 100644 index 00000000000..f95d0d82138 --- /dev/null +++ b/erpnext/accounts/doctype/opening_invoice_creation_tool/test_opening_invoice_creation_tool.js @@ -0,0 +1,23 @@ +/* eslint-disable */ +// rename this file from _test_[name] to test_[name] to activate +// and remove above this line + +QUnit.test("test: Opening Invoice Creation Tool", function (assert) { + let done = assert.async(); + + // number of asserts + assert.expect(1); + + frappe.run_serially([ + // insert a new Opening Invoice Creation Tool + () => frappe.tests.make('Opening Invoice Creation Tool', [ + // values to be set + {key: 'value'} + ]), + () => { + assert.equal(cur_frm.doc.key, 'value'); + }, + () => done() + ]); + +}); diff --git a/erpnext/accounts/doctype/opening_invoice_creation_tool/test_opening_invoice_creation_tool.py b/erpnext/accounts/doctype/opening_invoice_creation_tool/test_opening_invoice_creation_tool.py new file mode 100644 index 00000000000..3bfc10dda55 --- /dev/null +++ b/erpnext/accounts/doctype/opening_invoice_creation_tool/test_opening_invoice_creation_tool.py @@ -0,0 +1,79 @@ +# -*- coding: utf-8 -*- +# Copyright (c) 2017, Frappe Technologies Pvt. Ltd. and Contributors +# See license.txt +from __future__ import unicode_literals + +import frappe +import unittest + +test_dependencies = ["Customer", "Supplier"] +from erpnext.accounts.doctype.opening_invoice_creation_tool.opening_invoice_creation_tool import get_temporary_opening_account + +class TestOpeningInvoiceCreationTool(unittest.TestCase): + def make_invoices(self, invoice_type="Sales"): + doc = frappe.get_single("Opening Invoice Creation Tool") + args = get_opening_invoice_creation_dict(invoice_type=invoice_type) + doc.update(args) + return doc.make_invoices() + + def test_opening_sales_invoice_creation(self): + invoices = self.make_invoices() + + self.assertEqual(len(invoices), 2) + expected_value = { + "keys": ["customer", "outstanding_amount", "status"], + 0: ["_Test Customer", 300, "Overdue"], + 1: ["_Test Customer 1", 250, "Overdue"], + } + self.check_expected_values(invoices, expected_value) + + def check_expected_values(self, invoices, expected_value, invoice_type="Sales"): + doctype = "Sales Invoice" if invoice_type == "Sales" else "Purchase Invoice" + + for invoice_idx, invoice in enumerate(invoices or []): + si = frappe.get_doc(doctype, invoice) + for field_idx, field in enumerate(expected_value["keys"]): + self.assertEqual(si.get(field, ""), expected_value[invoice_idx][field_idx]) + + def test_opening_purchase_invoice_creation(self): + invoices = self.make_invoices(invoice_type="Purchase") + + self.assertEqual(len(invoices), 2) + expected_value = { + "keys": ["supplier", "outstanding_amount", "status"], + 0: ["_Test Supplier", 300, "Overdue"], + 1: ["_Test Supplier 1", 250, "Overdue"], + } + self.check_expected_values(invoices, expected_value, invoice_type="Purchase", ) + +def get_opening_invoice_creation_dict(**args): + party = "Customer" if args.get("invoice_type", "Sales") == "Sales" else "Supplier" + company = args.get("company", "_Test Company") + + invoice_dict = frappe._dict({ + "company": company, + "invoice_type": args.get("invoice_type", "Sales"), + "invoices": [ + { + "qty": 1.0, + "outstanding_amount": 300, + "party": "_Test {0}".format(party), + "item_name": "Opening Item", + "due_date": "2016-09-10", + "posting_date": "2016-09-05", + "temporary_opening_account": get_temporary_opening_account(company) + }, + { + "qty": 2.0, + "outstanding_amount": 250, + "party": "_Test {0} 1".format(party), + "item_name": "Opening Item", + "due_date": "2016-09-10", + "posting_date": "2016-09-05", + "temporary_opening_account": get_temporary_opening_account(company) + } + ] + }) + + invoice_dict.update(args) + return invoice_dict \ No newline at end of file diff --git a/erpnext/accounts/doctype/opening_invoice_creation_tool_item/__init__.py b/erpnext/accounts/doctype/opening_invoice_creation_tool_item/__init__.py new file mode 100644 index 00000000000..e69de29bb2d diff --git a/erpnext/accounts/doctype/opening_invoice_creation_tool_item/opening_invoice_creation_tool_item.json b/erpnext/accounts/doctype/opening_invoice_creation_tool_item/opening_invoice_creation_tool_item.json new file mode 100644 index 00000000000..ef0bfaeb194 --- /dev/null +++ b/erpnext/accounts/doctype/opening_invoice_creation_tool_item/opening_invoice_creation_tool_item.json @@ -0,0 +1,318 @@ +{ + "allow_copy": 0, + "allow_guest_to_view": 0, + "allow_import": 0, + "allow_rename": 0, + "beta": 0, + "creation": "2017-08-29 04:26:36.159247", + "custom": 0, + "docstatus": 0, + "doctype": "DocType", + "document_type": "", + "editable_grid": 1, + "engine": "InnoDB", + "fields": [ + { + "allow_bulk_edit": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "fieldname": "party_type", + "fieldtype": "Link", + "hidden": 1, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 0, + "in_global_search": 0, + "in_list_view": 0, + "in_standard_filter": 0, + "label": "Party Type", + "length": 0, + "no_copy": 0, + "options": "DocType", + "permlevel": 0, + "precision": "", + "print_hide": 0, + "print_hide_if_no_value": 0, + "read_only": 1, + "remember_last_selected_value": 0, + "report_hide": 0, + "reqd": 0, + "search_index": 0, + "set_only_once": 0, + "unique": 0 + }, + { + "allow_bulk_edit": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "fieldname": "party", + "fieldtype": "Dynamic 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": "Party", + "length": 0, + "no_copy": 0, + "options": "party_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": 1, + "search_index": 0, + "set_only_once": 0, + "unique": 0 + }, + { + "allow_bulk_edit": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "fieldname": "temporary_opening_account", + "fieldtype": "Link", + "hidden": 0, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 0, + "in_global_search": 0, + "in_list_view": 0, + "in_standard_filter": 0, + "label": "Temporary Opening Account", + "length": 0, + "no_copy": 0, + "options": "Account", + "permlevel": 0, + "precision": "", + "print_hide": 0, + "print_hide_if_no_value": 0, + "read_only": 0, + "remember_last_selected_value": 0, + "report_hide": 0, + "reqd": 0, + "search_index": 0, + "set_only_once": 0, + "unique": 0 + }, + { + "allow_bulk_edit": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "fieldname": "column_break_3", + "fieldtype": "Column Break", + "hidden": 0, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 0, + "in_global_search": 0, + "in_list_view": 0, + "in_standard_filter": 0, + "length": 0, + "no_copy": 0, + "permlevel": 0, + "precision": "", + "print_hide": 0, + "print_hide_if_no_value": 0, + "read_only": 0, + "remember_last_selected_value": 0, + "report_hide": 0, + "reqd": 0, + "search_index": 0, + "set_only_once": 0, + "unique": 0 + }, + { + "allow_bulk_edit": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "default": "Today", + "fieldname": "posting_date", + "fieldtype": "Date", + "hidden": 0, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 0, + "in_global_search": 0, + "in_list_view": 1, + "in_standard_filter": 0, + "label": "Posting Date", + "length": 0, + "no_copy": 0, + "options": "", + "permlevel": 0, + "precision": "", + "print_hide": 0, + "print_hide_if_no_value": 0, + "read_only": 0, + "remember_last_selected_value": 0, + "report_hide": 0, + "reqd": 1, + "search_index": 0, + "set_only_once": 0, + "unique": 0 + }, + { + "allow_bulk_edit": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "default": "Today", + "fieldname": "due_date", + "fieldtype": "Date", + "hidden": 0, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 0, + "in_global_search": 0, + "in_list_view": 1, + "in_standard_filter": 0, + "label": "Due Date", + "length": 0, + "no_copy": 0, + "permlevel": 0, + "precision": "", + "print_hide": 0, + "print_hide_if_no_value": 0, + "read_only": 0, + "remember_last_selected_value": 0, + "report_hide": 0, + "reqd": 1, + "search_index": 0, + "set_only_once": 0, + "unique": 0 + }, + { + "allow_bulk_edit": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "fieldname": "section_break_5", + "fieldtype": "Section Break", + "hidden": 0, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 0, + "in_global_search": 0, + "in_list_view": 0, + "in_standard_filter": 0, + "length": 0, + "no_copy": 0, + "permlevel": 0, + "precision": "", + "print_hide": 0, + "print_hide_if_no_value": 0, + "read_only": 0, + "remember_last_selected_value": 0, + "report_hide": 0, + "reqd": 0, + "search_index": 0, + "set_only_once": 0, + "unique": 0 + }, + { + "allow_bulk_edit": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "default": "Opening Invoice Item", + "fieldname": "item_name", + "fieldtype": "Data", + "hidden": 0, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 0, + "in_global_search": 0, + "in_list_view": 1, + "in_standard_filter": 0, + "label": "Item Name", + "length": 0, + "no_copy": 0, + "options": "", + "permlevel": 0, + "precision": "", + "print_hide": 0, + "print_hide_if_no_value": 0, + "read_only": 0, + "remember_last_selected_value": 0, + "report_hide": 0, + "reqd": 0, + "search_index": 0, + "set_only_once": 0, + "unique": 0 + }, + { + "allow_bulk_edit": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "default": "0", + "fieldname": "outstanding_amount", + "fieldtype": "Currency", + "hidden": 0, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 0, + "in_global_search": 0, + "in_list_view": 1, + "in_standard_filter": 0, + "label": "Outstanding Amount", + "length": 0, + "no_copy": 0, + "permlevel": 0, + "precision": "", + "print_hide": 0, + "print_hide_if_no_value": 0, + "read_only": 0, + "remember_last_selected_value": 0, + "report_hide": 0, + "reqd": 0, + "search_index": 0, + "set_only_once": 0, + "unique": 0 + } + ], + "has_web_view": 0, + "hide_heading": 0, + "hide_toolbar": 0, + "idx": 0, + "image_view": 0, + "in_create": 0, + "is_submittable": 0, + "issingle": 0, + "istable": 1, + "max_attachments": 0, + "modified": "2017-11-15 14:19:00.433148", + "modified_by": "Administrator", + "module": "Accounts", + "name": "Opening Invoice Creation Tool Item", + "name_case": "", + "owner": "Administrator", + "permissions": [], + "quick_entry": 1, + "read_only": 0, + "read_only_onload": 0, + "show_name_in_global_search": 0, + "sort_field": "modified", + "sort_order": "DESC", + "track_changes": 1, + "track_seen": 0 +} \ No newline at end of file diff --git a/erpnext/accounts/doctype/opening_invoice_creation_tool_item/opening_invoice_creation_tool_item.py b/erpnext/accounts/doctype/opening_invoice_creation_tool_item/opening_invoice_creation_tool_item.py new file mode 100644 index 00000000000..d47c3e94ecc --- /dev/null +++ b/erpnext/accounts/doctype/opening_invoice_creation_tool_item/opening_invoice_creation_tool_item.py @@ -0,0 +1,10 @@ +# -*- coding: utf-8 -*- +# Copyright (c) 2017, 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 OpeningInvoiceCreationToolItem(Document): + pass diff --git a/erpnext/accounts/doctype/sales_invoice/sales_invoice.json b/erpnext/accounts/doctype/sales_invoice/sales_invoice.json index 0a5f64fd23f..c0ea00ccd82 100644 --- a/erpnext/accounts/doctype/sales_invoice/sales_invoice.json +++ b/erpnext/accounts/doctype/sales_invoice/sales_invoice.json @@ -369,7 +369,7 @@ "in_filter": 0, "in_global_search": 0, "in_list_view": 0, - "in_standard_filter": 0, + "in_standard_filter": 1, "label": "Company", "length": 0, "no_copy": 0, diff --git a/erpnext/accounts/report/general_ledger/general_ledger.py b/erpnext/accounts/report/general_ledger/general_ledger.py index 5d1aabe54b2..d5180a62728 100644 --- a/erpnext/accounts/report/general_ledger/general_ledger.py +++ b/erpnext/accounts/report/general_ledger/general_ledger.py @@ -3,8 +3,8 @@ from __future__ import unicode_literals import frappe -from frappe.utils import flt, getdate, cstr -from frappe import _ +from frappe.utils import getdate, cstr, flt +from frappe import _, _dict from erpnext.accounts.utils import get_account_currency def execute(filters=None): @@ -163,129 +163,89 @@ def get_data_with_opening_closing(filters, account_details, gl_entries): data = [] gle_map = initialize_gle_map(gl_entries) - opening, total_debit, total_credit, opening_in_account_currency, total_debit_in_account_currency, \ - total_credit_in_account_currency, gle_map = get_accountwise_gle(filters, gl_entries, gle_map) + totals, entries = get_accountwise_gle(filters, gl_entries, gle_map) # Opening for filtered account - if filters.get("account") or filters.get("party"): - data += [get_balance_row(_("Opening"), opening, opening_in_account_currency), {}] + data.append(totals.opening) if filters.get("group_by_account"): for acc, acc_dict in gle_map.items(): if acc_dict.entries: - # Opening for individual ledger, if grouped by account - data.append(get_balance_row(_("Opening"), acc_dict.opening, - acc_dict.opening_in_account_currency)) + # opening + data.append({}) + data.append(acc_dict.totals.opening) data += acc_dict.entries - # Totals and closing for individual ledger, if grouped by account - account_closing = acc_dict.opening + acc_dict.total_debit - acc_dict.total_credit - account_closing_in_account_currency = acc_dict.opening_in_account_currency \ - + acc_dict.total_debit_in_account_currency - acc_dict.total_credit_in_account_currency + # totals + data.append(acc_dict.totals.total) - data += [{"account": "'" + _("Totals") + "'", "debit": acc_dict.total_debit, - "credit": acc_dict.total_credit}, - get_balance_row(_("Closing (Opening + Totals)"), - account_closing, account_closing_in_account_currency), {}] + # closing + data.append(acc_dict.totals.closing) + data.append({}) else: - for gl in gl_entries: - if gl.posting_date >= getdate(filters.from_date) and gl.posting_date <= getdate(filters.to_date) \ - and gl.is_opening == "No": - data.append(gl) + data += entries + # totals + data.append(totals.total) - # Total debit and credit between from and to date - if total_debit or total_credit: - data.append({ - "account": "'" + _("Totals") + "'", - "debit": total_debit, - "credit": total_credit, - "debit_in_account_currency": total_debit_in_account_currency, - "credit_in_account_currency": total_credit_in_account_currency - }) - - # Closing for filtered account - if filters.get("account") or filters.get("party"): - closing = opening + total_debit - total_credit - closing_in_account_currency = opening_in_account_currency + \ - total_debit_in_account_currency - total_credit_in_account_currency - - data.append(get_balance_row(_("Closing (Opening + Totals)"), - closing, closing_in_account_currency)) + # closing + data.append(totals.closing) return data +def get_totals_dict(): + def _get_debit_credit_dict(label): + return _dict( + account = "'{0}'".format(label), + debit = 0.0, + credit = 0.0, + debit_in_account_currency = 0.0, + credit_in_account_currency = 0.0 + ) + return _dict( + opening = _get_debit_credit_dict(_('Opening')), + total = _get_debit_credit_dict(_('Total')), + closing = _get_debit_credit_dict(_('Closing (Opening + Total)')) + ) + def initialize_gle_map(gl_entries): gle_map = frappe._dict() for gle in gl_entries: - gle_map.setdefault(gle.account, frappe._dict({ - "opening": 0, - "opening_in_account_currency": 0, - "entries": [], - "total_debit": 0, - "total_debit_in_account_currency": 0, - "total_credit": 0, - "total_credit_in_account_currency": 0, - "closing": 0, - "closing_in_account_currency": 0 - })) + gle_map.setdefault(gle.account, _dict(totals = get_totals_dict(), entries = [])) return gle_map def get_accountwise_gle(filters, gl_entries, gle_map): - opening, total_debit, total_credit = 0, 0, 0 - opening_in_account_currency, total_debit_in_account_currency, total_credit_in_account_currency = 0, 0, 0 + totals = get_totals_dict() + entries = [] + + def update_value_in_dict(data, key, gle): + data[key].debit += flt(gle.debit) + data[key].credit += flt(gle.credit) + + data[key].debit_in_account_currency += flt(gle.debit_in_account_currency) + data[key].credit_in_account_currency += flt(gle.credit_in_account_currency) + from_date, to_date = getdate(filters.from_date), getdate(filters.to_date) for gle in gl_entries: - amount = flt(gle.debit, 3) - flt(gle.credit, 3) - amount_in_account_currency = flt(gle.debit_in_account_currency, 3) - flt(gle.credit_in_account_currency, 3) - - if (filters.get("account") or filters.get("party") or filters.get("group_by_account")) \ - and (gle.posting_date < from_date or cstr(gle.is_opening) == "Yes"): - - gle_map[gle.account].opening += amount - if filters.get("show_in_account_currency"): - gle_map[gle.account].opening_in_account_currency += amount_in_account_currency - - if filters.get("account") or filters.get("party"): - opening += amount - if filters.get("show_in_account_currency"): - opening_in_account_currency += amount_in_account_currency + if gle.posting_date < from_date or cstr(gle.is_opening) == "Yes": + update_value_in_dict(gle_map[gle.account].totals, 'opening', gle) + update_value_in_dict(totals, 'opening', gle) elif gle.posting_date <= to_date: - gle_map[gle.account].entries.append(gle) - gle_map[gle.account].total_debit += flt(gle.debit, 3) - gle_map[gle.account].total_credit += flt(gle.credit, 3) + update_value_in_dict(gle_map[gle.account].totals, 'total', gle) + update_value_in_dict(totals, 'total', gle) + if filters.get("group_by_account"): + gle_map[gle.account].entries.append(gle) + else: + entries.append(gle) - total_debit += flt(gle.debit, 3) - total_credit += flt(gle.credit, 3) + update_value_in_dict(gle_map[gle.account].totals, 'closing', gle) + update_value_in_dict(totals, 'closing', gle) - if filters.get("show_in_account_currency"): - gle_map[gle.account].total_debit_in_account_currency += flt(gle.debit_in_account_currency, 3) - gle_map[gle.account].total_credit_in_account_currency += flt(gle.credit_in_account_currency, 3) - - total_debit_in_account_currency += flt(gle.debit_in_account_currency, 3) - total_credit_in_account_currency += flt(gle.credit_in_account_currency, 3) - - return opening, total_debit, total_credit, opening_in_account_currency, \ - total_debit_in_account_currency, total_credit_in_account_currency, gle_map - -def get_balance_row(label, balance, balance_in_account_currency=None): - balance_row = { - "account": "'" + label + "'", - "debit": balance if balance > 0 else 0, - "credit": -1*balance if balance < 0 else 0 - } - - if balance_in_account_currency != None: - balance_row.update({ - "debit_in_account_currency": balance_in_account_currency if balance_in_account_currency > 0 else 0, - "credit_in_account_currency": -1*balance_in_account_currency if balance_in_account_currency < 0 else 0 - }) - - return balance_row + return totals, entries def get_result_as_list(data, filters): result = [] diff --git a/erpnext/buying/doctype/supplier/test_records.json b/erpnext/buying/doctype/supplier/test_records.json index d2b399544f9..cae4aac6355 100644 --- a/erpnext/buying/doctype/supplier/test_records.json +++ b/erpnext/buying/doctype/supplier/test_records.json @@ -15,6 +15,11 @@ "supplier_name": "_Test Supplier 1", "supplier_type": "_Test Supplier Type" }, + { + "doctype": "Supplier", + "supplier_name": "_Test Supplier 2", + "supplier_type": "_Test Supplier Type" + }, { "doctype": "Supplier", "supplier_name": "_Test Supplier USD", diff --git a/erpnext/config/accounts.py b/erpnext/config/accounts.py index 58fb2f0b32e..6f4dd03883e 100644 --- a/erpnext/config/accounts.py +++ b/erpnext/config/accounts.py @@ -284,6 +284,11 @@ def get_data(): "name": "Cheque Print Template", "description": _("Setup cheque dimensions for printing") }, + { + "type": "doctype", + "name": "Opening Invoice Creation Tool", + "description": _("Make Opening Sales and Purchase Invoices") + }, ] }, { diff --git a/erpnext/controllers/accounts_controller.py b/erpnext/controllers/accounts_controller.py index d3f6d38e144..74a90004c43 100644 --- a/erpnext/controllers/accounts_controller.py +++ b/erpnext/controllers/accounts_controller.py @@ -83,9 +83,6 @@ class AccountsController(TransactionBase): self.set(fieldname, today()) break - # set taxes table if missing from `taxes_and_charges` - self.set_taxes() - def calculate_taxes_and_totals(self): from erpnext.controllers.taxes_and_totals import calculate_taxes_and_totals calculate_taxes_and_totals(self) diff --git a/erpnext/docs/assets/img/accounts/opening-invoice-creation-tool.png b/erpnext/docs/assets/img/accounts/opening-invoice-creation-tool.png new file mode 100644 index 00000000000..638b9e6bcfa Binary files /dev/null and b/erpnext/docs/assets/img/accounts/opening-invoice-creation-tool.png differ diff --git a/erpnext/docs/user/manual/en/accounts/opening-accounts.md b/erpnext/docs/user/manual/en/accounts/opening-accounts.md index 40546a39405..958c20b5756 100644 --- a/erpnext/docs/user/manual/en/accounts/opening-accounts.md +++ b/erpnext/docs/user/manual/en/accounts/opening-accounts.md @@ -1,4 +1,4 @@ -#Updating Opening Balance in Accounts +# Updating Opening Balance in Accounts If you are a new company you can start using ERPNext accounting module by going to chart of accounts. However, if you are migrating from a legacy accounting system like Tally or a Fox Pro based software @@ -13,28 +13,28 @@ If you were using another accounting software before, firstly you should close f > Opening entry is only for Balance Sheet accounts and not for the Accounts in the Profit and Loss statement. * For all assets (excluding Accounts Receivables): This entry will contain all your assets except the amounts you are expecting from your Customers against outstanding Sales Invoices. You will have to update your receivables by making an individual entry for each Invoice (this is because, the system will help you track the invoices which are yet to be paid). You can credit the sum of all these debits against the **Temporary Opening** account. - + * For all liabilities: Similarly you need to pass a Journal Entry for your Opening Liabilities (except for the bills you have to pay) against **Temporary Opening** account. ###Opening Entry -####Step 1: New Journal Entry +#### Step 1: New Journal Entry To open new Journal Entry, go to: `Explore > Accounts > Journal Entry` -####Step 2: Entry Type +#### Step 2: Entry Type If Entry Type is selected as Opening Entry, all the Balance Sheet Accounts will be auto-fetched in the Journal Entry. Opening Account -####Step 3: Posting Date +#### Step 3: Posting Date Select Posting Date on which Accounts Opening Balance will be updated. -####Step 4: Enter Debit/Credit Value +#### Step 4: Enter Debit/Credit Value For each Account, enter opening value in the Debit or Credit column. As per the double entry valuation system, Total Debit value in a entry must be equal to Total Credit value. @@ -86,9 +86,9 @@ To update stock opening balance, create [Stock Reconciliation entry](/docs/user/ Opening balance for the fixed asset account should be updated via Journal Entry. Assets which are not fully depreciated should be added in the [Asset master](/docs/user/manual/en/accounts/managing-fixed-assets.html). For adding Assets in your possession, ensure to check **Is Existing Asset** field. -### Outstanding Invoices +### Outstanding Payables and Receivables -After opening Journal Entries are made, you will need to enter each Sales Invoice and Purchase Invoice that is yet to be paid. +After opening Journal Entries are made, you will need to enter the Sales Invoice and Purchase Invoice that is yet to be paid. Since you have already booked the income or expense on these invoices in the previous period, select **Temporary Opening** in the “Income” and “Expense” accounts. @@ -96,4 +96,12 @@ Since you have already booked the income or expense on these invoices in the pre If you don’t care what items are in that invoice, just make a dummy item entry in the Invoice. Item code in the Invoice is not necessary, so it should not be such a problem. +You can also do this quickly using the **Opening Invoice Creation Tool** + +To use this tool, just type "Opening Invoice" in the search bar and select the **Opening Invoice Creation Tool** + +Here, select the company and type of invoice (sales or purchase) and add a line item for each invoice you want to create. + +Opening Invoice Creation Tool + {next}