Merge remote-tracking branch 'frappe/develop' into encash

This commit is contained in:
Ranjith 2018-05-15 20:00:26 +05:30
commit c60c6981a5
308 changed files with 15201 additions and 8080 deletions

View File

@ -13,6 +13,7 @@ install:
- pip install flake8==3.3.0 - pip install flake8==3.3.0
- flake8 . --count --select=E901,E999,F821,F822,F823 --show-source --statistics - flake8 . --count --select=E901,E999,F821,F822,F823 --show-source --statistics
- sudo rm /etc/apt/sources.list.d/docker.list - sudo rm /etc/apt/sources.list.d/docker.list
- sudo apt-get install hhvm && rm -rf /home/travis/.kiex/
- sudo apt-get purge -y mysql-common mysql-server mysql-client - sudo apt-get purge -y mysql-common mysql-server mysql-client
- nvm install v7.10.0 - nvm install v7.10.0
- wget https://raw.githubusercontent.com/frappe/bench/master/playbooks/install.py - wget https://raw.githubusercontent.com/frappe/bench/master/playbooks/install.py
@ -34,6 +35,7 @@ before_script:
- bench reinstall --yes - bench reinstall --yes
- bench build - bench build
- bench scheduler disable - bench scheduler disable
- sed -i 's/9000/9001/g' sites/common_site_config.json
- bench start & - bench start &
- sleep 10 - sleep 10

View File

@ -5,7 +5,7 @@ import frappe
from erpnext.hooks import regional_overrides from erpnext.hooks import regional_overrides
from frappe.utils import getdate from frappe.utils import getdate
__version__ = '10.1.27' __version__ = '10.1.33'
def get_default_company(user=None): def get_default_company(user=None):
'''Get default company for user''' '''Get default company for user'''

View File

