[feature] Opening Invoice Creation Tool (#11589)

* [enhance] Opening Invoice Tool create Opening Sales/Purchase Invoices

* [minor] added progress bar while creating invoice

* [minor] allow bulk upload for the invoices

* [minor] calculate item rate from net total

* [minor] added defaults for invoices fields, create gl entries only if grand_total is greater than 0

* [minor] check mandatory values before making opening invoices

* [minor] enable primary button if there is any error while creating invoices

* [minor] minor fixes, set company currency as invoice currency added tests

* [dashboard] added Opening Invoice Summery section in Dashboard

* [minor] added total invoices count on dashboard

* [minor] don't show dashboard if there are no opening invoices available

* [minor] added Opening Invoice Tools to Account's Tools section

* [minor] codecy fixes

* [fix] refactored general ledger and buttons on charts

* [fix] tests
This commit is contained in:
Rushabh Mehta 2017-11-15 16:29:53 +05:30 committed by GitHub
parent 78ab8235f6
commit d3f5d0961a
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
19 changed files with 1048 additions and 108 deletions

View File

@ -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) {

View File

@ -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'));
},
}

View File

@ -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');
}
});

View File

@ -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
}

View File

@ -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

View File

@ -0,0 +1,32 @@
<h5 style="margin-top: 0px;">{{ __("Opening Invoices Summary") }}</h5>
{% $.each(data, (company, summary) => { %}
<h6 style="margin: 15px 0px -10px 0px;"><a class="company-link"> {{ company }}</a></h6>
<table class="table table-bordered small">
<thead>
<tr>
<td style="width: 33%">{{ __("Invoice Type") }}</td>
<td style="width: 33%" class="text-right">{{ __("Opening Invoices") }}</td>
<td style="width: 33%" class="text-right">{{ __("Total Outstanding") }}</td>
</tr>
</thead>
<tbody>
{% $.each(["Sales Invoice", "Purchase Invoice"], (idx, doctype) => { %}
{% if summary[doctype] %}
<tr>
<td>
<a class="invoice-link" data-type="{{ doctype }}" data-company="{{ company }}">
{{ __(doctype) }}</a>
</td>
<td class="text-right">
{{ summary[doctype].total_invoices }}
</td>
<td class="text-right">
{{ format_currency(summary[doctype].outstanding_amount, summary.currency, 2) }}
</td>
</div>
{% endif %}
{% }); %}
</tbody>
</table>
{% }); %}

View File

@ -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()
]);
});

View File

@ -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

View File

@ -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
}

View File

@ -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

View File

@ -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,

View File

@ -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 = []

View File

@ -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",

View File

@ -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")
},
]
},
{

View File

@ -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)

Binary file not shown.

After

Width:  |  Height:  |  Size: 137 KiB

View File

@ -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
@ -18,23 +18,23 @@ If you were using another accounting software before, firstly you should close f
###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.
<img class="screenshot" alt="Opening Account" src="/docs/assets/img/accounts/opening-account-1.png">
####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 dont 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.
<img class="screenshot" alt="Opening Invoice Creation Tool" src="/docs/assets/img/accounts/opening-invoice-creation-tool.png">
{next}