@ -32,9 +32,7 @@ frappe.treeview_settings["Account"] = {
options: ['Asset', 'Liability', 'Equity', 'Income', 'Expense'].join('\n'), options: ['Asset', 'Liability', 'Equity', 'Income', 'Expense'].join('\n'),
depends_on: 'eval:doc.is_group && !doc.parent_account'}, depends_on: 'eval:doc.is_group && !doc.parent_account'},
{fieldtype:'Select', fieldname:'account_type', label:__('Account Type'), {fieldtype:'Select', fieldname:'account_type', label:__('Account Type'),
options: ['', 'Accumulated Depreciation', 'Bank', 'Cash', 'Chargeable', 'Cost of Goods Sold', 'Depreciation', options: frappe.get_meta("Account").fields.filter(d => d.fieldname=='account_type')[0].options,
'Equity', 'Expense Account', 'Expenses Included In Valuation', 'Fixed Asset', 'Income Account', 'Payable', 'Receivable',
'Round Off', 'Stock', 'Stock Adjustment', 'Stock Received But Not Billed', 'Tax', 'Temporary'].join('\n'),
description: __("Optional. This setting will be used to filter in various transactions.") description: __("Optional. This setting will be used to filter in various transactions.")
}, },
{fieldtype:'Float', fieldname:'tax_rate', label:__('Tax Rate'), {fieldtype:'Float', fieldname:'tax_rate', label:__('Tax Rate'),

View File

@ -24,7 +24,8 @@ def get():
"account_type": "Cash" "account_type": "Cash"
}, },
_("Loans and Advances (Assets)"): { _("Loans and Advances (Assets)"): {
"is_group": 1 _("Employee Advances"): {
},
}, },
_("Securities and Deposits"): { _("Securities and Deposits"): {
_("Earnest Money"): {} _("Earnest Money"): {}

View File

@ -29,7 +29,9 @@ def get():
"account_number": "1100" "account_number": "1100"
}, },
_("Loans and Advances (Assets)"): { _("Loans and Advances (Assets)"): {
"is_group": 1, _("Employee Advances"): {
"account_number": "1610"
},
"account_number": "1600" "account_number": "1600"
}, },
_("Securities and Deposits"): { _("Securities and Deposits"): {

View File

@ -0,0 +1,8 @@
// Copyright (c) 2018, Frappe Technologies Pvt. Ltd. and contributors
// For license information, please see license.txt
frappe.ui.form.on('Bank', {
refresh: function(frm) {
}
});

View File

@ -0,0 +1,94 @@
{
"allow_copy": 0,
"allow_guest_to_view": 0,
"allow_import": 0,
"allow_rename": 0,
"autoname": "field:bank_name",
"beta": 0,
"creation": "2018-04-07 16:59:59.496668",
"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": "bank_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": "Bank Name",
"length": 0,
"no_copy": 0,
"permlevel": 0,
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 0,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 1,
"search_index": 0,
"set_only_once": 0,
"translatable": 0,
"unique": 0
}
],
"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": "2018-04-07 17:00:21.246202",
"modified_by": "Administrator",
"module": "Accounts",
"name": "Bank",
"name_case": "",
"owner": "Administrator",
"permissions": [
{
"amend": 0,
"apply_user_permissions": 0,
"cancel": 0,
"create": 1,
"delete": 1,
"email": 1,
"export": 1,
"if_owner": 0,
"import": 0,
"permlevel": 0,
"print": 1,
"read": 1,
"report": 1,
"role": "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,10 @@
# -*- coding: utf-8 -*-
# Copyright (c) 2018, 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 Bank(Document):
pass

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: Bank", function (assert) {
let done = assert.async();
// number of asserts
assert.expect(1);
frappe.run_serially([
// insert a new Bank
() => frappe.tests.make('Bank', [
// values to be set
{key: 'value'}
]),
() => {
assert.equal(cur_frm.doc.key, 'value');
},
() => done()
]);
});

View File

@ -0,0 +1,10 @@
# -*- coding: utf-8 -*-
# Copyright (c) 2018, Frappe Technologies Pvt. Ltd. and Contributors
# See license.txt
from __future__ import unicode_literals
import frappe
import unittest
class TestBank(unittest.TestCase):
pass

View File

@ -2,7 +2,7 @@
// For license information, please see license.txt // For license information, please see license.txt
frappe.ui.form.on('Bank Guarantee', { frappe.ui.form.on('Bank Guarantee', {
refresh: function(frm) { setup: function(frm) {
cur_frm.set_query("account", function() { cur_frm.set_query("account", function() {
return { return {
"filters": { "filters": {
@ -19,6 +19,39 @@ frappe.ui.form.on('Bank Guarantee', {
}; };
}); });
}, },
bg_type: function(frm) {
if (frm.doc.bg_type == "Receiving") {
frm.set_value("reference_doctype", "Sales Order");
} else if (frm.doc.bg_type == "Providing") {
frm.set_value("reference_doctype", "Purchase Order");
}
},
reference_docname: function(frm) {
if (frm.doc.reference_docname && frm.doc.reference_doctype) {
let fields_to_fetch = ["project", "grand_total"];
let party_field = frm.doc.reference_doctype == "Sales Order" ? "customer" : "supplier";
fields_to_fetch.push(party_field);
frappe.call({
method: "erpnext.accounts.doctype.bank_guarantee.bank_guarantee.get_vouchar_detials",
args: {
"column_list": fields_to_fetch,
"doctype": frm.doc.reference_doctype,
"docname": frm.doc.reference_docname
},
callback: function(r) {
if (r.message) {
if (r.message[party_field]) frm.set_value(party_field, r.message[party_field]);
if (r.message.project) frm.set_value("project", r.message.project);
if (r.message.grand_total) frm.set_value("amount", r.message.grand_total);
}
}
});
}
},
start_date: function(frm) { start_date: function(frm) {
var end_date = frappe.datetime.add_days(cur_frm.doc.start_date, cur_frm.doc.validity - 1); var end_date = frappe.datetime.add_days(cur_frm.doc.start_date, cur_frm.doc.validity - 1);
cur_frm.set_value("end_date", end_date); cur_frm.set_value("end_date", end_date);

View File

@ -19,6 +19,103 @@
"bold": 0, "bold": 0,
"collapsible": 0, "collapsible": 0,
"columns": 0, "columns": 0,
"fieldname": "bg_type",
"fieldtype": "Select",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 0,
"in_standard_filter": 0,
"label": "Bank Guarantee Type",
"length": 0,
"no_copy": 0,
"options": "\nReceiving\nProviding",
"permlevel": 0,
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 0,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 1,
"search_index": 0,
"set_only_once": 0,
"translatable": 0,
"unique": 0
},
{
"allow_bulk_edit": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "reference_doctype",
"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": "Reference Document 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,
"translatable": 0,
"unique": 0
},
{
"allow_bulk_edit": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "reference_docname",
"fieldtype": "Dynamic 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": "Reference Document Name",
"length": 0,
"no_copy": 0,
"options": "reference_doctype",
"permlevel": 0,
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 0,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"translatable": 0,
"unique": 0
},
{
"allow_bulk_edit": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"depends_on": "eval: doc.bg_type == \"Receiving\"",
"fieldname": "customer", "fieldname": "customer",
"fieldtype": "Link", "fieldtype": "Link",
"hidden": 0, "hidden": 0,
@ -39,9 +136,43 @@
"read_only": 0, "read_only": 0,
"remember_last_selected_value": 0, "remember_last_selected_value": 0,
"report_hide": 0, "report_hide": 0,
"reqd": 1, "reqd": 0,
"search_index": 0, "search_index": 0,
"set_only_once": 0, "set_only_once": 0,
"translatable": 0,
"unique": 0
},
{
"allow_bulk_edit": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"depends_on": "eval: doc.bg_type == \"Providing\"",
"fieldname": "supplier",
"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": "Supplier",
"length": 0,
"no_copy": 0,
"options": "Supplier",
"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 "unique": 0
}, },
{ {
@ -73,6 +204,37 @@
"reqd": 0, "reqd": 0,
"search_index": 0, "search_index": 0,
"set_only_once": 0, "set_only_once": 0,
"translatable": 0,
"unique": 0
},
{
"allow_bulk_edit": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "column_break_6",
"fieldtype": "Column Break",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 0,
"in_standard_filter": 0,
"length": 0,
"no_copy": 0,
"permlevel": 0,
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 0,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"translatable": 0,
"unique": 0 "unique": 0
}, },
{ {
@ -101,9 +263,10 @@
"read_only": 0, "read_only": 0,
"remember_last_selected_value": 0, "remember_last_selected_value": 0,
"report_hide": 0, "report_hide": 0,
"reqd": 1, "reqd": 0,
"search_index": 0, "search_index": 0,
"set_only_once": 0, "set_only_once": 0,
"translatable": 0,
"unique": 0 "unique": 0
}, },
{ {
@ -134,35 +297,7 @@
"reqd": 1, "reqd": 1,
"search_index": 0, "search_index": 0,
"set_only_once": 0, "set_only_once": 0,
"unique": 0 "translatable": 0,
},
{
"allow_bulk_edit": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "column_break_6",
"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 "unique": 0
}, },
{ {
@ -193,6 +328,7 @@
"reqd": 1, "reqd": 1,
"search_index": 0, "search_index": 0,
"set_only_once": 0, "set_only_once": 0,
"translatable": 0,
"unique": 0 "unique": 0
}, },
{ {
@ -221,9 +357,10 @@
"read_only": 0, "read_only": 0,
"remember_last_selected_value": 0, "remember_last_selected_value": 0,
"report_hide": 0, "report_hide": 0,
"reqd": 1, "reqd": 0,
"search_index": 0, "search_index": 0,
"set_only_once": 0, "set_only_once": 0,
"translatable": 0,
"unique": 0 "unique": 0
}, },
{ {
@ -251,9 +388,103 @@
"read_only": 1, "read_only": 1,
"remember_last_selected_value": 0, "remember_last_selected_value": 0,
"report_hide": 0, "report_hide": 0,
"reqd": 1, "reqd": 0,
"search_index": 0, "search_index": 0,
"set_only_once": 0, "set_only_once": 0,
"translatable": 0,
"unique": 0
},
{
"allow_bulk_edit": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "section_break_14",
"fieldtype": "Section Break",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 0,
"in_standard_filter": 0,
"label": "",
"length": 0,
"no_copy": 0,
"permlevel": 0,
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 0,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"translatable": 0,
"unique": 0
},
{
"allow_bulk_edit": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "more_information",
"fieldtype": "Text Editor",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 0,
"in_standard_filter": 0,
"label": "Clauses and Conditions",
"length": 0,
"no_copy": 0,
"permlevel": 0,
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 0,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"translatable": 0,
"unique": 0
},
{
"allow_bulk_edit": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "margin_details",
"fieldtype": "Section Break",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 0,
"in_standard_filter": 0,
"label": "Other Details",
"length": 0,
"no_copy": 0,
"permlevel": 0,
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 0,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"translatable": 0,
"unique": 0 "unique": 0
}, },
{ {
@ -281,19 +512,20 @@
"read_only": 0, "read_only": 0,
"remember_last_selected_value": 0, "remember_last_selected_value": 0,
"report_hide": 0, "report_hide": 0,
"reqd": 1, "reqd": 0,
"search_index": 0, "search_index": 0,
"set_only_once": 0, "set_only_once": 0,
"translatable": 0,
"unique": 1 "unique": 1
}, },
{ {
"allow_bulk_edit": 0, "allow_bulk_edit": 0,
"allow_on_submit": 0, "allow_on_submit": 0,
"bold": 0, "bold": 0,
"collapsible": 1, "collapsible": 0,
"columns": 0, "columns": 0,
"fieldname": "section_break_10", "fieldname": "name_of_beneficiary",
"fieldtype": "Section Break", "fieldtype": "Data",
"hidden": 0, "hidden": 0,
"ignore_user_permissions": 0, "ignore_user_permissions": 0,
"ignore_xss_filter": 0, "ignore_xss_filter": 0,
@ -301,7 +533,7 @@
"in_global_search": 0, "in_global_search": 0,
"in_list_view": 0, "in_list_view": 0,
"in_standard_filter": 0, "in_standard_filter": 0,
"label": "More Information", "label": "Name of Beneficiary",
"length": 0, "length": 0,
"no_copy": 0, "no_copy": 0,
"permlevel": 0, "permlevel": 0,
@ -314,6 +546,7 @@
"reqd": 0, "reqd": 0,
"search_index": 0, "search_index": 0,
"set_only_once": 0, "set_only_once": 0,
"translatable": 0,
"unique": 0 "unique": 0
}, },
{ {
@ -322,8 +555,8 @@
"bold": 0, "bold": 0,
"collapsible": 0, "collapsible": 0,
"columns": 0, "columns": 0,
"fieldname": "more_information", "fieldname": "name_of_bank",
"fieldtype": "Text Editor", "fieldtype": "Data",
"hidden": 0, "hidden": 0,
"ignore_user_permissions": 0, "ignore_user_permissions": 0,
"ignore_xss_filter": 0, "ignore_xss_filter": 0,
@ -331,7 +564,7 @@
"in_global_search": 0, "in_global_search": 0,
"in_list_view": 0, "in_list_view": 0,
"in_standard_filter": 0, "in_standard_filter": 0,
"label": "Notes", "label": "Name of Bank",
"length": 0, "length": 0,
"no_copy": 0, "no_copy": 0,
"permlevel": 0, "permlevel": 0,
@ -344,6 +577,133 @@
"reqd": 0, "reqd": 0,
"search_index": 0, "search_index": 0,
"set_only_once": 0, "set_only_once": 0,
"translatable": 0,
"unique": 0
},
{
"allow_bulk_edit": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "column_break_19",
"fieldtype": "Column Break",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 0,
"in_standard_filter": 0,
"length": 0,
"no_copy": 0,
"permlevel": 0,
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 0,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"translatable": 0,
"unique": 0
},
{
"allow_bulk_edit": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "margin_money",
"fieldtype": "Currency",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 0,
"in_standard_filter": 0,
"label": "Margin Money",
"length": 0,
"no_copy": 0,
"permlevel": 0,
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 0,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"translatable": 0,
"unique": 0
},
{
"allow_bulk_edit": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"default": "",
"depends_on": "",
"fieldname": "charges",
"fieldtype": "Currency",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 0,
"in_standard_filter": 0,
"label": "Charges Incurred",
"length": 0,
"no_copy": 0,
"permlevel": 0,
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 0,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"translatable": 0,
"unique": 0
},
{
"allow_bulk_edit": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"depends_on": "",
"fieldname": "fixed_deposit_number",
"fieldtype": "Data",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 0,
"in_standard_filter": 0,
"label": "Fixed Deposit Number",
"length": 0,
"no_copy": 0,
"permlevel": 0,
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 0,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"translatable": 0,
"unique": 0 "unique": 0
}, },
{ {
@ -374,6 +734,7 @@
"reqd": 0, "reqd": 0,
"search_index": 0, "search_index": 0,
"set_only_once": 0, "set_only_once": 0,
"translatable": 0,
"unique": 0 "unique": 0
} }
], ],
@ -387,7 +748,7 @@
"issingle": 0, "issingle": 0,
"istable": 0, "istable": 0,
"max_attachments": 0, "max_attachments": 0,
"modified": "2017-04-25 13:31:49.627831", "modified": "2018-05-08 06:27:08.959864",
"modified_by": "Administrator", "modified_by": "Administrator",
"module": "Accounts", "module": "Accounts",
"name": "Bank Guarantee", "name": "Bank Guarantee",
@ -396,7 +757,6 @@
"permissions": [ "permissions": [
{ {
"amend": 0, "amend": 0,
"apply_user_permissions": 0,
"cancel": 0, "cancel": 0,
"create": 1, "create": 1,
"delete": 0, "delete": 0,
@ -416,7 +776,6 @@
}, },
{ {
"amend": 0, "amend": 0,
"apply_user_permissions": 0,
"cancel": 1, "cancel": 1,
"create": 1, "create": 1,
"delete": 1, "delete": 1,

View File

@ -3,8 +3,26 @@
# For license information, please see license.txt # For license information, please see license.txt
from __future__ import unicode_literals from __future__ import unicode_literals
import frappe import frappe, json
from frappe.model.document import Document from frappe.model.document import Document
from frappe import _
class BankGuarantee(Document): class BankGuarantee(Document):
pass def validate(self):
if not (self.customer or self.supplier):
frappe.throw(_("Select the customer or supplier."))
def on_submit(self):
if not self.bank_guarantee_number:
frappe.throw(_("Enter the Bank Guarantee Number before submittting."))
if not self.name_of_beneficiary:
frappe.throw(_("Enter the name of the Beneficiary before submittting."))
if not self.name_of_bank:
frappe.throw(_("Enter the name of the bank or lending institution before submittting."))
@frappe.whitelist()
def get_vouchar_detials(column_list, doctype, docname):
print (column_list, doctype, docname)
return frappe.db.sql(''' select {columns} from `tab{doctype}` where name=%s'''
.format(columns=", ".join(json.loads(column_list)), doctype=doctype), docname, as_dict=1)[0]

View File

@ -1,12 +0,0 @@
# -*- coding: utf-8 -*-
# Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors
# See license.txt
from __future__ import unicode_literals
import frappe
import unittest
# test_records = frappe.get_test_records('Bank Guarantee')
class TestBankGuarantee(unittest.TestCase):
pass

View File

@ -0,0 +1,8 @@
// Copyright (c) 2017, sathishpy@gmail.com and contributors
// For license information, please see license.txt
frappe.ui.form.on('Bank Statement Settings', {
refresh: function(frm) {
}
});

View File

@ -0,0 +1,272 @@
{
"allow_copy": 0,
"allow_guest_to_view": 0,
"allow_import": 0,
"allow_rename": 1,
"beta": 0,
"creation": "2017-11-13 13:38:10.863592",
"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": "bank",
"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": "Bank Account",
"length": 0,
"no_copy": 0,
"options": "Bank",
"permlevel": 0,
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 1,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 1,
"search_index": 0,
"set_only_once": 0,
"translatable": 0,
"unique": 0
},
{
"allow_bulk_edit": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"default": "'%d/%m/%Y'",
"fieldname": "date_format",
"fieldtype": "Data",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 0,
"in_standard_filter": 0,
"label": "Date Format",
"length": 0,
"no_copy": 0,
"permlevel": 0,
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 0,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"translatable": 0,
"unique": 0
},
{
"allow_bulk_edit": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "statement_header_mapping",
"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": "Statement Header Mapping",
"length": 0,
"no_copy": 0,
"permlevel": 0,
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 0,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"translatable": 0,
"unique": 0
},
{
"allow_bulk_edit": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "header_items",
"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": "Statement Headers",
"length": 0,
"no_copy": 0,
"options": "Bank Statement Settings Item",
"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,
"translatable": 0,
"unique": 0
},
{
"allow_bulk_edit": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "transaction_data_mapping",
"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": "Transaction Data Mapping",
"length": 0,
"no_copy": 0,
"permlevel": 0,
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 0,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"translatable": 0,
"unique": 0
},
{
"allow_bulk_edit": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "mapped_items",
"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": "Mapped Items",
"length": 0,
"no_copy": 0,
"options": "Bank Statement Transaction Settings 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": 0,
"search_index": 0,
"set_only_once": 0,
"translatable": 0,
"unique": 0
}
],
"has_web_view": 0,
"hide_heading": 0,
"hide_toolbar": 0,
"idx": 0,
"image_view": 0,
"in_create": 0,
"is_submittable": 0,
"issingle": 0,
"istable": 0,
"max_attachments": 0,
"modified": "2018-04-07 18:57:04.048423",
"modified_by": "Administrator",
"module": "Accounts",
"name": "Bank Statement Settings",
"name_case": "",
"owner": "Administrator",
"permissions": [
{
"amend": 0,
"apply_user_permissions": 0,
"cancel": 0,
"create": 1,
"delete": 1,
"email": 1,
"export": 1,
"if_owner": 0,
"import": 0,
"permlevel": 0,
"print": 1,
"read": 1,
"report": 1,
"role": "System Manager",
"set_user_permissions": 0,
"share": 1,
"submit": 0,
"write": 1
},
{
"amend": 0,
"apply_user_permissions": 0,
"cancel": 0,
"create": 1,
"delete": 1,
"email": 1,
"export": 1,
"if_owner": 0,
"import": 0,
"permlevel": 0,
"print": 1,
"read": 1,
"report": 1,
"role": "Accounts 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,11 @@
# -*- coding: utf-8 -*-
# Copyright (c) 2017, sathishpy@gmail.com and contributors
# For license information, please see license.txt
from __future__ import unicode_literals
import frappe
from frappe.model.document import Document
class BankStatementSettings(Document):
def autoname(self):
self.name = self.bank + "-Statement-Settings"

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: Bank Statement Settings", function (assert) {
let done = assert.async();
// number of asserts
assert.expect(1);
frappe.run_serially([
// insert a new Bank Statement Settings
() => frappe.tests.make('Bank Statement Settings', [
// values to be set
{key: 'value'}
]),
() => {
assert.equal(cur_frm.doc.key, 'value');
},
() => done()
]);
});

View File

@ -0,0 +1,10 @@
# -*- coding: utf-8 -*-
# Copyright (c) 2017, sathishpy@gmail.com and Contributors
# See license.txt
from __future__ import unicode_literals
import frappe
import unittest
class TestBankStatementSettings(unittest.TestCase):
pass

View File

@ -0,0 +1,101 @@
{
"allow_copy": 0,
"allow_guest_to_view": 0,
"allow_import": 0,
"allow_rename": 0,
"beta": 0,
"creation": "2018-01-08 00:16:42.762980",
"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": "mapped_header",
"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": "Mapped Header",
"length": 0,
"no_copy": 0,
"permlevel": 0,
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 1,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 1,
"search_index": 0,
"set_only_once": 0,
"unique": 0
},
{
"allow_bulk_edit": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "stmt_header",
"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": "Bank Header",
"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
}
],
"has_web_view": 0,
"hide_heading": 0,
"hide_toolbar": 0,
"idx": 0,
"image_view": 0,
"in_create": 0,
"is_submittable": 0,
"issingle": 0,
"istable": 1,
"max_attachments": 0,
"modified": "2018-01-08 00:19:14.841134",
"modified_by": "Administrator",
"module": "Accounts",
"name": "Bank Statement Settings 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) 2018, sathishpy@gmail.com and contributors
# For license information, please see license.txt
from __future__ import unicode_literals
import frappe
from frappe.model.document import Document
class BankStatementSettingsItem(Document):
pass

View File

@ -0,0 +1,75 @@
// Copyright (c) 2017, sathishpy@gmail.com and contributors
// For license information, please see license.txt
frappe.ui.form.on('Bank Statement Transaction Entry', {
setup: function(frm) {
frm.events.account_filters(frm)
frm.events.invoice_filter(frm)
},
refresh: function(frm) {
frm.set_df_property("bank_account", "read_only", frm.doc.__islocal ? 0 : 1);
frm.set_df_property("from_date", "read_only", frm.doc.__islocal ? 0 : 1);
frm.set_df_property("to_date", "read_only", frm.doc.__islocal ? 0 : 1);
},
invoke_doc_function(frm, method) {
frappe.call({
doc: frm.doc,
method: method,
callback: function(r) {
if(!r.exe) {
frm.refresh_fields();
}
}
});
},
account_filters: function(frm) {
frm.fields_dict['bank_account'].get_query = function(doc, dt, dn) {
return {
filters:[
["Account", "account_type", "in", ["Bank"]]
]
}
};
frm.fields_dict['receivable_account'].get_query = function(doc, dt, dn) {
return {
filters: {"account_type": "Receivable"}
}
};
frm.fields_dict['payable_account'].get_query = function(doc, dt, dn) {
return {
filters: {"account_type": "Payable"}
}
};
},
invoice_filter: function(frm) {
frm.set_query("invoice", "payment_invoice_items", function(doc, cdt, cdn) {
row = locals[cdt][cdn]
if (row.party_type == "Customer") {
return {
filters:[[row.invoice_type, "customer", "in", [row.party]],
[row.invoice_type, "status", "!=", "Cancelled" ],
[row.invoice_type, "posting_date", "<", row.transaction_date ],
[row.invoice_type, "outstanding_amount", ">", 0 ]]
}
} else if (row.party_type == "Supplier") {
return {
filters:[[row.invoice_type, "supplier", "in", [row.party]],
[row.invoice_type, "status", "!=", "Cancelled" ],
[row.invoice_type, "posting_date", "<", row.transaction_date ],
[row.invoice_type, "outstanding_amount", ">", 0 ]]
}
}
});
},
match_invoices: function(frm) {
frm.events.invoke_doc_function(frm, "populate_matching_invoices");
},
create_payments: function(frm) {
frm.events.invoke_doc_function(frm, "create_payment_entries");
},
submit_payments: function(frm) {
frm.events.invoke_doc_function(frm, "submit_payment_entries");
},
});

View File

@ -0,0 +1,431 @@
# -*- coding: utf-8 -*-
# Copyright (c) 2017, sathishpy@gmail.com and contributors
# For license information, please see license.txt
from __future__ import unicode_literals
import frappe
from frappe import _
from frappe.model.document import Document
from erpnext.accounts.utils import get_outstanding_invoices
from frappe.utils import nowdate
from datetime import datetime
import csv, os, re, io
import difflib
import copy
class BankStatementTransactionEntry(Document):
def autoname(self):
self.name = self.bank_account + "-" + self.from_date + "-" + self.to_date
mapper_name = self.bank + "-Statement-Settings"
if not frappe.db.exists("Bank Statement Settings", mapper_name):
self.create_settings(self.bank)
self.bank_settings = mapper_name
def create_settings(self, bank):
mapper = frappe.new_doc("Bank Statement Settings")
mapper.bank = bank
mapper.date_format = "%Y-%m-%d"
for header in ["Date", "Particulars", "Withdrawals", "Deposits", "Balance"]:
header_item = mapper.append("header_items", {})
header_item.mapped_header = header_item.stmt_header = header
mapper.save()
def on_update(self):
if (not self.bank_statement):
self.reconciled_transaction_items = self.new_transaction_items = []
return
if len(self.new_transaction_items + self.reconciled_transaction_items) == 0:
self.populate_payment_entries()
else:
self.match_invoice_to_payment()
def get_statement_headers(self):
if not self.bank_settings:
frappe.throw("Bank Data mapper doesn't exist")
mapper_doc = frappe.get_doc("Bank Statement Settings", self.bank_settings)
headers = [entry.stmt_header for entry in mapper_doc.header_items]
return headers
def populate_payment_entries(self):
if self.bank_statement is None: return
filename = self.bank_statement.split("/")[-1]
if (len(self.new_transaction_items + self.reconciled_transaction_items) > 0):
frappe.throw("Transactions already retreived from the statement")
date_format = frappe.get_value("Bank Statement Settings", self.bank_settings, "date_format")
if (date_format is None):
date_format = '%Y-%m-%d'
if self.bank_settings:
mapped_items = frappe.get_doc("Bank Statement Settings", self.bank_settings).mapped_items
transactions = get_transaction_entries(filename, self.get_statement_headers())
for entry in transactions:
date = entry["Date"].strip()
#print("Processing entry DESC:{0}-W:{1}-D:{2}-DT:{3}".format(entry["Particulars"], entry["Withdrawals"], entry["Deposits"], entry["Date"]))
if (not date): continue
transaction_date = datetime.strptime(date, date_format).date()
if (self.from_date and transaction_date < datetime.strptime(self.from_date, '%Y-%m-%d').date()): continue
if (self.to_date and transaction_date > datetime.strptime(self.to_date, '%Y-%m-%d').date()): continue
bank_entry = self.append('new_transaction_items', {})
bank_entry.transaction_date = transaction_date
bank_entry.description = entry["Particulars"]
mapped_item = next((entry for entry in mapped_items if entry.mapping_type == "Transaction" and entry.bank_data.lower() in bank_entry.description.lower()), None)
if (mapped_item is not None):
bank_entry.party_type = mapped_item.mapped_data_type
bank_entry.party = mapped_item.mapped_data
else:
bank_entry.party_type = "Supplier" if not entry["Deposits"].strip() else "Customer"
party_list = frappe.get_all(bank_entry.party_type, fields=["name"])
parties = [party.name for party in party_list]
matches = difflib.get_close_matches(bank_entry.description.lower(), parties, 1, 0.4)
if len(matches) > 0: bank_entry.party = matches[0]
bank_entry.amount = -float(entry["Withdrawals"]) if not entry["Deposits"].strip() else float(entry["Deposits"])
self.map_unknown_transactions()
self.map_transactions_on_journal_entry()
def map_transactions_on_journal_entry(self):
for entry in self.new_transaction_items:
vouchers = frappe.db.sql("""select name, posting_date from `tabJournal Entry`
where posting_date='{0}' and total_credit={1} and cheque_no='{2}' and docstatus != 2
""".format(entry.transaction_date, abs(entry.amount), entry.description), as_dict=True)
if (len(vouchers) == 1):
entry.reference_name = vouchers[0].name
def populate_matching_invoices(self):
self.payment_invoice_items = []
self.map_unknown_transactions()
added_invoices = []
for entry in self.new_transaction_items:
if (not entry.party or entry.party_type == "Account"): continue
account = self.receivable_account if entry.party_type == "Customer" else self.payable_account
invoices = get_outstanding_invoices(entry.party_type, entry.party, account)
transaction_date = datetime.strptime(entry.transaction_date, "%Y-%m-%d").date()
outstanding_invoices = [invoice for invoice in invoices if invoice.posting_date <= transaction_date]
amount = abs(entry.amount)
matching_invoices = [invoice for invoice in outstanding_invoices if invoice.outstanding_amount == amount]
sorted(outstanding_invoices, key=lambda k: k['posting_date'])
for e in (matching_invoices + outstanding_invoices):
added = next((inv for inv in added_invoices if inv == e.get('voucher_no')), None)
if (added is not None): continue
ent = self.append('payment_invoice_items', {})
ent.transaction_date = entry.transaction_date
ent.payment_description = entry.description
ent.party_type = entry.party_type
ent.party = entry.party
ent.invoice = e.get('voucher_no')
added_invoices += [ent.invoice]
ent.invoice_type = "Sales Invoice" if entry.party_type == "Customer" else "Purchase Invoice"
ent.invoice_date = e.get('posting_date')
ent.outstanding_amount = e.get('outstanding_amount')
ent.allocated_amount = min(float(e.get('outstanding_amount')), amount)
amount -= float(e.get('outstanding_amount'))
if (amount <= 5): break
self.match_invoice_to_payment()
self.populate_matching_vouchers()
self.map_transactions_on_journal_entry()
def match_invoice_to_payment(self):
added_payments = []
for entry in self.new_transaction_items:
if (not entry.party or entry.party_type == "Account"): continue
entry.account = self.receivable_account if entry.party_type == "Customer" else self.payable_account
amount = abs(entry.amount)
payment, matching_invoices = None, []
for inv_entry in self.payment_invoice_items:
if (inv_entry.payment_description != entry.description or inv_entry.transaction_date != entry.transaction_date): continue
if (inv_entry.party != entry.party): continue
matching_invoices += [inv_entry.invoice_type + "|" + inv_entry.invoice]
payment = get_payments_matching_invoice(inv_entry.invoice, entry.amount, entry.transaction_date)
doc = frappe.get_doc(inv_entry.invoice_type, inv_entry.invoice)
inv_entry.invoice_date = doc.posting_date
inv_entry.outstanding_amount = doc.outstanding_amount
inv_entry.allocated_amount = min(float(doc.outstanding_amount), amount)
amount -= inv_entry.allocated_amount
if (amount < 0): break
amount = abs(entry.amount)
if (payment is None):
order_doctype = "Sales Order" if entry.party_type=="Customer" else "Purchase Order"
from erpnext.controllers.accounts_controller import get_advance_payment_entries
payment_entries = get_advance_payment_entries(entry.party_type, entry.party, entry.account, order_doctype, against_all_orders=True)
payment_entries += self.get_matching_payments(entry.party, amount, entry.transaction_date)
payment = next((payment for payment in payment_entries if payment.amount == amount and payment not in added_payments), None)
if (payment is None):
print("Failed to find payments for {0}:{1}".format(entry.party, amount))
continue
added_payments += [payment]
entry.reference_type = payment.reference_type
entry.reference_name = payment.reference_name
entry.mode_of_payment = "Wire Transfer"
entry.outstanding_amount = min(amount, 0)
if (entry.payment_reference is None):
entry.payment_reference = entry.description
entry.invoices = ",".join(matching_invoices)
#print("Matching payment is {0}:{1}".format(entry.reference_type, entry.reference_name))
def get_matching_payments(self, party, amount, pay_date):
query = """select 'Payment Entry' as reference_type, name as reference_name, paid_amount as amount
from `tabPayment Entry` where party='{0}' and paid_amount={1} and posting_date='{2}' and docstatus != 2
""".format(party, amount, pay_date)
matching_payments = frappe.db.sql(query, as_dict=True)
return matching_payments
def map_unknown_transactions(self):
for entry in self.new_transaction_items:
if (entry.party): continue
inv_type = "Sales Invoice" if (entry.amount > 0) else "Purchase Invoice"
party_type = "customer" if (entry.amount > 0) else "supplier"
query = """select posting_date, name, {0}, outstanding_amount
from `tab{1}` where ROUND(outstanding_amount)={2} and posting_date < '{3}'
""".format(party_type, inv_type, round(abs(entry.amount)), entry.transaction_date)
invoices = frappe.db.sql(query, as_dict = True)
if(len(invoices) > 0):
entry.party = invoices[0].get(party_type)
def populate_matching_vouchers(self):
for entry in self.new_transaction_items:
if (not entry.party or entry.reference_name): continue
print("Finding matching voucher for {0}".format(entry.description))
amount = abs(entry.amount)
invoices = []
vouchers = get_matching_journal_entries(self.from_date, self.to_date, entry.party, self.bank_account, amount)
if len(vouchers) == 0: continue
for voucher in vouchers:
added = next((entry.invoice for entry in self.payment_invoice_items if entry.invoice == voucher.voucher_no), None)
if (added):
print("Found voucher {0}".format(added))
continue
print("Adding voucher {0} {1} {2}".format(voucher.voucher_no, voucher.posting_date, voucher.debit))
ent = self.append('payment_invoice_items', {})
ent.invoice_date = voucher.posting_date
ent.invoice_type = "Journal Entry"
ent.invoice = voucher.voucher_no
ent.payment_description = entry.description
ent.allocated_amount = max(voucher.debit, voucher.credit)
invoices += [ent.invoice_type + "|" + ent.invoice]
entry.reference_type = "Journal Entry"
entry.mode_of_payment = "Wire Transfer"
entry.reference_name = ent.invoice
#entry.account = entry.party
entry.invoices = ",".join(invoices)
break
def create_payment_entries(self):
for payment_entry in self.new_transaction_items:
if (not payment_entry.party): continue
if (payment_entry.reference_name): continue
print("Creating payment entry for {0}".format(payment_entry.description))
if (payment_entry.party_type == "Account"):
payment = self.create_journal_entry(payment_entry)
invoices = [payment.doctype + "|" + payment.name]
payment_entry.invoices = ",".join(invoices)
else:
payment = self.create_payment_entry(payment_entry)
invoices = [entry.reference_doctype + "|" + entry.reference_name for entry in payment.references if entry is not None]
payment_entry.invoices = ",".join(invoices)
payment_entry.mode_of_payment = payment.mode_of_payment
payment_entry.account = self.receivable_account if payment_entry.party_type == "Customer" else self.payable_account
payment_entry.reference_name = payment.name
payment_entry.reference_type = payment.doctype
frappe.msgprint(_("Successfully created payment entries"))
def create_payment_entry(self, pe):
payment = frappe.new_doc("Payment Entry")
payment.posting_date = pe.transaction_date
payment.payment_type = "Receive" if pe.party_type == "Customer" else "Pay"
payment.mode_of_payment = "Wire Transfer"
payment.party_type = pe.party_type
payment.party = pe.party
payment.paid_to = self.bank_account if pe.party_type == "Customer" else self.payable_account
payment.paid_from = self.receivable_account if pe.party_type == "Customer" else self.bank_account
payment.paid_amount = payment.received_amount = abs(pe.amount)
payment.reference_no = pe.description
payment.reference_date = pe.transaction_date
payment.save()
for inv_entry in self.payment_invoice_items:
if (pe.description != inv_entry.payment_description or pe.transaction_date != inv_entry.transaction_date): continue
if (pe.party != inv_entry.party): continue
reference = payment.append("references", {})
reference.reference_doctype = inv_entry.invoice_type
reference.reference_name = inv_entry.invoice
reference.allocated_amount = inv_entry.allocated_amount
print ("Adding invoice {0} {1}".format(reference.reference_name, reference.allocated_amount))
payment.setup_party_account_field()
payment.set_missing_values()
#payment.set_exchange_rate()
#payment.set_amounts()
#print("Created payment entry {0}".format(payment.as_dict()))
payment.save()
return payment
def create_journal_entry(self, pe):
je = frappe.new_doc("Journal Entry")
je.is_opening = "No"
je.voucher_type = "Bank Entry"
je.cheque_no = pe.description
je.cheque_date = pe.transaction_date
je.remark = pe.description
je.posting_date = pe.transaction_date
if (pe.amount < 0):
je.append("accounts", {"account": pe.party, "debit_in_account_currency": abs(pe.amount)})
je.append("accounts", {"account": self.bank_account, "credit_in_account_currency": abs(pe.amount)})
else:
je.append("accounts", {"account": pe.party, "credit_in_account_currency": pe.amount})
je.append("accounts", {"account": self.bank_account, "debit_in_account_currency": pe.amount})
je.save()
return je
def update_payment_entry(self, payment):
lst = []
invoices = payment.invoices.strip().split(',')
if (len(invoices) == 0): return
amount = float(abs(payment.amount))
for invoice_entry in invoices:
if (not invoice_entry.strip()): continue
invs = invoice_entry.split('|')
invoice_type, invoice = invs[0], invs[1]
outstanding_amount = frappe.get_value(invoice_type, invoice, 'outstanding_amount')
lst.append(frappe._dict({
'voucher_type': payment.reference_type,
'voucher_no' : payment.reference_name,
'against_voucher_type' : invoice_type,
'against_voucher' : invoice,
'account' : payment.account,
'party_type': payment.party_type,
'party': frappe.get_value("Payment Entry", payment.reference_name, "party"),
'unadjusted_amount' : float(amount),
'allocated_amount' : min(outstanding_amount, amount)
}))
amount -= outstanding_amount
if lst:
from erpnext.accounts.utils import reconcile_against_document
try:
reconcile_against_document(lst)
except:
frappe.throw("Exception occurred while reconciling {0}".format(payment.reference_name))
def submit_payment_entries(self):
for payment in self.new_transaction_items:
if payment.reference_name is None: continue
doc = frappe.get_doc(payment.reference_type, payment.reference_name)
if doc.docstatus == 1:
if (payment.reference_type == "Journal Entry"): continue
if doc.unallocated_amount == 0: continue
print("Reconciling payment {0}".format(payment.reference_name))
self.update_payment_entry(payment)
else:
print("Submitting payment {0}".format(payment.reference_name))
if (payment.reference_type == "Payment Entry"):
if (payment.payment_reference):
doc.reference_no = payment.payment_reference
doc.mode_of_payment = payment.mode_of_payment
doc.save()
doc.submit()
self.move_reconciled_entries()
self.populate_matching_invoices()
def move_reconciled_entries(self):
idx = 0
while idx < len(self.new_transaction_items):
entry = self.new_transaction_items[idx]
print("Checking transaction {0}: {2} in {1} entries".format(idx, len(self.new_transaction_items), entry.description))
idx += 1
if entry.reference_name is None: continue
doc = frappe.get_doc(entry.reference_type, entry.reference_name)
if doc.docstatus == 1 and (entry.reference_type == "Journal Entry" or doc.unallocated_amount == 0):
self.remove(entry)
rc_entry = self.append('reconciled_transaction_items', {})
dentry = entry.as_dict()
dentry.pop('idx', None)
rc_entry.update(dentry)
idx -= 1
def get_matching_journal_entries(from_date, to_date, account, against, amount):
query = """select voucher_no, posting_date, account, against, debit_in_account_currency as debit, credit_in_account_currency as credit
from `tabGL Entry`
where posting_date between '{0}' and '{1}' and account = '{2}' and against = '{3}' and debit = '{4}'
""".format(from_date, to_date, account, against, amount)
jv_entries = frappe.db.sql(query, as_dict=True)
#print("voucher query:{0}\n Returned {1} entries".format(query, len(jv_entries)))
return jv_entries
def get_payments_matching_invoice(invoice, amount, pay_date):
query = """select pe.name as reference_name, per.reference_doctype as reference_type, per.outstanding_amount, per.allocated_amount
from `tabPayment Entry Reference` as per JOIN `tabPayment Entry` as pe on pe.name = per.parent
where per.reference_name='{0}' and (posting_date='{1}' or reference_date='{1}') and pe.docstatus != 2
""".format(invoice, pay_date)
payments = frappe.db.sql(query, as_dict=True)
if (len(payments) == 0): return
payment = next((payment for payment in payments if payment.allocated_amount == amount), payments[0])
#Hack: Update the reference type which is set to invoice type
payment.reference_type = "Payment Entry"
return payment
def is_headers_present(headers, row):
for header in headers:
if header not in row:
return False
return True
def get_header_index(headers, row):
header_index = {}
for header in headers:
if header in row:
header_index[header] = row.index(header)
return header_index
def get_transaction_info(headers, header_index, row):
transaction = {}
for header in headers:
transaction[header] = row[header_index[header]]
if (transaction[header] == None):
transaction[header] = ""
return transaction
def get_transaction_entries(filename, headers):
header_index = {}
rows, transactions = [], []
if (filename.lower().endswith("xlsx")):
from frappe.utils.xlsxutils import read_xlsx_file_from_attached_file
rows = read_xlsx_file_from_attached_file(file_id=filename)
elif (filename.lower().endswith("csv")):
from frappe.utils.file_manager import get_file_path
from frappe.utils.csvutils import read_csv_content
filepath = get_file_path(filename)
with open(filepath,'rb') as csvfile:
rows = read_csv_content(csvfile.read())
elif (filename.lower().endswith("xls")):
rows = get_rows_from_xls_file(filename)
else:
frappe.throw("Only .csv and .xlsx files are supported currently")
for row in rows:
if len(row) == 0 or row[0] == None or not row[0]: continue
#print("Processing row {0}".format(row))
if header_index:
transaction = get_transaction_info(headers, header_index, row)
transactions.append(transaction)
elif is_headers_present(headers, row):
header_index = get_header_index(headers, row)
return transactions
def get_rows_from_xls_file(filename):
from frappe.utils.file_manager import get_file_path
filepath = get_file_path(filename)
import xlrd
book = xlrd.open_workbook(filepath)
sheets = book.sheets()
rows = []
for row in range(1, sheets[0].nrows):
row_values = []
for col in range(1, sheets[0].ncols):
row_values.append(sheets[0].cell_value(row, col))
rows.append(row_values)
return rows

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: Bank Statement Transaction Entry", function (assert) {
let done = assert.async();
// number of asserts
assert.expect(1);
frappe.run_serially([
// insert a new Bank Statement Transaction Entry
() => frappe.tests.make('Bank Statement Transaction Entry', [
// values to be set
{key: 'value'}
]),
() => {
assert.equal(cur_frm.doc.key, 'value');
},
() => done()
]);
});

View File

@ -0,0 +1,10 @@
# -*- coding: utf-8 -*-
# Copyright (c) 2017, sathishpy@gmail.com and Contributors
# See license.txt
from __future__ import unicode_literals
import frappe
import unittest
class TestBankStatementTransactionEntry(unittest.TestCase):
pass

View File

@ -0,0 +1,344 @@
{
"allow_copy": 0,
"allow_guest_to_view": 0,
"allow_import": 0,
"allow_rename": 0,
"beta": 0,
"creation": "2017-11-07 13:58:53.827058",
"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": "transaction_date",
"fieldtype": "Date",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 0,
"in_standard_filter": 0,
"label": "Transaction 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": 0,
"search_index": 0,
"set_only_once": 0,
"unique": 0
},
{
"allow_bulk_edit": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 4,
"fieldname": "payment_description",
"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": "Payment Description",
"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,
"fieldname": "party_type",
"fieldtype": "Select",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 0,
"in_standard_filter": 0,
"label": "Party Type",
"length": 0,
"no_copy": 0,
"options": "Customer\nSupplier\nAccount",
"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": 0,
"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": 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": "column_break_4",
"fieldtype": "Column Break",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 0,
"in_standard_filter": 0,
"length": 0,
"no_copy": 0,
"permlevel": 0,
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 0,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"unique": 0
},
{
"allow_bulk_edit": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 2,
"fieldname": "invoice_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": "Invoice Date",
"length": 0,
"no_copy": 0,
"permlevel": 0,
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 1,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 1,
"search_index": 0,
"set_only_once": 0,
"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": 0,
"in_standard_filter": 0,
"label": "Invoice Type",
"length": 0,
"no_copy": 0,
"options": "Purchase Invoice\nSales Invoice\nJournal Entry",
"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": 2,
"fieldname": "invoice",
"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": "invoice",
"length": 0,
"no_copy": 0,
"options": "invoice_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": 1,
"fieldname": "outstanding_amount",
"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": "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
},
{
"allow_bulk_edit": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 1,
"fieldname": "allocated_amount",
"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": "Allocated 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 09:41:45.840947",
"modified_by": "Administrator",
"module": "Accounts",
"name": "Bank Statement Transaction Invoice 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, sathishpy@gmail.com and contributors
# For license information, please see license.txt
from __future__ import unicode_literals
import frappe
from frappe.model.document import Document
class BankStatementTransactionInvoiceItem(Document):
pass

View File

@ -0,0 +1,494 @@
{
"allow_copy": 0,
"allow_guest_to_view": 0,
"allow_import": 0,
"allow_rename": 0,
"beta": 0,
"creation": "2017-11-07 14:03:05.651413",
"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": 1,
"fieldname": "transaction_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": "Transaction Date",
"length": 0,
"no_copy": 0,
"permlevel": 0,
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 1,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 1,
"search_index": 0,
"set_only_once": 0,
"unique": 0
},
{
"allow_bulk_edit": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 4,
"fieldname": "description",
"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": "Description",
"length": 0,
"no_copy": 0,
"permlevel": 0,
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 1,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 1,
"search_index": 0,
"set_only_once": 0,
"unique": 0
},
{
"allow_bulk_edit": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 1,
"fieldname": "amount",
"fieldtype": "Currency",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 1,
"in_standard_filter": 0,
"label": "Amount",
"length": 0,
"no_copy": 0,
"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": "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": 1,
"fieldname": "party_type",
"fieldtype": "Select",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 0,
"in_standard_filter": 0,
"label": "Party Type",
"length": 0,
"no_copy": 0,
"options": "Customer\nSupplier\nAccount",
"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": 2,
"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": 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": "section_break_6",
"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,
"fieldname": "reference_type",
"fieldtype": "Select",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 0,
"in_standard_filter": 0,
"label": "Reference Type",
"length": 0,
"no_copy": 0,
"options": "Payment Entry\nJournal Entry",
"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": "account",
"fieldtype": "Link",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 0,
"in_standard_filter": 0,
"label": "Account",
"length": 0,
"no_copy": 0,
"options": "Account",
"permlevel": 0,
"precision": "",
"print_hide": 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": "mode_of_payment",
"fieldtype": "Link",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 0,
"in_standard_filter": 0,
"label": "Mode of Payment",
"length": 0,
"no_copy": 0,
"options": "Mode of Payment",
"permlevel": 0,
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 0,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 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": "outstanding_amount",
"fieldtype": "Currency",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 0,
"in_standard_filter": 0,
"label": "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
},
{
"allow_bulk_edit": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "column_break_10",
"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": 2,
"fieldname": "reference_name",
"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": "Reference Name",
"length": 0,
"no_copy": 0,
"options": "reference_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,
"unique": 0
},
{
"allow_bulk_edit": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "payment_reference",
"fieldtype": "Data",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 0,
"in_standard_filter": 0,
"label": "Payment Reference",
"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,
"fieldname": "invoices",
"fieldtype": "Text",
"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
}
],
"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 19:18:52.876221",
"modified_by": "Administrator",
"module": "Accounts",
"name": "Bank Statement Transaction Payment 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, sathishpy@gmail.com and contributors
# For license information, please see license.txt
from __future__ import unicode_literals
import frappe
from frappe.model.document import Document
class BankStatementTransactionPaymentItem(Document):
pass

View File

@ -0,0 +1,8 @@
// Copyright (c) 2017, sathishpy@gmail.com and contributors
// For license information, please see license.txt
frappe.ui.form.on('Bank Statement Settings', {
refresh: function(frm) {
}
});

View File

@ -0,0 +1,266 @@
{
"allow_copy": 0,
"allow_guest_to_view": 0,
"allow_import": 0,
"allow_rename": 1,
"beta": 0,
"creation": "2017-11-13 13:38:10.863592",
"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": "bank_account",
"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": "Bank Account",
"length": 0,
"no_copy": 0,
"options": "Account",
"permlevel": 0,
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 1,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 1,
"search_index": 0,
"set_only_once": 0,
"unique": 0
},
{
"allow_bulk_edit": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"default": "'%d/%m/%Y'",
"fieldname": "date_format",
"fieldtype": "Data",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 0,
"in_standard_filter": 0,
"label": "Date Format",
"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,
"fieldname": "statement_header_mapping",
"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": "Statement Header Mapping",
"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,
"fieldname": "header_items",
"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": "Statement Headers",
"length": 0,
"no_copy": 0,
"options": "Bank Statement Settings Item",
"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": "transaction_data_mapping",
"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": "Transaction Data Mapping",
"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,
"fieldname": "mapped_items",
"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": "Mapped Items",
"length": 0,
"no_copy": 0,
"options": "Bank Statement Transaction Settings 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": 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": 0,
"max_attachments": 0,
"modified": "2018-01-12 10:34:32.840487",
"modified_by": "Administrator",
"module": "Accounts",
"name": "Bank Statement Settings",
"name_case": "",
"owner": "Administrator",
"permissions": [
{
"amend": 0,
"apply_user_permissions": 0,
"cancel": 0,
"create": 1,
"delete": 1,
"email": 1,
"export": 1,
"if_owner": 0,
"import": 0,
"permlevel": 0,
"print": 1,
"read": 1,
"report": 1,
"role": "System Manager",
"set_user_permissions": 0,
"share": 1,
"submit": 0,
"write": 1
},
{
"amend": 0,
"apply_user_permissions": 0,
"cancel": 0,
"create": 1,
"delete": 1,
"email": 1,
"export": 1,
"if_owner": 0,
"import": 0,
"permlevel": 0,
"print": 1,
"read": 1,
"report": 1,
"role": "Accounts 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,11 @@
# -*- coding: utf-8 -*-
# Copyright (c) 2017, sathishpy@gmail.com and contributors
# For license information, please see license.txt
from __future__ import unicode_literals
import frappe
from frappe.model.document import Document
class BankStatementSettings(Document):
def autoname(self):
self.name = self.bank_account + "-Mappings"

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: Bank Statement Settings", function (assert) {
let done = assert.async();
// number of asserts
assert.expect(1);
frappe.run_serially([
// insert a new Bank Statement Settings
() => frappe.tests.make('Bank Statement Settings', [
// values to be set
{key: 'value'}
]),
() => {
assert.equal(cur_frm.doc.key, 'value');
},
() => done()
]);
});

View File

@ -0,0 +1,10 @@
# -*- coding: utf-8 -*-
# Copyright (c) 2017, sathishpy@gmail.com and Contributors
# See license.txt
from __future__ import unicode_literals
import frappe
import unittest
class TestBankStatementSettings(unittest.TestCase):
pass

View File

@ -0,0 +1,166 @@
{
"allow_copy": 0,
"allow_guest_to_view": 0,
"allow_import": 0,
"allow_rename": 0,
"beta": 0,
"creation": "2017-11-13 13:42:00.335432",
"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,
"default": "Transaction",
"fieldname": "mapping_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": "Mapping Type",
"length": 0,
"no_copy": 0,
"options": "Transaction",
"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": "bank_data",
"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": "Bank Data",
"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,
"default": "Account",
"fieldname": "mapped_data_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": "Mapped Data Type",
"length": 0,
"no_copy": 0,
"options": "Account\nCustomer\nSupplier\nAccount",
"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": "mapped_data",
"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": "Mapped Data",
"length": 0,
"no_copy": 0,
"options": "mapped_data_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
}
],
"has_web_view": 0,
"hide_heading": 0,
"hide_toolbar": 0,
"idx": 0,
"image_view": 0,
"in_create": 0,
"is_submittable": 0,
"issingle": 0,
"istable": 1,
"max_attachments": 0,
"modified": "2018-01-08 00:13:49.973501",
"modified_by": "Administrator",
"module": "Accounts",
"name": "Bank Statement Transaction Settings 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, sathishpy@gmail.com and contributors
# For license information, please see license.txt
from __future__ import unicode_literals
import frappe
from frappe.model.document import Document
class BankStatementTransactionSettingsItem(Document):
pass

View File

@ -27,10 +27,20 @@ def create_or_update_cheque_print_format(template_name):
doc = frappe.get_doc("Cheque Print Template", template_name) doc = frappe.get_doc("Cheque Print Template", template_name)
cheque_print.html = """ cheque_print.html = """
<style>
.print-format {
padding: 0px;
}
@media screen {
.print-format {
padding: 0in;
}
}
</style>
<div style="position: relative; top:%(starting_position_from_top_edge)scm"> <div style="position: relative; top:%(starting_position_from_top_edge)scm">
<div style="width:%(cheque_width)scm;height:%(cheque_height)scm;"> <div style="width:%(cheque_width)scm;height:%(cheque_height)scm;">
<span style="top:%(acc_pay_dist_from_top_edge)scm; left:%(acc_pay_dist_from_left_edge)scm; <span style="top:%(acc_pay_dist_from_top_edge)scm; left:%(acc_pay_dist_from_left_edge)scm;
border-bottom: solid 1px;border-top:solid 1px; position: absolute;"> border-bottom: solid 1px;border-top:solid 1px; width:2cm;text-align: center; position: absolute;">
%(message_to_show)s %(message_to_show)s
</span> </span>
<span style="top:%(date_dist_from_top_edge)scm; left:%(date_dist_from_left_edge)scm; <span style="top:%(date_dist_from_top_edge)scm; left:%(date_dist_from_left_edge)scm;
@ -38,11 +48,11 @@ def create_or_update_cheque_print_format(template_name):
{{ frappe.utils.formatdate(doc.reference_date) or '' }} {{ frappe.utils.formatdate(doc.reference_date) or '' }}
</span> </span>
<span style="top:%(acc_no_dist_from_top_edge)scm;left:%(acc_no_dist_from_left_edge)scm; <span style="top:%(acc_no_dist_from_top_edge)scm;left:%(acc_no_dist_from_left_edge)scm;
position: absolute;"> position: absolute; min-width: 6cm;">
{{ doc.account_no or '' }} {{ doc.account_no or '' }}
</span> </span>
<span style="top:%(payer_name_from_top_edge)scm;left: %(payer_name_from_left_edge)scm; <span style="top:%(payer_name_from_top_edge)scm;left: %(payer_name_from_left_edge)scm;
position: absolute;"> position: absolute; min-width: 6cm;">
{{doc.party_name}} {{doc.party_name}}
</span> </span>
<span style="top:%(amt_in_words_from_top_edge)scm; left:%(amt_in_words_from_left_edge)scm; <span style="top:%(amt_in_words_from_top_edge)scm; left:%(amt_in_words_from_left_edge)scm;
@ -51,11 +61,11 @@ def create_or_update_cheque_print_format(template_name):
{{frappe.utils.money_in_words(doc.base_paid_amount or doc.base_received_amount)}} {{frappe.utils.money_in_words(doc.base_paid_amount or doc.base_received_amount)}}
</span> </span>
<span style="top:%(amt_in_figures_from_top_edge)scm;left: %(amt_in_figures_from_left_edge)scm; <span style="top:%(amt_in_figures_from_top_edge)scm;left: %(amt_in_figures_from_left_edge)scm;
position: absolute;"> position: absolute; min-width: 4cm;">
{{doc.get_formatted("base_paid_amount") or doc.get_formatted("base_received_amount")}} {{doc.get_formatted("base_paid_amount") or doc.get_formatted("base_received_amount")}}
</span> </span>
<span style="top:%(signatory_from_top_edge)scm;left: %(signatory_from_left_edge)scm; <span style="top:%(signatory_from_top_edge)scm;left: %(signatory_from_left_edge)scm;
position: absolute;"> position: absolute; min-width: 6cm;">
{{doc.company}} {{doc.company}}
</span> </span>
</div> </div>

View File

@ -1,5 +1,6 @@
{ {
"allow_copy": 0, "allow_copy": 0,
"allow_guest_to_view": 0,
"allow_import": 1, "allow_import": 1,
"allow_rename": 0, "allow_rename": 0,
"autoname": "field:year", "autoname": "field:year",
@ -13,6 +14,7 @@
"editable_grid": 0, "editable_grid": 0,
"fields": [ "fields": [
{ {
"allow_bulk_edit": 0,
"allow_on_submit": 0, "allow_on_submit": 0,
"bold": 0, "bold": 0,
"collapsible": 0, "collapsible": 0,
@ -41,9 +43,11 @@
"reqd": 1, "reqd": 1,
"search_index": 0, "search_index": 0,
"set_only_once": 0, "set_only_once": 0,
"translatable": 0,
"unique": 0 "unique": 0
}, },
{ {
"allow_bulk_edit": 0,
"allow_on_submit": 0, "allow_on_submit": 0,
"bold": 0, "bold": 0,
"collapsible": 0, "collapsible": 0,
@ -70,9 +74,11 @@
"reqd": 0, "reqd": 0,
"search_index": 0, "search_index": 0,
"set_only_once": 0, "set_only_once": 0,
"translatable": 0,
"unique": 0 "unique": 0
}, },
{ {
"allow_bulk_edit": 0,
"allow_on_submit": 0, "allow_on_submit": 0,
"bold": 0, "bold": 0,
"collapsible": 0, "collapsible": 0,
@ -100,9 +106,11 @@
"reqd": 1, "reqd": 1,
"search_index": 0, "search_index": 0,
"set_only_once": 0, "set_only_once": 0,
"translatable": 0,
"unique": 0 "unique": 0
}, },
{ {
"allow_bulk_edit": 0,
"allow_on_submit": 0, "allow_on_submit": 0,
"bold": 0, "bold": 0,
"collapsible": 0, "collapsible": 0,
@ -128,9 +136,11 @@
"reqd": 1, "reqd": 1,
"search_index": 0, "search_index": 0,
"set_only_once": 0, "set_only_once": 0,
"translatable": 0,
"unique": 0 "unique": 0
}, },
{ {
"allow_bulk_edit": 0,
"allow_on_submit": 0, "allow_on_submit": 0,
"bold": 0, "bold": 0,
"collapsible": 0, "collapsible": 0,
@ -158,21 +168,54 @@
"reqd": 0, "reqd": 0,
"search_index": 0, "search_index": 0,
"set_only_once": 0, "set_only_once": 0,
"translatable": 0,
"unique": 0
},
{
"allow_bulk_edit": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"default": "0",
"fieldname": "auto_created",
"fieldtype": "Check",
"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": "Auto Created",
"length": 0,
"no_copy": 1,
"permlevel": 0,
"precision": "",
"print_hide": 1,
"print_hide_if_no_value": 0,
"read_only": 1,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"translatable": 0,
"unique": 0 "unique": 0
} }
], ],
"has_web_view": 0,
"hide_heading": 0, "hide_heading": 0,
"hide_toolbar": 0, "hide_toolbar": 0,
"icon": "fa fa-calendar", "icon": "fa fa-calendar",
"idx": 1, "idx": 1,
"image_view": 0, "image_view": 0,
"in_create": 0, "in_create": 0,
"in_dialog": 0,
"is_submittable": 0, "is_submittable": 0,
"issingle": 0, "issingle": 0,
"istable": 0, "istable": 0,
"max_attachments": 0, "max_attachments": 0,
"modified": "2017-02-17 16:22:08.431278", "modified": "2018-04-25 14:21:41.273354",
"modified_by": "Administrator", "modified_by": "Administrator",
"module": "Accounts", "module": "Accounts",
"name": "Fiscal Year", "name": "Fiscal Year",
@ -180,7 +223,6 @@
"permissions": [ "permissions": [
{ {
"amend": 0, "amend": 0,
"apply_user_permissions": 0,
"cancel": 0, "cancel": 0,
"create": 1, "create": 1,
"delete": 1, "delete": 1,
@ -200,7 +242,6 @@
}, },
{ {
"amend": 0, "amend": 0,
"apply_user_permissions": 0,
"cancel": 0, "cancel": 0,
"create": 0, "create": 0,
"delete": 0, "delete": 0,
@ -220,7 +261,6 @@
}, },
{ {
"amend": 0, "amend": 0,
"apply_user_permissions": 0,
"cancel": 0, "cancel": 0,
"create": 0, "create": 0,
"delete": 0, "delete": 0,
@ -240,7 +280,6 @@
}, },
{ {
"amend": 0, "amend": 0,
"apply_user_permissions": 0,
"cancel": 0, "cancel": 0,
"create": 0, "create": 0,
"delete": 0, "delete": 0,
@ -260,7 +299,6 @@
}, },
{ {
"amend": 0, "amend": 0,
"apply_user_permissions": 0,
"cancel": 0, "cancel": 0,
"create": 0, "create": 0,
"delete": 0, "delete": 0,
@ -280,7 +318,6 @@
}, },
{ {
"amend": 0, "amend": 0,
"apply_user_permissions": 0,
"cancel": 0, "cancel": 0,
"create": 0, "create": 0,
"delete": 0, "delete": 0,

View File

@ -104,6 +104,7 @@ def auto_create_fiscal_year():
start_year = cstr(new_fy.year_start_date.year) start_year = cstr(new_fy.year_start_date.year)
end_year = cstr(new_fy.year_end_date.year) end_year = cstr(new_fy.year_end_date.year)
new_fy.year = start_year if start_year==end_year else (start_year + "-" + end_year) new_fy.year = start_year if start_year==end_year else (start_year + "-" + end_year)
new_fy.auto_created = 1
new_fy.insert(ignore_permissions=True) new_fy.insert(ignore_permissions=True)
except frappe.NameError: except frappe.NameError:

View File

@ -532,6 +532,37 @@
"translatable": 0, "translatable": 0,
"unique": 0 "unique": 0
}, },
{
"allow_bulk_edit": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "voucher_detail_no",
"fieldtype": "Data",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 0,
"in_standard_filter": 0,
"label": "Voucher Detail No",
"length": 0,
"no_copy": 0,
"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,
"translatable": 0,
"unique": 0
},
{ {
"allow_bulk_edit": 0, "allow_bulk_edit": 0,
"allow_on_submit": 0, "allow_on_submit": 0,
@ -772,6 +803,7 @@
"issingle": 0, "issingle": 0,
"istable": 0, "istable": 0,
"max_attachments": 0, "max_attachments": 0,
"modified": "2018-05-15 02:12:50.035755",
"modified": "2018-04-23 02:15:22.297509", "modified": "2018-04-23 02:15:22.297509",
"modified_by": "Administrator", "modified_by": "Administrator",
"module": "Accounts", "module": "Accounts",

View File

@ -1513,7 +1513,7 @@
"bold": 0, "bold": 0,
"collapsible": 0, "collapsible": 0,
"columns": 0, "columns": 0,
"fieldname": "subscription", "fieldname": "auto_repeat",
"fieldtype": "Link", "fieldtype": "Link",
"hidden": 0, "hidden": 0,
"ignore_user_permissions": 0, "ignore_user_permissions": 0,
@ -1522,10 +1522,10 @@
"in_global_search": 0, "in_global_search": 0,
"in_list_view": 0, "in_list_view": 0,
"in_standard_filter": 0, "in_standard_filter": 0,
"label": "Subscription", "label": "Auto Repeat",
"length": 0, "length": 0,
"no_copy": 1, "no_copy": 1,
"options": "Subscription", "options": "Auto Repeat",
"permlevel": 0, "permlevel": 0,
"precision": "", "precision": "",
"print_hide": 1, "print_hide": 1,

View File

@ -161,7 +161,8 @@ class OpeningInvoiceCreationTool(Document):
income_expense_account_field = "expense_account" income_expense_account_field = "expense_account"
item = get_item_dict() item = get_item_dict()
return frappe._dict({
args = frappe._dict({
"items": [item], "items": [item],
"is_opening": "Yes", "is_opening": "Yes",
"set_posting_time": 1, "set_posting_time": 1,
@ -173,6 +174,11 @@ class OpeningInvoiceCreationTool(Document):
"currency": frappe.db.get_value("Company", self.company, "default_currency") "currency": frappe.db.get_value("Company", self.company, "default_currency")
}) })
if self.invoice_type == "Sales":
args["is_pos"] = 0
return args
@frappe.whitelist() @frappe.whitelist()
def get_temporary_opening_account(company=None): def get_temporary_opening_account(company=None):
if not company: if not company:

View File

@ -1749,7 +1749,7 @@
"bold": 0, "bold": 0,
"collapsible": 0, "collapsible": 0,
"columns": 0, "columns": 0,
"fieldname": "subscription", "fieldname": "auto_repeat",
"fieldtype": "Link", "fieldtype": "Link",
"hidden": 0, "hidden": 0,
"ignore_user_permissions": 0, "ignore_user_permissions": 0,
@ -1758,10 +1758,10 @@
"in_global_search": 0, "in_global_search": 0,
"in_list_view": 0, "in_list_view": 0,
"in_standard_filter": 0, "in_standard_filter": 0,
"label": "Subscription", "label": "Auto Repeat",
"length": 0, "length": 0,
"no_copy": 1, "no_copy": 1,
"options": "Subscription", "options": "Auto Repeat",
"permlevel": 0, "permlevel": 0,
"precision": "", "precision": "",
"print_hide": 1, "print_hide": 1,
@ -1848,7 +1848,7 @@
"issingle": 0, "issingle": 0,
"istable": 0, "istable": 0,
"max_attachments": 0, "max_attachments": 0,
"modified": "2018-02-19 16:58:23.899015", "modified": "2018-03-10 07:31:49.264576",
"modified_by": "Administrator", "modified_by": "Administrator",
"module": "Accounts", "module": "Accounts",
"name": "Payment Entry", "name": "Payment Entry",

View File

@ -508,7 +508,7 @@ class PaymentEntry(AccountsController):
doc = frappe.get_doc("Expense Claim", d.reference_name) doc = frappe.get_doc("Expense Claim", d.reference_name)
update_reimbursed_amount(doc) update_reimbursed_amount(doc)
def on_recurring(self, reference_doc, subscription_doc): def on_recurring(self, reference_doc, auto_repeat_doc):
self.reference_no = reference_doc.name self.reference_no = reference_doc.name
self.reference_date = nowdate() self.reference_date = nowdate()

View File

@ -4033,7 +4033,7 @@
"bold": 0, "bold": 0,
"collapsible": 0, "collapsible": 0,
"columns": 0, "columns": 0,
"fieldname": "subscription", "fieldname": "auto_repeat",
"fieldtype": "Link", "fieldtype": "Link",
"hidden": 0, "hidden": 0,
"ignore_user_permissions": 0, "ignore_user_permissions": 0,
@ -4042,10 +4042,10 @@
"in_global_search": 0, "in_global_search": 0,
"in_list_view": 0, "in_list_view": 0,
"in_standard_filter": 0, "in_standard_filter": 0,
"label": "Subscription", "label": "Auto Repeat",
"length": 0, "length": 0,
"no_copy": 1, "no_copy": 1,
"options": "Subscription", "options": "Auto Repeat",
"permlevel": 0, "permlevel": 0,
"precision": "", "precision": "",
"print_hide": 1, "print_hide": 1,

View File

@ -727,7 +727,7 @@ class PurchaseInvoice(BuyingController):
if account_type != 'Fixed Asset': if account_type != 'Fixed Asset':
frappe.throw(_("Row {0}# Account must be of type 'Fixed Asset'").format(d.idx)) frappe.throw(_("Row {0}# Account must be of type 'Fixed Asset'").format(d.idx))
def on_recurring(self, reference_doc, subscription_doc): def on_recurring(self, reference_doc, auto_repeat_doc):
self.due_date = None self.due_date = None
def set_tax_withholding(self): def set_tax_withholding(self):

View File

@ -9,7 +9,7 @@ def get_data():
'Payment Request': 'reference_name', 'Payment Request': 'reference_name',
'Landed Cost Voucher': 'receipt_document', 'Landed Cost Voucher': 'receipt_document',
'Purchase Invoice': 'return_against', 'Purchase Invoice': 'return_against',
'Subscription': 'reference_document' 'Auto Repeat': 'reference_document'
}, },
'internal_links': { 'internal_links': {
'Purchase Order': ['items', 'purchase_order'], 'Purchase Order': ['items', 'purchase_order'],
@ -30,7 +30,7 @@ def get_data():
}, },
{ {
'label': _('Subscription'), 'label': _('Subscription'),
'items': ['Subscription'] 'items': ['Auto Repeat']
}, },
] ]
} }

View File

@ -97,7 +97,7 @@ def update_pos_profile_data(doc, pos_profile, company_data):
doc.conversion_rate = 1.0 doc.conversion_rate = 1.0
if doc.currency != company_data.default_currency: if doc.currency != company_data.default_currency:
doc.conversion_rate = get_exchange_rate(doc.currency, company_data.default_currency, doc.posting_date) doc.conversion_rate = get_exchange_rate(doc.currency, company_data.default_currency, doc.posting_date, args="for_selling")
doc.selling_price_list = pos_profile.get('selling_price_list') or \ doc.selling_price_list = pos_profile.get('selling_price_list') or \
frappe.db.get_value('Selling Settings', None, 'selling_price_list') frappe.db.get_value('Selling Settings', None, 'selling_price_list')

View File

@ -126,18 +126,6 @@ erpnext.accounts.SalesInvoiceController = erpnext.selling.SellingController.exte
$.each(doc["items"], function(i, row) { $.each(doc["items"], function(i, row) {
if(row.delivery_note) frappe.model.clear_doc("Delivery Note", row.delivery_note) if(row.delivery_note) frappe.model.clear_doc("Delivery Note", row.delivery_note)
}) })
if(this.frm.doc.is_pos) {
this.frm.msgbox = frappe.msgprint(
`<a class="btn btn-primary" onclick="cur_frm.print_preview.printit(true)" style="margin-right: 5px;">
${__('Print')}</a>
<a class="btn btn-default" href="javascript:frappe.new_doc(cur_frm.doctype);">
${__('New')}</a>`
);
} else if(cint(frappe.boot.notification_settings.sales_invoice)) {
this.frm.email_doc(frappe.boot.notification_settings.sales_invoice_message);
}
}, },
set_default_print_format: function() { set_default_print_format: function() {

View File

@ -4795,7 +4795,7 @@
"bold": 0, "bold": 0,
"collapsible": 0, "collapsible": 0,
"columns": 0, "columns": 0,
"fieldname": "subscription", "fieldname": "auto_repeat",
"fieldtype": "Link", "fieldtype": "Link",
"hidden": 0, "hidden": 0,
"ignore_user_permissions": 0, "ignore_user_permissions": 0,
@ -4804,10 +4804,10 @@
"in_global_search": 0, "in_global_search": 0,
"in_list_view": 0, "in_list_view": 0,
"in_standard_filter": 0, "in_standard_filter": 0,
"label": "Subscription", "label": "Auto Repeat",
"length": 0, "length": 0,
"no_copy": 1, "no_copy": 1,
"options": "Subscription", "options": "Auto Repeat",
"permlevel": 0, "permlevel": 0,
"precision": "", "precision": "",
"print_hide": 1, "print_hide": 1,
@ -4897,7 +4897,7 @@
"istable": 0, "istable": 0,
"max_attachments": 0, "max_attachments": 0,
"menu_index": 0, "menu_index": 0,
"modified": "2018-04-19 15:46:45.766533", "modified": "2018-04-30 16:19:54.711885",
"modified_by": "Administrator", "modified_by": "Administrator",
"module": "Accounts", "module": "Accounts",
"name": "Sales Invoice", "name": "Sales Invoice",

View File

@ -4,7 +4,7 @@
from __future__ import unicode_literals from __future__ import unicode_literals
import frappe, erpnext import frappe, erpnext
import frappe.defaults import frappe.defaults
from frappe.utils import cint, flt from frappe.utils import cint, flt, add_months, today, date_diff, getdate, add_days
from frappe import _, msgprint, throw from frappe import _, msgprint, throw
from erpnext.accounts.party import get_party_account, get_due_date from erpnext.accounts.party import get_party_account, get_due_date
from erpnext.controllers.stock_controller import update_gl_entries_after from erpnext.controllers.stock_controller import update_gl_entries_after
@ -111,7 +111,7 @@ class SalesInvoice(SellingController):
def on_submit(self): def on_submit(self):
self.validate_pos_paid_amount() self.validate_pos_paid_amount()
if not self.subscription: if not self.auto_repeat:
frappe.get_doc('Authorization Control').validate_approving_authority(self.doctype, frappe.get_doc('Authorization Control').validate_approving_authority(self.doctype,
self.company, self.base_grand_total, self) self.company, self.base_grand_total, self)
@ -699,7 +699,7 @@ class SalesInvoice(SellingController):
account_currency = get_account_currency(item.income_account) account_currency = get_account_currency(item.income_account)
gl_entries.append( gl_entries.append(
self.get_gl_dict({ self.get_gl_dict({
"account": item.income_account, "account": item.income_account if not item.enable_deferred_revenue else item.deferred_revenue_account,
"against": self.customer, "against": self.customer,
"credit": item.base_net_amount, "credit": item.base_net_amount,
"credit_in_account_currency": item.base_net_amount \ "credit_in_account_currency": item.base_net_amount \
@ -832,7 +832,7 @@ class SalesInvoice(SellingController):
for dn in set(updated_delivery_notes): for dn in set(updated_delivery_notes):
frappe.get_doc("Delivery Note", dn).update_billing_percentage(update_modified=update_modified) frappe.get_doc("Delivery Note", dn).update_billing_percentage(update_modified=update_modified)
def on_recurring(self, reference_doc, subscription_doc): def on_recurring(self, reference_doc, auto_repeat_doc):
for fieldname in ("c_form_applicable", "c_form_no", "write_off_amount"): for fieldname in ("c_form_applicable", "c_form_no", "write_off_amount"):
self.set(fieldname, reference_doc.get(fieldname)) self.set(fieldname, reference_doc.get(fieldname))
@ -917,6 +917,86 @@ class SalesInvoice(SellingController):
if entry.amount < 0: if entry.amount < 0:
frappe.throw(_("Row #{0} (Payment Table): Amount must be positive").format(entry.idx)) frappe.throw(_("Row #{0} (Payment Table): Amount must be positive").format(entry.idx))
def book_income_for_deferred_revenue(self):
# book the income on the last day, but it will be trigger on the 1st of month at 12:00 AM
# start_date: 1st of the last month or the start date
# end_date: end_date or today-1
gl_entries = []
for item in self.get('items'):
last_gl_entry = False
booking_start_date = getdate(add_months(today(), -1))
booking_start_date = booking_start_date if booking_start_date>item.service_start_date else item.service_start_date
booking_end_date = getdate(add_days(today(), -1))
if booking_end_date>=item.service_end_date:
last_gl_entry = True
booking_end_date = item.service_end_date
total_days = date_diff(item.service_end_date, item.service_start_date)
total_booking_days = date_diff(booking_end_date, booking_start_date) + 1
account_currency = get_account_currency(item.income_account)
if not last_gl_entry:
base_amount = flt(item.base_net_amount*total_booking_days/flt(total_days), item.precision("base_net_amount"))
if account_currency==self.company_currency:
amount = base_amount
else:
amount = flt(item.net_amount*total_booking_days/flt(total_days), item.precision("net_amount"))
else:
gl_entries_details = frappe.db.sql('''
select sum(debit) as total_debit, sum(debit_in_account_currency) as total_debit_in_account_currency, voucher_detail_no
from `tabGL Entry` where company=%s and account=%s and voucher_type=%s and voucher_no=%s and voucher_detail_no=%s
group by voucher_detail_no
''', (self.company, item.deferred_revenue_account, "Sales Invoice", self.name, item.name), as_dict=True)[0]
base_amount = flt(item.base_net_amount - gl_entries_details.total_debit, item.precision("base_net_amount"))
if account_currency==self.company_currency:
amount = base_amount
else:
amount = flt(item.net_amount - gl_entries_details.total_debit_in_account_currency, item.precision("net_amount"))
# GL Entry for crediting the amount in the income
gl_entries.append(
self.get_gl_dict({
"account": item.income_account,
"against": self.customer,
"credit": base_amount,
"credit_in_account_currency": amount,
"cost_center": item.cost_center,
'posting_date': booking_end_date
}, account_currency)
)
# GL Entry to debit the amount from the deferred account
gl_entries.append(
self.get_gl_dict({
"account": item.deferred_revenue_account,
"against": self.customer,
"debit": base_amount,
"debit_in_account_currency": amount,
"cost_center": item.cost_center,
"voucher_detail_no": item.name,
'posting_date': booking_end_date
}, account_currency)
)
if gl_entries:
from erpnext.accounts.general_ledger import make_gl_entries
make_gl_entries(gl_entries, cancel=(self.docstatus == 2), merge_entries=True)
def booked_deferred_revenue():
# check for the sales invoice for which GL entries has to be done
invoices = frappe.db.sql_list('''
select parent from `tabSales Invoice Item` where service_start_date<=%s and service_end_date>=%s
''', (today(), add_months(today(), -1)))
# ToDo also find the list on the basic of the GL entry, and make another list
for invoice in invoices:
doc = frappe.get_doc("Sales Invoice", invoice)
doc.book_income_for_deferred_revenue()
def validate_inter_company_party(doctype, party, company, inter_company_invoice_reference): def validate_inter_company_party(doctype, party, company, inter_company_invoice_reference):
if doctype == "Sales Invoice": if doctype == "Sales Invoice":
partytype, ref_partytype, internal = "Customer", "Supplier", "is_internal_customer" partytype, ref_partytype, internal = "Customer", "Supplier", "is_internal_customer"

View File

@ -9,7 +9,7 @@ def get_data():
'Payment Entry': 'reference_name', 'Payment Entry': 'reference_name',
'Payment Request': 'reference_name', 'Payment Request': 'reference_name',
'Sales Invoice': 'return_against', 'Sales Invoice': 'return_against',
'Subscription': 'reference_document', 'Auto Repeat': 'reference_document',
}, },
'internal_links': { 'internal_links': {
'Sales Order': ['items', 'sales_order'] 'Sales Order': ['items', 'sales_order']
@ -29,7 +29,7 @@ def get_data():
}, },
{ {
'label': _('Subscription'), 'label': _('Subscription'),
'items': ['Subscription'] 'items': ['Auto Repeat']
}, },
] ]
} }

View File

@ -40,6 +40,7 @@
"reqd": 0, "reqd": 0,
"search_index": 0, "search_index": 0,
"set_only_once": 0, "set_only_once": 0,
"translatable": 0,
"unique": 0 "unique": 0
}, },
{ {
@ -72,6 +73,7 @@
"reqd": 0, "reqd": 0,
"search_index": 1, "search_index": 1,
"set_only_once": 0, "set_only_once": 0,
"translatable": 0,
"unique": 0 "unique": 0
}, },
{ {
@ -100,6 +102,7 @@
"reqd": 0, "reqd": 0,
"search_index": 0, "search_index": 0,
"set_only_once": 0, "set_only_once": 0,
"translatable": 0,
"unique": 0 "unique": 0
}, },
{ {
@ -131,6 +134,7 @@
"reqd": 1, "reqd": 1,
"search_index": 0, "search_index": 0,
"set_only_once": 0, "set_only_once": 0,
"translatable": 0,
"unique": 0 "unique": 0
}, },
{ {
@ -160,6 +164,7 @@
"reqd": 0, "reqd": 0,
"search_index": 0, "search_index": 0,
"set_only_once": 0, "set_only_once": 0,
"translatable": 0,
"unique": 0 "unique": 0
}, },
{ {
@ -190,6 +195,7 @@
"reqd": 0, "reqd": 0,
"search_index": 0, "search_index": 0,
"set_only_once": 0, "set_only_once": 0,
"translatable": 0,
"unique": 0 "unique": 0
}, },
{ {
@ -222,6 +228,7 @@
"reqd": 1, "reqd": 1,
"search_index": 0, "search_index": 0,
"set_only_once": 0, "set_only_once": 0,
"translatable": 0,
"unique": 0, "unique": 0,
"width": "200px" "width": "200px"
}, },
@ -252,6 +259,7 @@
"reqd": 0, "reqd": 0,
"search_index": 0, "search_index": 0,
"set_only_once": 0, "set_only_once": 0,
"translatable": 0,
"unique": 0 "unique": 0
}, },
{ {
@ -283,6 +291,7 @@
"reqd": 0, "reqd": 0,
"search_index": 0, "search_index": 0,
"set_only_once": 0, "set_only_once": 0,
"translatable": 0,
"unique": 0 "unique": 0
}, },
{ {
@ -313,6 +322,7 @@
"reqd": 0, "reqd": 0,
"search_index": 0, "search_index": 0,
"set_only_once": 0, "set_only_once": 0,
"translatable": 0,
"unique": 0 "unique": 0
}, },
{ {
@ -342,6 +352,7 @@
"reqd": 0, "reqd": 0,
"search_index": 0, "search_index": 0,
"set_only_once": 0, "set_only_once": 0,
"translatable": 0,
"unique": 0 "unique": 0
}, },
{ {
@ -373,6 +384,7 @@
"reqd": 0, "reqd": 0,
"search_index": 0, "search_index": 0,
"set_only_once": 0, "set_only_once": 0,
"translatable": 0,
"unique": 0 "unique": 0
}, },
{ {
@ -403,6 +415,7 @@
"reqd": 0, "reqd": 0,
"search_index": 0, "search_index": 0,
"set_only_once": 0, "set_only_once": 0,
"translatable": 0,
"unique": 0 "unique": 0
}, },
{ {
@ -431,6 +444,7 @@
"reqd": 0, "reqd": 0,
"search_index": 0, "search_index": 0,
"set_only_once": 0, "set_only_once": 0,
"translatable": 0,
"unique": 0 "unique": 0
}, },
{ {
@ -462,6 +476,7 @@
"reqd": 1, "reqd": 1,
"search_index": 0, "search_index": 0,
"set_only_once": 0, "set_only_once": 0,
"translatable": 0,
"unique": 0 "unique": 0
}, },
{ {
@ -492,6 +507,7 @@
"reqd": 1, "reqd": 1,
"search_index": 0, "search_index": 0,
"set_only_once": 0, "set_only_once": 0,
"translatable": 0,
"unique": 0 "unique": 0
}, },
{ {
@ -522,6 +538,7 @@
"reqd": 0, "reqd": 0,
"search_index": 0, "search_index": 0,
"set_only_once": 0, "set_only_once": 0,
"translatable": 0,
"unique": 0 "unique": 0
}, },
{ {
@ -551,6 +568,7 @@
"reqd": 0, "reqd": 0,
"search_index": 0, "search_index": 0,
"set_only_once": 0, "set_only_once": 0,
"translatable": 0,
"unique": 0 "unique": 0
}, },
{ {
@ -583,6 +601,7 @@
"reqd": 0, "reqd": 0,
"search_index": 0, "search_index": 0,
"set_only_once": 0, "set_only_once": 0,
"translatable": 0,
"unique": 0 "unique": 0
}, },
{ {
@ -615,6 +634,7 @@
"reqd": 0, "reqd": 0,
"search_index": 0, "search_index": 0,
"set_only_once": 0, "set_only_once": 0,
"translatable": 0,
"unique": 0 "unique": 0
}, },
{ {
@ -645,6 +665,7 @@
"reqd": 0, "reqd": 0,
"search_index": 0, "search_index": 0,
"set_only_once": 0, "set_only_once": 0,
"translatable": 0,
"unique": 0 "unique": 0
}, },
{ {
@ -677,6 +698,7 @@
"reqd": 0, "reqd": 0,
"search_index": 0, "search_index": 0,
"set_only_once": 0, "set_only_once": 0,
"translatable": 0,
"unique": 0 "unique": 0
}, },
{ {
@ -708,6 +730,7 @@
"reqd": 0, "reqd": 0,
"search_index": 0, "search_index": 0,
"set_only_once": 0, "set_only_once": 0,
"translatable": 0,
"unique": 0 "unique": 0
}, },
{ {
@ -740,6 +763,7 @@
"reqd": 0, "reqd": 0,
"search_index": 0, "search_index": 0,
"set_only_once": 0, "set_only_once": 0,
"translatable": 0,
"unique": 0 "unique": 0
}, },
{ {
@ -769,6 +793,7 @@
"reqd": 0, "reqd": 0,
"search_index": 0, "search_index": 0,
"set_only_once": 0, "set_only_once": 0,
"translatable": 0,
"unique": 0 "unique": 0
}, },
{ {
@ -802,6 +827,7 @@
"reqd": 0, "reqd": 0,
"search_index": 0, "search_index": 0,
"set_only_once": 0, "set_only_once": 0,
"translatable": 0,
"unique": 0 "unique": 0
}, },
{ {
@ -834,6 +860,7 @@
"reqd": 0, "reqd": 0,
"search_index": 0, "search_index": 0,
"set_only_once": 0, "set_only_once": 0,
"translatable": 0,
"unique": 0 "unique": 0
}, },
{ {
@ -862,6 +889,7 @@
"reqd": 0, "reqd": 0,
"search_index": 0, "search_index": 0,
"set_only_once": 0, "set_only_once": 0,
"translatable": 0,
"unique": 0 "unique": 0
}, },
{ {
@ -894,6 +922,7 @@
"reqd": 1, "reqd": 1,
"search_index": 0, "search_index": 0,
"set_only_once": 0, "set_only_once": 0,
"translatable": 0,
"unique": 0 "unique": 0
}, },
{ {
@ -926,6 +955,7 @@
"reqd": 1, "reqd": 1,
"search_index": 0, "search_index": 0,
"set_only_once": 0, "set_only_once": 0,
"translatable": 0,
"unique": 0 "unique": 0
}, },
{ {
@ -954,6 +984,7 @@
"reqd": 0, "reqd": 0,
"search_index": 0, "search_index": 0,
"set_only_once": 0, "set_only_once": 0,
"translatable": 0,
"unique": 0 "unique": 0
}, },
{ {
@ -986,6 +1017,7 @@
"reqd": 1, "reqd": 1,
"search_index": 0, "search_index": 0,
"set_only_once": 0, "set_only_once": 0,
"translatable": 0,
"unique": 0 "unique": 0
}, },
{ {
@ -1018,6 +1050,7 @@
"reqd": 1, "reqd": 1,
"search_index": 0, "search_index": 0,
"set_only_once": 0, "set_only_once": 0,
"translatable": 0,
"unique": 0 "unique": 0
}, },
{ {
@ -1048,6 +1081,7 @@
"reqd": 0, "reqd": 0,
"search_index": 0, "search_index": 0,
"set_only_once": 0, "set_only_once": 0,
"translatable": 0,
"unique": 0 "unique": 0
}, },
{ {
@ -1077,6 +1111,7 @@
"reqd": 0, "reqd": 0,
"search_index": 0, "search_index": 0,
"set_only_once": 0, "set_only_once": 0,
"translatable": 0,
"unique": 0 "unique": 0
}, },
{ {
@ -1108,6 +1143,7 @@
"reqd": 0, "reqd": 0,
"search_index": 0, "search_index": 0,
"set_only_once": 0, "set_only_once": 0,
"translatable": 0,
"unique": 0 "unique": 0
}, },
{ {
@ -1139,6 +1175,7 @@
"reqd": 0, "reqd": 0,
"search_index": 0, "search_index": 0,
"set_only_once": 0, "set_only_once": 0,
"translatable": 0,
"unique": 0 "unique": 0
}, },
{ {
@ -1168,6 +1205,7 @@
"reqd": 0, "reqd": 0,
"search_index": 0, "search_index": 0,
"set_only_once": 0, "set_only_once": 0,
"translatable": 0,
"unique": 0 "unique": 0
}, },
{ {
@ -1199,6 +1237,7 @@
"reqd": 0, "reqd": 0,
"search_index": 0, "search_index": 0,
"set_only_once": 0, "set_only_once": 0,
"translatable": 0,
"unique": 0 "unique": 0
}, },
{ {
@ -1230,6 +1269,7 @@
"reqd": 0, "reqd": 0,
"search_index": 0, "search_index": 0,
"set_only_once": 0, "set_only_once": 0,
"translatable": 0,
"unique": 0 "unique": 0
}, },
{ {
@ -1261,6 +1301,7 @@
"reqd": 0, "reqd": 0,
"search_index": 0, "search_index": 0,
"set_only_once": 0, "set_only_once": 0,
"translatable": 0,
"unique": 0 "unique": 0
}, },
{ {
@ -1291,6 +1332,7 @@
"reqd": 0, "reqd": 0,
"search_index": 0, "search_index": 0,
"set_only_once": 0, "set_only_once": 0,
"translatable": 0,
"unique": 0 "unique": 0
}, },
{ {
@ -1320,6 +1362,7 @@
"reqd": 0, "reqd": 0,
"search_index": 0, "search_index": 0,
"set_only_once": 0, "set_only_once": 0,
"translatable": 0,
"unique": 0 "unique": 0
}, },
{ {
@ -1353,6 +1396,7 @@
"reqd": 1, "reqd": 1,
"search_index": 0, "search_index": 0,
"set_only_once": 0, "set_only_once": 0,
"translatable": 0,
"unique": 0, "unique": 0,
"width": "120px" "width": "120px"
}, },
@ -1384,6 +1428,7 @@
"reqd": 0, "reqd": 0,
"search_index": 0, "search_index": 0,
"set_only_once": 0, "set_only_once": 0,
"translatable": 0,
"unique": 0, "unique": 0,
"width": "120px" "width": "120px"
}, },
@ -1413,6 +1458,7 @@
"reqd": 0, "reqd": 0,
"search_index": 0, "search_index": 0,
"set_only_once": 0, "set_only_once": 0,
"translatable": 0,
"unique": 0 "unique": 0
}, },
{ {
@ -1447,9 +1493,200 @@
"reqd": 1, "reqd": 1,
"search_index": 0, "search_index": 0,
"set_only_once": 0, "set_only_once": 0,
"translatable": 0,
"unique": 0, "unique": 0,
"width": "120px" "width": "120px"
}, },
{
"allow_bulk_edit": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 1,
"columns": 0,
"fieldname": "deferred_revenue",
"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": "Deferred Revenue",
"length": 0,
"no_copy": 0,
"permlevel": 0,
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 0,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"translatable": 0,
"unique": 0
},
{
"allow_bulk_edit": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"default": "0",
"fieldname": "enable_deferred_revenue",
"fieldtype": "Check",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 0,
"in_standard_filter": 0,
"label": "Enable Deferred Revenue",
"length": 0,
"no_copy": 0,
"permlevel": 0,
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 0,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"translatable": 0,
"unique": 0
},
{
"allow_bulk_edit": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"depends_on": "enable_deferred_revenue",
"fieldname": "deferred_revenue_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": "Deferred Revenue 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,
"translatable": 0,
"unique": 0
},
{
"allow_bulk_edit": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "column_break_50",
"fieldtype": "Column Break",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 0,
"in_standard_filter": 0,
"length": 0,
"no_copy": 0,
"permlevel": 0,
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 0,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"translatable": 0,
"unique": 0
},
{
"allow_bulk_edit": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"depends_on": "enable_deferred_revenue",
"fieldname": "service_start_date",
"fieldtype": "Date",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 0,
"in_standard_filter": 0,
"label": "Service Start 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": 0,
"search_index": 0,
"set_only_once": 0,
"translatable": 0,
"unique": 0
},
{
"allow_bulk_edit": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"depends_on": "enable_deferred_revenue",
"fieldname": "service_end_date",
"fieldtype": "Date",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 0,
"in_standard_filter": 0,
"label": "Service End 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": 0,
"search_index": 0,
"set_only_once": 0,
"translatable": 0,
"unique": 0
},
{ {
"allow_bulk_edit": 0, "allow_bulk_edit": 0,
"allow_on_submit": 0, "allow_on_submit": 0,
@ -1478,6 +1715,7 @@
"reqd": 0, "reqd": 0,
"search_index": 0, "search_index": 0,
"set_only_once": 0, "set_only_once": 0,
"translatable": 0,
"unique": 0 "unique": 0
}, },
{ {
@ -1509,6 +1747,7 @@
"reqd": 0, "reqd": 0,
"search_index": 0, "search_index": 0,
"set_only_once": 0, "set_only_once": 0,
"translatable": 0,
"unique": 0 "unique": 0
}, },
{ {
@ -1539,6 +1778,7 @@
"reqd": 0, "reqd": 0,
"search_index": 0, "search_index": 0,
"set_only_once": 0, "set_only_once": 0,
"translatable": 0,
"unique": 0 "unique": 0
}, },
{ {
@ -1568,6 +1808,7 @@
"reqd": 0, "reqd": 0,
"search_index": 0, "search_index": 0,
"set_only_once": 0, "set_only_once": 0,
"translatable": 0,
"unique": 0 "unique": 0
}, },
{ {
@ -1599,6 +1840,7 @@
"reqd": 0, "reqd": 0,
"search_index": 0, "search_index": 0,
"set_only_once": 0, "set_only_once": 0,
"translatable": 0,
"unique": 0 "unique": 0
}, },
{ {
@ -1630,6 +1872,7 @@
"reqd": 0, "reqd": 0,
"search_index": 0, "search_index": 0,
"set_only_once": 0, "set_only_once": 0,
"translatable": 0,
"unique": 0 "unique": 0
}, },
{ {
@ -1662,6 +1905,7 @@
"reqd": 0, "reqd": 0,
"search_index": 0, "search_index": 0,
"set_only_once": 0, "set_only_once": 0,
"translatable": 0,
"unique": 0 "unique": 0
}, },
{ {
@ -1693,6 +1937,7 @@
"reqd": 0, "reqd": 0,
"search_index": 0, "search_index": 0,
"set_only_once": 0, "set_only_once": 0,
"translatable": 0,
"unique": 0 "unique": 0
}, },
{ {
@ -1725,6 +1970,7 @@
"reqd": 0, "reqd": 0,
"search_index": 0, "search_index": 0,
"set_only_once": 0, "set_only_once": 0,
"translatable": 0,
"unique": 0 "unique": 0
}, },
{ {
@ -1755,6 +2001,7 @@
"reqd": 0, "reqd": 0,
"search_index": 0, "search_index": 0,
"set_only_once": 0, "set_only_once": 0,
"translatable": 0,
"unique": 0 "unique": 0
}, },
{ {
@ -1783,6 +2030,7 @@
"reqd": 0, "reqd": 0,
"search_index": 0, "search_index": 0,
"set_only_once": 0, "set_only_once": 0,
"translatable": 0,
"unique": 0 "unique": 0
}, },
{ {
@ -1813,6 +2061,7 @@
"reqd": 0, "reqd": 0,
"search_index": 0, "search_index": 0,
"set_only_once": 0, "set_only_once": 0,
"translatable": 0,
"unique": 0 "unique": 0
}, },
{ {
@ -1844,6 +2093,7 @@
"reqd": 0, "reqd": 0,
"search_index": 0, "search_index": 0,
"set_only_once": 0, "set_only_once": 0,
"translatable": 0,
"unique": 0 "unique": 0
}, },
{ {
@ -1877,6 +2127,7 @@
"reqd": 0, "reqd": 0,
"search_index": 0, "search_index": 0,
"set_only_once": 0, "set_only_once": 0,
"translatable": 0,
"unique": 0 "unique": 0
}, },
{ {
@ -1908,6 +2159,7 @@
"reqd": 0, "reqd": 0,
"search_index": 0, "search_index": 0,
"set_only_once": 0, "set_only_once": 0,
"translatable": 0,
"unique": 0 "unique": 0
}, },
{ {
@ -1939,6 +2191,7 @@
"reqd": 0, "reqd": 0,
"search_index": 0, "search_index": 0,
"set_only_once": 0, "set_only_once": 0,
"translatable": 0,
"unique": 0 "unique": 0
}, },
{ {
@ -1970,6 +2223,7 @@
"reqd": 0, "reqd": 0,
"search_index": 0, "search_index": 0,
"set_only_once": 0, "set_only_once": 0,
"translatable": 0,
"unique": 0, "unique": 0,
"width": "150px" "width": "150px"
}, },
@ -2002,6 +2256,7 @@
"reqd": 0, "reqd": 0,
"search_index": 0, "search_index": 0,
"set_only_once": 0, "set_only_once": 0,
"translatable": 0,
"unique": 0 "unique": 0
}, },
{ {
@ -2032,6 +2287,7 @@
"reqd": 0, "reqd": 0,
"search_index": 0, "search_index": 0,
"set_only_once": 0, "set_only_once": 0,
"translatable": 0,
"unique": 0 "unique": 0
}, },
{ {
@ -2064,6 +2320,7 @@
"reqd": 0, "reqd": 0,
"search_index": 1, "search_index": 1,
"set_only_once": 0, "set_only_once": 0,
"translatable": 0,
"unique": 0 "unique": 0
}, },
{ {
@ -2095,6 +2352,7 @@
"reqd": 0, "reqd": 0,
"search_index": 1, "search_index": 1,
"set_only_once": 0, "set_only_once": 0,
"translatable": 0,
"unique": 0 "unique": 0
}, },
{ {
@ -2103,7 +2361,7 @@
"bold": 0, "bold": 0,
"collapsible": 0, "collapsible": 0,
"columns": 0, "columns": 0,
"fieldname": "column_break_50", "fieldname": "column_break_74",
"fieldtype": "Column Break", "fieldtype": "Column Break",
"hidden": 0, "hidden": 0,
"ignore_user_permissions": 0, "ignore_user_permissions": 0,
@ -2124,6 +2382,7 @@
"reqd": 0, "reqd": 0,
"search_index": 0, "search_index": 0,
"set_only_once": 0, "set_only_once": 0,
"translatable": 0,
"unique": 0 "unique": 0
}, },
{ {
@ -2156,6 +2415,7 @@
"reqd": 0, "reqd": 0,
"search_index": 1, "search_index": 1,
"set_only_once": 0, "set_only_once": 0,
"translatable": 0,
"unique": 0 "unique": 0
}, },
{ {
@ -2187,6 +2447,7 @@
"reqd": 0, "reqd": 0,
"search_index": 1, "search_index": 1,
"set_only_once": 0, "set_only_once": 0,
"translatable": 0,
"unique": 0 "unique": 0
}, },
{ {
@ -2218,6 +2479,7 @@
"reqd": 0, "reqd": 0,
"search_index": 0, "search_index": 0,
"set_only_once": 0, "set_only_once": 0,
"translatable": 0,
"unique": 0 "unique": 0
}, },
{ {
@ -2248,6 +2510,7 @@
"reqd": 0, "reqd": 0,
"search_index": 0, "search_index": 0,
"set_only_once": 0, "set_only_once": 0,
"translatable": 0,
"unique": 0 "unique": 0
}, },
{ {
@ -2279,6 +2542,7 @@
"reqd": 0, "reqd": 0,
"search_index": 0, "search_index": 0,
"set_only_once": 0, "set_only_once": 0,
"translatable": 0,
"unique": 0 "unique": 0
}, },
{ {
@ -2308,6 +2572,7 @@
"reqd": 0, "reqd": 0,
"search_index": 0, "search_index": 0,
"set_only_once": 0, "set_only_once": 0,
"translatable": 0,
"unique": 0 "unique": 0
}, },
{ {
@ -2337,6 +2602,7 @@
"reqd": 0, "reqd": 0,
"search_index": 0, "search_index": 0,
"set_only_once": 0, "set_only_once": 0,
"translatable": 0,
"unique": 0 "unique": 0
} }
], ],
@ -2350,7 +2616,7 @@
"issingle": 0, "issingle": 0,
"istable": 1, "istable": 1,
"max_attachments": 0, "max_attachments": 0,
"modified": "2017-11-29 13:42:59.003120", "modified": "2018-05-14 06:26:59.609228",
"modified_by": "Administrator", "modified_by": "Administrator",
"module": "Accounts", "module": "Accounts",
"name": "Sales Invoice Item", "name": "Sales Invoice Item",

View File

@ -0,0 +1,2 @@
// Copyright (c) 2018, Frappe Technologies Pvt. Ltd. and contributors
// For license information, please see license.txt

View File

@ -0,0 +1,126 @@
{
"allow_copy": 0,
"allow_guest_to_view": 0,
"allow_import": 0,
"allow_rename": 0,
"autoname": "field:subscriber_name",
"beta": 0,
"creation": "2018-02-24 11:17:46.809140",
"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": "subscriber_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": "Subscriber Name",
"length": 0,
"no_copy": 0,
"permlevel": 0,
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 0,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 1,
"search_index": 0,
"set_only_once": 0,
"translatable": 0,
"unique": 0
},
{
"allow_bulk_edit": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "customer",
"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": "Customer",
"length": 0,
"no_copy": 0,
"options": "Customer",
"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
}
],
"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": "2018-02-26 04:40:16.510290",
"modified_by": "Administrator",
"module": "Accounts",
"name": "Subscriber",
"name_case": "",
"owner": "Administrator",
"permissions": [
{
"amend": 0,
"apply_user_permissions": 0,
"cancel": 0,
"create": 1,
"delete": 1,
"email": 1,
"export": 1,
"if_owner": 0,
"import": 0,
"permlevel": 0,
"print": 1,
"read": 1,
"report": 1,
"role": "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,9 @@
# -*- coding: utf-8 -*-
# Copyright (c) 2018, Frappe Technologies Pvt. Ltd. and contributors
# For license information, please see license.txt
from __future__ import unicode_literals
from frappe.model.document import Document
class Subscriber(Document):
pass

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: Subscriber", function (assert) {
let done = assert.async();
// number of asserts
assert.expect(1);
frappe.run_serially([
// insert a new Subscriber
() => frappe.tests.make('Subscriber', [
// values to be set
{key: 'value'}
]),
() => {
assert.equal(cur_frm.doc.key, 'value');
},
() => done()
]);
});

View File

@ -0,0 +1,9 @@
# -*- coding: utf-8 -*-
# Copyright (c) 2018, Frappe Technologies Pvt. Ltd. and Contributors
# See license.txt
from __future__ import unicode_literals
import unittest
class TestSubscriber(unittest.TestCase):
pass

View File

@ -1,72 +1,75 @@
// Copyright (c) 2017, Frappe Technologies Pvt. Ltd. and contributors // Copyright (c) 2018, Frappe Technologies Pvt. Ltd. and contributors
// For license information, please see license.txt // For license information, please see license.txt
frappe.ui.form.on('Subscription', { frappe.ui.form.on('Subscription', {
setup: function(frm) {
frm.fields_dict['reference_doctype'].get_query = function(doc) {
return {
query: "erpnext.accounts.doctype.subscription.subscription.subscription_doctype_query"
};
};
frm.fields_dict['reference_document'].get_query = function() {
return {
filters: {
"docstatus": 1,
"subscription": ''
}
};
};
frm.fields_dict['print_format'].get_query = function() {
return {
filters: {
"doc_type": frm.doc.reference_doctype
}
};
};
},
refresh: function(frm) { refresh: function(frm) {
if(frm.doc.docstatus == 1) { if(!frm.is_new()){
let label = __('View {0}', [frm.doc.reference_doctype]); if(frm.doc.status !== 'Cancelled'){
frm.add_custom_button(__(label), frm.add_custom_button(
function() { __('Cancel Subscription'),
frappe.route_options = { () => frm.events.cancel_this_subscription(frm)
"subscription": frm.doc.name, );
}; frm.add_custom_button(
frappe.set_route("List", frm.doc.reference_doctype); __('Fetch Subscription Updates'),
} () => frm.events.get_subscription_updates(frm)
);
if(frm.doc.status != 'Stopped') {
frm.add_custom_button(__("Stop"),
function() {
frm.events.stop_resume_subscription(frm, "Stopped");
}
); );
} }
else if(frm.doc.status === 'Cancelled'){
if(frm.doc.status == 'Stopped') { frm.add_custom_button(
frm.add_custom_button(__("Resume"), __('Restart Subscription'),
function() { () => frm.events.renew_this_subscription(frm)
frm.events.stop_resume_subscription(frm, "Resumed");
}
); );
} }
} }
}, },
stop_resume_subscription: function(frm, status) { cancel_this_subscription: function(frm) {
const doc = frm.doc;
frappe.confirm(
__('This action will stop future billing. Are you sure you want to cancel this subscription?'),
function() {
frappe.call({
method:
"erpnext.accounts.doctype.subscription.subscription.cancel_subscription",
args: {name: doc.name},
callback: function(data){
if(!data.exc){
frm.reload_doc();
}
}
});
}
);
},
renew_this_subscription: function(frm) {
const doc = frm.doc;
frappe.confirm(
__('You will lose records of previously generated invoices. Are you sure you want to restart this subscription?'),
function() {
frappe.call({
method:
"erpnext.accounts.doctype.subscription.subscription.restart_subscription",
args: {name: doc.name},
callback: function(data){
if(!data.exc){
frm.reload_doc();
}
}
});
}
);
},
get_subscription_updates: function(frm) {
const doc = frm.doc;
frappe.call({ frappe.call({
method: "erpnext.accounts.doctype.subscription.subscription.stop_resume_subscription", method:
args: { "erpnext.accounts.doctype.subscription.subscription.get_subscription_updates",
subscription: frm.doc.name, args: {name: doc.name},
status: status freeze: true,
}, callback: function(data){
callback: function(r) { if(!data.exc){
if(r.message) {
frm.set_value("status", r.message);
frm.reload_doc(); frm.reload_doc();
} }
} }

File diff suppressed because it is too large Load Diff

View File

@ -1,315 +1,500 @@
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
# Copyright (c) 2017, Frappe Technologies Pvt. Ltd. and contributors # Copyright (c) 2018, Frappe Technologies Pvt. Ltd. and contributors
# For license information, please see license.txt # For license information, please see license.txt
from __future__ import unicode_literals from __future__ import unicode_literals
import frappe import frappe
import calendar
from frappe import _ from frappe import _
from frappe.desk.form import assign_to
from frappe.utils.jinja import validate_template
from dateutil.relativedelta import relativedelta
from frappe.utils.user import get_system_managers
from frappe.utils import cstr, getdate, split_emails, add_days, today, get_last_day, get_first_day
from frappe.model.document import Document from frappe.model.document import Document
from frappe.utils.data import nowdate, getdate, cint, add_days, date_diff, get_last_day, add_to_date, flt
month_map = {'Monthly': 1, 'Quarterly': 3, 'Half-yearly': 6, 'Yearly': 12}
class Subscription(Document): class Subscription(Document):
def validate(self): def before_insert(self):
self.update_status() # update start just before the subscription doc is created
self.validate_reference_doctype() self.update_subscription_period(self.start)
self.validate_dates()
self.validate_next_schedule_date()
self.validate_email_id()
validate_template(self.subject or "") def update_subscription_period(self, date=None):
validate_template(self.message or "") """
Subscription period is the period to be billed. This method updates the
beginning of the billing period and end of the billing period.
def before_submit(self): The beginning of the billing period is represented in the doctype as
if not self.next_schedule_date: `current_invoice_start` and the end of the billing period is represented
self.next_schedule_date = get_next_schedule_date(self.start_date, as `current_invoice_end`.
self.frequency, self.repeat_on_day) """
self.set_current_invoice_start(date)
self.set_current_invoice_end()
def on_submit(self): def set_current_invoice_start(self, date=None):
self.update_subscription_id() """
This sets the date of the beginning of the current billing period.
If the `date` parameter is not given , it will be automatically set as today's
date.
"""
if self.trial_period_start and self.is_trialling():
self.current_invoice_start = self.trial_period_start
elif not date:
self.current_invoice_start = nowdate()
elif date:
self.current_invoice_start = date
def on_update_after_submit(self): def set_current_invoice_end(self):
self.validate_dates() """
self.set_next_schedule_date() This sets the date of the end of the current billing period.
def before_cancel(self): If the subscription is in trial period, it will be set as the end of the
self.unlink_subscription_id() trial period.
self.next_schedule_date = None
def unlink_subscription_id(self): If is not in a trial period, it will be `x` days from the beginning of the
frappe.db.sql("update `tab{0}` set subscription = null where subscription=%s" current billing period where `x` is the billing interval from the
.format(self.reference_doctype), self.name) `Subscription Plan` in the `Subscription`.
"""
def validate_reference_doctype(self): if self.is_trialling():
if not frappe.get_meta(self.reference_doctype).has_field('subscription'): self.current_invoice_end = self.trial_period_end
frappe.throw(_("Add custom field Subscription in the doctype {0}").format(self.reference_doctype))
def validate_dates(self):
if self.end_date and getdate(self.start_date) > getdate(self.end_date):
frappe.throw(_("End date must be greater than start date"))
def validate_next_schedule_date(self):
if self.repeat_on_day and self.next_schedule_date:
next_date = getdate(self.next_schedule_date)
if next_date.day != self.repeat_on_day:
# if the repeat day is the last day of the month (31)
# and the current month does not have as many days,
# then the last day of the current month is a valid date
lastday = calendar.monthrange(next_date.year, next_date.month)[1]
if self.repeat_on_day < lastday:
# the specified day of the month is not same as the day specified
# or the last day of the month
frappe.throw(_("Next Date's day and Repeat on Day of Month must be equal"))
def validate_email_id(self):
if self.notify_by_email:
if self.recipients:
email_list = split_emails(self.recipients.replace("\n", ""))
from frappe.utils import validate_email_add
for email in email_list:
if not validate_email_add(email):
frappe.throw(_("{0} is an invalid email address in 'Recipients'").format(email))
else:
frappe.throw(_("'Recipients' not specified"))
def set_next_schedule_date(self):
if self.repeat_on_day:
self.next_schedule_date = get_next_date(self.next_schedule_date, 0, self.repeat_on_day)
def update_subscription_id(self):
frappe.db.set_value(self.reference_doctype, self.reference_document, "subscription", self.name)
def update_status(self, status=None):
self.status = {
'0': 'Draft',
'1': 'Submitted',
'2': 'Cancelled'
}[cstr(self.docstatus or 0)]
if status and status != 'Resumed':
self.status = status
def get_next_schedule_date(start_date, frequency, repeat_on_day):
mcount = month_map.get(frequency)
if mcount:
next_date = get_next_date(start_date, mcount, repeat_on_day)
else:
days = 7 if frequency == 'Weekly' else 1
next_date = add_days(start_date, days)
return next_date
def make_subscription_entry(date=None):
date = date or today()
for data in get_subscription_entries(date):
schedule_date = getdate(data.next_schedule_date)
while schedule_date <= getdate(today()):
create_documents(data, schedule_date)
schedule_date = get_next_schedule_date(schedule_date,
data.frequency, data.repeat_on_day)
if schedule_date and not frappe.db.get_value('Subscription', data.name, 'disabled'):
frappe.db.set_value('Subscription', data.name, 'next_schedule_date', schedule_date)
def get_subscription_entries(date):
return frappe.db.sql(""" select * from `tabSubscription`
where docstatus = 1 and next_schedule_date <=%s
and reference_document is not null and reference_document != ''
and next_schedule_date <= ifnull(end_date, '2199-12-31')
and ifnull(disabled, 0) = 0 and status != 'Stopped' """, (date), as_dict=1)
def create_documents(data, schedule_date):
try:
doc = make_new_document(data, schedule_date)
if data.notify_by_email and data.recipients:
print_format = data.print_format or "Standard"
send_notification(doc, data, print_format=print_format)
frappe.db.commit()
except Exception:
frappe.db.rollback()
frappe.db.begin()
frappe.log_error(frappe.get_traceback())
disable_subscription(data)
frappe.db.commit()
if data.reference_document and not frappe.flags.in_test:
notify_error_to_user(data)
def disable_subscription(data):
subscription = frappe.get_doc('Subscription', data.name)
subscription.db_set('disabled', 1)
def notify_error_to_user(data):
party = ''
party_type = ''
if data.reference_doctype in ['Sales Order', 'Sales Invoice', 'Delivery Note']:
party_type = 'customer'
elif data.reference_doctype in ['Purchase Order', 'Purchase Invoice', 'Purchase Receipt']:
party_type = 'supplier'
if party_type:
party = frappe.db.get_value(data.reference_doctype, data.reference_document, party_type)
notify_errors(data.reference_document, data.reference_doctype, party, data.owner, data.name)
def make_new_document(args, schedule_date):
doc = frappe.get_doc(args.reference_doctype, args.reference_document)
new_doc = frappe.copy_doc(doc, ignore_no_copy=False)
update_doc(new_doc, doc , args, schedule_date)
new_doc.insert(ignore_permissions=True)
if args.submit_on_creation:
new_doc.submit()
return new_doc
def update_doc(new_document, reference_doc, args, schedule_date):
new_document.docstatus = 0
if new_document.meta.get_field('set_posting_time'):
new_document.set('set_posting_time', 1)
mcount = month_map.get(args.frequency)
if new_document.meta.get_field('subscription'):
new_document.set('subscription', args.name)
for fieldname in ['naming_series', 'ignore_pricing_rule', 'posting_time'
'select_print_heading', 'remarks', 'owner']:
if new_document.meta.get_field(fieldname):
new_document.set(fieldname, reference_doc.get(fieldname))
# copy item fields
if new_document.meta.get_field('items'):
for i, item in enumerate(new_document.items):
for fieldname in ("page_break",):
item.set(fieldname, reference_doc.items[i].get(fieldname))
for data in new_document.meta.fields:
if data.fieldtype == 'Date' and data.reqd:
new_document.set(data.fieldname, schedule_date)
set_subscription_period(args, mcount, new_document)
new_document.run_method("on_recurring", reference_doc=reference_doc, subscription_doc=args)
def set_subscription_period(args, mcount, new_document):
if mcount and new_document.meta.get_field('from_date') and new_document.meta.get_field('to_date'):
last_ref_doc = frappe.db.sql("""
select name, from_date, to_date
from `tab{0}`
where subscription=%s and docstatus < 2
order by creation desc
limit 1
""".format(args.reference_doctype), args.name, as_dict=1)
if not last_ref_doc:
return
from_date = get_next_date(last_ref_doc[0].from_date, mcount)
if (cstr(get_first_day(last_ref_doc[0].from_date)) == cstr(last_ref_doc[0].from_date)) and \
(cstr(get_last_day(last_ref_doc[0].to_date)) == cstr(last_ref_doc[0].to_date)):
to_date = get_last_day(get_next_date(last_ref_doc[0].to_date, mcount))
else: else:
to_date = get_next_date(last_ref_doc[0].to_date, mcount) billing_cycle_info = self.get_billing_cycle()
if billing_cycle_info:
self.current_invoice_end = add_to_date(self.current_invoice_start, **billing_cycle_info)
else:
self.current_invoice_end = get_last_day(self.current_invoice_start)
new_document.set('from_date', from_date) def get_billing_cycle(self):
new_document.set('to_date', to_date) """
Returns a dict containing billing cycle information deduced from the
`Subscription Plan` in the `Subscription`.
"""
return self.get_billing_cycle_data()
def get_next_date(dt, mcount, day=None): @staticmethod
dt = getdate(dt) def validate_plans_billing_cycle(billing_cycle_data):
dt += relativedelta(months=mcount, day=day) """
Makes sure that all `Subscription Plan` in the `Subscription` have the
same billing interval
"""
if billing_cycle_data and len(billing_cycle_data) != 1:
frappe.throw(_('You can only have Plans with the same billing cycle in a Subscription'))
return dt def get_billing_cycle_and_interval(self):
"""
Returns a dict representing the billing interval and cycle for this `Subscription`.
def send_notification(new_rv, subscription_doc, print_format='Standard'): You shouldn't need to call this directly. Use `get_billing_cycle` instead.
"""Notify concerned persons about recurring document generation""" """
print_format = print_format plan_names = [plan.plan for plan in self.plans]
subject = subscription_doc.subject or '' billing_info = frappe.db.sql(
message = subscription_doc.message or '' 'select distinct `billing_interval`, `billing_interval_count` '
'from `tabSubscription Plan` '
'where name in %s',
(plan_names,), as_dict=1
)
if not subscription_doc.subject: return billing_info
subject = _("New {0}: #{1}").format(new_rv.doctype, new_rv.name)
elif "{" in subscription_doc.subject:
subject = frappe.render_template(subscription_doc.subject, {'doc': new_rv})
if not subscription_doc.message: def get_billing_cycle_data(self):
message = _("Please find attached {0} #{1}").format(new_rv.doctype, new_rv.name) """
elif "{" in subscription_doc.message: Returns dict contain the billing cycle data.
message = frappe.render_template(subscription_doc.message, {'doc': new_rv})
attachments = [frappe.attach_print(new_rv.doctype, new_rv.name, You shouldn't need to call this directly. Use `get_billing_cycle` instead.
file_name=new_rv.name, print_format=print_format)] """
billing_info = self.get_billing_cycle_and_interval()
frappe.sendmail(subscription_doc.recipients, self.validate_plans_billing_cycle(billing_info)
subject=subject, message=message, attachments=attachments)
def notify_errors(doc, doctype, party, owner, name): if billing_info:
recipients = get_system_managers(only_name=True) data = dict()
frappe.sendmail(recipients + [frappe.db.get_value("User", owner, "email")], interval = billing_info[0]['billing_interval']
subject=_("[Urgent] Error while creating recurring %s for %s" % (doctype, doc)), interval_count = billing_info[0]['billing_interval_count']
message = frappe.get_template("templates/emails/recurring_document_failed.html").render({ if interval not in ['Day', 'Week']:
"type": _(doctype), data['days'] = -1
"name": doc, if interval == 'Day':
"party": party or "", data['days'] = interval_count - 1
"subscription": name elif interval == 'Month':
})) data['months'] = interval_count
elif interval == 'Year':
data['years'] = interval_count
# todo: test week
elif interval == 'Week':
data['days'] = interval_count * 7 - 1
assign_task_to_owner(name, "Recurring Documents Failed", recipients) return data
def set_status_grace_period(self):
"""
Sets the `Subscription` `status` based on the preference set in `Subscription Settings`.
Used when the `Subscription` needs to decide what to do after the current generated
invoice is past it's due date and grace period.
"""
subscription_settings = frappe.get_single('Subscription Settings')
if self.status == 'Past Due Date' and self.is_past_grace_period():
self.status = 'Cancelled' if cint(subscription_settings.cancel_after_grace) else 'Unpaid'
def set_subscription_status(self):
"""
Sets the status of the `Subscription`
"""
if self.is_trialling():
self.status = 'Trialling'
elif self.status == 'Past Due Date' and self.is_past_grace_period():
subscription_settings = frappe.get_single('Subscription Settings')
self.status = 'Cancelled' if cint(subscription_settings.cancel_after_grace) else 'Unpaid'
elif self.status == 'Past Due Date' and not self.has_outstanding_invoice():
self.status = 'Active'
elif self.current_invoice_is_past_due():
self.status = 'Past Due Date'
elif self.is_new_subscription():
self.status = 'Active'
# todo: then generate new invoice
self.save()
def is_trialling(self):
"""
Returns `True` if the `Subscription` is trial period.
"""
return not self.period_has_passed(self.trial_period_end) and self.is_new_subscription()
@staticmethod
def period_has_passed(end_date):
"""
Returns true if the given `end_date` has passed
"""
# todo: test for illegal time
if not end_date:
return True
end_date = getdate(end_date)
return getdate(nowdate()) > getdate(end_date)
def is_past_grace_period(self):
"""
Returns `True` if the grace period for the `Subscription` has passed
"""
current_invoice = self.get_current_invoice()
if self.current_invoice_is_past_due(current_invoice):
subscription_settings = frappe.get_single('Subscription Settings')
grace_period = cint(subscription_settings.grace_period)
return getdate(nowdate()) > add_days(current_invoice.due_date, grace_period)
def current_invoice_is_past_due(self, current_invoice=None):
"""
Returns `True` if the current generated invoice is overdue
"""
if not current_invoice:
current_invoice = self.get_current_invoice()
if not current_invoice:
return False
else:
return getdate(nowdate()) > getdate(current_invoice.due_date)
def get_current_invoice(self):
"""
Returns the most recent generated invoice.
"""
if len(self.invoices):
current = self.invoices[-1]
if frappe.db.exists('Sales Invoice', current.invoice):
doc = frappe.get_doc('Sales Invoice', current.invoice)
return doc
else:
frappe.throw(_('Invoice {0} no longer exists'.format(current.invoice)))
def is_new_subscription(self):
"""
Returns `True` if `Subscription` has never generated an invoice
"""
return len(self.invoices) == 0
def validate(self):
self.validate_trial_period()
self.validate_plans_billing_cycle(self.get_billing_cycle_and_interval())
def validate_trial_period(self):
"""
Runs sanity checks on trial period dates for the `Subscription`
"""
if self.trial_period_start and self.trial_period_end:
if getdate(self.trial_period_end) < getdate(self.trial_period_start):
frappe.throw(_('Trial Period End Date Cannot be before Trial Period Start Date'))
elif self.trial_period_start or self.trial_period_end:
frappe.throw(_('Both Trial Period Start Date and Trial Period End Date must be set'))
def after_insert(self):
# todo: deal with users who collect prepayments. Maybe a new Subscription Invoice doctype?
self.set_subscription_status()
def generate_invoice(self, prorate=0):
"""
Creates a `Sales Invoice` for the `Subscription`, updates `self.invoices` and
saves the `Subscription`.
"""
invoice = self.create_invoice(prorate)
self.append('invoices', {'invoice': invoice.name})
self.save()
return invoice
def create_invoice(self, prorate):
"""
Creates a `Sales Invoice`, submits it and returns it
"""
invoice = frappe.new_doc('Sales Invoice')
invoice.set_posting_time = 1
invoice.posting_date = self.current_invoice_start
invoice.customer = self.get_customer(self.subscriber)
# Subscription is better suited for service items. I won't update `update_stock`
# for that reason
items_list = self.get_items_from_plans(self.plans, prorate)
for item in items_list:
item['qty'] = self.quantity
invoice.append('items', item)
# Taxes
if self.tax_template:
invoice.taxes_and_charges = self.tax_template
invoice.set_taxes()
# Due date
invoice.append(
'payment_schedule',
{
'due_date': add_days(self.current_invoice_end, cint(self.days_until_due)),
'invoice_portion': 100
}
)
# Discounts
if self.additional_discount_percentage:
invoice.additional_discount_percentage = self.additional_discount_percentage
if self.additional_discount_amount:
invoice.discount_amount = self.additional_discount_amount
if self.additional_discount_percentage or self.additional_discount_amount:
discount_on = self.apply_additional_discount
invoice.apply_additional_discount = discount_on if discount_on else 'Grand Total'
invoice.flags.ignore_mandatory = True
invoice.save()
invoice.submit()
return invoice
@staticmethod
def get_customer(subscriber_name):
"""
Returns the `Customer` linked to the `Subscriber`
"""
return frappe.get_value('Subscriber', subscriber_name)
def get_items_from_plans(self, plans, prorate=0):
"""
Returns the `Item`s linked to `Subscription Plan`
"""
plan_items = [plan.plan for plan in plans]
item_names = None
if plan_items and not prorate:
item_names = frappe.db.sql(
'select item as item_code, cost as rate from `tabSubscription Plan` where name in %s',
(plan_items,), as_dict=1
)
elif plan_items:
prorate_factor = get_prorata_factor(self.current_invoice_end, self.current_invoice_start)
item_names = frappe.db.sql(
'select item as item_code, cost * %s as rate from `tabSubscription Plan` where name in %s',
(prorate_factor, plan_items,), as_dict=1
)
return item_names
def process(self):
"""
To be called by task periodically. It checks the subscription and takes appropriate action
as need be. It calls either of these methods depending the `Subscription` status:
1. `process_for_active`
2. `process_for_past_due`
"""
if self.status == 'Active':
self.process_for_active()
elif self.status in ['Past Due Date', 'Unpaid']:
self.process_for_past_due_date()
self.save()
def process_for_active(self):
"""
Called by `process` if the status of the `Subscription` is 'Active'.
The possible outcomes of this method are:
1. Generate a new invoice
2. Change the `Subscription` status to 'Past Due Date'
3. Change the `Subscription` status to 'Cancelled'
"""
if getdate(nowdate()) > getdate(self.current_invoice_end) and not self.has_outstanding_invoice():
self.generate_invoice()
if self.current_invoice_is_past_due():
self.status = 'Past Due Date'
if self.current_invoice_is_past_due() and getdate(nowdate()) > getdate(self.current_invoice_end):
self.status = 'Past Due Date'
if self.cancel_at_period_end and getdate(nowdate()) > self.current_invoice_end:
self.cancel_subscription_at_period_end()
def cancel_subscription_at_period_end(self):
"""
Called when `Subscription.cancel_at_period_end` is truthy
"""
self.status = 'Cancelled'
if not self.cancelation_date:
self.cancelation_date = nowdate()
def process_for_past_due_date(self):
"""
Called by `process` if the status of the `Subscription` is 'Past Due Date'.
The possible outcomes of this method are:
1. Change the `Subscription` status to 'Active'
2. Change the `Subscription` status to 'Cancelled'
3. Change the `Subscription` status to 'Unpaid'
"""
current_invoice = self.get_current_invoice()
if not current_invoice:
frappe.throw(_('Current invoice {0} is missing'.format(current_invoice.invoice)))
else:
if self.is_not_outstanding(current_invoice):
self.status = 'Active'
self.update_subscription_period(nowdate())
else:
self.set_status_grace_period()
@staticmethod
def is_not_outstanding(invoice):
"""
Return `True` if the given invoice is paid
"""
return invoice.status == 'Paid'
def has_outstanding_invoice(self):
"""
Returns `True` if the most recent invoice for the `Subscription` is not paid
"""
current_invoice = self.get_current_invoice()
if not current_invoice:
return False
else:
return not self.is_not_outstanding(current_invoice)
def cancel_subscription(self):
"""
This sets the subscription as cancelled. It will stop invoices from being generated
but it will not affect already created invoices.
"""
if self.status != 'Cancelled':
to_generate_invoice = True if self.status == 'Active' else False
to_prorate = frappe.db.get_single_value('Subscription Settings', 'prorate')
self.status = 'Cancelled'
self.cancelation_date = nowdate()
if to_generate_invoice:
self.generate_invoice(prorate=to_prorate)
self.save()
def restart_subscription(self):
"""
This sets the subscription as active. The subscription will be made to be like a new
subscription and the `Subscription` will lose all the history of generated invoices
it has.
"""
if self.status == 'Cancelled':
self.status = 'Active'
self.db_set('start', nowdate())
self.update_subscription_period(nowdate())
self.invoices = []
self.save()
else:
frappe.throw(_('You cannot restart a Subscription that is not cancelled.'))
def get_precision(self):
invoice = self.get_current_invoice()
if invoice:
return invoice.precision('grand_total')
def get_prorata_factor(period_end, period_start):
diff = flt(date_diff(nowdate(), period_start) + 1)
plan_days = flt(date_diff(period_end, period_start) + 1)
prorate_factor = diff / plan_days
return prorate_factor
def process_all():
"""
Task to updates the status of all `Subscription` apart from those that are cancelled
"""
subscriptions = get_all_subscriptions()
for subscription in subscriptions:
process(subscription)
def get_all_subscriptions():
"""
Returns all `Subscription` documents
"""
return frappe.db.sql(
'select name from `tabSubscription` where status != "Cancelled"',
as_dict=1
)
def process(data):
"""
Checks a `Subscription` and updates it status as necessary
"""
if data:
try:
subscription = frappe.get_doc('Subscription', data['name'])
subscription.process()
frappe.db.commit()
except frappe.ValidationError:
frappe.db.rollback()
frappe.db.begin()
frappe.log_error(frappe.get_traceback())
frappe.db.commit()
def assign_task_to_owner(name, msg, users):
for d in users:
args = {
'doctype' : 'Subscription',
'assign_to' : d,
'name' : name,
'description' : msg,
'priority' : 'High'
}
assign_to.add(args)
@frappe.whitelist() @frappe.whitelist()
def make_subscription(doctype, docname): def cancel_subscription(name):
doc = frappe.new_doc('Subscription') """
Cancels a `Subscription`. This will stop the `Subscription` from further invoicing the
`Subscriber` but all already outstanding invoices will not be affected.
"""
subscription = frappe.get_doc('Subscription', name)
subscription.cancel_subscription()
reference_doc = frappe.get_doc(doctype, docname)
doc.reference_doctype = doctype
doc.reference_document = docname
doc.start_date = reference_doc.get('posting_date') or reference_doc.get('transaction_date')
return doc
@frappe.whitelist() @frappe.whitelist()
def stop_resume_subscription(subscription, status): def restart_subscription(name):
doc = frappe.get_doc('Subscription', subscription) """
frappe.msgprint(_("Subscription has been {0}").format(status)) Restarts a cancelled `Subscription`. The `Subscription` will 'forget' the history of
if status == 'Resumed': all invoices it has generated
doc.next_schedule_date = get_next_schedule_date(today(), """
doc.frequency, doc.repeat_on_day) subscription = frappe.get_doc('Subscription', name)
subscription.restart_subscription()
doc.update_status(status)
doc.save()
return doc.status @frappe.whitelist()
def get_subscription_updates(name):
def subscription_doctype_query(doctype, txt, searchfield, start, page_len, filters): """
return frappe.db.sql("""select parent from `tabDocField` Use this to get the latest state of the given `Subscription`
where fieldname = 'subscription' """
and parent like %(txt)s subscription = frappe.get_doc('Subscription', name)
order by subscription.process()
if(locate(%(_txt)s, parent), locate(%(_txt)s, parent), 99999),
parent
limit %(start)s, %(page_len)s""".format(**{
'key': searchfield,
}), {
'txt': "%%%s%%" % txt,
'_txt': txt.replace("%", ""),
'start': start,
'page_len': page_len
})

View File

@ -1,16 +1,15 @@
frappe.listview_settings['Subscription'] = { frappe.listview_settings['Subscription'] = {
add_fields: ["next_schedule_date"],
get_indicator: function(doc) { get_indicator: function(doc) {
if(doc.disabled) { if(doc.status === 'Trialling') {
return [__("Disabled"), "red"]; return [__("Trialling"), "green"];
} else if(doc.next_schedule_date >= frappe.datetime.get_today() && doc.status != 'Stopped') { } else if(doc.status === 'Active') {
return [__("Active"), "green"]; return [__("Active"), "green"];
} else if(doc.docstatus === 0) { } else if(doc.status === 'Past Due Date') {
return [__("Draft"), "red", "docstatus,=,0"]; return [__("Past Due Date"), "orange"];
} else if(doc.status === 'Stopped') { } else if(doc.status === 'Unpaid') {
return [__("Stopped"), "red"]; return [__("Unpaid"), "red"];
} else { } else if(doc.status === 'Cancelled') {
return [__("Expired"), "darkgrey"]; return [__("Cancelled"), "darkgrey"];
} }
} }
}; };

View File

@ -1,93 +1,498 @@
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
# Copyright (c) 2017, Frappe Technologies Pvt. Ltd. and Contributors # Copyright (c) 2018, Frappe Technologies Pvt. Ltd. and Contributors
# See license.txt # See license.txt
from __future__ import unicode_literals from __future__ import unicode_literals
import frappe
import unittest import unittest
from frappe.utils import today, add_days, getdate
from erpnext.accounts.utils import get_fiscal_year import frappe
from erpnext.accounts.report.financial_statements import get_months from erpnext.accounts.doctype.subscription.subscription import get_prorata_factor
from erpnext.accounts.doctype.sales_invoice.test_sales_invoice import create_sales_invoice from frappe.utils.data import nowdate, add_days, add_to_date, add_months, date_diff, flt
from erpnext.selling.doctype.sales_order.test_sales_order import make_sales_order
from erpnext.accounts.doctype.subscription.subscription import make_subscription_entry
def create_plan():
if not frappe.db.exists('Subscription Plan', '_Test Plan Name'):
plan = frappe.new_doc('Subscription Plan')
plan.plan_name = '_Test Plan Name'
plan.item = '_Test Non Stock Item'
plan.cost = 900
plan.billing_interval = 'Month'
plan.billing_interval_count = 1
plan.insert()
if not frappe.db.exists('Subscription Plan', '_Test Plan Name 2'):
plan = frappe.new_doc('Subscription Plan')
plan.plan_name = '_Test Plan Name 2'
plan.item = '_Test Non Stock Item'
plan.cost = 1999
plan.billing_interval = 'Month'
plan.billing_interval_count = 1
plan.insert()
if not frappe.db.exists('Subscription Plan', '_Test Plan Name 3'):
plan = frappe.new_doc('Subscription Plan')
plan.plan_name = '_Test Plan Name 3'
plan.item = '_Test Non Stock Item'
plan.cost = 1999
plan.billing_interval = 'Day'
plan.billing_interval_count = 14
plan.insert()
def create_subscriber():
if not frappe.db.exists('Subscriber', '_Test Customer'):
subscriber = frappe.new_doc('Subscriber')
subscriber.subscriber_name = '_Test Customer'
subscriber.customer = '_Test Customer'
subscriber.insert()
class TestSubscription(unittest.TestCase): class TestSubscription(unittest.TestCase):
def test_daily_subscription(self):
qo = frappe.copy_doc(quotation_records[0])
qo.submit()
doc = make_subscription(reference_document=qo.name) def setUp(self):
self.assertEqual(doc.next_schedule_date, today()) create_plan()
make_subscription_entry() create_subscriber()
frappe.db.commit()
quotation = frappe.get_doc(doc.reference_doctype, doc.reference_document) def test_create_subscription_with_trial_with_correct_period(self):
self.assertEqual(quotation.subscription, doc.name) subscription = frappe.new_doc('Subscription')
subscription.subscriber = '_Test Customer'
subscription.trial_period_start = nowdate()
subscription.trial_period_end = add_days(nowdate(), 30)
subscription.append('plans', {'plan': '_Test Plan Name'})
subscription.save()
new_quotation = frappe.db.get_value('Quotation', self.assertEqual(subscription.trial_period_start, nowdate())
{'subscription': doc.name, 'name': ('!=', quotation.name)}, 'name') self.assertEqual(subscription.trial_period_end, add_days(nowdate(), 30))
self.assertEqual(subscription.trial_period_start, subscription.current_invoice_start)
self.assertEqual(subscription.trial_period_end, subscription.current_invoice_end)
self.assertEqual(subscription.invoices, [])
self.assertEqual(subscription.status, 'Trialling')
new_quotation = frappe.get_doc('Quotation', new_quotation) subscription.delete()
for fieldname in ['customer', 'company', 'order_type', 'total', 'net_total']: def test_create_subscription_without_trial_with_correct_period(self):
self.assertEqual(quotation.get(fieldname), new_quotation.get(fieldname)) subscription = frappe.new_doc('Subscription')
subscription.subscriber = '_Test Customer'
subscription.append('plans', {'plan': '_Test Plan Name'})
subscription.save()
for fieldname in ['item_code', 'qty', 'rate', 'amount']: self.assertEqual(subscription.trial_period_start, None)
self.assertEqual(quotation.items[0].get(fieldname), self.assertEqual(subscription.trial_period_end, None)
new_quotation.items[0].get(fieldname)) self.assertEqual(subscription.current_invoice_start, nowdate())
self.assertEqual(subscription.current_invoice_end, add_to_date(nowdate(), months=1, days=-1))
# No invoice is created
self.assertEqual(len(subscription.invoices), 0)
self.assertEqual(subscription.status, 'Active')
def test_monthly_subscription_for_so(self): subscription.delete()
current_fiscal_year = get_fiscal_year(today(), as_dict=True)
start_date = current_fiscal_year.year_start_date
end_date = current_fiscal_year.year_end_date
for doctype in ['Sales Order', 'Sales Invoice']: def test_create_subscription_trial_with_wrong_dates(self):
if doctype == 'Sales Invoice': subscription = frappe.new_doc('Subscription')
docname = create_sales_invoice(posting_date=start_date) subscription.subscriber = '_Test Customer'
else: subscription.trial_period_end = nowdate()
docname = make_sales_order() subscription.trial_period_start = add_days(nowdate(), 30)
subscription.append('plans', {'plan': '_Test Plan Name'})
self.monthly_subscription(doctype, docname.name, start_date, end_date) self.assertRaises(frappe.ValidationError, subscription.save)
subscription.delete()
def monthly_subscription(self, doctype, docname, start_date, end_date): def test_create_subscription_multi_with_different_billing_fails(self):
doc = make_subscription(reference_doctype=doctype, frequency = 'Monthly', subscription = frappe.new_doc('Subscription')
reference_document = docname, start_date=start_date, end_date=end_date) subscription.subscriber = '_Test Customer'
subscription.trial_period_end = nowdate()
subscription.trial_period_start = add_days(nowdate(), 30)
subscription.append('plans', {'plan': '_Test Plan Name'})
subscription.append('plans', {'plan': '_Test Plan Name 3'})
doc.disabled = 1 self.assertRaises(frappe.ValidationError, subscription.save)
doc.save() subscription.delete()
frappe.db.commit()
make_subscription_entry() def test_invoice_is_generated_at_end_of_billing_period(self):
docnames = frappe.get_all(doc.reference_doctype, {'subscription': doc.name}) subscription = frappe.new_doc('Subscription')
self.assertEqual(len(docnames), 1) subscription.subscriber = '_Test Customer'
subscription.start = '2018-01-01'
subscription.append('plans', {'plan': '_Test Plan Name'})
subscription.insert()
doc = frappe.get_doc('Subscription', doc.name) self.assertEqual(subscription.status, 'Active')
doc.disabled = 0 self.assertEqual(subscription.current_invoice_start, '2018-01-01')
doc.save() self.assertEqual(subscription.current_invoice_end, '2018-01-31')
subscription.process()
months = get_months(getdate(start_date), getdate(today())) self.assertEqual(len(subscription.invoices), 1)
make_subscription_entry() self.assertEqual(subscription.current_invoice_start, '2018-01-01')
self.assertEqual(subscription.status, 'Past Due Date')
subscription.delete()
docnames = frappe.get_all(doc.reference_doctype, {'subscription': doc.name}) def test_status_goes_back_to_active_after_invoice_is_paid(self):
self.assertEqual(len(docnames), months) subscription = frappe.new_doc('Subscription')
subscription.subscriber = '_Test Customer'
subscription.append('plans', {'plan': '_Test Plan Name'})
subscription.start = '2018-01-01'
subscription.insert()
subscription.process() # generate first invoice
self.assertEqual(len(subscription.invoices), 1)
self.assertEqual(subscription.status, 'Past Due Date')
quotation_records = frappe.get_test_records('Quotation') subscription.get_current_invoice()
current_invoice = subscription.get_current_invoice()
def make_subscription(**args): self.assertIsNotNone(current_invoice)
args = frappe._dict(args)
doc = frappe.get_doc({
'doctype': 'Subscription',
'reference_doctype': args.reference_doctype or 'Quotation',
'reference_document': args.reference_document or \
frappe.db.get_value('Quotation', {'docstatus': 1}, 'name'),
'frequency': args.frequency or 'Daily',
'start_date': args.start_date or add_days(today(), -1),
'end_date': args.end_date or add_days(today(), 1),
'submit_on_creation': args.submit_on_creation or 0
}).insert(ignore_permissions=True)
if not args.do_not_submit: current_invoice.db_set('outstanding_amount', 0)
doc.submit() current_invoice.db_set('status', 'Paid')
subscription.process()
return doc self.assertEqual(subscription.status, 'Active')
self.assertEqual(subscription.current_invoice_start, nowdate())
self.assertEqual(len(subscription.invoices), 1)
subscription.delete()
def test_subscription_cancel_after_grace_period(self):
settings = frappe.get_single('Subscription Settings')
default_grace_period_action = settings.cancel_after_grace
settings.cancel_after_grace = 1
settings.save()
subscription = frappe.new_doc('Subscription')
subscription.subscriber = '_Test Customer'
subscription.append('plans', {'plan': '_Test Plan Name'})
subscription.start = '2018-01-01'
subscription.insert()
subscription.process() # generate first invoice
self.assertEqual(subscription.status, 'Past Due Date')
subscription.process()
# This should change status to Cancelled since grace period is 0
self.assertEqual(subscription.status, 'Cancelled')
settings.cancel_after_grace = default_grace_period_action
settings.save()
subscription.delete()
def test_subscription_unpaid_after_grace_period(self):
settings = frappe.get_single('Subscription Settings')
default_grace_period_action = settings.cancel_after_grace
settings.cancel_after_grace = 0
settings.save()
subscription = frappe.new_doc('Subscription')
subscription.subscriber = '_Test Customer'
subscription.append('plans', {'plan': '_Test Plan Name'})
subscription.start = '2018-01-01'
subscription.insert()
subscription.process() # generate first invoice
self.assertEqual(subscription.status, 'Past Due Date')
subscription.process()
# This should change status to Cancelled since grace period is 0
self.assertEqual(subscription.status, 'Unpaid')
settings.cancel_after_grace = default_grace_period_action
settings.save()
subscription.delete()
def test_subscription_invoice_days_until_due(self):
subscription = frappe.new_doc('Subscription')
subscription.subscriber = '_Test Customer'
subscription.append('plans', {'plan': '_Test Plan Name'})
subscription.days_until_due = 10
subscription.start = add_months(nowdate(), -1)
subscription.insert()
subscription.process() # generate first invoice
self.assertEqual(len(subscription.invoices), 1)
self.assertEqual(subscription.status, 'Active')
subscription.delete()
def test_subscription_is_past_due_doesnt_change_within_grace_period(self):
settings = frappe.get_single('Subscription Settings')
grace_period = settings.grace_period
settings.grace_period = 1000
settings.save()
subscription = frappe.new_doc('Subscription')
subscription.subscriber = '_Test Customer'
subscription.append('plans', {'plan': '_Test Plan Name'})
subscription.start = '2018-01-01'
subscription.insert()
subscription.process() # generate first invoice
self.assertEqual(subscription.status, 'Past Due Date')
subscription.process()
# Grace period is 1000 days so status should remain as Past Due Date
self.assertEqual(subscription.status, 'Past Due Date')
subscription.process()
self.assertEqual(subscription.status, 'Past Due Date')
subscription.process()
self.assertEqual(subscription.status, 'Past Due Date')
settings.grace_period = grace_period
settings.save()
subscription.delete()
def test_subscription_remains_active_during_invoice_period(self):
subscription = frappe.new_doc('Subscription')
subscription.subscriber = '_Test Customer'
subscription.append('plans', {'plan': '_Test Plan Name'})
subscription.save()
subscription.process() # no changes expected
self.assertEqual(subscription.status, 'Active')
self.assertEqual(subscription.current_invoice_start, nowdate())
self.assertEqual(subscription.current_invoice_end, add_to_date(nowdate(), months=1, days=-1))
self.assertEqual(len(subscription.invoices), 0)
subscription.process() # no changes expected still
self.assertEqual(subscription.status, 'Active')
self.assertEqual(subscription.current_invoice_start, nowdate())
self.assertEqual(subscription.current_invoice_end, add_to_date(nowdate(), months=1, days=-1))
self.assertEqual(len(subscription.invoices), 0)
subscription.process() # no changes expected yet still
self.assertEqual(subscription.status, 'Active')
self.assertEqual(subscription.current_invoice_start, nowdate())
self.assertEqual(subscription.current_invoice_end, add_to_date(nowdate(), months=1, days=-1))
self.assertEqual(len(subscription.invoices), 0)
subscription.delete()
def test_subscription_cancelation(self):
subscription = frappe.new_doc('Subscription')
subscription.subscriber = '_Test Customer'
subscription.append('plans', {'plan': '_Test Plan Name'})
subscription.save()
subscription.cancel_subscription()
self.assertEqual(subscription.status, 'Cancelled')
subscription.delete()
def test_subscription_cancellation_invoices(self):
settings = frappe.get_single('Subscription Settings')
to_prorate = settings.prorate
settings.prorate = 1
settings.save()
subscription = frappe.new_doc('Subscription')
subscription.subscriber = '_Test Customer'
subscription.append('plans', {'plan': '_Test Plan Name'})
subscription.save()
self.assertEqual(subscription.status, 'Active')
subscription.cancel_subscription()
# Invoice must have been generated
self.assertEqual(len(subscription.invoices), 1)
invoice = subscription.get_current_invoice()
diff = flt(date_diff(nowdate(), subscription.current_invoice_start) + 1)
plan_days = flt(date_diff(subscription.current_invoice_end, subscription.current_invoice_start) + 1)
prorate_factor = flt(diff/plan_days)
self.assertEqual(
flt(
get_prorata_factor(subscription.current_invoice_end, subscription.current_invoice_start),
2),
flt(prorate_factor, 2)
)
self.assertEqual(flt(invoice.grand_total, 2), flt(prorate_factor * 900, 2))
self.assertEqual(subscription.status, 'Cancelled')
subscription.delete()
settings.prorate = to_prorate
settings.save()
def test_subscription_cancellation_invoices_with_prorata_false(self):
settings = frappe.get_single('Subscription Settings')
to_prorate = settings.prorate
settings.prorate = 0
settings.save()
subscription = frappe.new_doc('Subscription')
subscription.subscriber = '_Test Customer'
subscription.append('plans', {'plan': '_Test Plan Name'})
subscription.save()
subscription.cancel_subscription()
invoice = subscription.get_current_invoice()
self.assertEqual(invoice.grand_total, 900)
settings.prorate = to_prorate
settings.save()
subscription.delete()
def test_subscription_cancellation_invoices_with_prorata_true(self):
settings = frappe.get_single('Subscription Settings')
to_prorate = settings.prorate
settings.prorate = 1
settings.save()
subscription = frappe.new_doc('Subscription')
subscription.subscriber = '_Test Customer'
subscription.append('plans', {'plan': '_Test Plan Name'})
subscription.save()
subscription.cancel_subscription()
invoice = subscription.get_current_invoice()
diff = flt(date_diff(nowdate(), subscription.current_invoice_start) + 1)
plan_days = flt(date_diff(subscription.current_invoice_end, subscription.current_invoice_start) + 1)
prorate_factor = flt(diff / plan_days)
self.assertEqual(flt(invoice.grand_total, 2), flt(prorate_factor * 900, 2))
settings.prorate = to_prorate
settings.save()
subscription.delete()
def test_subcription_cancellation_and_process(self):
settings = frappe.get_single('Subscription Settings')
default_grace_period_action = settings.cancel_after_grace
settings.cancel_after_grace = 1
settings.save()
subscription = frappe.new_doc('Subscription')
subscription.subscriber = '_Test Customer'
subscription.append('plans', {'plan': '_Test Plan Name'})
subscription.start = '2018-01-01'
subscription.insert()
subscription.process() # generate first invoice
invoices = len(subscription.invoices)
self.assertEqual(subscription.status, 'Past Due Date')
self.assertEqual(len(subscription.invoices), invoices)
subscription.cancel_subscription()
self.assertEqual(subscription.status, 'Cancelled')
self.assertEqual(len(subscription.invoices), invoices)
subscription.process()
self.assertEqual(subscription.status, 'Cancelled')
self.assertEqual(len(subscription.invoices), invoices)
subscription.process()
self.assertEqual(subscription.status, 'Cancelled')
self.assertEqual(len(subscription.invoices), invoices)
settings.cancel_after_grace = default_grace_period_action
settings.save()
subscription.delete()
def test_subscription_restart_and_process(self):
settings = frappe.get_single('Subscription Settings')
default_grace_period_action = settings.cancel_after_grace
settings.grace_period = 0
settings.cancel_after_grace = 0
settings.save()
subscription = frappe.new_doc('Subscription')
subscription.subscriber = '_Test Customer'
subscription.append('plans', {'plan': '_Test Plan Name'})
subscription.start = '2018-01-01'
subscription.insert()
subscription.process() # generate first invoice
self.assertEqual(subscription.status, 'Past Due Date')
subscription.process()
self.assertEqual(subscription.status, 'Unpaid')
subscription.cancel_subscription()
self.assertEqual(subscription.status, 'Cancelled')
subscription.restart_subscription()
self.assertEqual(subscription.status, 'Active')
self.assertEqual(len(subscription.invoices), 0)
subscription.process()
self.assertEqual(subscription.status, 'Active')
self.assertEqual(len(subscription.invoices), 0)
subscription.process()
self.assertEqual(subscription.status, 'Active')
self.assertEqual(len(subscription.invoices), 0)
settings.cancel_after_grace = default_grace_period_action
settings.save()
subscription.delete()
def test_subscription_unpaid_back_to_active(self):
settings = frappe.get_single('Subscription Settings')
default_grace_period_action = settings.cancel_after_grace
settings.cancel_after_grace = 0
settings.save()
subscription = frappe.new_doc('Subscription')
subscription.subscriber = '_Test Customer'
subscription.append('plans', {'plan': '_Test Plan Name'})
subscription.start = '2018-01-01'
subscription.insert()
subscription.process() # generate first invoice
self.assertEqual(subscription.status, 'Past Due Date')
subscription.process()
# This should change status to Cancelled since grace period is 0
self.assertEqual(subscription.status, 'Unpaid')
invoice = subscription.get_current_invoice()
invoice.db_set('outstanding_amount', 0)
invoice.db_set('status', 'Paid')
subscription.process()
self.assertEqual(subscription.status, 'Active')
subscription.process()
self.assertEqual(subscription.status, 'Active')
settings.cancel_after_grace = default_grace_period_action
settings.save()
subscription.delete()
def test_restart_active_subscription(self):
subscription = frappe.new_doc('Subscription')
subscription.subscriber = '_Test Customer'
subscription.append('plans', {'plan': '_Test Plan Name'})
subscription.save()
self.assertRaises(frappe.ValidationError, subscription.restart_subscription)
subscription.delete()
def test_subscription_invoice_discount_percentage(self):
subscription = frappe.new_doc('Subscription')
subscription.subscriber = '_Test Customer'
subscription.additional_discount_percentage = 10
subscription.append('plans', {'plan': '_Test Plan Name'})
subscription.save()
subscription.cancel_subscription()
invoice = subscription.get_current_invoice()
self.assertEqual(invoice.additional_discount_percentage, 10)
self.assertEqual(invoice.apply_discount_on, 'Grand Total')
subscription.delete()
def test_subscription_invoice_discount_amount(self):
subscription = frappe.new_doc('Subscription')
subscription.subscriber = '_Test Customer'
subscription.additional_discount_amount = 11
subscription.append('plans', {'plan': '_Test Plan Name'})
subscription.save()
subscription.cancel_subscription()
invoice = subscription.get_current_invoice()
self.assertEqual(invoice.discount_amount, 11)
self.assertEqual(invoice.apply_discount_on, 'Grand Total')
subscription.delete()

View File

@ -0,0 +1,2 @@
// Copyright (c) 2018, Frappe Technologies Pvt. Ltd. and contributors
// For license information, please see license.txt

View File

@ -0,0 +1,73 @@
{
"allow_copy": 0,
"allow_guest_to_view": 0,
"allow_import": 0,
"allow_rename": 0,
"beta": 0,
"creation": "2018-02-26 04:21:41.265055",
"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": "invoice",
"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": "Invoice",
"length": 0,
"no_copy": 0,
"options": "Sales Invoice",
"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,
"translatable": 0,
"unique": 0
}
],
"has_web_view": 0,
"hide_heading": 0,
"hide_toolbar": 0,
"idx": 0,
"image_view": 0,
"in_create": 0,
"is_submittable": 0,
"issingle": 0,
"istable": 1,
"max_attachments": 0,
"modified": "2018-02-26 10:48:07.033422",
"modified_by": "Administrator",
"module": "Accounts",
"name": "Subscription Invoice",
"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,9 @@
# -*- coding: utf-8 -*-
# Copyright (c) 2018, Frappe Technologies Pvt. Ltd. and contributors
# For license information, please see license.txt
from __future__ import unicode_literals
from frappe.model.document import Document
class SubscriptionInvoice(Document):
pass

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: Subscription Invoice", function (assert) {
let done = assert.async();
// number of asserts
assert.expect(1);
frappe.run_serially([
// insert a new Subscription Invoice
() => frappe.tests.make('Subscription Invoice', [
// values to be set
{key: 'value'}
]),
() => {
assert.equal(cur_frm.doc.key, 'value');
},
() => done()
]);
});

View File

@ -0,0 +1,9 @@
# -*- coding: utf-8 -*-
# Copyright (c) 2018, Frappe Technologies Pvt. Ltd. and Contributors
# See license.txt
from __future__ import unicode_literals
import unittest
class TestSubscriptionInvoice(unittest.TestCase):
pass

View File

@ -0,0 +1,2 @@
// Copyright (c) 2018, Frappe Technologies Pvt. Ltd. and contributors
// For license information, please see license.txt

View File

@ -0,0 +1,255 @@
{
"allow_copy": 0,
"allow_guest_to_view": 0,
"allow_import": 0,
"allow_rename": 1,
"autoname": "field:plan_name",
"beta": 0,
"creation": "2018-02-24 11:31:23.066506",
"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": "plan_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": "Plan Name",
"length": 0,
"no_copy": 0,
"permlevel": 0,
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 0,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 1,
"search_index": 0,
"set_only_once": 0,
"translatable": 0,
"unique": 0
},
{
"allow_bulk_edit": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "item",
"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": "Item",
"length": 0,
"no_copy": 0,
"options": "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,
"translatable": 0,
"unique": 0
},
{
"allow_bulk_edit": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "currency",
"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": "Currency",
"length": 0,
"no_copy": 0,
"options": "Currency",
"permlevel": 0,
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 0,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"translatable": 0,
"unique": 0
},
{
"allow_bulk_edit": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "cost",
"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": "Cost",
"length": 0,
"no_copy": 0,
"permlevel": 0,
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 0,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 1,
"search_index": 0,
"set_only_once": 0,
"translatable": 0,
"unique": 0
},
{
"allow_bulk_edit": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"default": "Day",
"fieldname": "billing_interval",
"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": "Billing Interval",
"length": 0,
"no_copy": 0,
"options": "Day\nWeek\nMonth\nYear",
"permlevel": 0,
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 0,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 1,
"search_index": 0,
"set_only_once": 0,
"translatable": 0,
"unique": 0
},
{
"allow_bulk_edit": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"default": "1",
"description": "Number of intervals for the interval field e.g if Interval is 'Days' and Billing Interval Count is 3, invoices will be generated every 3 days",
"fieldname": "billing_interval_count",
"fieldtype": "Int",
"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": "Billing Interval Count",
"length": 0,
"no_copy": 0,
"permlevel": 0,
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 0,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 1,
"search_index": 0,
"set_only_once": 0,
"translatable": 0,
"unique": 0
}
],
"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": "2018-02-27 09:12:58.330140",
"modified_by": "Administrator",
"module": "Accounts",
"name": "Subscription Plan",
"name_case": "",
"owner": "Administrator",
"permissions": [
{
"amend": 0,
"apply_user_permissions": 0,
"cancel": 0,
"create": 1,
"delete": 1,
"email": 1,
"export": 1,
"if_owner": 0,
"import": 0,
"permlevel": 0,
"print": 1,
"read": 1,
"report": 1,
"role": "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,15 @@
# -*- coding: utf-8 -*-
# Copyright (c) 2018, 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 SubscriptionPlan(Document):
def validate(self):
self.validate_interval_count()
def validate_interval_count(self):
if self.billing_interval_count < 1:
frappe.throw('Billing Interval Count cannot be less than 1')

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: Subscription Plan", function (assert) {
let done = assert.async();
// number of asserts
assert.expect(1);
frappe.run_serially([
// insert a new Subscription Plan
() => frappe.tests.make('Subscription Plan', [
// values to be set
{key: 'value'}
]),
() => {
assert.equal(cur_frm.doc.key, 'value');
},
() => done()
]);
});

View File

@ -0,0 +1,9 @@
# -*- coding: utf-8 -*-
# Copyright (c) 2018, Frappe Technologies Pvt. Ltd. and Contributors
# See license.txt
from __future__ import unicode_literals
import unittest
class TestSubscriptionPlan(unittest.TestCase):
pass

View File

@ -0,0 +1,73 @@
{
"allow_copy": 0,
"allow_guest_to_view": 0,
"allow_import": 0,
"allow_rename": 0,
"beta": 0,
"creation": "2018-02-25 07:35:07.736146",
"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": "plan",
"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": "Plan",
"length": 0,
"no_copy": 0,
"options": "Subscription Plan",
"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
}
],
"has_web_view": 0,
"hide_heading": 0,
"hide_toolbar": 0,
"idx": 0,
"image_view": 0,
"in_create": 0,
"is_submittable": 0,
"issingle": 0,
"istable": 1,
"max_attachments": 0,
"modified": "2018-02-25 07:35:07.736146",
"modified_by": "Administrator",
"module": "Accounts",
"name": "Subscription Plan Detail",
"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,9 @@
# -*- coding: utf-8 -*-
# Copyright (c) 2018, Frappe Technologies Pvt. Ltd. and contributors
# For license information, please see license.txt
from __future__ import unicode_literals
from frappe.model.document import Document
class SubscriptionPlanDetail(Document):
pass

View File

@ -0,0 +1,2 @@
// Copyright (c) 2018, Frappe Technologies Pvt. Ltd. and contributors
// For license information, please see license.txt

View File

@ -0,0 +1,179 @@
{
"allow_copy": 0,
"allow_guest_to_view": 0,
"allow_import": 0,
"allow_rename": 0,
"beta": 0,
"creation": "2018-02-26 06:13:37.910139",
"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,
"default": "1",
"description": "Number of days after invoice date has elapsed before canceling subscription or marking subscription as unpaid",
"fieldname": "grace_period",
"fieldtype": "Int",
"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": "Grace Period",
"length": 0,
"no_copy": 0,
"permlevel": 0,
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 0,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"translatable": 0,
"unique": 0
},
{
"allow_bulk_edit": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"default": "0",
"fieldname": "cancel_after_grace",
"fieldtype": "Check",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 0,
"in_standard_filter": 0,
"label": "Cancel Invoice After Grace Period",
"length": 0,
"no_copy": 0,
"permlevel": 0,
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 0,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"translatable": 0,
"unique": 0
},
{
"allow_bulk_edit": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"default": "1",
"fieldname": "prorate",
"fieldtype": "Check",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 0,
"in_standard_filter": 0,
"label": "Prorate",
"length": 0,
"no_copy": 0,
"permlevel": 0,
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 0,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"translatable": 0,
"unique": 0
}
],
"has_web_view": 0,
"hide_heading": 0,
"hide_toolbar": 0,
"idx": 0,
"image_view": 0,
"in_create": 0,
"is_submittable": 0,
"issingle": 1,
"istable": 0,
"max_attachments": 0,
"modified": "2018-02-26 13:58:09.455832",
"modified_by": "Administrator",
"module": "Accounts",
"name": "Subscription Settings",
"name_case": "",
"owner": "Administrator",
"permissions": [
{
"amend": 0,
"apply_user_permissions": 0,
"cancel": 0,
"create": 1,
"delete": 1,
"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
},
{
"amend": 0,
"apply_user_permissions": 0,
"cancel": 0,
"create": 1,
"delete": 1,
"email": 1,
"export": 0,
"if_owner": 0,
"import": 0,
"permlevel": 0,
"print": 1,
"read": 1,
"report": 0,
"role": "Administrator",
"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,9 @@
# -*- coding: utf-8 -*-
# Copyright (c) 2018, Frappe Technologies Pvt. Ltd. and contributors
# For license information, please see license.txt
from __future__ import unicode_literals
from frappe.model.document import Document
class SubscriptionSettings(Document):
pass

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: Subscription Settings", function (assert) {
let done = assert.async();
// number of asserts
assert.expect(1);
frappe.run_serially([
// insert a new Subscription Settings
() => frappe.tests.make('Subscription Settings', [
// values to be set
{key: 'value'}
]),
() => {
assert.equal(cur_frm.doc.key, 'value');
},
() => done()
]);
});

View File

@ -0,0 +1,9 @@
# -*- coding: utf-8 -*-
# Copyright (c) 2018, Frappe Technologies Pvt. Ltd. and Contributors
# See license.txt
from __future__ import unicode_literals
import unittest
class TestSubscriptionSettings(unittest.TestCase):
pass

View File

View File

@ -0,0 +1,28 @@
{
"attach_print": 0,
"condition": "doc.auto_created",
"creation": "2018-04-25 14:19:05.440361",
"days_in_advance": 0,
"docstatus": 0,
"doctype": "Email Alert",
"document_type": "Fiscal Year",
"enabled": 1,
"event": "New",
"idx": 0,
"is_standard": 1,
"message": "<h3>{{_(\"Fiscal Year\")}}</h3>\n\n<p>{{ _(\"New fiscal year created :- \") }} {{ doc.name }}</p>",
"modified": "2018-04-25 14:30:38.588534",
"modified_by": "Administrator",
"module": "Accounts",
"name": "Notification for new fiscal year",
"owner": "Administrator",
"recipients": [
{
"email_by_role": "Accounts User"
},
{
"email_by_role": "Accounts Manager"
}
],
"subject": "Notification for new fiscal year {{ doc.name }}"
}

View File

@ -0,0 +1,3 @@
<h3>{{_("Fiscal Year")}}</h3>
<p>{{ _("New fiscal year created :- ") }} {{ doc.name }}</p>

View File

@ -0,0 +1,5 @@
from __future__ import unicode_literals
def get_context(context):
# do your magic here
pass

View File

@ -405,7 +405,7 @@ erpnext.pos.PointOfSale = erpnext.taxes_and_totals.extend({
make_search: function () { make_search: function () {
var me = this; var me = this;
this.serach_item = frappe.ui.form.make_control({ this.search_item = frappe.ui.form.make_control({
df: { df: {
"fieldtype": "Data", "fieldtype": "Data",
"label": "Item", "label": "Item",
@ -416,13 +416,13 @@ erpnext.pos.PointOfSale = erpnext.taxes_and_totals.extend({
only_input: true, only_input: true,
}); });
this.serach_item.make_input(); this.search_item.make_input();
this.serach_item.$input.on("keypress", function (event) { this.search_item.$input.on("keypress", function (event) {
clearTimeout(me.last_search_timeout); clearTimeout(me.last_search_timeout);
me.last_search_timeout = setTimeout(() => { me.last_search_timeout = setTimeout(() => {
if((me.serach_item.$input.val() != "") || (event.which == 13)) { if((me.search_item.$input.val() != "") || (event.which == 13)) {
me.items = me.get_items(); me.items = me.get_items();
me.make_item_list(); me.make_item_list();
} }
@ -679,7 +679,7 @@ erpnext.pos.PointOfSale = erpnext.taxes_and_totals.extend({
set_focus: function () { set_focus: function () {
if (this.default_customer || this.frm.doc.customer) { if (this.default_customer || this.frm.doc.customer) {
this.set_customer_value_in_party_field(); this.set_customer_value_in_party_field();
this.serach_item.$input.focus(); this.search_item.$input.focus();
} else { } else {
this.party_field.$input.focus(); this.party_field.$input.focus();
} }
@ -1073,8 +1073,8 @@ erpnext.pos.PointOfSale = erpnext.taxes_and_totals.extend({
} }
if (this.items.length == 1 if (this.items.length == 1
&& this.serach_item.$input.val()) { && this.search_item.$input.val()) {
this.serach_item.$input.val(""); this.search_item.$input.val("");
this.add_to_cart(); this.add_to_cart();
} }
}, },
@ -1096,7 +1096,7 @@ erpnext.pos.PointOfSale = erpnext.taxes_and_totals.extend({
this.items_list = this.apply_category(); this.items_list = this.apply_category();
key = this.serach_item.$input.val().toLowerCase().replace(/[&\/\\#,+()\[\]$~.'":*?<>{}]/g, '\\$&'); key = this.search_item.$input.val().toLowerCase().replace(/[&\/\\#,+()\[\]$~.'":*?<>{}]/g, '\\$&');
var re = new RegExp('%', 'g'); var re = new RegExp('%', 'g');
var reg = new RegExp(key.replace(re, '[\\w*\\s*[a-zA-Z0-9]*]*')) var reg = new RegExp(key.replace(re, '[\\w*\\s*[a-zA-Z0-9]*]*'))
search_status = true search_status = true
@ -1104,15 +1104,15 @@ erpnext.pos.PointOfSale = erpnext.taxes_and_totals.extend({
if (key) { if (key) {
return $.grep(this.items_list, function (item) { return $.grep(this.items_list, function (item) {
if (search_status) { if (search_status) {
if (in_list(me.batch_no_data[item.item_code], me.serach_item.$input.val())) { if (in_list(me.batch_no_data[item.item_code], me.search_item.$input.val())) {
search_status = false; search_status = false;
return me.item_batch_no[item.item_code] = me.serach_item.$input.val() return me.item_batch_no[item.item_code] = me.search_item.$input.val()
} else if (me.serial_no_data[item.item_code] } else if (me.serial_no_data[item.item_code]
&& in_list(Object.keys(me.serial_no_data[item.item_code]), me.serach_item.$input.val())) { && in_list(Object.keys(me.serial_no_data[item.item_code]), me.search_item.$input.val())) {
search_status = false; search_status = false;
me.item_serial_no[item.item_code] = [me.serach_item.$input.val(), me.serial_no_data[item.item_code][me.serach_item.$input.val()]] me.item_serial_no[item.item_code] = [me.search_item.$input.val(), me.serial_no_data[item.item_code][me.search_item.$input.val()]]
return true return true
} else if (in_list(me.barcode_data[item.item_code], me.serach_item.$input.val())) { } else if (in_list(me.barcode_data[item.item_code], me.search_item.$input.val())) {
search_status = false; search_status = false;
return true; return true;
} else if (reg.test(item.item_code.toLowerCase()) || (item.description && reg.test(item.description.toLowerCase())) || } else if (reg.test(item.item_code.toLowerCase()) || (item.description && reg.test(item.description.toLowerCase())) ||

Some files were not shown because too many files have changed in this diff Show More