Merge branch 'develop' into multi_company

This commit is contained in:
Anoop 2020-04-28 11:38:07 +05:30 committed by GitHub
commit b5e7ea7ab8
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
158 changed files with 4367 additions and 3112 deletions

View File

@ -1,134 +0,0 @@
{
"allow_copy": 0,
"allow_events_in_timeline": 0,
"allow_guest_to_view": 0,
"allow_import": 1,
"allow_rename": 1,
"autoname": "field:account_subtype",
"beta": 0,
"creation": "2018-10-25 15:46:08.054586",
"custom": 0,
"docstatus": 0,
"doctype": "DocType",
"document_type": "",
"editable_grid": 1,
"engine": "InnoDB",
"fields": [
{
"allow_bulk_edit": 0,
"allow_in_quick_entry": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "account_subtype",
"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": "Account Subtype",
"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": 1
}
],
"has_web_view": 0,
"hide_heading": 0,
"hide_toolbar": 0,
"idx": 0,
"image_view": 0,
"in_create": 0,
"is_submittable": 0,
"issingle": 0,
"istable": 0,
"max_attachments": 0,
"modified": "2018-10-25 15:47:03.841390",
"modified_by": "Administrator",
"module": "Accounts",
"name": "Account Subtype",
"name_case": "",
"owner": "Administrator",
"permissions": [
{
"amend": 0,
"cancel": 0,
"create": 1,
"delete": 1,
"email": 1,
"export": 1,
"if_owner": 0,
"import": 0,
"permlevel": 0,
"print": 1,
"read": 1,
"report": 1,
"role": "System Manager",
"set_user_permissions": 0,
"share": 1,
"submit": 0,
"write": 1
},
{
"amend": 0,
"cancel": 0,
"create": 1,
"delete": 1,
"email": 1,
"export": 1,
"if_owner": 0,
"import": 0,
"permlevel": 0,
"print": 1,
"read": 1,
"report": 1,
"role": "Accounts Manager",
"set_user_permissions": 0,
"share": 1,
"submit": 0,
"write": 1
},
{
"amend": 0,
"cancel": 0,
"create": 1,
"delete": 1,
"email": 1,
"export": 1,
"if_owner": 0,
"import": 0,
"permlevel": 0,
"print": 1,
"read": 1,
"report": 1,
"role": "Accounts User",
"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": 0,
"track_seen": 0,
"track_views": 0
}

View File

@ -1,23 +0,0 @@
/* eslint-disable */
// rename this file from _test_[name] to test_[name] to activate
// and remove above this line
QUnit.test("test: Account Subtype", function (assert) {
let done = assert.async();
// number of asserts
assert.expect(1);
frappe.run_serially([
// insert a new Account Subtype
() => frappe.tests.make('Account Subtype', [
// values to be set
{key: 'value'}
]),
() => {
assert.equal(cur_frm.doc.key, 'value');
},
() => done()
]);
});

View File

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

View File

@ -1,134 +0,0 @@
{
"allow_copy": 0,
"allow_events_in_timeline": 0,
"allow_guest_to_view": 0,
"allow_import": 1,
"allow_rename": 1,
"autoname": "field:account_type",
"beta": 0,
"creation": "2018-10-25 15:45:45.789963",
"custom": 0,
"docstatus": 0,
"doctype": "DocType",
"document_type": "",
"editable_grid": 1,
"engine": "InnoDB",
"fields": [
{
"allow_bulk_edit": 0,
"allow_in_quick_entry": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "account_type",
"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": "Account Type",
"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": 1
}
],
"has_web_view": 0,
"hide_heading": 0,
"hide_toolbar": 0,
"idx": 0,
"image_view": 0,
"in_create": 0,
"is_submittable": 0,
"issingle": 0,
"istable": 0,
"max_attachments": 0,
"modified": "2018-10-25 15:46:51.042604",
"modified_by": "Administrator",
"module": "Accounts",
"name": "Account Type",
"name_case": "",
"owner": "Administrator",
"permissions": [
{
"amend": 0,
"cancel": 0,
"create": 1,
"delete": 1,
"email": 1,
"export": 1,
"if_owner": 0,
"import": 0,
"permlevel": 0,
"print": 1,
"read": 1,
"report": 1,
"role": "System Manager",
"set_user_permissions": 0,
"share": 1,
"submit": 0,
"write": 1
},
{
"amend": 0,
"cancel": 0,
"create": 1,
"delete": 1,
"email": 1,
"export": 1,
"if_owner": 0,
"import": 0,
"permlevel": 0,
"print": 1,
"read": 1,
"report": 1,
"role": "Accounts Manager",
"set_user_permissions": 0,
"share": 1,
"submit": 0,
"write": 1
},
{
"amend": 0,
"cancel": 0,
"create": 1,
"delete": 1,
"email": 1,
"export": 1,
"if_owner": 0,
"import": 0,
"permlevel": 0,
"print": 1,
"read": 1,
"report": 1,
"role": "Accounts User",
"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": 0,
"track_seen": 0,
"track_views": 0
}

View File

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

View File

@ -64,13 +64,13 @@
"fieldname": "account_type",
"fieldtype": "Link",
"label": "Account Type",
"options": "Account Type"
"options": "Bank Account Type"
},
{
"fieldname": "account_subtype",
"fieldtype": "Link",
"label": "Account Subtype",
"options": "Account Subtype"
"options": "Bank Account Subtype"
},
{
"fieldname": "column_break_7",
@ -200,7 +200,7 @@
}
],
"links": [],
"modified": "2020-01-30 20:42:26.458316",
"modified": "2020-04-06 21:00:45.379804",
"modified_by": "Administrator",
"module": "Accounts",
"name": "Bank Account",

View File

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

View File

@ -0,0 +1,134 @@
{
"allow_copy": 0,
"allow_events_in_timeline": 0,
"allow_guest_to_view": 0,
"allow_import": 1,
"allow_rename": 1,
"autoname": "field:account_subtype",
"beta": 0,
"creation": "2018-10-25 15:46:08.054586",
"custom": 0,
"docstatus": 0,
"doctype": "DocType",
"document_type": "",
"editable_grid": 1,
"engine": "InnoDB",
"fields": [
{
"allow_bulk_edit": 0,
"allow_in_quick_entry": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "account_subtype",
"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": "Account Subtype",
"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": 1
}
],
"has_web_view": 0,
"hide_heading": 0,
"hide_toolbar": 0,
"idx": 0,
"image_view": 0,
"in_create": 0,
"is_submittable": 0,
"issingle": 0,
"istable": 0,
"max_attachments": 0,
"modified": "2018-10-25 15:47:03.841390",
"modified_by": "Administrator",
"module": "Accounts",
"name": "Bank Account Subtype",
"name_case": "",
"owner": "Administrator",
"permissions": [
{
"amend": 0,
"cancel": 0,
"create": 1,
"delete": 1,
"email": 1,
"export": 1,
"if_owner": 0,
"import": 0,
"permlevel": 0,
"print": 1,
"read": 1,
"report": 1,
"role": "System Manager",
"set_user_permissions": 0,
"share": 1,
"submit": 0,
"write": 1
},
{
"amend": 0,
"cancel": 0,
"create": 1,
"delete": 1,
"email": 1,
"export": 1,
"if_owner": 0,
"import": 0,
"permlevel": 0,
"print": 1,
"read": 1,
"report": 1,
"role": "Accounts Manager",
"set_user_permissions": 0,
"share": 1,
"submit": 0,
"write": 1
},
{
"amend": 0,
"cancel": 0,
"create": 1,
"delete": 1,
"email": 1,
"export": 1,
"if_owner": 0,
"import": 0,
"permlevel": 0,
"print": 1,
"read": 1,
"report": 1,
"role": "Accounts User",
"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": 0,
"track_seen": 0,
"track_views": 0
}

View File

@ -5,5 +5,5 @@
from __future__ import unicode_literals
from frappe.model.document import Document
class AccountType(Document):
class BankAccountSubtype(Document):
pass

View File

@ -2,15 +2,15 @@
// rename this file from _test_[name] to test_[name] to activate
// and remove above this line
QUnit.test("test: Account Type", function (assert) {
QUnit.test("test: Bank Account Subtype", function (assert) {
let done = assert.async();
// number of asserts
assert.expect(1);
frappe.run_serially([
// insert a new Account Type
() => frappe.tests.make('Account Type', [
// insert a new Bank Account Subtype
() => frappe.tests.make('Bank Account Subtype', [
// values to be set
{key: 'value'}
]),

View File

@ -5,5 +5,5 @@ from __future__ import unicode_literals
import unittest
class TestAccountSubtype(unittest.TestCase):
class TestBankAccountSubtype(unittest.TestCase):
pass

View File

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

View File

@ -0,0 +1,68 @@
{
"actions": [],
"allow_import": 1,
"allow_rename": 1,
"autoname": "field:account_type",
"creation": "2018-10-25 15:45:45.789963",
"doctype": "DocType",
"editable_grid": 1,
"engine": "InnoDB",
"field_order": [
"account_type"
],
"fields": [
{
"fieldname": "account_type",
"fieldtype": "Data",
"label": "Account Type",
"unique": 1
}
],
"links": [],
"modified": "2020-04-10 21:13:09.137898",
"modified_by": "Administrator",
"module": "Accounts",
"name": "Bank Account Type",
"owner": "Administrator",
"permissions": [
{
"create": 1,
"delete": 1,
"email": 1,
"export": 1,
"print": 1,
"read": 1,
"report": 1,
"role": "System Manager",
"share": 1,
"write": 1
},
{
"create": 1,
"delete": 1,
"email": 1,
"export": 1,
"print": 1,
"read": 1,
"report": 1,
"role": "Accounts Manager",
"share": 1,
"write": 1
},
{
"create": 1,
"delete": 1,
"email": 1,
"export": 1,
"print": 1,
"read": 1,
"report": 1,
"role": "Accounts User",
"share": 1,
"write": 1
}
],
"quick_entry": 1,
"sort_field": "modified",
"sort_order": "DESC"
}

View File

@ -0,0 +1,10 @@
# -*- coding: utf-8 -*-
# Copyright (c) 2020, Frappe Technologies Pvt. Ltd. and contributors
# For license information, please see license.txt
from __future__ import unicode_literals
# import frappe
from frappe.model.document import Document
class BankAccountType(Document):
pass

View File

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

View File

@ -9,6 +9,7 @@ from frappe.utils import flt, getdate, add_months, get_last_day, fmt_money, nowd
from frappe.model.naming import make_autoname
from erpnext.accounts.utils import get_fiscal_year
from frappe.model.document import Document
from erpnext.accounts.doctype.accounting_dimension.accounting_dimension import get_accounting_dimensions
class BudgetError(frappe.ValidationError): pass
class DuplicateBudgetError(frappe.ValidationError): pass
@ -98,24 +99,26 @@ def validate_expense_against_budget(args):
if not (args.get('account') and args.get('cost_center')) and args.item_code:
args.cost_center, args.account = get_item_details(args)
if not (args.cost_center or args.project) and not args.account:
if not args.account:
return
for budget_against in ['project', 'cost_center']:
for budget_against in ['project', 'cost_center'] + get_accounting_dimensions():
if (args.get(budget_against) and args.account
and frappe.db.get_value("Account", {"name": args.account, "root_type": "Expense"})):
if args.project and budget_against == 'project':
condition = "and b.project=%s" % frappe.db.escape(args.project)
args.budget_against_field = "Project"
doctype = frappe.unscrub(budget_against)
elif args.cost_center and budget_against == 'cost_center':
cc_lft, cc_rgt = frappe.db.get_value("Cost Center", args.cost_center, ["lft", "rgt"])
condition = """and exists(select name from `tabCost Center`
where lft<=%s and rgt>=%s and name=b.cost_center)""" % (cc_lft, cc_rgt)
args.budget_against_field = "Cost Center"
if frappe.get_cached_value('DocType', doctype, 'is_tree'):
lft, rgt = frappe.db.get_value(doctype, args.get(budget_against), ["lft", "rgt"])
condition = """and exists(select name from `tab%s`
where lft<=%s and rgt>=%s and name=b.%s)""" % (doctype, lft, rgt, budget_against) #nosec
args.is_tree = True
else:
condition = "and b.%s=%s" % (budget_against, frappe.db.escape(args.get(budget_against)))
args.is_tree = False
args.budget_against = args.get(budget_against)
args.budget_against_field = budget_against
args.budget_against_doctype = doctype
budget_records = frappe.db.sql("""
select
@ -132,9 +135,7 @@ def validate_expense_against_budget(args):
b.name=ba.parent and b.fiscal_year=%s
and ba.account=%s and b.docstatus=1
{condition}
""".format(condition=condition,
budget_against_field=frappe.scrub(args.get("budget_against_field"))),
(args.fiscal_year, args.account), as_dict=True)
""".format(condition=condition, budget_against_field=budget_against), (args.fiscal_year, args.account), as_dict=True) #nosec
if budget_records:
validate_budget_records(args, budget_records)
@ -230,7 +231,7 @@ def get_ordered_amount(args, budget):
def get_other_condition(args, budget, for_doc):
condition = "expense_account = '%s'" % (args.expense_account)
budget_against_field = frappe.scrub(args.get("budget_against_field"))
budget_against_field = args.get("budget_against_field")
if budget_against_field and args.get(budget_against_field):
condition += " and child.%s = '%s'" % (budget_against_field, args.get(budget_against_field))
@ -246,19 +247,30 @@ def get_other_condition(args, budget, for_doc):
return condition
def get_actual_expense(args):
if not args.budget_against_doctype:
args.budget_against_doctype = frappe.unscrub(args.budget_against_field)
budget_against_field = args.get('budget_against_field')
condition1 = " and gle.posting_date <= %(month_end_date)s" \
if args.get("month_end_date") else ""
if args.budget_against_field == "Cost Center":
lft_rgt = frappe.db.get_value(args.budget_against_field,
args.budget_against, ["lft", "rgt"], as_dict=1)
if args.is_tree:
lft_rgt = frappe.db.get_value(args.budget_against_doctype,
args.get(budget_against_field), ["lft", "rgt"], as_dict=1)
args.update(lft_rgt)
condition2 = """and exists(select name from `tabCost Center`
where lft>=%(lft)s and rgt<=%(rgt)s and name=gle.cost_center)"""
elif args.budget_against_field == "Project":
condition2 = "and exists(select name from `tabProject` where name=gle.project and gle.project = %(budget_against)s)"
condition2 = """and exists(select name from `tab{doctype}`
where lft>=%(lft)s and rgt<=%(rgt)s
and name=gle.{budget_against_field})""".format(doctype=args.budget_against_doctype, #nosec
budget_against_field=budget_against_field)
else:
condition2 = """and exists(select name from `tab{doctype}`
where name=gle.{budget_against} and
gle.{budget_against} = %({budget_against})s)""".format(doctype=args.budget_against_doctype,
budget_against = budget_against_field)
return flt(frappe.db.sql("""
amount = flt(frappe.db.sql("""
select sum(gle.debit) - sum(gle.credit)
from `tabGL Entry` gle
where gle.account=%(account)s
@ -267,7 +279,9 @@ def get_actual_expense(args):
and gle.company=%(company)s
and gle.docstatus=1
{condition2}
""".format(condition1=condition1, condition2=condition2), (args))[0][0])
""".format(condition1=condition1, condition2=condition2), (args))[0][0]) #nosec
return amount
def get_accumulated_monthly_budget(monthly_distribution, posting_date, fiscal_year, annual_budget):
distribution = {}

View File

@ -13,7 +13,7 @@ from erpnext.accounts.doctype.journal_entry.test_journal_entry import make_journ
class TestBudget(unittest.TestCase):
def test_monthly_budget_crossed_ignore(self):
set_total_expense_zero("2013-02-28", "Cost Center")
set_total_expense_zero("2013-02-28", "cost_center")
budget = make_budget(budget_against="Cost Center")
@ -26,7 +26,7 @@ class TestBudget(unittest.TestCase):
budget.cancel()
def test_monthly_budget_crossed_stop1(self):
set_total_expense_zero("2013-02-28", "Cost Center")
set_total_expense_zero("2013-02-28", "cost_center")
budget = make_budget(budget_against="Cost Center")
@ -41,7 +41,7 @@ class TestBudget(unittest.TestCase):
budget.cancel()
def test_exception_approver_role(self):
set_total_expense_zero("2013-02-28", "Cost Center")
set_total_expense_zero("2013-02-28", "cost_center")
budget = make_budget(budget_against="Cost Center")
@ -114,7 +114,7 @@ class TestBudget(unittest.TestCase):
budget.cancel()
def test_monthly_budget_crossed_stop2(self):
set_total_expense_zero("2013-02-28", "Project")
set_total_expense_zero("2013-02-28", "project")
budget = make_budget(budget_against="Project")
@ -129,7 +129,7 @@ class TestBudget(unittest.TestCase):
budget.cancel()
def test_yearly_budget_crossed_stop1(self):
set_total_expense_zero("2013-02-28", "Cost Center")
set_total_expense_zero("2013-02-28", "cost_center")
budget = make_budget(budget_against="Cost Center")
@ -141,7 +141,7 @@ class TestBudget(unittest.TestCase):
budget.cancel()
def test_yearly_budget_crossed_stop2(self):
set_total_expense_zero("2013-02-28", "Project")
set_total_expense_zero("2013-02-28", "project")
budget = make_budget(budget_against="Project")
@ -153,7 +153,7 @@ class TestBudget(unittest.TestCase):
budget.cancel()
def test_monthly_budget_on_cancellation1(self):
set_total_expense_zero("2013-02-28", "Cost Center")
set_total_expense_zero("2013-02-28", "cost_center")
budget = make_budget(budget_against="Cost Center")
@ -177,7 +177,7 @@ class TestBudget(unittest.TestCase):
budget.cancel()
def test_monthly_budget_on_cancellation2(self):
set_total_expense_zero("2013-02-28", "Project")
set_total_expense_zero("2013-02-28", "project")
budget = make_budget(budget_against="Project")
@ -201,8 +201,8 @@ class TestBudget(unittest.TestCase):
budget.cancel()
def test_monthly_budget_against_group_cost_center(self):
set_total_expense_zero("2013-02-28", "Cost Center")
set_total_expense_zero("2013-02-28", "Cost Center", "_Test Cost Center 2 - _TC")
set_total_expense_zero("2013-02-28", "cost_center")
set_total_expense_zero("2013-02-28", "cost_center", "_Test Cost Center 2 - _TC")
budget = make_budget(budget_against="Cost Center", cost_center="_Test Company - _TC")
frappe.db.set_value("Budget", budget.name, "action_if_accumulated_monthly_budget_exceeded", "Stop")
@ -241,25 +241,30 @@ class TestBudget(unittest.TestCase):
def set_total_expense_zero(posting_date, budget_against_field=None, budget_against_CC=None):
if budget_against_field == "Project":
if budget_against_field == "project":
budget_against = "_Test Project"
else:
budget_against = budget_against_CC or "_Test Cost Center - _TC"
existing_expense = get_actual_expense(frappe._dict({
args = frappe._dict({
"account": "_Test Account Cost for Goods Sold - _TC",
"cost_center": "_Test Cost Center - _TC",
"monthly_end_date": posting_date,
"company": "_Test Company",
"fiscal_year": "_Test Fiscal Year 2013",
"budget_against_field": budget_against_field,
"budget_against": budget_against
}))
})
if not args.get(budget_against_field):
args[budget_against_field] = budget_against
existing_expense = get_actual_expense(args)
if existing_expense:
if budget_against_field == "Cost Center":
if budget_against_field == "cost_center":
make_journal_entry("_Test Account Cost for Goods Sold - _TC",
"_Test Bank - _TC", -existing_expense, "_Test Cost Center - _TC", posting_date="2013-02-28", submit=True)
elif budget_against_field == "Project":
elif budget_against_field == "project":
make_journal_entry("_Test Account Cost for Goods Sold - _TC",
"_Test Bank - _TC", -existing_expense, "_Test Cost Center - _TC", submit=True, project="_Test Project", posting_date="2013-02-28")

View File

@ -106,6 +106,21 @@ frappe.ui.form.on('Payment Entry', {
};
});
frm.set_query('payment_term', 'references', function(frm, cdt, cdn) {
const child = locals[cdt][cdn];
if (in_list(['Purchase Invoice', 'Sales Invoice'], child.reference_doctype) && child.reference_name) {
let payment_term_list = frappe.get_list('Payment Schedule', {'parent': child.reference_name});
payment_term_list = payment_term_list.map(pt => pt.payment_term);
return {
filters: {
'name': ['in', payment_term_list]
}
}
}
});
frm.set_query("reference_name", "references", function(doc, cdt, cdn) {
const child = locals[cdt][cdn];
const filters = {"docstatus": 1, "company": doc.company};
@ -287,7 +302,7 @@ frappe.ui.form.on('Payment Entry', {
frm.set_value("contact_email", "");
frm.set_value("contact_person", "");
}
if(frm.doc.payment_type && frm.doc.party_type && frm.doc.party) {
if(frm.doc.payment_type && frm.doc.party_type && frm.doc.party && frm.doc.company) {
if(!frm.doc.posting_date) {
frappe.msgprint(__("Please select Posting Date before selecting Party"))
frm.set_value("party", "");

View File

@ -71,9 +71,9 @@ class PaymentEntry(AccountsController):
self.update_outstanding_amounts()
self.update_advance_paid()
self.update_expense_claim()
self.update_payment_schedule()
self.set_status()
def on_cancel(self):
self.setup_party_account_field()
self.make_gl_entries(cancel=1)
@ -81,6 +81,7 @@ class PaymentEntry(AccountsController):
self.update_advance_paid()
self.update_expense_claim()
self.delink_advance_entry_references()
self.update_payment_schedule(cancel=1)
self.set_payment_req_status()
self.set_status()
@ -94,10 +95,10 @@ class PaymentEntry(AccountsController):
def validate_duplicate_entry(self):
reference_names = []
for d in self.get("references"):
if (d.reference_doctype, d.reference_name) in reference_names:
if (d.reference_doctype, d.reference_name, d.payment_term) in reference_names:
frappe.throw(_("Row #{0}: Duplicate entry in References {1} {2}")
.format(d.idx, d.reference_doctype, d.reference_name))
reference_names.append((d.reference_doctype, d.reference_name))
reference_names.append((d.reference_doctype, d.reference_name, d.payment_term))
def set_bank_account_data(self):
if self.bank_account:
@ -285,6 +286,36 @@ class PaymentEntry(AccountsController):
frappe.throw(_("Against Journal Entry {0} does not have any unmatched {1} entry")
.format(d.reference_name, dr_or_cr))
def update_payment_schedule(self, cancel=0):
invoice_payment_amount_map = {}
invoice_paid_amount_map = {}
for reference in self.get('references'):
if reference.payment_term and reference.reference_name:
key = (reference.payment_term, reference.reference_name)
invoice_payment_amount_map.setdefault(key, 0.0)
invoice_payment_amount_map[key] += reference.allocated_amount
if not invoice_paid_amount_map.get(reference.reference_name):
payment_schedule = frappe.get_all('Payment Schedule', filters={'parent': reference.reference_name},
fields=['paid_amount', 'payment_amount', 'payment_term'])
for term in payment_schedule:
invoice_key = (term.payment_term, reference.reference_name)
invoice_paid_amount_map.setdefault(invoice_key, {})
invoice_paid_amount_map[invoice_key]['outstanding'] = term.payment_amount - term.paid_amount
for key, amount in iteritems(invoice_payment_amount_map):
if cancel:
frappe.db.sql(""" UPDATE `tabPayment Schedule` SET paid_amount = `paid_amount` - %s
WHERE parent = %s and payment_term = %s""", (amount, key[1], key[0]))
else:
outstanding = invoice_paid_amount_map.get(key)['outstanding']
if amount > outstanding:
frappe.throw(_('Cannot allocate more than {0} against payment term {1}').format(outstanding, key[0]))
frappe.db.sql(""" UPDATE `tabPayment Schedule` SET paid_amount = `paid_amount` + %s
WHERE parent = %s and payment_term = %s""", (amount, key[1], key[0]))
def set_status(self):
if self.docstatus == 2:
self.status = 'Cancelled'
@ -1011,6 +1042,13 @@ def get_payment_entry(dt, dn, party_amount=None, bank_account=None, bank_amount=
# only Purchase Invoice can be blocked individually
if doc.doctype == "Purchase Invoice" and doc.invoice_is_blocked():
frappe.msgprint(_('{0} is on hold till {1}').format(doc.name, doc.release_date))
else:
if (doc.doctype in ('Sales Invoice', 'Purchase Invoice')
and frappe.get_value('Payment Terms Template',
{'name': doc.payment_terms_template}, 'allocate_payment_based_on_payment_terms')):
for reference in get_reference_as_per_payment_terms(doc.payment_schedule, dt, dn, doc, grand_total, outstanding_amount):
pe.append('references', reference)
else:
pe.append("references", {
'reference_doctype': dt,
@ -1029,6 +1067,22 @@ def get_payment_entry(dt, dn, party_amount=None, bank_account=None, bank_amount=
pe.set_amounts()
return pe
def get_reference_as_per_payment_terms(payment_schedule, dt, dn, doc, grand_total, outstanding_amount):
references = []
for payment_term in payment_schedule:
references.append({
'reference_doctype': dt,
'reference_name': dn,
'bill_no': doc.get('bill_no'),
'due_date': doc.get('due_date'),
'total_amount': grand_total,
'outstanding_amount': outstanding_amount,
'payment_term': payment_term.payment_term,
'allocated_amount': flt(payment_term.payment_amount - payment_term.paid_amount,
payment_term.precision('payment_amount'))
})
return references
def get_paid_amount(dt, dn, party_type, party, account, due_date):
if party_type=="Customer":

View File

@ -171,6 +171,32 @@ class TestPaymentEntry(unittest.TestCase):
self.assertEqual(flt(outstanding_amount), 100)
self.assertEqual(status, 'Unpaid')
def test_payment_entry_against_payment_terms(self):
si = create_sales_invoice(do_not_save=1, qty=1, rate=200)
create_payment_terms_template()
si.payment_terms_template = 'Test Receivable Template'
si.append('taxes', {
"charge_type": "On Net Total",
"account_head": "_Test Account Service Tax - _TC",
"cost_center": "_Test Cost Center - _TC",
"description": "Service Tax",
"rate": 18
})
si.save()
si.submit()
pe = get_payment_entry("Sales Invoice", si.name, bank_account="_Test Cash - _TC")
pe.submit()
si.load_from_db()
self.assertEqual(pe.references[0].payment_term, 'Basic Amount Receivable')
self.assertEqual(pe.references[1].payment_term, 'Tax Receivable')
self.assertEqual(si.payment_schedule[0].paid_amount, 200.0)
self.assertEqual(si.payment_schedule[1].paid_amount, 36.0)
def test_payment_against_purchase_invoice_to_check_status(self):
pi = make_purchase_invoice(supplier="_Test Supplier USD", debit_to="_Test Payable USD - _TC",
currency="USD", conversion_rate=50)
@ -610,3 +636,37 @@ class TestPaymentEntry(unittest.TestCase):
accounts_settings.allow_cost_center_in_entry_of_bs_account = 0
accounts_settings.save()
def create_payment_terms_template():
create_payment_term('Basic Amount Receivable')
create_payment_term('Tax Receivable')
if not frappe.db.exists('Payment Terms Template', 'Test Receivable Template'):
payment_term_template = frappe.get_doc({
'doctype': 'Payment Terms Template',
'template_name': 'Test Receivable Template',
'allocate_payment_based_on_payment_terms': 1,
'terms': [{
'doctype': 'Payment Terms Template Detail',
'payment_term': 'Basic Amount Receivable',
'invoice_portion': 84.746,
'credit_days_based_on': 'Day(s) after invoice date',
'credit_days': 1
},
{
'doctype': 'Payment Terms Template Detail',
'payment_term': 'Tax Receivable',
'invoice_portion': 15.254,
'credit_days_based_on': 'Day(s) after invoice date',
'credit_days': 2
}]
}).insert()
def create_payment_term(name):
if not frappe.db.exists('Payment Term', name):
frappe.get_doc({
'doctype': 'Payment Term',
'payment_term_name': name
}).insert()

View File

@ -1,343 +1,107 @@
{
"allow_copy": 0,
"allow_events_in_timeline": 0,
"allow_guest_to_view": 0,
"allow_import": 0,
"allow_rename": 0,
"beta": 0,
"actions": [],
"creation": "2016-06-01 16:55:32.196722",
"custom": 0,
"docstatus": 0,
"doctype": "DocType",
"document_type": "",
"editable_grid": 1,
"engine": "InnoDB",
"field_order": [
"reference_doctype",
"reference_name",
"due_date",
"bill_no",
"payment_term",
"column_break_4",
"total_amount",
"outstanding_amount",
"allocated_amount",
"exchange_rate"
],
"fields": [
{
"allow_bulk_edit": 0,
"allow_in_quick_entry": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 2,
"fetch_if_empty": 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": 1,
"in_standard_filter": 0,
"label": "Type",
"length": 0,
"no_copy": 0,
"options": "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": 1,
"search_index": 0,
"set_only_once": 0,
"translatable": 0,
"unique": 0
"reqd": 1
},
{
"allow_bulk_edit": 0,
"allow_in_quick_entry": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 2,
"fetch_if_empty": 0,
"fieldname": "reference_name",
"fieldtype": "Dynamic Link",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 1,
"in_list_view": 1,
"in_standard_filter": 0,
"label": "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": 1,
"search_index": 0,
"set_only_once": 0,
"translatable": 0,
"unique": 0
"reqd": 1
},
{
"allow_bulk_edit": 0,
"allow_in_quick_entry": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fetch_if_empty": 0,
"fieldname": "due_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": "Due 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": 0,
"search_index": 0,
"set_only_once": 0,
"translatable": 0,
"unique": 0
"read_only": 1
},
{
"allow_bulk_edit": 0,
"allow_in_quick_entry": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"depends_on": "",
"fetch_if_empty": 0,
"fieldname": "bill_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": "Supplier Invoice No",
"length": 0,
"no_copy": 1,
"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
"read_only": 1
},
{
"allow_bulk_edit": 0,
"allow_in_quick_entry": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fetch_if_empty": 0,
"fieldname": "column_break_4",
"fieldtype": "Column Break",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 0,
"in_standard_filter": 0,
"length": 0,
"no_copy": 0,
"permlevel": 0,
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 0,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"translatable": 0,
"unique": 0
"fieldtype": "Column Break"
},
{
"allow_bulk_edit": 0,
"allow_in_quick_entry": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 2,
"fetch_if_empty": 0,
"fieldname": "total_amount",
"fieldtype": "Float",
"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": "Total Amount",
"length": 0,
"no_copy": 0,
"permlevel": 0,
"precision": "",
"print_hide": 1,
"print_hide_if_no_value": 0,
"read_only": 1,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"translatable": 0,
"unique": 0
"read_only": 1
},
{
"allow_bulk_edit": 0,
"allow_in_quick_entry": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 2,
"fetch_if_empty": 0,
"fieldname": "outstanding_amount",
"fieldtype": "Float",
"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",
"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
"read_only": 1
},
{
"allow_bulk_edit": 0,
"allow_in_quick_entry": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 2,
"fetch_if_empty": 0,
"fieldname": "allocated_amount",
"fieldtype": "Float",
"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",
"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
"label": "Allocated"
},
{
"allow_bulk_edit": 0,
"allow_in_quick_entry": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"depends_on": "eval:(doc.reference_doctype=='Purchase Invoice')",
"fetch_if_empty": 0,
"fieldname": "exchange_rate",
"fieldtype": "Float",
"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": "Exchange Rate",
"length": 0,
"no_copy": 0,
"permlevel": 0,
"precision": "",
"print_hide": 1,
"print_hide_if_no_value": 0,
"read_only": 1,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"translatable": 0,
"unique": 0
"read_only": 1
},
{
"fieldname": "payment_term",
"fieldtype": "Link",
"label": "Payment Term",
"options": "Payment Term"
}
],
"has_web_view": 0,
"hide_heading": 0,
"hide_toolbar": 0,
"idx": 0,
"image_view": 0,
"in_create": 0,
"is_submittable": 0,
"issingle": 0,
"istable": 1,
"max_attachments": 0,
"modified": "2019-05-01 13:24:56.586677",
"links": [],
"modified": "2020-03-13 12:07:19.362539",
"modified_by": "Administrator",
"module": "Accounts",
"name": "Payment Entry Reference",
"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,
"track_views": 0
"track_changes": 1
}

View File

@ -1,243 +1,82 @@
{
"allow_copy": 0,
"allow_guest_to_view": 0,
"allow_import": 0,
"allow_rename": 0,
"autoname": "",
"beta": 0,
"actions": [],
"creation": "2017-08-10 15:38:00.080575",
"custom": 0,
"docstatus": 0,
"doctype": "DocType",
"document_type": "",
"editable_grid": 1,
"engine": "InnoDB",
"field_order": [
"payment_term",
"description",
"due_date",
"invoice_portion",
"payment_amount",
"mode_of_payment",
"paid_amount"
],
"fields": [
{
"allow_bulk_edit": 0,
"allow_in_quick_entry": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 2,
"fieldname": "payment_term",
"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": "Payment Term",
"length": 0,
"no_copy": 0,
"options": "Payment Term",
"permlevel": 0,
"precision": "",
"print_hide": 1,
"print_hide_if_no_value": 0,
"read_only": 0,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"translatable": 0,
"unique": 0
"print_hide": 1
},
{
"allow_bulk_edit": 0,
"allow_in_quick_entry": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 2,
"fetch_from": "",
"fieldname": "description",
"fieldtype": "Small Text",
"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,
"options": "",
"permlevel": 0,
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 0,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"translatable": 0,
"unique": 0
"label": "Description"
},
{
"allow_bulk_edit": 0,
"allow_in_quick_entry": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 2,
"fieldname": "due_date",
"fieldtype": "Date",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 1,
"in_standard_filter": 0,
"label": "Due Date",
"length": 0,
"no_copy": 0,
"options": "",
"permlevel": 0,
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 0,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 1,
"search_index": 0,
"set_only_once": 0,
"translatable": 0,
"unique": 0
"reqd": 1
},
{
"allow_bulk_edit": 0,
"allow_in_quick_entry": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 2,
"fetch_from": "",
"fieldname": "invoice_portion",
"fieldtype": "Percent",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 1,
"in_standard_filter": 0,
"label": "Invoice Portion",
"length": 0,
"no_copy": 0,
"options": "",
"permlevel": 0,
"precision": "",
"print_hide": 1,
"print_hide_if_no_value": 0,
"read_only": 0,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"translatable": 0,
"unique": 0
"print_hide": 1
},
{
"allow_bulk_edit": 0,
"allow_in_quick_entry": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 2,
"fieldname": "payment_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": "Payment Amount",
"length": 0,
"no_copy": 0,
"options": "currency",
"permlevel": 0,
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 0,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 1,
"search_index": 0,
"set_only_once": 0,
"translatable": 0,
"unique": 0
"reqd": 1
},
{
"allow_bulk_edit": 0,
"allow_in_quick_entry": 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,
"translatable": 0,
"unique": 0
"options": "Mode of Payment"
},
{
"fieldname": "paid_amount",
"fieldtype": "Currency",
"label": "Paid Amount"
}
],
"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-09-06 17:35:44.580209",
"links": [],
"modified": "2020-03-13 17:58:24.729526",
"modified_by": "Administrator",
"module": "Accounts",
"name": "Payment Schedule",
"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,
"track_views": 0
"track_changes": 1
}

View File

@ -1,164 +1,84 @@
{
"allow_copy": 0,
"allow_guest_to_view": 0,
"actions": [],
"allow_import": 1,
"allow_rename": 1,
"autoname": "field:template_name",
"beta": 0,
"creation": "2017-08-10 15:34:28.058054",
"custom": 0,
"docstatus": 0,
"doctype": "DocType",
"document_type": "",
"editable_grid": 1,
"engine": "InnoDB",
"field_order": [
"template_name",
"allocate_payment_based_on_payment_terms",
"terms"
],
"fields": [
{
"allow_bulk_edit": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "template_name",
"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": "Template 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": 0,
"search_index": 0,
"set_only_once": 0,
"unique": 0
"unique": 1
},
{
"allow_bulk_edit": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "terms",
"fieldtype": "Table",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 0,
"in_standard_filter": 0,
"label": "Payment Terms",
"length": 0,
"no_copy": 0,
"options": "Payment Terms Template Detail",
"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
"reqd": 1
},
{
"default": "0",
"description": "If this checkbox is checked, paid amount will be splitted and allocated as per the amounts in payment schedule against each payment term",
"fieldname": "allocate_payment_based_on_payment_terms",
"fieldtype": "Check",
"label": "Allocate Payment Based On Payment Terms"
}
],
"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-24 11:13:31.158613",
"links": [],
"modified": "2020-04-01 15:35:18.112619",
"modified_by": "Administrator",
"module": "Accounts",
"name": "Payment Terms Template",
"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 User",
"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": 0,
"read_only": 0,
"read_only_onload": 0,
"show_name_in_global_search": 0,
"sort_field": "modified",
"sort_order": "DESC",
"track_changes": 1,
"track_seen": 0
"track_changes": 1
}

View File

@ -237,6 +237,7 @@ def get_pricing_rule_for_item(args, price_list_rate=0, doc=None, for_validate=Fa
if pricing_rule.mixed_conditions or pricing_rule.apply_rule_on_other:
item_details.update({
'apply_rule_on_other_items': json.dumps(pricing_rule.apply_rule_on_other_items),
'price_or_product_discount': pricing_rule.price_or_product_discount,
'apply_rule_on': (frappe.scrub(pricing_rule.apply_rule_on_other)
if pricing_rule.apply_rule_on_other else frappe.scrub(pricing_rule.get('apply_on')))
})

View File

@ -261,12 +261,25 @@ erpnext.accounts.PurchaseInvoice = erpnext.buying.BuyingController.extend({
price_list: this.frm.doc.buying_price_list
}, function() {
me.apply_pricing_rule();
me.frm.doc.apply_tds = me.frm.supplier_tds ? 1 : 0;
me.frm.doc.tax_withholding_category = me.frm.supplier_tds;
me.frm.set_df_property("apply_tds", "read_only", me.frm.supplier_tds ? 0 : 1);
me.frm.set_df_property("tax_withholding_category", "hidden", me.frm.supplier_tds ? 0 : 1);
})
},
apply_tds: function(frm) {
var me = this;
if (!me.frm.doc.apply_tds) {
me.frm.set_value("tax_withholding_category", '');
me.frm.set_df_property("tax_withholding_category", "hidden", 1);
} else {
me.frm.set_value("tax_withholding_category", me.frm.supplier_tds);
me.frm.set_df_property("tax_withholding_category", "hidden", 0);
}
},
credit_to: function() {
var me = this;
if(this.frm.doc.credit_to) {

View File

@ -13,6 +13,7 @@
"supplier_name",
"tax_id",
"due_date",
"tax_withholding_category",
"column_break1",
"company",
"posting_date",
@ -1294,13 +1295,21 @@
"fieldtype": "Check",
"label": "Is Internal Supplier",
"read_only": 1
},
{
"fieldname": "tax_withholding_category",
"fieldtype": "Link",
"hidden": 1,
"label": "Tax Withholding Category",
"options": "Tax Withholding Category",
"print_hide": 1
}
],
"icon": "fa fa-file-text",
"idx": 204,
"is_submittable": 1,
"links": [],
"modified": "2020-04-17 13:05:25.199832",
"modified": "2020-04-18 13:05:25.199832",
"modified_by": "Administrator",
"module": "Accounts",
"name": "Purchase Invoice",

View File

@ -1002,7 +1002,7 @@ class PurchaseInvoice(BuyingController):
if not self.apply_tds:
return
tax_withholding_details = get_party_tax_withholding_details(self)
tax_withholding_details = get_party_tax_withholding_details(self, self.tax_withholding_category)
if not tax_withholding_details:
return

View File

@ -1,4 +1,5 @@
{
"actions": [],
"autoname": "hash",
"creation": "2013-05-21 16:16:04",
"doctype": "DocType",
@ -14,11 +15,11 @@
"col_break1",
"account_head",
"description",
"section_break_10",
"rate",
"accounting_dimensions_section",
"cost_center",
"dimension_col_break",
"section_break_10",
"rate",
"section_break_9",
"tax_amount",
"tax_amount_after_discount_amount",
@ -27,8 +28,7 @@
"base_tax_amount",
"base_total",
"base_tax_amount_after_discount_amount",
"item_wise_tax_detail",
"parenttype"
"item_wise_tax_detail"
],
"fields": [
{
@ -53,6 +53,7 @@
},
{
"columns": 2,
"default": "On Net Total",
"fieldname": "charge_type",
"fieldtype": "Select",
"in_list_view": 1,
@ -196,15 +197,6 @@
"print_hide": 1,
"read_only": 1
},
{
"fieldname": "parenttype",
"fieldtype": "Data",
"hidden": 1,
"label": "Parenttype",
"oldfieldname": "parenttype",
"oldfieldtype": "Data",
"print_hide": 1
},
{
"fieldname": "accounting_dimensions_section",
"fieldtype": "Section Break",
@ -217,11 +209,14 @@
],
"idx": 1,
"istable": 1,
"modified": "2019-05-25 23:08:38.281025",
"links": [],
"modified": "2020-03-12 14:53:47.679439",
"modified_by": "Administrator",
"module": "Accounts",
"name": "Purchase Taxes and Charges",
"owner": "Administrator",
"permissions": [],
"sort_field": "modified",
"sort_order": "DESC",
"track_changes": 1
}

View File

@ -26,16 +26,24 @@ frappe.ui.form.on("Sales Invoice", {
&& !frm.doc.is_return && !frm.doc.ewaybill) {
frm.add_custom_button('E-Way Bill JSON', () => {
var w = window.open(
frappe.urllib.get_full_url(
"/api/method/erpnext.regional.india.utils.generate_ewb_json?"
+ "dt=" + encodeURIComponent(frm.doc.doctype)
+ "&dn=" + encodeURIComponent(frm.doc.name)
)
);
if (!w) {
frappe.msgprint(__("Please enable pop-ups")); return;
frappe.call({
method: 'erpnext.regional.india.utils.generate_ewb_json',
args: {
'dt': frm.doc.doctype,
'dn': [frm.doc.name]
},
callback: function(r) {
if (r.message) {
const args = {
cmd: 'erpnext.regional.india.utils.download_ewb_json',
data: r.message,
docname: frm.doc.name
};
open_url_post(frappe.request.url, args);
}
}
});
}, __("Create"));
}
}

View File

@ -16,17 +16,23 @@ frappe.listview_settings['Sales Invoice'].onload = function (doclist) {
}
}
var w = window.open(
frappe.urllib.get_full_url(
"/api/method/erpnext.regional.india.utils.generate_ewb_json?"
+ "dt=" + encodeURIComponent(doclist.doctype)
+ "&dn=" + encodeURIComponent(docnames)
)
);
if (!w) {
frappe.msgprint(__("Please enable pop-ups")); return;
frappe.call({
method: 'erpnext.regional.india.utils.generate_ewb_json',
args: {
'dt': doclist.doctype,
'dn': docnames
},
callback: function(r) {
if (r.message) {
const args = {
cmd: 'erpnext.regional.india.utils.download_ewb_json',
data: r.message,
docname: docnames
};
open_url_post(frappe.request.url, args);
}
}
});
};
doclist.page.add_actions_menu_item(__('Generate E-Way Bill JSON'), action, false);

View File

@ -32,6 +32,7 @@ erpnext.accounts.SalesInvoiceController = erpnext.selling.SellingController.exte
me.frm.script_manager.trigger("is_pos");
me.frm.refresh_fields();
}
erpnext.queries.setup_warehouse_query(this.frm);
},
refresh: function(doc, dt, dn) {
@ -586,7 +587,9 @@ frappe.ui.form.on('Sales Invoice', {
frm.set_query("account_for_change_amount", function() {
return {
filters: {
account_type: ['in', ["Cash", "Bank"]]
account_type: ['in', ["Cash", "Bank"]],
company: frm.doc.company,
is_group: 0
}
};
});
@ -667,7 +670,8 @@ frappe.ui.form.on('Sales Invoice', {
frm.fields_dict["loyalty_redemption_account"].get_query = function() {
return {
filters:{
"company": frm.doc.company
"company": frm.doc.company,
"is_group": 0
}
}
};
@ -676,7 +680,8 @@ frappe.ui.form.on('Sales Invoice', {
frm.fields_dict["loyalty_redemption_cost_center"].get_query = function() {
return {
filters:{
"company": frm.doc.company
"company": frm.doc.company,
"is_group": 0
}
}
};

View File

@ -1892,7 +1892,7 @@ class TestSalesInvoice(unittest.TestCase):
si.submit()
data = get_ewb_data("Sales Invoice", si.name)
data = get_ewb_data("Sales Invoice", [si.name])
self.assertEqual(data['version'], '1.0.1118')
self.assertEqual(data['billLists'][0]['fromGstin'], '27AAECE4835E1ZR')

View File

@ -6,23 +6,42 @@ from __future__ import unicode_literals
import frappe
from frappe import _
from frappe.model.document import Document
from frappe.utils import flt
from frappe.utils import flt, getdate
from erpnext.accounts.utils import get_fiscal_year
class TaxWithholdingCategory(Document):
pass
def get_party_tax_withholding_details(ref_doc):
tax_withholding_category = frappe.db.get_value('Supplier', ref_doc.supplier, 'tax_withholding_category')
def get_party_tax_withholding_details(ref_doc, tax_withholding_category=None):
pan_no = ''
suppliers = []
if not tax_withholding_category:
tax_withholding_category, pan_no = frappe.db.get_value('Supplier', ref_doc.supplier, ['tax_withholding_category', 'pan'])
if not tax_withholding_category:
return
if not pan_no:
pan_no = frappe.db.get_value('Supplier', ref_doc.supplier, 'pan')
# Get others suppliers with the same PAN No
if pan_no:
suppliers = [d.name for d in frappe.get_all('Supplier', fields=['name'], filters={'pan': pan_no})]
if not suppliers:
suppliers.append(ref_doc.supplier)
fy = get_fiscal_year(ref_doc.posting_date, company=ref_doc.company)
tax_details = get_tax_withholding_details(tax_withholding_category, fy[0], ref_doc.company)
if not tax_details:
frappe.throw(_('Please set associated account in Tax Withholding Category {0} against Company {1}')
.format(tax_withholding_category, ref_doc.company))
tds_amount = get_tds_amount(ref_doc, tax_details, fy)
tds_amount = get_tds_amount(suppliers, ref_doc.net_total, ref_doc.company,
tax_details, fy, ref_doc.posting_date, pan_no)
tax_row = get_tax_row(tax_details, tds_amount)
return tax_row
@ -51,6 +70,7 @@ def get_tax_withholding_rates(tax_withholding, fiscal_year):
frappe.throw(_("No Tax Withholding data found for the current Fiscal Year."))
def get_tax_row(tax_details, tds_amount):
return {
"category": "Total",
"add_deduct_tax": "Deduct",
@ -60,25 +80,36 @@ def get_tax_row(tax_details, tds_amount):
"tax_amount": tds_amount
}
def get_tds_amount(ref_doc, tax_details, fiscal_year_details):
def get_tds_amount(suppliers, net_total, company, tax_details, fiscal_year_details, posting_date, pan_no=None):
fiscal_year, year_start_date, year_end_date = fiscal_year_details
tds_amount = 0
tds_deducted = 0
def _get_tds(amount):
def _get_tds(amount, rate):
if amount <= 0:
return 0
return amount * tax_details.rate / 100
return amount * rate / 100
ldc_name = frappe.db.get_value('Lower Deduction Certificate',
{
'pan_no': pan_no,
'fiscal_year': fiscal_year
}, 'name')
ldc = ''
if ldc_name:
ldc = frappe.get_doc('Lower Deduction Certificate', ldc_name)
entries = frappe.db.sql("""
select voucher_no, credit
from `tabGL Entry`
where party=%s and fiscal_year=%s and credit > 0
""", (ref_doc.supplier, fiscal_year), as_dict=1)
where company = %s and
party in %s and fiscal_year=%s and credit > 0
""", (company, tuple(suppliers), fiscal_year), as_dict=1)
vouchers = [d.voucher_no for d in entries]
advance_vouchers = get_advance_vouchers(ref_doc.supplier, fiscal_year)
advance_vouchers = get_advance_vouchers(suppliers, fiscal_year=fiscal_year, company=company)
tds_vouchers = vouchers + advance_vouchers
@ -93,7 +124,20 @@ def get_tds_amount(ref_doc, tax_details, fiscal_year_details):
tds_deducted = tds_deducted[0][0] if tds_deducted and tds_deducted[0][0] else 0
if tds_deducted:
tds_amount = _get_tds(ref_doc.net_total)
if ldc:
limit_consumed = frappe.db.get_value('Purchase Invoice',
{
'supplier': ('in', suppliers),
'apply_tds': 1,
'docstatus': 1
}, 'sum(net_total)')
if ldc and is_valid_certificate(ldc.valid_from, ldc.valid_upto, posting_date, limit_consumed, net_total,
ldc.certificate_limit):
tds_amount = get_ltds_amount(net_total, limit_consumed, ldc.certificate_limit, ldc.rate, tax_details)
else:
tds_amount = _get_tds(net_total, tax_details.rate)
else:
supplier_credit_amount = frappe.get_all('Purchase Invoice Item',
fields = ['sum(net_amount)'],
@ -106,43 +150,79 @@ def get_tds_amount(ref_doc, tax_details, fiscal_year_details):
fields = ['sum(credit_in_account_currency)'],
filters = {
'parent': ('in', vouchers), 'docstatus': 1,
'party': ref_doc.supplier,
'party': ('in', suppliers),
'reference_type': ('not in', ['Purchase Invoice'])
}, as_list=1)
supplier_credit_amount += (jv_supplier_credit_amt[0][0]
if jv_supplier_credit_amt and jv_supplier_credit_amt[0][0] else 0)
supplier_credit_amount += ref_doc.net_total
supplier_credit_amount += net_total
debit_note_amount = get_debit_note_amount(ref_doc.supplier, year_start_date, year_end_date)
debit_note_amount = get_debit_note_amount(suppliers, year_start_date, year_end_date)
supplier_credit_amount -= debit_note_amount
if ((tax_details.get('threshold', 0) and supplier_credit_amount >= tax_details.threshold)
or (tax_details.get('cumulative_threshold', 0) and supplier_credit_amount >= tax_details.cumulative_threshold)):
tds_amount = _get_tds(supplier_credit_amount)
if ldc and is_valid_certificate(ldc.valid_from, ldc.valid_upto, posting_date, tds_deducted, net_total,
ldc.certificate_limit):
tds_amount = get_ltds_amount(supplier_credit_amount, 0, ldc.certificate_limit, ldc.rate,
tax_details)
else:
tds_amount = _get_tds(supplier_credit_amount, tax_details.rate)
return tds_amount
def get_advance_vouchers(supplier, fiscal_year=None, company=None, from_date=None, to_date=None):
def get_advance_vouchers(suppliers, fiscal_year=None, company=None, from_date=None, to_date=None):
condition = "fiscal_year=%s" % fiscal_year
if company:
condition += "and company =%s" % (company)
if from_date and to_date:
condition = "company=%s and posting_date between %s and %s" % (company, from_date, to_date)
condition += "and posting_date between %s and %s" % (company, from_date, to_date)
## Appending the same supplier again if length of suppliers list is 1
## since tuple of single element list contains None, For example ('Test Supplier 1', )
## and the below query fails
if len(suppliers) == 1:
suppliers.append(suppliers[0])
return frappe.db.sql_list("""
select distinct voucher_no
from `tabGL Entry`
where party=%s and %s and debit > 0
""", (supplier, condition)) or []
where party in %s and %s and debit > 0
""", (tuple(suppliers), condition)) or []
def get_debit_note_amount(supplier, year_start_date, year_end_date, company=None):
condition = ""
def get_debit_note_amount(suppliers, year_start_date, year_end_date, company=None):
condition = "and 1=1"
if company:
condition = " and company=%s " % company
if len(suppliers) == 1:
suppliers.append(suppliers[0])
return flt(frappe.db.sql("""
select abs(sum(net_total))
from `tabPurchase Invoice`
where supplier=%s %s and is_return=1 and docstatus=1
and posting_date between %s and %s
""", (supplier, condition, year_start_date, year_end_date)))
where supplier in %s and is_return=1 and docstatus=1
and posting_date between %s and %s %s
""", (tuple(suppliers), year_start_date, year_end_date, condition)))
def get_ltds_amount(current_amount, deducted_amount, certificate_limit, rate, tax_details):
if current_amount < (certificate_limit - deducted_amount):
return current_amount * rate/100
else:
ltds_amount = (certificate_limit - deducted_amount)
tds_amount = current_amount - ltds_amount
return ltds_amount * rate/100 + tds_amount * tax_details.rate/100
def is_valid_certificate(valid_from, valid_upto, posting_date, deducted_amount, current_amount, certificate_limit):
valid = False
if ((getdate(valid_from) <= getdate(posting_date) <= getdate(valid_upto)) and
certificate_limit > deducted_amount):
valid = True
return valid

View File

@ -344,26 +344,28 @@ class ReceivablePayableReport(object):
def allocate_outstanding_based_on_payment_terms(self, row):
self.get_payment_terms(row)
for term in row.payment_terms:
term.outstanding = term.invoiced
# update "paid" and "oustanding" for this term
if not term.paid:
self.allocate_closing_to_term(row, term, 'paid')
# update "credit_note" and "oustanding" for this term
if term.outstanding:
self.allocate_closing_to_term(row, term, 'credit_note')
row.payment_terms = sorted(row.payment_terms, key=lambda x: x['due_date'])
def get_payment_terms(self, row):
# build payment_terms for row
payment_terms_details = frappe.db.sql("""
select
si.name, si.party_account_currency, si.currency, si.conversion_rate,
ps.due_date, ps.payment_amount, ps.description
ps.due_date, ps.payment_amount, ps.description, ps.paid_amount
from `tab{0}` si, `tabPayment Schedule` ps
where
si.name = ps.parent and
si.name = %s
order by ps.due_date
order by ps.paid_amount desc, due_date
""".format(row.voucher_type), row.voucher_no, as_dict = 1)
@ -389,11 +391,14 @@ class ReceivablePayableReport(object):
"invoiced": invoiced,
"invoice_grand_total": row.invoiced,
"payment_term": d.description,
"paid": 0.0,
"paid": d.paid_amount,
"credit_note": 0.0,
"outstanding": 0.0
"outstanding": invoiced - d.paid_amount
}))
if d.paid_amount:
row['paid'] -= d.paid_amount
def allocate_closing_to_term(self, row, term, key):
if row[key]:
if row[key] > term.outstanding:

View File

@ -8,7 +8,6 @@ from frappe.utils import flt
from erpnext.accounts.report.financial_statements import (get_period_list, get_columns, get_data)
import copy
def execute(filters=None):
period_list = get_period_list(filters.from_fiscal_year, filters.to_fiscal_year,
filters.periodicity, filters.accumulated_values, filters.company)
@ -27,17 +26,19 @@ def execute(filters=None):
gross_income = get_revenue(income, period_list)
gross_expense = get_revenue(expense, period_list)
if(len(gross_income)==0 and len(gross_expense)== 0):
data.append({"account_name": "'" + _("Nothing is included in gross") + "'",
"account": "'" + _("Nothing is included in gross") + "'"})
data.append({
"account_name": "'" + _("Nothing is included in gross") + "'",
"account": "'" + _("Nothing is included in gross") + "'"
})
return columns, data
data.append({"account_name": "'" + _("Included in Gross Profit") + "'",
"account": "'" + _("Included in Gross Profit") + "'"})
data.append({
"account_name": "'" + _("Included in Gross Profit") + "'",
"account": "'" + _("Included in Gross Profit") + "'"
})
data.append({})
data.extend(gross_income or [])
@ -111,7 +112,6 @@ def set_total(node, value, complete_list, totals):
def get_profit(gross_income, gross_expense, period_list, company, profit_type, currency=None, consolidated=False):
profit_loss = {
"account_name": "'" + _(profit_type) + "'",
"account": "'" + _(profit_type) + "'",
@ -123,7 +123,9 @@ def get_profit(gross_income, gross_expense, period_list, company, profit_type, c
for period in period_list:
key = period if consolidated else period.key
profit_loss[key] = flt(gross_income[0].get(key, 0)) - flt(gross_expense[0].get(key, 0))
gross_income_for_period = flt(gross_income[0].get(key, 0)) if gross_income else 0
gross_expense_for_period = flt(gross_expense[0].get(key, 0)) if gross_expense else 0
profit_loss[key] = gross_income_for_period - gross_expense_for_period
if profit_loss[key]:
has_value=True
@ -143,8 +145,14 @@ def get_net_profit(non_gross_income, gross_income, gross_expense, non_gross_expe
for period in period_list:
key = period if consolidated else period.key
total_income = flt(gross_income[0].get(key, 0)) + flt(non_gross_income[0].get(key, 0))
total_expense = flt(gross_expense[0].get(key, 0)) + flt(non_gross_expense[0].get(key, 0))
gross_income_for_period = flt(gross_income[0].get(key, 0)) if gross_income else 0
non_gross_income_for_period = flt(non_gross_income[0].get(key, 0)) if non_gross_income else 0
gross_expense_for_period = flt(gross_expense[0].get(key, 0)) if gross_expense else 0
non_gross_expense_for_period = flt(non_gross_expense[0].get(key, 0)) if non_gross_expense else 0
total_income = gross_income_for_period + non_gross_income_for_period
total_expense = gross_expense_for_period + non_gross_expense_for_period
profit_loss[key] = flt(total_income) - flt(total_expense)
if profit_loss[key]:

View File

@ -44,9 +44,14 @@ def get_result(filters):
out = []
for supplier in filters.supplier:
tds = frappe.get_doc("Tax Withholding Category", supplier.tax_withholding_category)
rate = [d.tax_withholding_rate for d in tds.rates if d.fiscal_year == filters.fiscal_year][0]
rate = [d.tax_withholding_rate for d in tds.rates if d.fiscal_year == filters.fiscal_year]
if rate:
rate = rate[0]
try:
account = [d.account for d in tds.accounts if d.company == filters.company][0]
except IndexError:
account = []
total_invoiced_amount, tds_deducted = get_invoice_and_tds_amount(supplier.name, account,
@ -76,7 +81,7 @@ def get_invoice_and_tds_amount(supplier, account, company, from_date, to_date):
supplier_credit_amount = flt(sum([d.credit for d in entries]))
vouchers = [d.voucher_no for d in entries]
vouchers += get_advance_vouchers(supplier, company=company,
vouchers += get_advance_vouchers([supplier], company=company,
from_date=from_date, to_date=to_date)
tds_deducted = 0
@ -89,7 +94,7 @@ def get_invoice_and_tds_amount(supplier, account, company, from_date, to_date):
""".format(', '.join(["'%s'" % d for d in vouchers])),
(account, from_date, to_date, company))[0][0])
debit_note_amount = get_debit_note_amount(supplier, from_date, to_date, company=company)
debit_note_amount = get_debit_note_amount([supplier], from_date, to_date, company=company)
total_invoiced_amount = supplier_credit_amount + tds_deducted - debit_note_amount

View File

@ -365,9 +365,7 @@ erpnext.buying.PurchaseOrderController = erpnext.buying.BuyingController.extend(
method: "erpnext.stock.doctype.material_request.material_request.make_purchase_order",
source_doctype: "Material Request",
target: me.frm,
setters: {
company: me.frm.doc.company
},
setters: {},
get_query_filters: {
material_request_type: "Purchase",
docstatus: 1,
@ -384,7 +382,7 @@ erpnext.buying.PurchaseOrderController = erpnext.buying.BuyingController.extend(
source_doctype: "Supplier Quotation",
target: me.frm,
setters: {
company: me.frm.doc.company
supplier: me.frm.doc.supplier
},
get_query_filters: {
docstatus: 1,

View File

@ -54,11 +54,6 @@
"items_section",
"scan_barcode",
"items",
"section_break_48",
"pricing_rules",
"raw_material_details",
"set_reserve_warehouse",
"supplied_items",
"sb_last_purchase",
"total_qty",
"base_total",
@ -67,6 +62,11 @@
"total_net_weight",
"total",
"net_total",
"section_break_48",
"pricing_rules",
"raw_material_details",
"set_reserve_warehouse",
"supplied_items",
"taxes_section",
"tax_category",
"column_break_50",
@ -105,23 +105,25 @@
"payment_schedule_section",
"payment_terms_template",
"payment_schedule",
"tracking_section",
"per_billed",
"column_break_75",
"per_received",
"terms_section_break",
"tc_name",
"terms",
"more_info",
"status",
"ref_sq",
"column_break_74",
"party_account_currency",
"inter_company_order_reference",
"column_break_74",
"per_received",
"per_billed",
"column_break5",
"letter_head",
"select_print_heading",
"column_break_86",
"group_same_items",
"language",
"group_same_items",
"subscription_section",
"from_date",
"to_date",
@ -220,7 +222,7 @@
"allow_on_submit": 1,
"fieldname": "schedule_date",
"fieldtype": "Date",
"label": "Reqd By Date"
"label": "Required By"
},
{
"allow_on_submit": 1,
@ -432,6 +434,7 @@
"fieldtype": "Section Break"
},
{
"description": "Sets 'Warehouse' in each row of the Items table.",
"fieldname": "set_warehouse",
"fieldtype": "Link",
"label": "Set Target Warehouse",
@ -827,6 +830,7 @@
"read_only": 1
},
{
"collapsible": 1,
"fieldname": "payment_schedule_section",
"fieldtype": "Section Break",
"label": "Payment Terms"
@ -917,7 +921,8 @@
"fieldname": "inter_company_order_reference",
"fieldtype": "Link",
"label": "Inter Company Order Reference",
"options": "Sales Order"
"options": "Sales Order",
"read_only": 1
},
{
"fieldname": "column_break_74",
@ -930,8 +935,6 @@
"in_list_view": 1,
"label": "% Received",
"no_copy": 1,
"oldfieldname": "per_received",
"oldfieldtype": "Currency",
"print_hide": 1,
"read_only": 1
},
@ -942,8 +945,6 @@
"in_list_view": 1,
"label": "% Billed",
"no_copy": 1,
"oldfieldname": "per_billed",
"oldfieldtype": "Currency",
"print_hide": 1,
"read_only": 1
},
@ -998,6 +999,7 @@
"print_hide": 1
},
{
"collapsible": 1,
"fieldname": "subscription_section",
"fieldtype": "Section Break",
"label": "Subscription Section"
@ -1050,13 +1052,23 @@
"fieldtype": "Link",
"label": "Set Reserve Warehouse",
"options": "Warehouse"
},
{
"collapsible": 1,
"fieldname": "tracking_section",
"fieldtype": "Section Break",
"label": "Tracking"
},
{
"fieldname": "column_break_75",
"fieldtype": "Column Break"
}
],
"icon": "fa fa-file-text",
"idx": 105,
"is_submittable": 1,
"links": [],
"modified": "2020-04-17 13:04:28.185197",
"modified": "2020-04-24 12:13:14.186280",
"modified_by": "Administrator",
"module": "Buying",
"name": "Purchase Order",

View File

@ -18,10 +18,6 @@
"col_break1",
"image",
"image_view",
"manufacture_details",
"manufacturer",
"column_break_14",
"manufacturer_part_no",
"quantity_and_rate",
"qty",
"stock_uom",
@ -44,7 +40,6 @@
"base_amount",
"pricing_rules",
"is_free_item",
"is_fixed_asset",
"section_break_29",
"net_rate",
"net_amount",
@ -52,11 +47,6 @@
"base_net_rate",
"base_net_amount",
"billed_amt",
"item_weight_details",
"weight_per_unit",
"total_weight",
"column_break_40",
"weight_uom",
"warehouse_and_reference",
"warehouse",
"delivered_by_supplier",
@ -80,20 +70,31 @@
"column_break_60",
"received_qty",
"returned_qty",
"manufacture_details",
"manufacturer",
"column_break_14",
"manufacturer_part_no",
"more_info_section_break",
"is_fixed_asset",
"item_tax_rate",
"accounting_details",
"expense_account",
"column_break_68",
"item_weight_details",
"weight_per_unit",
"total_weight",
"column_break_40",
"weight_uom",
"accounting_dimensions_section",
"cost_center",
"dimension_col_break",
"section_break_72",
"page_break",
"item_tax_rate"
"page_break"
],
"fields": [
{
"bold": 1,
"columns": 3,
"columns": 2,
"fieldname": "item_code",
"fieldtype": "Link",
"in_list_view": 1,
@ -133,7 +134,7 @@
"fieldname": "schedule_date",
"fieldtype": "Date",
"in_list_view": 1,
"label": "Reqd By Date",
"label": "Required By",
"oldfieldname": "schedule_date",
"oldfieldtype": "Date",
"print_hide": 1,
@ -216,15 +217,16 @@
"print_hide": 1
},
{
"columns": 1,
"fieldname": "uom",
"fieldtype": "Link",
"in_list_view": 1,
"label": "UOM",
"oldfieldname": "uom",
"oldfieldtype": "Link",
"options": "UOM",
"print_width": "100px",
"reqd": 1,
"width": "100px"
"reqd": 1
},
{
"fieldname": "conversion_factor",
@ -685,6 +687,7 @@
"fieldtype": "Column Break"
},
{
"collapsible": 1,
"fieldname": "manufacture_details",
"fieldtype": "Section Break",
"label": "Manufacture"
@ -717,12 +720,17 @@
"fieldtype": "Check",
"label": "Is Fixed Asset",
"read_only": 1
},
{
"fieldname": "more_info_section_break",
"fieldtype": "Section Break",
"label": "More Information"
}
],
"idx": 1,
"istable": 1,
"links": [],
"modified": "2020-04-07 18:35:17.558928",
"modified": "2020-04-21 11:55:58.643393",
"modified_by": "Administrator",
"module": "Buying",
"name": "Purchase Order Item",

View File

@ -1,20 +1,26 @@
{
"actions": [],
"creation": "2013-02-22 01:27:42",
"doctype": "DocType",
"editable_grid": 1,
"engine": "InnoDB",
"field_order": [
"main_item_code",
"rm_item_code",
"required_qty",
"supplied_qty",
"rate",
"amount",
"column_break_6",
"bom_detail_no",
"reference_name",
"conversion_factor",
"stock_uom",
"reserve_warehouse"
"conversion_factor",
"column_break_6",
"rm_item_code",
"reference_name",
"reserve_warehouse",
"section_break2",
"rate",
"col_break2",
"amount",
"section_break1",
"required_qty",
"col_break1",
"supplied_qty"
],
"fields": [
{
@ -120,15 +126,34 @@
"in_list_view": 1,
"label": "Supplied Qty",
"read_only": 1
},
{
"fieldname": "section_break1",
"fieldtype": "Section Break"
},
{
"fieldname": "col_break1",
"fieldtype": "Column Break"
},
{
"fieldname": "section_break2",
"fieldtype": "Section Break"
},
{
"fieldname": "col_break2",
"fieldtype": "Column Break"
}
],
"hide_toolbar": 1,
"idx": 1,
"istable": 1,
"modified": "2019-08-20 13:37:32.702068",
"links": [],
"modified": "2020-03-12 15:43:53.862897",
"modified_by": "Administrator",
"module": "Buying",
"name": "Purchase Order Item Supplied",
"owner": "dhanalekshmi@webnotestech.com",
"permissions": []
"permissions": [],
"sort_field": "modified",
"sort_order": "DESC"
}

View File

@ -141,19 +141,18 @@ def get_conditions(filters):
conditions = ""
if filters.get("company"):
conditions += " AND company=%s"% frappe.db.escape(filters.get('company'))
conditions += " AND par.company=%s" % frappe.db.escape(filters.get('company'))
if filters.get("cost_center") or filters.get("project"):
conditions += """
AND (cost_center=%s
OR project=%s)
AND (child.`cost_center`=%s OR child.`project`=%s)
""" % (frappe.db.escape(filters.get('cost_center')), frappe.db.escape(filters.get('project')))
if filters.get("from_date"):
conditions += " AND transaction_date>=%s"% filters.get('from_date')
conditions += " AND par.transaction_date>='%s'" % filters.get('from_date')
if filters.get("to_date"):
conditions += " AND transaction_date<=%s"% filters.get('to_date')
conditions += " AND par.transaction_date<='%s'" % filters.get('to_date')
return conditions
def get_data(filters):
@ -162,7 +161,6 @@ def get_data(filters):
mr_records, procurement_record_against_mr = get_mapped_mr_details(conditions)
pr_records = get_mapped_pr_records()
pi_records = get_mapped_pi_records()
print(pi_records)
procurement_record=[]
if procurement_record_against_mr:
@ -198,16 +196,16 @@ def get_mapped_mr_details(conditions):
mr_records = {}
mr_details = frappe.db.sql("""
SELECT
mr.transaction_date,
mr.per_ordered,
mr_item.name,
mr_item.parent,
mr_item.amount
FROM `tabMaterial Request` mr, `tabMaterial Request Item` mr_item
par.transaction_date,
par.per_ordered,
child.name,
child.parent,
child.amount
FROM `tabMaterial Request` par, `tabMaterial Request Item` child
WHERE
mr.per_ordered>=0
AND mr.name=mr_item.parent
AND mr.docstatus=1
par.per_ordered>=0
AND par.name=child.parent
AND par.docstatus=1
{conditions}
""".format(conditions=conditions), as_dict=1) #nosec
@ -254,29 +252,29 @@ def get_mapped_pr_records():
def get_po_entries(conditions):
return frappe.db.sql("""
SELECT
po_item.name,
po_item.parent,
po_item.cost_center,
po_item.project,
po_item.warehouse,
po_item.material_request,
po_item.material_request_item,
po_item.description,
po_item.stock_uom,
po_item.qty,
po_item.amount,
po_item.base_amount,
po_item.schedule_date,
po.transaction_date,
po.supplier,
po.status,
po.owner
FROM `tabPurchase Order` po, `tabPurchase Order Item` po_item
child.name,
child.parent,
child.cost_center,
child.project,
child.warehouse,
child.material_request,
child.material_request_item,
child.description,
child.stock_uom,
child.qty,
child.amount,
child.base_amount,
child.schedule_date,
par.transaction_date,
par.supplier,
par.status,
par.owner
FROM `tabPurchase Order` par, `tabPurchase Order Item` child
WHERE
po.docstatus = 1
AND po.name = po_item.parent
AND po.status not in ("Closed","Completed","Cancelled")
par.docstatus = 1
AND par.name = child.parent
AND par.status not in ("Closed","Completed","Cancelled")
{conditions}
GROUP BY
po.name,po_item.item_code
par.name, child.item_code
""".format(conditions=conditions), as_dict=1) #nosec

View File

@ -819,7 +819,7 @@ class AccountsController(TransactionBase):
else:
for d in self.get("payment_schedule"):
if d.invoice_portion:
d.payment_amount = grand_total * flt(d.invoice_portion) / 100
d.payment_amount = flt(grand_total * flt(d.invoice_portion) / 100, d.precision('payment_amount'))
def set_due_date(self):
due_dates = [d.due_date for d in self.get("payment_schedule") if d.due_date]

View File

@ -46,6 +46,7 @@ class SellingController(StockController):
set_default_income_account_for_item(self)
self.set_customer_address()
self.validate_for_duplicate_items()
self.validate_target_warehouse()
def set_missing_values(self, for_validate=False):
@ -403,6 +404,14 @@ class SellingController(StockController):
else:
chk_dupl_itm.append(f)
def validate_target_warehouse(self):
items = self.get("items") + (self.get("packed_items") or [])
for d in items:
if d.get("target_warehouse") and d.get("warehouse") == d.get("target_warehouse"):
warehouse = frappe.bold(d.get("target_warehouse"))
frappe.throw(_("Row {0}: Delivery Warehouse ({1}) and Customer Warehouse ({2}) can not be same")
.format(d.idx, warehouse, warehouse))
def validate_items(self):
# validate items to see if they have is_sales_item enabled

View File

@ -12,13 +12,18 @@
},
{
"hidden": 0,
"label": "Settings",
"links": "[\n {\n \"description\": \"Manage Customer Group Tree.\",\n \"icon\": \"fa fa-sitemap\",\n \"label\": \"Customer Group\",\n \"link\": \"Tree/Customer Group\",\n \"name\": \"Customer Group\",\n \"onboard\": 1,\n \"type\": \"doctype\"\n },\n {\n \"description\": \"Manage Territory Tree.\",\n \"icon\": \"fa fa-sitemap\",\n \"label\": \"Territory\",\n \"link\": \"Tree/Territory\",\n \"name\": \"Territory\",\n \"onboard\": 1,\n \"type\": \"doctype\"\n },\n {\n \"description\": \"Manage Sales Person Tree.\",\n \"icon\": \"fa fa-sitemap\",\n \"label\": \"Sales Person\",\n \"link\": \"Tree/Sales Person\",\n \"name\": \"Sales Person\",\n \"onboard\": 1,\n \"type\": \"doctype\"\n },\n {\n \"description\": \"Sales campaigns.\",\n \"label\": \"Campaign\",\n \"name\": \"Campaign\",\n \"type\": \"doctype\"\n },\n {\n \"description\": \"Sends Mails to lead or contact based on a Campaign schedule\",\n \"label\": \"Email Campaign\",\n \"name\": \"Email Campaign\",\n \"type\": \"doctype\"\n },\n {\n \"description\": \"Send mass SMS to your contacts\",\n \"label\": \"SMS Center\",\n \"name\": \"SMS Center\",\n \"type\": \"doctype\"\n },\n {\n \"description\": \"Logs for maintaining sms delivery status\",\n \"label\": \"SMS Log\",\n \"name\": \"SMS Log\",\n \"type\": \"doctype\"\n },\n {\n \"description\": \"Setup SMS gateway settings\",\n \"label\": \"SMS Settings\",\n \"name\": \"SMS Settings\",\n \"type\": \"doctype\"\n },\n {\n \"label\": \"Email Group\",\n \"name\": \"Email Group\",\n \"type\": \"doctype\"\n }\n]"
"label": "Maintenance",
"links": "[\n {\n \"description\": \"Plan for maintenance visits.\",\n \"label\": \"Maintenance Schedule\",\n \"name\": \"Maintenance Schedule\",\n \"onboard\": 1,\n \"type\": \"doctype\"\n },\n {\n \"description\": \"Visit report for maintenance call.\",\n \"label\": \"Maintenance Visit\",\n \"name\": \"Maintenance Visit\",\n \"type\": \"doctype\"\n },\n {\n \"description\": \"Warranty Claim against Serial No.\",\n \"label\": \"Warranty Claim\",\n \"name\": \"Warranty Claim\",\n \"type\": \"doctype\"\n }\n]"
},
{
"hidden": 0,
"label": "Maintenance",
"links": "[\n {\n \"description\": \"Plan for maintenance visits.\",\n \"label\": \"Maintenance Schedule\",\n \"name\": \"Maintenance Schedule\",\n \"onboard\": 1,\n \"type\": \"doctype\"\n },\n {\n \"description\": \"Visit report for maintenance call.\",\n \"label\": \"Maintenance Visit\",\n \"name\": \"Maintenance Visit\",\n \"type\": \"doctype\"\n },\n {\n \"description\": \"Warranty Claim against Serial No.\",\n \"label\": \"Warranty Claim\",\n \"name\": \"Warranty Claim\",\n \"type\": \"doctype\"\n }\n]"
"label": "Campaign",
"links": "[\n {\n \"description\": \"Sales campaigns.\",\n \"label\": \"Campaign\",\n \"name\": \"Campaign\",\n \"type\": \"doctype\"\n },\n {\n \"description\": \"Sends Mails to lead or contact based on a Campaign schedule\",\n \"label\": \"Email Campaign\",\n \"name\": \"Email Campaign\",\n \"type\": \"doctype\"\n },\n {\n \"description\": \"Create and Schedule social media posts\",\n \"label\": \"Social Media Post\",\n \"name\": \"Social Media Post\",\n \"type\": \"doctype\"\n }\n]"
},
{
"hidden": 0,
"label": "Settings",
"links": "[\n {\n \"description\": \"Manage Customer Group Tree.\",\n \"icon\": \"fa fa-sitemap\",\n \"label\": \"Customer Group\",\n \"link\": \"Tree/Customer Group\",\n \"name\": \"Customer Group\",\n \"onboard\": 1,\n \"type\": \"doctype\"\n },\n {\n \"description\": \"Manage Territory Tree.\",\n \"icon\": \"fa fa-sitemap\",\n \"label\": \"Territory\",\n \"link\": \"Tree/Territory\",\n \"name\": \"Territory\",\n \"onboard\": 1,\n \"type\": \"doctype\"\n },\n {\n \"description\": \"Manage Sales Person Tree.\",\n \"icon\": \"fa fa-sitemap\",\n \"label\": \"Sales Person\",\n \"link\": \"Tree/Sales Person\",\n \"name\": \"Sales Person\",\n \"onboard\": 1,\n \"type\": \"doctype\"\n },\n {\n \"description\": \"Send mass SMS to your contacts\",\n \"label\": \"SMS Center\",\n \"name\": \"SMS Center\",\n \"type\": \"doctype\"\n },\n {\n \"description\": \"Logs for maintaining sms delivery status\",\n \"label\": \"SMS Log\",\n \"name\": \"SMS Log\",\n \"type\": \"doctype\"\n },\n {\n \"description\": \"Setup SMS gateway settings\",\n \"label\": \"SMS Settings\",\n \"name\": \"SMS Settings\",\n \"type\": \"doctype\"\n },\n {\n \"label\": \"Email Group\",\n \"name\": \"Email Group\",\n \"type\": \"doctype\"\n },\n {\n \"label\": \"Twitter Settings\",\n \"name\": \"Twitter Settings\",\n \"type\": \"doctype\"\n },\n {\n \"label\": \"LinkedIn Settings\",\n \"name\": \"LinkedIn Settings\",\n \"type\": \"doctype\"\n }\n]"
}
],
"category": "Modules",
@ -33,7 +38,7 @@
"idx": 0,
"is_standard": 1,
"label": "CRM",
"modified": "2020-04-01 11:28:51.219999",
"modified": "2020-04-27 22:32:26.682911",
"modified_by": "Administrator",
"module": "CRM",
"name": "CRM",
@ -42,7 +47,7 @@
"pin_to_top": 0,
"shortcuts": [
{
"format": "Open",
"format": "{} Open",
"label": "Lead",
"link_to": "Lead",
"stats_filter": "{\"status\":\"Open\"}",

View File

@ -27,7 +27,7 @@ class EmailCampaign(Document):
for entry in campaign.get("campaign_schedules"):
send_after_days.append(entry.send_after_days)
try:
end_date = add_days(getdate(self.start_date), max(send_after_days))
self.end_date = add_days(getdate(self.start_date), max(send_after_days))
except ValueError:
frappe.throw(_("Please set up the Campaign Schedule in the Campaign {0}").format(self.campaign_name))

View File

@ -0,0 +1,71 @@
// Copyright (c) 2020, Frappe Technologies Pvt. Ltd. and contributors
// For license information, please see license.txt
frappe.ui.form.on('LinkedIn Settings', {
onload: function(frm){
if (frm.doc.session_status == 'Expired' && frm.doc.consumer_key && frm.doc.consumer_secret){
frappe.confirm(
__('Session not valid, Do you want to login?'),
function(){
frm.trigger("login");
},
function(){
window.close();
}
);
}
},
refresh: function(frm){
if (frm.doc.session_status=="Expired"){
let msg = __("Session Not Active. Save doc to login.");
frm.dashboard.set_headline_alert(
`<div class="row">
<div class="col-xs-12">
<span class="indicator whitespace-nowrap red"><span class="hidden-xs">${msg}</span></span>
</div>
</div>`
);
}
if (frm.doc.session_status=="Active"){
let d = new Date(frm.doc.modified);
d.setDate(d.getDate()+60);
let dn = new Date();
let days = d.getTime() - dn.getTime();
days = Math.floor(days/(1000 * 3600 * 24));
let msg,color;
if (days>0){
msg = __("Your Session will be expire in ") + days + __(" days.");
color = "green";
}
else {
msg = __("Session is expired. Save doc to login.");
color = "red";
}
frm.dashboard.set_headline_alert(
`<div class="row">
<div class="col-xs-12">
<span class="indicator whitespace-nowrap ${color}"><span class="hidden-xs">${msg}</span></span>
</div>
</div>`
);
}
},
login: function(frm){
if (frm.doc.consumer_key && frm.doc.consumer_secret){
frappe.dom.freeze();
frappe.call({
doc: frm.doc,
method: "get_authorization_url",
callback : function(r) {
window.location.href = r.message;
}
});
}
},
after_save: function(frm){
frm.trigger("login");
}
});

View File

@ -0,0 +1,111 @@
{
"actions": [],
"creation": "2020-01-30 13:36:39.492931",
"doctype": "DocType",
"editable_grid": 1,
"engine": "InnoDB",
"field_order": [
"account_name",
"column_break_2",
"company_id",
"oauth_details",
"consumer_key",
"column_break_5",
"consumer_secret",
"user_details_section",
"access_token",
"person_urn",
"session_status"
],
"fields": [
{
"fieldname": "account_name",
"fieldtype": "Data",
"label": "Account Name",
"read_only": 1
},
{
"fieldname": "oauth_details",
"fieldtype": "Section Break",
"label": "OAuth Credentials"
},
{
"fieldname": "consumer_key",
"fieldtype": "Data",
"in_list_view": 1,
"label": "Consumer Key",
"reqd": 1
},
{
"fieldname": "consumer_secret",
"fieldtype": "Password",
"in_list_view": 1,
"label": "Consumer Secret",
"reqd": 1
},
{
"fieldname": "access_token",
"fieldtype": "Data",
"hidden": 1,
"label": "Access Token",
"read_only": 1
},
{
"fieldname": "person_urn",
"fieldtype": "Data",
"hidden": 1,
"label": "Person URN",
"read_only": 1
},
{
"fieldname": "column_break_5",
"fieldtype": "Column Break"
},
{
"fieldname": "user_details_section",
"fieldtype": "Section Break",
"label": "User Details"
},
{
"fieldname": "session_status",
"fieldtype": "Select",
"hidden": 1,
"label": "Session Status",
"options": "Expired\nActive",
"read_only": 1
},
{
"fieldname": "column_break_2",
"fieldtype": "Column Break"
},
{
"fieldname": "company_id",
"fieldtype": "Data",
"label": "Company ID",
"reqd": 1
}
],
"issingle": 1,
"links": [],
"modified": "2020-04-16 23:22:51.966397",
"modified_by": "Administrator",
"module": "CRM",
"name": "LinkedIn Settings",
"owner": "Administrator",
"permissions": [
{
"create": 1,
"delete": 1,
"email": 1,
"print": 1,
"read": 1,
"role": "System Manager",
"share": 1,
"write": 1
}
],
"quick_entry": 1,
"sort_field": "modified",
"sort_order": "DESC",
"track_changes": 1
}

View File

@ -0,0 +1,166 @@
# -*- coding: utf-8 -*-
# Copyright (c) 2020, Frappe Technologies Pvt. Ltd. and contributors
# For license information, please see license.txt
from __future__ import unicode_literals
import frappe, requests, json
from frappe import _
from frappe.utils import get_site_url, get_url_to_form, get_link_to_form
from frappe.model.document import Document
from frappe.utils.file_manager import get_file, get_file_path
from six.moves.urllib.parse import urlencode
class LinkedInSettings(Document):
def get_authorization_url(self):
params = urlencode({
"response_type":"code",
"client_id": self.consumer_key,
"redirect_uri": get_site_url(frappe.local.site) + "/?cmd=erpnext.crm.doctype.linkedin_settings.linkedin_settings.callback",
"scope": "r_emailaddress w_organization_social r_basicprofile r_liteprofile r_organization_social rw_organization_admin w_member_social"
})
url = "https://www.linkedin.com/oauth/v2/authorization?{}".format(params)
return url
def get_access_token(self, code):
url = "https://www.linkedin.com/oauth/v2/accessToken"
body = {
"grant_type": "authorization_code",
"code": code,
"client_id": self.consumer_key,
"client_secret": self.get_password(fieldname="consumer_secret"),
"redirect_uri": get_site_url(frappe.local.site) + "/?cmd=erpnext.crm.doctype.linkedin_settings.linkedin_settings.callback",
}
headers = {
"Content-Type": "application/x-www-form-urlencoded"
}
response = self.http_post(url=url, data=body, headers=headers)
response = frappe.parse_json(response.content.decode())
self.db_set("access_token", response["access_token"])
def get_member_profile(self):
headers = {
"Authorization": "Bearer {}".format(self.access_token)
}
url = "https://api.linkedin.com/v2/me"
response = requests.get(url=url, headers=headers)
response = frappe.parse_json(response.content.decode())
frappe.db.set_value(self.doctype, self.name, {
"person_urn": response["id"],
"account_name": response["vanityName"],
"session_status": "Active"
})
frappe.local.response["type"] = "redirect"
frappe.local.response["location"] = get_url_to_form("LinkedIn Settings","LinkedIn Settings")
def post(self, text, media=None):
if not media:
return self.post_text(text)
else:
media_id = self.upload_image(media)
if media_id:
return self.post_text(text, media_id=media_id)
else:
frappe.log_error("Failed to upload media.","LinkedIn Upload Error")
def upload_image(self, media):
media = get_file_path(media)
register_url = "https://api.linkedin.com/v2/assets?action=registerUpload"
body = {
"registerUploadRequest": {
"recipes": ["urn:li:digitalmediaRecipe:feedshare-image"],
"owner": "urn:li:organization:{0}".format(self.company_id),
"serviceRelationships": [{
"relationshipType": "OWNER",
"identifier": "urn:li:userGeneratedContent"
}]
}
}
headers = {
"Authorization": "Bearer {}".format(self.access_token)
}
response = self.http_post(url=register_url, body=body, headers=headers)
if response.status_code == 200:
response = response.json()
asset = response["value"]["asset"]
upload_url = response["value"]["uploadMechanism"]["com.linkedin.digitalmedia.uploading.MediaUploadHttpRequest"]["uploadUrl"]
headers['Content-Type']='image/jpeg'
response = self.http_post(upload_url, headers=headers, data=open(media,"rb"))
if response.status_code < 200 and response.status_code > 299:
frappe.throw(_("Error While Uploading Image"), title="{0} {1}".format(response.status_code, response.reason))
return None
return asset
return None
def post_text(self, text, media_id=None):
url = "https://api.linkedin.com/v2/shares"
headers = {
"X-Restli-Protocol-Version": "2.0.0",
"Authorization": "Bearer {}".format(self.access_token),
"Content-Type": "application/json; charset=UTF-8"
}
body = {
"distribution": {
"linkedInDistributionTarget": {}
},
"owner":"urn:li:organization:{0}".format(self.company_id),
"subject": "Test Share Subject",
"text": {
"text": text
}
}
if media_id:
body["content"]= {
"contentEntities": [{
"entity": media_id
}],
"shareMediaCategory": "IMAGE"
}
response = self.http_post(url=url, headers=headers, body=body)
return response
def http_post(self, url, headers=None, body=None, data=None):
try:
response = requests.post(
url = url,
json = body,
data = data,
headers = headers
)
if response.status_code not in [201,200]:
raise
except Exception as e:
content = json.loads(response.content)
if response.status_code == 401:
self.db_set("session_status", "Expired")
frappe.db.commit()
frappe.throw(content["message"], title="LinkedIn Error - Unauthorized")
elif response.status_code == 403:
frappe.msgprint(_("You Didn't have permission to access this API"))
frappe.throw(content["message"], title="LinkedIn Error - Access Denied")
else:
frappe.throw(response.reason, title=response.status_code)
return response
@frappe.whitelist()
def callback(code=None, error=None, error_description=None):
if not error:
linkedin_settings = frappe.get_doc("LinkedIn Settings")
linkedin_settings.get_access_token(code)
linkedin_settings.get_member_profile()
frappe.db.commit()
else:
frappe.local.response["type"] = "redirect"
frappe.local.response["location"] = get_url_to_form("LinkedIn Settings","LinkedIn Settings")

View File

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

View File

@ -0,0 +1,67 @@
// Copyright (c) 2020, Frappe Technologies Pvt. Ltd. and contributors
// For license information, please see license.txt
frappe.ui.form.on('Social Media Post', {
validate: function(frm){
if (frm.doc.twitter === 0 && frm.doc.linkedin === 0){
frappe.throw(__("Select atleast one Social Media from Share on."))
}
if (frm.doc.scheduled_time) {
let scheduled_time = new Date(frm.doc.scheduled_time);
let date_time = new Date();
if (scheduled_time.getTime() < date_time.getTime()){
frappe.throw(__("Invalid Scheduled Time"));
}
}
if (frm.doc.text?.length > 280){
frappe.throw(__("Length Must be less than 280."))
}
},
refresh: function(frm){
if (frm.doc.docstatus === 1){
if (frm.doc.post_status != "Posted"){
add_post_btn(frm);
}
else if (frm.doc.post_status == "Posted"){
frm.set_df_property('sheduled_time', 'read_only', 1);
}
let html='';
if (frm.doc.twitter){
let color = frm.doc.twitter_post_id ? "green" : "red";
let status = frm.doc.twitter_post_id ? "Posted" : "Not Posted";
html += `<div class="col-xs-6">
<span class="indicator whitespace-nowrap ${color}"><span class="hidden-xs">Twitter : ${status} </span></span>
</div>` ;
}
if (frm.doc.linkedin){
let color = frm.doc.linkedin_post_id ? "green" : "red";
let status = frm.doc.linkedin_post_id ? "Posted" : "Not Posted";
html += `<div class="col-xs-6">
<span class="indicator whitespace-nowrap ${color}"><span class="hidden-xs">LinkedIn : ${status} </span></span>
</div>` ;
}
html = `<div class="row">${html}</div>`;
frm.dashboard.set_headline_alert(html);
}
}
});
var add_post_btn = function(frm){
frm.add_custom_button(('Post Now'), function(){
post(frm);
});
}
var post = function(frm){
frappe.dom.freeze();
frappe.call({
method: "erpnext.crm.doctype.social_media_post.social_media_post.publish",
args: {
doctype: frm.doc.doctype,
name: frm.doc.name
},
callback: function(r) {
frm.reload_doc();
frappe.dom.unfreeze();
}
})
}

View File

@ -0,0 +1,166 @@
{
"actions": [],
"autoname": "format: CRM-SMP-{YYYY}-{MM}-{DD}-{###}",
"creation": "2020-01-30 11:53:13.872864",
"doctype": "DocType",
"editable_grid": 1,
"engine": "InnoDB",
"field_order": [
"campaign_name",
"scheduled_time",
"post_status",
"column_break_6",
"twitter",
"linkedin",
"twitter_post_id",
"linkedin_post_id",
"content",
"text",
"column_break_14",
"tweet_preview",
"linkedin_section",
"linkedin_post",
"column_break_15",
"attachments_section",
"image",
"amended_from"
],
"fields": [
{
"fieldname": "text",
"fieldtype": "Small Text",
"label": "Tweet",
"mandatory_depends_on": "eval:doc.twitter ==1"
},
{
"fieldname": "image",
"fieldtype": "Attach Image",
"label": "Image"
},
{
"default": "0",
"fieldname": "twitter",
"fieldtype": "Check",
"label": "Twitter"
},
{
"default": "0",
"fieldname": "linkedin",
"fieldtype": "Check",
"label": "LinkedIn"
},
{
"fieldname": "amended_from",
"fieldtype": "Link",
"label": "Amended From",
"no_copy": 1,
"options": "Social Media Post",
"print_hide": 1,
"read_only": 1
},
{
"depends_on": "eval:doc.twitter ==1",
"fieldname": "content",
"fieldtype": "Section Break",
"label": "Twitter"
},
{
"allow_on_submit": 1,
"fieldname": "post_status",
"fieldtype": "Select",
"label": "Post Status",
"options": "\nScheduled\nPosted\nError",
"read_only": 1
},
{
"allow_on_submit": 1,
"fieldname": "twitter_post_id",
"fieldtype": "Data",
"hidden": 1,
"label": "Twitter Post Id",
"read_only": 1
},
{
"allow_on_submit": 1,
"fieldname": "linkedin_post_id",
"fieldtype": "Data",
"hidden": 1,
"label": "LinkedIn Post Id",
"read_only": 1
},
{
"fieldname": "campaign_name",
"fieldtype": "Link",
"in_list_view": 1,
"label": "Campaign",
"options": "Campaign"
},
{
"fieldname": "column_break_6",
"fieldtype": "Column Break",
"label": "Share On"
},
{
"fieldname": "column_break_14",
"fieldtype": "Column Break"
},
{
"fieldname": "tweet_preview",
"fieldtype": "HTML"
},
{
"collapsible": 1,
"depends_on": "eval:doc.linkedin==1",
"fieldname": "linkedin_section",
"fieldtype": "Section Break",
"label": "LinkedIn"
},
{
"collapsible": 1,
"fieldname": "attachments_section",
"fieldtype": "Section Break",
"label": "Attachments"
},
{
"fieldname": "linkedin_post",
"fieldtype": "Text",
"label": "Post",
"mandatory_depends_on": "eval:doc.linkedin ==1"
},
{
"fieldname": "column_break_15",
"fieldtype": "Column Break"
},
{
"allow_on_submit": 1,
"fieldname": "scheduled_time",
"fieldtype": "Datetime",
"label": "Scheduled Time",
"read_only_depends_on": "eval:doc.post_status == \"Posted\""
}
],
"is_submittable": 1,
"links": [],
"modified": "2020-04-21 15:10:04.953713",
"modified_by": "Administrator",
"module": "CRM",
"name": "Social Media Post",
"owner": "Administrator",
"permissions": [
{
"create": 1,
"delete": 1,
"email": 1,
"export": 1,
"print": 1,
"read": 1,
"report": 1,
"role": "System Manager",
"share": 1,
"write": 1
}
],
"sort_field": "modified",
"sort_order": "DESC",
"track_changes": 1
}

View File

@ -0,0 +1,56 @@
# -*- coding: utf-8 -*-
# Copyright (c) 2020, Frappe Technologies Pvt. Ltd. and contributors
# For license information, please see license.txt
from __future__ import unicode_literals
import frappe
from frappe.model.document import Document
from frappe import _
import datetime
class SocialMediaPost(Document):
def validate(self):
if self.scheduled_time:
current_time = frappe.utils.now_datetime()
scheduled_time = frappe.utils.get_datetime(self.scheduled_time)
if scheduled_time < current_time:
frappe.throw(_("Invalid Scheduled Time"))
def submit(self):
if self.scheduled_time:
self.post_status = "Scheduled"
super(SocialMediaPost, self).submit()
def post(self):
try:
if self.twitter and not self.twitter_post_id:
twitter = frappe.get_doc("Twitter Settings")
twitter_post = twitter.post(self.text, self.image)
self.db_set("twitter_post_id", twitter_post.id)
if self.linkedin and not self.linkedin_post_id:
linkedin = frappe.get_doc("LinkedIn Settings")
linkedin_post = linkedin.post(self.linkedin_post, self.image)
self.db_set("linkedin_post_id", linkedin_post.headers['X-RestLi-Id'].split(":")[-1])
self.db_set("post_status", "Posted")
except:
self.db_set("post_status", "Error")
title = _("Error while POSTING {0}").format(self.name)
traceback = frappe.get_traceback()
frappe.log_error(message=traceback , title=title)
def process_scheduled_social_media_posts():
posts = frappe.get_list("Social Media Post", filters={"post_status": "Scheduled", "docstatus":1}, fields= ["name", "scheduled_time","post_status"])
start = frappe.utils.now_datetime()
end = start + datetime.timedelta(minutes=10)
for post in posts:
if post.scheduled_time:
post_time = frappe.utils.get_datetime(post.scheduled_time)
if post_time > start and post_time <= end:
publish('Social Media Post', post.name)
@frappe.whitelist()
def publish(doctype, name):
sm_post = frappe.get_doc(doctype, name)
sm_post.post()
frappe.db.commit()

View File

@ -0,0 +1,10 @@
frappe.listview_settings['Social Media Post'] = {
add_fields: ["status","post_status"],
get_indicator: function(doc) {
return [__(doc.post_status), {
"Scheduled": "orange",
"Posted": "green",
"Error": "red"
}[doc.post_status]];
}
}

View File

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

View File

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

View File

@ -0,0 +1,56 @@
// Copyright (c) 2020, Frappe Technologies Pvt. Ltd. and contributors
// For license information, please see license.txt
frappe.ui.form.on('Twitter Settings', {
onload: function(frm){
if (frm.doc.session_status == 'Expired' && frm.doc.consumer_key && frm.doc.consumer_secret){
frappe.confirm(
__('Session not valid, Do you want to login?'),
function(){
frm.trigger("login");
},
function(){
window.close();
}
);
}
},
refresh: function(frm){
let msg, color, flag=false;
if (frm.doc.session_status == "Active"){
msg = __("Session Active");
color = 'green';
flag = true;
}
else if(frm.doc.consumer_key && frm.doc.consumer_secret) {
msg = __("Session Not Active. Save doc to login.");
color = 'red';
flag = true;
}
if (flag){
frm.dashboard.set_headline_alert(
`<div class="row">
<div class="col-xs-12">
<span class="indicator whitespace-nowrap ${color}"><span class="hidden-xs">${msg}</span></span>
</div>
</div>`
);
}
},
login: function(frm){
if (frm.doc.consumer_key && frm.doc.consumer_secret){
frappe.dom.freeze();
frappe.call({
doc: frm.doc,
method: "get_authorize_url",
callback : function(r) {
window.location.href = r.message;
}
});
}
},
after_save: function(frm){
frm.trigger("login");
}
});

View File

@ -0,0 +1,101 @@
{
"actions": [],
"creation": "2020-01-30 10:29:08.562108",
"doctype": "DocType",
"editable_grid": 1,
"engine": "InnoDB",
"field_order": [
"account_name",
"profile_pic",
"oauth_details",
"consumer_key",
"column_break_5",
"consumer_secret",
"oauth_token",
"oauth_secret",
"session_status"
],
"fields": [
{
"fieldname": "account_name",
"fieldtype": "Data",
"label": "Account Name",
"read_only": 1
},
{
"fieldname": "oauth_details",
"fieldtype": "Section Break",
"label": "OAuth Credentials"
},
{
"fieldname": "consumer_key",
"fieldtype": "Data",
"in_list_view": 1,
"label": "API Key",
"reqd": 1
},
{
"fieldname": "consumer_secret",
"fieldtype": "Password",
"in_list_view": 1,
"label": "API Secret Key",
"reqd": 1
},
{
"fieldname": "oauth_token",
"fieldtype": "Data",
"hidden": 1,
"label": "OAuth Token",
"read_only": 1
},
{
"fieldname": "oauth_secret",
"fieldtype": "Password",
"hidden": 1,
"label": "OAuth Token Secret",
"read_only": 1
},
{
"fieldname": "column_break_5",
"fieldtype": "Column Break"
},
{
"fieldname": "profile_pic",
"fieldtype": "Attach Image",
"hidden": 1,
"read_only": 1
},
{
"fieldname": "session_status",
"fieldtype": "Select",
"hidden": 1,
"label": "Session Status",
"options": "Expired\nActive",
"read_only": 1
}
],
"image_field": "profile_pic",
"issingle": 1,
"links": [],
"modified": "2020-04-21 22:06:43.726798",
"modified_by": "Administrator",
"module": "CRM",
"name": "Twitter Settings",
"owner": "Administrator",
"permissions": [
{
"create": 1,
"delete": 1,
"email": 1,
"print": 1,
"read": 1,
"role": "System Manager",
"share": 1,
"write": 1
}
],
"quick_entry": 1,
"sort_field": "modified",
"sort_order": "DESC",
"track_changes": 1
}

View File

@ -0,0 +1,98 @@
# -*- coding: utf-8 -*-
# Copyright (c) 2020, Frappe Technologies Pvt. Ltd. and contributors
# For license information, please see license.txt
from __future__ import unicode_literals
import frappe, os, tweepy, json
from frappe import _
from frappe.model.document import Document
from frappe.utils.file_manager import get_file_path
from frappe.utils import get_url_to_form, get_link_to_form
from tweepy.error import TweepError
class TwitterSettings(Document):
def get_authorize_url(self):
callback_url = "{0}/?cmd=erpnext.crm.doctype.twitter_settings.twitter_settings.callback".format(frappe.utils.get_url())
auth = tweepy.OAuthHandler(self.consumer_key, self.get_password(fieldname="consumer_secret"), callback_url)
try:
redirect_url = auth.get_authorization_url()
return redirect_url
except:
frappe.msgprint(_("Error! Failed to get request token."))
frappe.throw(_('Invalid {0} or {1}').format(frappe.bold("Consumer Key"), frappe.bold("Consumer Secret Key")))
def get_access_token(self, oauth_token, oauth_verifier):
auth = tweepy.OAuthHandler(self.consumer_key, self.get_password(fieldname="consumer_secret"))
auth.request_token = {
'oauth_token' : oauth_token,
'oauth_token_secret' : oauth_verifier
}
try:
auth.get_access_token(oauth_verifier)
api = self.get_api()
user = api.me()
profile_pic = (user._json["profile_image_url"]).replace("_normal","")
frappe.db.set_value(self.doctype, self.name, {
"oauth_token" : auth.access_token,
"oauth_secret" : auth.access_token_secret,
"account_name" : user._json["screen_name"],
"profile_pic" : profile_pic,
"session_status" : "Active"
})
frappe.local.response["type"] = "redirect"
frappe.local.response["location"] = get_url_to_form("Twitter Settings","Twitter Settings")
except TweepError as e:
frappe.msgprint(_("Error! Failed to get access token."))
frappe.throw(_('Invalid Consumer Key or Consumer Secret Key'))
def get_api(self):
# authentication of consumer key and secret
auth = tweepy.OAuthHandler(self.consumer_key, self.get_password(fieldname="consumer_secret"))
# authentication of access token and secret
auth.set_access_token(self.oauth_token, self.get_password(fieldname="oauth_secret"))
return tweepy.API(auth)
def post(self, text, media=None):
if not media:
return self.send_tweet(text)
if media:
media_id = self.upload_image(media)
return self.send_tweet(text, media_id)
def upload_image(self, media):
media = get_file_path(media)
api = self.get_api()
media = api.media_upload(media)
return media.media_id
def send_tweet(self, text, media_id=None):
api = self.get_api()
try:
if media_id:
response = api.update_status(status = text, media_ids = [media_id])
else:
response = api.update_status(status = text)
return response
except TweepError as e:
content = json.loads(e.response.content)
content = content["errors"][0]
if e.response.status_code == 401:
self.db_set("session_status", "Expired")
frappe.db.commit()
frappe.throw(content["message"],title="Twitter Error {0} {1}".format(e.response.status_code, e.response.reason))
@frappe.whitelist()
def callback(oauth_token, oauth_verifier):
twitter_settings = frappe.get_single("Twitter Settings")
twitter_settings.get_access_token(oauth_token,oauth_verifier)
frappe.db.commit()

View File

@ -144,6 +144,10 @@ def create_sales_order(order, woocommerce_settings, customer_name, sys_lang):
def set_items_in_sales_order(new_sales_order, woocommerce_settings, order, sys_lang):
company_abbr = frappe.db.get_value('Company', woocommerce_settings.company, 'abbr')
default_warehouse = _("Stores - {0}", sys_lang).format(company_abbr)
if not frappe.db.exists("Warehouse", default_warehouse):
frappe.throw(_("Please set Warehouse in Woocommerce Settings"))
for item in order.get("line_items"):
woocomm_item_id = item.get("product_id")
found_item = frappe.get_doc("Item", {"woocommerce_id": woocomm_item_id})
@ -158,7 +162,7 @@ def set_items_in_sales_order(new_sales_order, woocommerce_settings, order, sys_l
"uom": woocommerce_settings.uom or _("Nos", sys_lang),
"qty": item.get("quantity"),
"rate": item.get("price"),
"warehouse": woocommerce_settings.warehouse or _("Stores - {0}", sys_lang).format(company_abbr)
"warehouse": woocommerce_settings.warehouse or default_warehouse
})
add_tax_details(new_sales_order, ordered_items_tax, "Ordered Item tax", woocommerce_settings.tax_account)

View File

@ -67,11 +67,11 @@ def add_bank_accounts(response, bank, company):
frappe.throw(_("Please setup a default bank account for company {0}").format(company))
for account in response["accounts"]:
acc_type = frappe.db.get_value("Account Type", account["type"])
acc_type = frappe.db.get_value("Bank Account Type", account["type"])
if not acc_type:
add_account_type(account["type"])
acc_subtype = frappe.db.get_value("Account Subtype", account["subtype"])
acc_subtype = frappe.db.get_value("Bank Account Subtype", account["subtype"])
if not acc_subtype:
add_account_subtype(account["subtype"])
@ -106,7 +106,7 @@ def add_bank_accounts(response, bank, company):
def add_account_type(account_type):
try:
frappe.get_doc({
"doctype": "Account Type",
"doctype": "Bank Account Type",
"account_type": account_type
}).insert()
except Exception:
@ -116,7 +116,7 @@ def add_account_type(account_type):
def add_account_subtype(account_subtype):
try:
frappe.get_doc({
"doctype": "Account Subtype",
"doctype": "Bank Account Subtype",
"account_subtype": account_subtype
}).insert()
except Exception:

View File

@ -23,11 +23,11 @@ class TestPlaidSettings(unittest.TestCase):
for ba in frappe.get_all("Bank Account"):
frappe.get_doc("Bank Account", ba.name).delete()
for at in frappe.get_all("Account Type"):
frappe.get_doc("Account Type", at.name).delete()
for at in frappe.get_all("Bank Account Type"):
frappe.get_doc("Bank Account Type", at.name).delete()
for ast in frappe.get_all("Account Subtype"):
frappe.get_doc("Account Subtype", ast.name).delete()
for ast in frappe.get_all("Bank Account Subtype"):
frappe.get_doc("Bank Account Subtype", ast.name).delete()
def test_plaid_disabled(self):
frappe.db.set_value("Plaid Settings", None, "enabled", 0)
@ -35,11 +35,11 @@ class TestPlaidSettings(unittest.TestCase):
def test_add_account_type(self):
add_account_type("brokerage")
self.assertEqual(frappe.get_doc("Account Type", "brokerage").name, "brokerage")
self.assertEqual(frappe.get_doc("Bank Account Type", "brokerage").name, "brokerage")
def test_add_account_subtype(self):
add_account_subtype("loan")
self.assertEqual(frappe.get_doc("Account Subtype", "loan").name, "loan")
self.assertEqual(frappe.get_doc("Bank Account Subtype", "loan").name, "loan")
def test_default_bank_account(self):
if not frappe.db.exists("Bank", "Citi"):

View File

@ -35,7 +35,6 @@ def validate_customer_created(patient):
msg += " <b><a href='#Form/Patient/{0}'>{0}</a></b>".format(patient.name)
frappe.throw(msg, title=_('Customer Not Found'))
def get_appointments_to_invoice(patient, company):
appointments_to_invoice = []
patient_appointments = frappe.get_list(
@ -112,7 +111,7 @@ def get_lab_tests_to_invoice(patient, company):
filters={'patient': patient.name, 'company': company, 'invoiced': False, 'docstatus': 1}
)
for lab_test in lab_tests:
item, is_billable = frappe.get_cached_value('Lab Test Template', lab_test.lab_test_code, ['item', 'is_billable'])
item, is_billable = frappe.get_cached_value('Lab Test Template', lab_test.template, ['item', 'is_billable'])
if is_billable:
lab_tests_to_invoice.append({
'reference_type': 'Lab Test',

View File

@ -270,7 +270,8 @@ auto_cancel_exempted_doctypes= [
scheduler_events = {
"all": [
"erpnext.projects.doctype.project.project.project_status_update_reminder",
"erpnext.healthcare_healthcare.doctype.patient_appointment.patient_appointment.send_appointment_reminder"
"erpnext.healthcare.doctype.patient_appointment.patient_appointment.send_appointment_reminder",
"erpnext.crm.doctype.social_media_post.social_media_post.process_scheduled_social_media_posts"
],
"hourly": [
'erpnext.hr.doctype.daily_work_summary_group.daily_work_summary_group.trigger_emails',

View File

@ -87,11 +87,12 @@
"search_index": 1
},
{
"depends_on": "eval:doc.status==\"On Leave\"",
"depends_on": "eval:in_list([\"On Leave\", \"Half Day\"], doc.status)",
"fieldname": "leave_type",
"fieldtype": "Link",
"in_standard_filter": 1,
"label": "Leave Type",
"mandatory_depends_on": "eval:in_list([\"On Leave\", \"Half Day\"], doc.status)",
"oldfieldname": "leave_type",
"oldfieldtype": "Link",
"options": "Leave Type"
@ -100,6 +101,7 @@
"fieldname": "leave_application",
"fieldtype": "Link",
"label": "Leave Application",
"no_copy": 1,
"options": "Leave Application",
"read_only": 1
},
@ -175,7 +177,8 @@
"icon": "fa fa-ok",
"idx": 1,
"is_submittable": 1,
"modified": "2020-02-19 14:25:32.945842",
"links": [],
"modified": "2020-04-11 11:40:14.319496",
"modified_by": "Administrator",
"module": "HR",
"name": "Attendance",

View File

@ -7,33 +7,15 @@ import frappe
from frappe.utils import getdate, nowdate
from frappe import _
from frappe.model.document import Document
from frappe.utils import cstr, get_datetime, get_datetime_str
from frappe.utils import update_progress_bar
from frappe.utils import cstr, get_datetime, formatdate
class Attendance(Document):
def validate_duplicate_record(self):
res = frappe.db.sql("""select name from `tabAttendance` where employee = %s and attendance_date = %s
and name != %s and docstatus != 2""",
(self.employee, getdate(self.attendance_date), self.name))
if res:
frappe.throw(_("Attendance for employee {0} is already marked").format(self.employee))
def check_leave_record(self):
leave_record = frappe.db.sql("""select leave_type, half_day, half_day_date from `tabLeave Application`
where employee = %s and %s between from_date and to_date and status = 'Approved'
and docstatus = 1""", (self.employee, self.attendance_date), as_dict=True)
if leave_record:
for d in leave_record:
if d.half_day_date == getdate(self.attendance_date):
self.status = 'Half Day'
frappe.msgprint(_("Employee {0} on Half day on {1}").format(self.employee, self.attendance_date))
else:
self.status = 'On Leave'
self.leave_type = d.leave_type
frappe.msgprint(_("Employee {0} is on Leave on {1}").format(self.employee, self.attendance_date))
if self.status == "On Leave" and not leave_record:
frappe.throw(_("No leave record found for employee {0} for {1}").format(self.employee, self.attendance_date))
def validate(self):
from erpnext.controllers.status_updater import validate_status
validate_status(self.status, ["Present", "Absent", "On Leave", "Half Day", "Work From Home"])
self.validate_attendance_date()
self.validate_duplicate_record()
self.check_leave_record()
def validate_attendance_date(self):
date_of_joining = frappe.db.get_value("Employee", self.employee, "date_of_joining")
@ -44,19 +26,52 @@ class Attendance(Document):
elif date_of_joining and getdate(self.attendance_date) < getdate(date_of_joining):
frappe.throw(_("Attendance date can not be less than employee's joining date"))
def validate_duplicate_record(self):
res = frappe.db.sql("""
select name from `tabAttendance`
where employee = %s
and attendance_date = %s
and name != %s
and docstatus != 2
""", (self.employee, getdate(self.attendance_date), self.name))
if res:
frappe.throw(_("Attendance for employee {0} is already marked").format(self.employee))
def check_leave_record(self):
leave_record = frappe.db.sql("""
select leave_type, half_day, half_day_date
from `tabLeave Application`
where employee = %s
and %s between from_date and to_date
and status = 'Approved'
and docstatus = 1
""", (self.employee, self.attendance_date), as_dict=True)
if leave_record:
for d in leave_record:
self.leave_type = d.leave_type
if d.half_day_date == getdate(self.attendance_date):
self.status = 'Half Day'
frappe.msgprint(_("Employee {0} on Half day on {1}")
.format(self.employee, formatdate(self.attendance_date)))
else:
self.status = 'On Leave'
frappe.msgprint(_("Employee {0} is on Leave on {1}")
.format(self.employee, formatdate(self.attendance_date)))
if self.status in ("On Leave", "Half Day"):
if not leave_record:
frappe.msgprint(_("No leave record found for employee {0} on {1}")
.format(self.employee, formatdate(self.attendance_date)), alert=1)
elif self.leave_type:
self.leave_type = None
self.leave_application = None
def validate_employee(self):
emp = frappe.db.sql("select name from `tabEmployee` where name = %s and status = 'Active'",
self.employee)
if not emp:
frappe.throw(_("Employee {0} is not active or does not exist").format(self.employee))
def validate(self):
from erpnext.controllers.status_updater import validate_status
validate_status(self.status, ["Present", "Absent", "On Leave", "Half Day", "Work From Home"])
self.validate_attendance_date()
self.validate_duplicate_record()
self.check_leave_record()
@frappe.whitelist()
def get_events(start, end, filters=None):
events = []
@ -90,18 +105,20 @@ def add_attendance(events, start, end, conditions=None):
if e not in events:
events.append(e)
def mark_attendance(employee, attendance_date, status, shift=None):
employee_doc = frappe.get_doc('Employee', employee)
def mark_attendance(employee, attendance_date, status, shift=None, leave_type=None, ignore_validate=False):
if not frappe.db.exists('Attendance', {'employee':employee, 'attendance_date':attendance_date, 'docstatus':('!=', '2')}):
doc_dict = {
company = frappe.db.get_value('Employee', employee, 'company')
attendance = frappe.get_doc({
'doctype': 'Attendance',
'employee': employee,
'attendance_date': attendance_date,
'status': status,
'company': employee_doc.company,
'shift': shift
}
attendance = frappe.get_doc(doc_dict).insert()
'company': company,
'shift': shift,
'leave_type': leave_type
})
attendance.flags.ignore_validate = ignore_validate
attendance.insert()
attendance.submit()
return attendance.name

View File

@ -136,9 +136,18 @@ def make_bank_entry(dt, dn):
def make_return_entry(employee, company, employee_advance_name,
return_amount, advance_account, mode_of_payment=None):
return_account = get_default_bank_cash_account(company, account_type='Cash', mode_of_payment = mode_of_payment)
mode_of_payment_type = ''
if mode_of_payment:
mode_of_payment_type = frappe.get_cached_value('Mode of Payment', mode_of_payment, 'type')
if mode_of_payment_type not in ["Cash", "Bank"]:
# if mode of payment is General then it unset the type
mode_of_payment_type = None
je = frappe.new_doc('Journal Entry')
je.posting_date = nowdate()
je.voucher_type = 'Bank Entry'
# if mode of payment is Bank then voucher type is Bank Entry
je.voucher_type = '{} Entry'.format(mode_of_payment_type) if mode_of_payment_type else 'Cash Entry'
je.company = company
je.remark = 'Return against Employee Advance: ' + employee_advance_name

View File

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

View File

@ -0,0 +1,138 @@
{
"actions": [],
"autoname": "HR-INCOME-.######",
"creation": "2020-03-18 15:04:40.767434",
"doctype": "DocType",
"editable_grid": 1,
"engine": "InnoDB",
"field_order": [
"employee",
"employee_name",
"payroll_period",
"column_break_3",
"company",
"source",
"amount",
"amended_from"
],
"fields": [
{
"fieldname": "employee",
"fieldtype": "Link",
"in_list_view": 1,
"label": "Employee",
"options": "Employee",
"reqd": 1
},
{
"fieldname": "payroll_period",
"fieldtype": "Link",
"in_list_view": 1,
"label": "Payroll Period",
"options": "Payroll Period",
"reqd": 1
},
{
"fieldname": "column_break_3",
"fieldtype": "Column Break"
},
{
"fieldname": "company",
"fieldtype": "Link",
"in_list_view": 1,
"label": "Company",
"options": "Company",
"reqd": 1
},
{
"fieldname": "source",
"fieldtype": "Data",
"label": "Source"
},
{
"fieldname": "amount",
"fieldtype": "Currency",
"in_list_view": 1,
"label": "Amount",
"options": "Company:company:default_currency",
"reqd": 1
},
{
"fetch_from": "employee.employee_name",
"fieldname": "employee_name",
"fieldtype": "Data",
"label": "Employee Name",
"read_only": 1
},
{
"fieldname": "amended_from",
"fieldtype": "Link",
"label": "Amended From",
"no_copy": 1,
"options": "Employee Other Income",
"print_hide": 1,
"read_only": 1
}
],
"is_submittable": 1,
"links": [],
"modified": "2020-03-19 18:06:45.361830",
"modified_by": "Administrator",
"module": "HR",
"name": "Employee Other Income",
"owner": "Administrator",
"permissions": [
{
"create": 1,
"delete": 1,
"email": 1,
"export": 1,
"print": 1,
"read": 1,
"report": 1,
"role": "System Manager",
"share": 1,
"write": 1
},
{
"create": 1,
"delete": 1,
"email": 1,
"export": 1,
"print": 1,
"read": 1,
"report": 1,
"role": "HR Manager",
"share": 1,
"write": 1
},
{
"create": 1,
"delete": 1,
"email": 1,
"export": 1,
"print": 1,
"read": 1,
"report": 1,
"role": "HR User",
"share": 1,
"write": 1
},
{
"create": 1,
"delete": 1,
"email": 1,
"export": 1,
"print": 1,
"read": 1,
"report": 1,
"role": "Employee",
"share": 1,
"write": 1
}
],
"quick_entry": 1,
"sort_field": "modified",
"sort_order": "DESC",
"track_changes": 1
}

View File

@ -0,0 +1,10 @@
# -*- coding: utf-8 -*-
# Copyright (c) 2020, Frappe Technologies Pvt. Ltd. and contributors
# For license information, please see license.txt
from __future__ import unicode_literals
# import frappe
from frappe.model.document import Document
class EmployeeOtherIncome(Document):
pass

View File

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

View File

@ -1,534 +1,116 @@
{
"allow_copy": 0,
"allow_events_in_timeline": 0,
"allow_guest_to_view": 0,
"actions": [],
"allow_import": 1,
"allow_rename": 1,
"autoname": "HR-TAX-DEC-.YYYY.-.#####",
"beta": 0,
"creation": "2018-04-13 16:53:36.175504",
"custom": 0,
"docstatus": 0,
"doctype": "DocType",
"document_type": "",
"editable_grid": 1,
"engine": "InnoDB",
"field_order": [
"employee",
"employee_name",
"department",
"column_break_2",
"payroll_period",
"company",
"amended_from",
"section_break_8",
"declarations",
"section_break_10",
"total_declared_amount",
"column_break_12",
"total_exemption_amount"
],
"fields": [
{
"allow_bulk_edit": 0,
"allow_in_quick_entry": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fetch_if_empty": 0,
"fieldname": "employee",
"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": "Employee",
"length": 0,
"no_copy": 0,
"options": "Employee",
"permlevel": 0,
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 0,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 1,
"search_index": 0,
"set_only_once": 0,
"translatable": 0,
"unique": 0
"reqd": 1
},
{
"allow_bulk_edit": 0,
"allow_in_quick_entry": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fetch_from": "employee.employee_name",
"fetch_if_empty": 0,
"fieldname": "employee_name",
"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": "Employee Name",
"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
"read_only": 1
},
{
"allow_bulk_edit": 0,
"allow_in_quick_entry": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fetch_from": "employee.department",
"fetch_if_empty": 0,
"fieldname": "department",
"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": "Department",
"length": 0,
"no_copy": 0,
"options": "Department",
"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
"read_only": 1
},
{
"allow_bulk_edit": 0,
"allow_in_quick_entry": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fetch_if_empty": 0,
"fieldname": "column_break_2",
"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
"fieldtype": "Column Break"
},
{
"allow_bulk_edit": 0,
"allow_in_quick_entry": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fetch_if_empty": 0,
"fieldname": "payroll_period",
"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": "Payroll Period",
"length": 0,
"no_copy": 0,
"options": "Payroll Period",
"permlevel": 0,
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 0,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 1,
"search_index": 0,
"set_only_once": 0,
"translatable": 0,
"unique": 0
"reqd": 1
},
{
"allow_bulk_edit": 0,
"allow_in_quick_entry": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fetch_from": "employee.company",
"fetch_if_empty": 0,
"fieldname": "company",
"fieldtype": "Link",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 0,
"in_standard_filter": 0,
"label": "Company",
"length": 0,
"no_copy": 0,
"options": "Company",
"permlevel": 0,
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 0,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"translatable": 0,
"unique": 0
"options": "Company"
},
{
"allow_bulk_edit": 0,
"allow_in_quick_entry": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fetch_if_empty": 0,
"fieldname": "amended_from",
"fieldtype": "Link",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 0,
"in_standard_filter": 0,
"label": "Amended From",
"length": 0,
"no_copy": 1,
"options": "Employee Tax Exemption Declaration",
"permlevel": 0,
"print_hide": 1,
"print_hide_if_no_value": 0,
"read_only": 1,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"translatable": 0,
"unique": 0
"read_only": 1
},
{
"allow_bulk_edit": 0,
"allow_in_quick_entry": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fetch_if_empty": 0,
"fieldname": "section_break_8",
"fieldtype": "Section Break",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 0,
"in_standard_filter": 0,
"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
"fieldtype": "Section Break"
},
{
"allow_bulk_edit": 0,
"allow_in_quick_entry": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fetch_if_empty": 0,
"fieldname": "declarations",
"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": "Declarations",
"length": 0,
"no_copy": 0,
"options": "Employee Tax Exemption Declaration Category",
"permlevel": 0,
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 0,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"translatable": 0,
"unique": 0
"options": "Employee Tax Exemption Declaration Category"
},
{
"allow_bulk_edit": 0,
"allow_in_quick_entry": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fetch_if_empty": 0,
"fieldname": "section_break_10",
"fieldtype": "Section Break",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 0,
"in_standard_filter": 0,
"length": 0,
"no_copy": 0,
"permlevel": 0,
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 0,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"translatable": 0,
"unique": 0
"fieldtype": "Section Break"
},
{
"allow_bulk_edit": 0,
"allow_in_quick_entry": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fetch_if_empty": 0,
"fieldname": "total_declared_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": "Total Declared 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,
"translatable": 0,
"unique": 0
"read_only": 1
},
{
"allow_bulk_edit": 0,
"allow_in_quick_entry": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fetch_if_empty": 0,
"fieldname": "column_break_12",
"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
"fieldtype": "Column Break"
},
{
"allow_bulk_edit": 0,
"allow_in_quick_entry": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fetch_if_empty": 0,
"fieldname": "total_exemption_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": "Total Exemption 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,
"translatable": 0,
"unique": 0
},
{
"allow_bulk_edit": 0,
"allow_in_quick_entry": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fetch_if_empty": 0,
"fieldname": "other_incomes_section",
"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 Incomes",
"length": 0,
"no_copy": 0,
"permlevel": 0,
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 0,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"translatable": 0,
"unique": 0
},
{
"allow_bulk_edit": 0,
"allow_in_quick_entry": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fetch_if_empty": 0,
"fieldname": "income_from_other_sources",
"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": "Income From Other Sources",
"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
"read_only": 1
}
],
"has_web_view": 0,
"hide_heading": 0,
"hide_toolbar": 0,
"idx": 0,
"image_view": 0,
"in_create": 0,
"is_submittable": 1,
"issingle": 0,
"istable": 0,
"max_attachments": 0,
"modified": "2019-05-11 16:13:50.472670",
"links": [],
"modified": "2020-03-18 14:56:25.625717",
"modified_by": "Administrator",
"module": "HR",
"name": "Employee Tax Exemption Declaration",
"name_case": "",
"owner": "Administrator",
"permissions": [
{
@ -538,14 +120,10 @@
"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": 1,
"write": 1
@ -557,14 +135,10 @@
"delete": 1,
"email": 1,
"export": 1,
"if_owner": 0,
"import": 0,
"permlevel": 0,
"print": 1,
"read": 1,
"report": 1,
"role": "HR Manager",
"set_user_permissions": 0,
"share": 1,
"submit": 1,
"write": 1
@ -576,14 +150,10 @@
"delete": 1,
"email": 1,
"export": 1,
"if_owner": 0,
"import": 0,
"permlevel": 0,
"print": 1,
"read": 1,
"report": 1,
"role": "HR User",
"set_user_permissions": 0,
"share": 1,
"submit": 1,
"write": 1
@ -595,26 +165,16 @@
"delete": 1,
"email": 1,
"export": 1,
"if_owner": 0,
"import": 0,
"permlevel": 0,
"print": 1,
"read": 1,
"report": 1,
"role": "Employee",
"set_user_permissions": 0,
"share": 1,
"submit": 1,
"write": 1
}
],
"quick_entry": 0,
"read_only": 0,
"read_only_onload": 0,
"show_name_in_global_search": 0,
"sort_field": "modified",
"sort_order": "DESC",
"track_changes": 1,
"track_seen": 0,
"track_views": 0
"track_changes": 1
}

View File

@ -8,31 +8,17 @@ from frappe.model.document import Document
from frappe import _
from frappe.utils import flt
from frappe.model.mapper import get_mapped_doc
from erpnext.hr.utils import validate_tax_declaration, get_total_exemption_amount, calculate_annual_eligible_hra_exemption
class DuplicateDeclarationError(frappe.ValidationError): pass
from erpnext.hr.utils import validate_tax_declaration, get_total_exemption_amount, \
calculate_annual_eligible_hra_exemption, validate_duplicate_exemption_for_payroll_period
class EmployeeTaxExemptionDeclaration(Document):
def validate(self):
validate_tax_declaration(self.declarations)
self.validate_duplicate()
validate_duplicate_exemption_for_payroll_period(self.doctype, self.name, self.payroll_period, self.employee)
self.set_total_declared_amount()
self.set_total_exemption_amount()
self.calculate_hra_exemption()
def validate_duplicate(self):
duplicate = frappe.db.get_value("Employee Tax Exemption Declaration",
filters = {
"employee": self.employee,
"payroll_period": self.payroll_period,
"name": ["!=", self.name],
"docstatus": ["!=", 2]
}
)
if duplicate:
frappe.throw(_("Duplicate Tax Declaration of {0} for period {1}")
.format(self.employee, self.payroll_period), DuplicateDeclarationError)
def set_total_declared_amount(self):
self.total_declared_amount = 0.0
for d in self.declarations:

View File

@ -6,7 +6,7 @@ from __future__ import unicode_literals
import frappe, erpnext
import unittest
from erpnext.hr.doctype.employee.test_employee import make_employee
from erpnext.hr.doctype.employee_tax_exemption_declaration.employee_tax_exemption_declaration import DuplicateDeclarationError
from erpnext.hr.utils import DuplicateDeclarationError
class TestEmployeeTaxExemptionDeclaration(unittest.TestCase):
def setUp(self):

View File

@ -21,8 +21,6 @@
"total_actual_amount",
"column_break_12",
"exemption_amount",
"other_incomes_section",
"income_from_other_sources",
"attachment_section",
"attachments",
"amended_from"
@ -111,16 +109,6 @@
"label": "Total Exemption Amount",
"read_only": 1
},
{
"fieldname": "other_incomes_section",
"fieldtype": "Section Break",
"label": "Other Incomes"
},
{
"fieldname": "income_from_other_sources",
"fieldtype": "Currency",
"label": "Income From Other Sources"
},
{
"fieldname": "attachment_section",
"fieldtype": "Section Break"
@ -142,7 +130,7 @@
],
"is_submittable": 1,
"links": [],
"modified": "2020-03-02 19:02:15.398486",
"modified": "2020-03-18 14:55:51.420016",
"modified_by": "Administrator",
"module": "HR",
"name": "Employee Tax Exemption Proof Submission",

View File

@ -7,7 +7,8 @@ import frappe
from frappe.model.document import Document
from frappe import _
from frappe.utils import flt
from erpnext.hr.utils import validate_tax_declaration, get_total_exemption_amount, calculate_hra_exemption_for_period
from erpnext.hr.utils import validate_tax_declaration, get_total_exemption_amount, \
calculate_hra_exemption_for_period, validate_duplicate_exemption_for_payroll_period
class EmployeeTaxExemptionProofSubmission(Document):
def validate(self):
@ -15,6 +16,7 @@ class EmployeeTaxExemptionProofSubmission(Document):
self.set_total_actual_amount()
self.set_total_exemption_amount()
self.calculate_hra_exemption()
validate_duplicate_exemption_for_payroll_period(self.doctype, self.name, self.payroll_period, self.employee)
def set_total_actual_amount(self):
self.total_actual_amount = flt(self.get("house_rent_payment_amount"))

View File

@ -13,10 +13,12 @@
"stop_birthday_reminders",
"expense_approver_mandatory_in_expense_claim",
"payroll_settings",
"payroll_based_on",
"max_working_hours_against_timesheet",
"include_holidays_in_total_working_days",
"disable_rounded_total",
"max_working_hours_against_timesheet",
"column_break_11",
"daily_wages_fraction_for_half_day",
"email_salary_slip_to_employee",
"encrypt_salary_slips_in_emails",
"password_policy",
@ -184,13 +186,27 @@
"fieldtype": "Link",
"label": "Role Allowed to Create Backdated Leave Application",
"options": "Role"
},
{
"default": "Leave",
"fieldname": "payroll_based_on",
"fieldtype": "Select",
"label": "Calculate Working Days in Payroll based on",
"options": "Leave\nAttendance"
},
{
"default": "0.5",
"description": "The fraction of daily wages to be paid for half-day attendance",
"fieldname": "daily_wages_fraction_for_half_day",
"fieldtype": "Float",
"label": "Daily Wages Fraction for Half Day"
}
],
"icon": "fa fa-cog",
"idx": 1,
"issingle": 1,
"links": [],
"modified": "2020-01-06 18:46:30.189815",
"modified": "2020-04-13 21:20:59.382394",
"modified_by": "Administrator",
"module": "HR",
"name": "HR Settings",

View File

@ -15,6 +15,9 @@ class HRSettings(Document):
self.set_naming_series()
self.validate_password_policy()
if not self.daily_wages_fraction_for_half_day:
self.daily_wages_fraction_for_half_day = 0.5
def set_naming_series(self):
from erpnext.setup.doctype.naming_series.naming_series import set_by_naming_series
set_by_naming_series("Employee", "employee_number",

View File

@ -0,0 +1,6 @@
// Copyright (c) 2020, Frappe Technologies Pvt. Ltd. and contributors
// For license information, please see license.txt
frappe.ui.form.on('Income Tax Slab', {
});

View File

@ -0,0 +1,160 @@
{
"actions": [],
"autoname": "Prompt",
"creation": "2020-03-17 16:50:35.564915",
"doctype": "DocType",
"editable_grid": 1,
"engine": "InnoDB",
"field_order": [
"effective_from",
"company",
"column_break_3",
"allow_tax_exemption",
"standard_tax_exemption_amount",
"disabled",
"amended_from",
"taxable_salary_slabs_section",
"slabs",
"taxes_and_charges_on_income_tax_section",
"other_taxes_and_charges"
],
"fields": [
{
"fieldname": "effective_from",
"fieldtype": "Date",
"in_list_view": 1,
"label": "Effective from",
"reqd": 1
},
{
"fieldname": "column_break_3",
"fieldtype": "Column Break"
},
{
"default": "0",
"description": "If enabled, Tax Exemption Declaration will be considered for income tax calculation.",
"fieldname": "allow_tax_exemption",
"fieldtype": "Check",
"label": "Allow Tax Exemption"
},
{
"fieldname": "taxable_salary_slabs_section",
"fieldtype": "Section Break",
"label": "Taxable Salary Slabs"
},
{
"fieldname": "amended_from",
"fieldtype": "Link",
"label": "Amended From",
"no_copy": 1,
"options": "Income Tax Slab",
"print_hide": 1,
"read_only": 1
},
{
"fieldname": "slabs",
"fieldtype": "Table",
"label": "Taxable Salary Slabs",
"options": "Taxable Salary Slab",
"reqd": 1
},
{
"allow_on_submit": 1,
"default": "0",
"fieldname": "disabled",
"fieldtype": "Check",
"label": "Disabled"
},
{
"depends_on": "allow_tax_exemption",
"fieldname": "standard_tax_exemption_amount",
"fieldtype": "Currency",
"label": "Standard Tax Exemption Amount",
"options": "Company:company:default_currency"
},
{
"fieldname": "company",
"fieldtype": "Link",
"label": "Company",
"options": "Company"
},
{
"collapsible": 1,
"fieldname": "taxes_and_charges_on_income_tax_section",
"fieldtype": "Section Break",
"label": "Taxes and Charges on Income Tax"
},
{
"fieldname": "other_taxes_and_charges",
"fieldtype": "Table",
"label": "Other Taxes and Charges",
"options": "Income Tax Slab Other Charges"
}
],
"is_submittable": 1,
"links": [],
"modified": "2020-04-24 12:28:36.805904",
"modified_by": "Administrator",
"module": "HR",
"name": "Income Tax Slab",
"owner": "Administrator",
"permissions": [
{
"create": 1,
"delete": 1,
"email": 1,
"export": 1,
"print": 1,
"read": 1,
"report": 1,
"role": "System Manager",
"share": 1,
"write": 1
},
{
"cancel": 1,
"create": 1,
"delete": 1,
"email": 1,
"export": 1,
"print": 1,
"read": 1,
"report": 1,
"role": "HR Manager",
"share": 1,
"submit": 1,
"write": 1
},
{
"cancel": 1,
"create": 1,
"delete": 1,
"email": 1,
"export": 1,
"print": 1,
"read": 1,
"report": 1,
"role": "HR User",
"share": 1,
"submit": 1,
"write": 1
},
{
"cancel": 1,
"create": 1,
"delete": 1,
"email": 1,
"export": 1,
"print": 1,
"read": 1,
"report": 1,
"role": "Administrator",
"share": 1,
"submit": 1,
"write": 1
}
],
"sort_field": "modified",
"sort_order": "DESC",
"track_changes": 1
}

View File

@ -1,9 +1,10 @@
# -*- coding: utf-8 -*-
# Copyright (c) 2018, Frappe Technologies Pvt. Ltd. and contributors
# Copyright (c) 2020, Frappe Technologies Pvt. Ltd. and contributors
# For license information, please see license.txt
from __future__ import unicode_literals
# import frappe
from frappe.model.document import Document
class AccountSubtype(Document):
class IncomeTaxSlab(Document):
pass

View File

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

View File

@ -0,0 +1,75 @@
{
"actions": [],
"creation": "2020-04-24 11:46:59.041180",
"doctype": "DocType",
"editable_grid": 1,
"engine": "InnoDB",
"field_order": [
"description",
"column_break_2",
"percent",
"conditions_section",
"min_taxable_income",
"column_break_7",
"max_taxable_income"
],
"fields": [
{
"fieldname": "column_break_2",
"fieldtype": "Column Break"
},
{
"columns": 2,
"fieldname": "min_taxable_income",
"fieldtype": "Currency",
"in_list_view": 1,
"label": "Min Taxable Income",
"options": "Company:company:default_currency"
},
{
"columns": 4,
"fieldname": "description",
"fieldtype": "Data",
"in_list_view": 1,
"label": "Description",
"reqd": 1
},
{
"columns": 2,
"fieldname": "percent",
"fieldtype": "Percent",
"in_list_view": 1,
"label": "Percent",
"reqd": 1
},
{
"fieldname": "conditions_section",
"fieldtype": "Section Break",
"label": "Conditions"
},
{
"fieldname": "column_break_7",
"fieldtype": "Column Break"
},
{
"columns": 2,
"fieldname": "max_taxable_income",
"fieldtype": "Currency",
"in_list_view": 1,
"label": "Max Taxable Income",
"options": "Company:company:default_currency"
}
],
"istable": 1,
"links": [],
"modified": "2020-04-24 13:27:43.598967",
"modified_by": "Administrator",
"module": "HR",
"name": "Income Tax Slab Other Charges",
"owner": "Administrator",
"permissions": [],
"quick_entry": 1,
"sort_field": "modified",
"sort_order": "DESC",
"track_changes": 1
}

View File

@ -0,0 +1,10 @@
# -*- coding: utf-8 -*-
# Copyright (c) 2020, Frappe Technologies Pvt. Ltd. and contributors
# For license information, please see license.txt
from __future__ import unicode_literals
# import frappe
from frappe.model.document import Document
class IncomeTaxSlabOtherCharges(Document):
pass

View File

@ -30,13 +30,13 @@ class LeaveAllocation(Document):
def validate_leave_allocation_days(self):
company = frappe.db.get_value("Employee", self.employee, "company")
leave_period = get_leave_period(self.from_date, self.to_date, company)
max_leaves_allowed = frappe.db.get_value("Leave Type", self.leave_type, "max_leaves_allowed")
max_leaves_allowed = flt(frappe.db.get_value("Leave Type", self.leave_type, "max_leaves_allowed"))
if max_leaves_allowed > 0:
leave_allocated = 0
if leave_period:
leave_allocated = get_leave_allocation_for_period(self.employee, self.leave_type,
leave_period[0].from_date, leave_period[0].to_date)
leave_allocated += self.new_leaves_allocated
leave_allocated += flt(self.new_leaves_allocated)
if leave_allocated > max_leaves_allowed:
frappe.throw(_("Total allocated leaves are more days than maximum allocation of {0} leave type for employee {1} in the period")
.format(self.leave_type, self.employee))

View File

@ -374,7 +374,8 @@ class LeaveApplication(Document):
leaves=self.total_leave_days * -1,
from_date=self.from_date,
to_date=self.to_date,
is_lwp=lwp
is_lwp=lwp,
holiday_list=get_holiday_list_for_employee(self.employee)
)
create_leave_ledger_entry(self, args, submit)
@ -384,7 +385,9 @@ class LeaveApplication(Document):
from_date=self.from_date,
to_date=expiry_date,
leaves=(date_diff(expiry_date, self.from_date) + 1) * -1,
is_lwp=lwp
is_lwp=lwp,
holiday_list=get_holiday_list_for_employee(self.employee),
)
create_leave_ledger_entry(self, args, submit)
@ -410,7 +413,7 @@ def get_allocation_expiry(employee, leave_type, to_date, from_date):
return expiry[0]['to_date'] if expiry else None
@frappe.whitelist()
def get_number_of_leave_days(employee, leave_type, from_date, to_date, half_day = None, half_day_date = None):
def get_number_of_leave_days(employee, leave_type, from_date, to_date, half_day = None, half_day_date = None, holiday_list = None):
number_of_days = 0
if cint(half_day) == 1:
if from_date == to_date:
@ -424,7 +427,7 @@ def get_number_of_leave_days(employee, leave_type, from_date, to_date, half_day
number_of_days = date_diff(to_date, from_date) + 1
if not frappe.db.get_value("Leave Type", leave_type, "include_holiday"):
number_of_days = flt(number_of_days) - flt(get_holidays(employee, from_date, to_date))
number_of_days = flt(number_of_days) - flt(get_holidays(employee, from_date, to_date, holiday_list=holiday_list))
return number_of_days
@frappe.whitelist()
@ -575,7 +578,7 @@ def get_leaves_for_period(employee, leave_type, from_date, to_date):
{'name': leave_entry.transaction_name}, ['half_day_date'])
leave_days += get_number_of_leave_days(employee, leave_type,
leave_entry.from_date, leave_entry.to_date, half_day, half_day_date) * -1
leave_entry.from_date, leave_entry.to_date, half_day, half_day_date, holiday_list=leave_entry.holiday_list) * -1
return leave_days
@ -589,7 +592,7 @@ def get_leave_entries(employee, leave_type, from_date, to_date):
''' Returns leave entries between from_date and to_date. '''
return frappe.db.sql("""
SELECT
employee, leave_type, from_date, to_date, leaves, transaction_name, transaction_type,
employee, leave_type, from_date, to_date, leaves, transaction_name, transaction_type, holiday_list,
is_carry_forward, is_expired
FROM `tabLeave Ledger Entry`
WHERE employee=%(employee)s AND leave_type=%(leave_type)s
@ -607,8 +610,9 @@ def get_leave_entries(employee, leave_type, from_date, to_date):
}, as_dict=1)
@frappe.whitelist()
def get_holidays(employee, from_date, to_date):
def get_holidays(employee, from_date, to_date, holiday_list = None):
'''get holidays between two dates for the given employee'''
if not holiday_list:
holiday_list = get_holiday_list_for_employee(employee)
holidays = frappe.db.sql("""select count(distinct holiday_date) from `tabHoliday` h1, `tabHoliday List` h2

View File

@ -1,4 +1,5 @@
{
"actions": [],
"creation": "2019-05-09 15:47:39.760406",
"doctype": "DocType",
"engine": "InnoDB",
@ -12,6 +13,7 @@
"column_break_7",
"from_date",
"to_date",
"holiday_list",
"is_carry_forward",
"is_expired",
"is_lwp",
@ -98,11 +100,18 @@
"fieldname": "is_lwp",
"fieldtype": "Check",
"label": "Is Leave Without Pay"
},
{
"fieldname": "holiday_list",
"fieldtype": "Link",
"label": "Holiday List",
"options": "Holiday List"
}
],
"in_create": 1,
"is_submittable": 1,
"modified": "2019-08-20 14:40:04.130799",
"links": [],
"modified": "2020-02-27 14:40:10.502605",
"modified_by": "Administrator",
"module": "HR",
"name": "Leave Ledger Entry",

View File

@ -1,401 +1,102 @@
{
"allow_copy": 0,
"allow_events_in_timeline": 0,
"allow_guest_to_view": 0,
"actions": [],
"allow_import": 1,
"allow_rename": 0,
"autoname": "Prompt",
"beta": 0,
"creation": "2018-04-13 15:18:53.698553",
"custom": 0,
"docstatus": 0,
"doctype": "DocType",
"document_type": "",
"editable_grid": 1,
"engine": "InnoDB",
"field_order": [
"company",
"column_break_2",
"start_date",
"end_date",
"section_break_5",
"periods"
],
"fields": [
{
"allow_bulk_edit": 0,
"allow_in_quick_entry": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fetch_if_empty": 0,
"fieldname": "company",
"fieldtype": "Link",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 1,
"in_standard_filter": 0,
"label": "Company",
"length": 0,
"no_copy": 0,
"options": "Company",
"permlevel": 0,
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 0,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 1,
"search_index": 0,
"set_only_once": 0,
"translatable": 0,
"unique": 0
"reqd": 1
},
{
"allow_bulk_edit": 0,
"allow_in_quick_entry": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fetch_if_empty": 0,
"fieldname": "column_break_2",
"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
"fieldtype": "Column Break"
},
{
"allow_bulk_edit": 0,
"allow_in_quick_entry": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fetch_if_empty": 0,
"fieldname": "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": "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": 1,
"search_index": 0,
"set_only_once": 0,
"translatable": 0,
"unique": 0
"reqd": 1
},
{
"allow_bulk_edit": 0,
"allow_in_quick_entry": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fetch_if_empty": 0,
"fieldname": "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": "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": 1,
"search_index": 0,
"set_only_once": 0,
"translatable": 0,
"unique": 0
"reqd": 1
},
{
"allow_bulk_edit": 0,
"allow_in_quick_entry": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fetch_if_empty": 0,
"fieldname": "section_break_5",
"fieldtype": "Section Break",
"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": "Payroll Periods",
"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
"label": "Payroll Periods"
},
{
"allow_bulk_edit": 0,
"allow_in_quick_entry": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fetch_if_empty": 0,
"fieldname": "periods",
"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": "Payroll Periods",
"length": 0,
"no_copy": 0,
"options": "Payroll Period Date",
"permlevel": 0,
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 0,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"translatable": 0,
"unique": 0
},
{
"allow_bulk_edit": 0,
"allow_in_quick_entry": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fetch_if_empty": 0,
"fieldname": "section_break_7",
"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": "Taxable Salary Slabs",
"length": 0,
"no_copy": 0,
"permlevel": 0,
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 0,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"translatable": 0,
"unique": 0
},
{
"allow_bulk_edit": 0,
"allow_in_quick_entry": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fetch_if_empty": 0,
"fieldname": "taxable_salary_slabs",
"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": "Taxable Salary Slabs",
"length": 0,
"no_copy": 0,
"options": "Taxable Salary Slab",
"permlevel": 0,
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 0,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"translatable": 0,
"unique": 0
},
{
"allow_bulk_edit": 0,
"allow_in_quick_entry": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fetch_if_empty": 0,
"fieldname": "standard_tax_exemption_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": "Standard Tax Exemption Amount",
"length": 0,
"no_copy": 0,
"permlevel": 0,
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 0,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"translatable": 0,
"unique": 0
"options": "Payroll Period Date"
}
],
"has_web_view": 0,
"hide_heading": 0,
"hide_toolbar": 0,
"idx": 0,
"image_view": 0,
"in_create": 0,
"is_submittable": 0,
"issingle": 0,
"istable": 0,
"max_attachments": 0,
"modified": "2019-04-26 01:45:03.160929",
"links": [],
"modified": "2020-03-18 18:13:23.859980",
"modified_by": "Administrator",
"module": "HR",
"name": "Payroll Period",
"name_case": "",
"owner": "Administrator",
"permissions": [
{
"amend": 0,
"cancel": 0,
"create": 1,
"delete": 1,
"email": 1,
"export": 1,
"if_owner": 0,
"import": 0,
"permlevel": 0,
"print": 1,
"read": 1,
"report": 1,
"role": "System Manager",
"set_user_permissions": 0,
"share": 1,
"submit": 0,
"write": 1
},
{
"amend": 0,
"cancel": 0,
"create": 1,
"delete": 1,
"email": 1,
"export": 1,
"if_owner": 0,
"import": 0,
"permlevel": 0,
"print": 1,
"read": 1,
"report": 1,
"role": "HR Manager",
"set_user_permissions": 0,
"share": 1,
"submit": 0,
"write": 1
},
{
"amend": 0,
"cancel": 0,
"create": 1,
"delete": 1,
"email": 1,
"export": 1,
"if_owner": 0,
"import": 0,
"permlevel": 0,
"print": 1,
"read": 1,
"report": 1,
"role": "HR User",
"set_user_permissions": 0,
"share": 1,
"submit": 0,
"write": 1
}
],
"quick_entry": 0,
"read_only": 0,
"read_only_onload": 0,
"show_name_in_global_search": 0,
"sort_field": "modified",
"sort_order": "DESC",
"track_changes": 1,
"track_seen": 0,
"track_views": 0
"track_changes": 1
}

View File

@ -45,7 +45,8 @@ class PayrollPeriod(Document):
+ _(") for {0}").format(self.company)
frappe.throw(msg)
def get_payroll_period_days(start_date, end_date, employee):
def get_payroll_period_days(start_date, end_date, employee, company=None):
if not company:
company = frappe.db.get_value("Employee", employee, "company")
payroll_period = frappe.db.sql("""
select name, start_date, end_date

View File

@ -1,4 +1,5 @@
{
"actions": [],
"allow_import": 1,
"allow_rename": 1,
"autoname": "field:salary_component",
@ -13,10 +14,11 @@
"type",
"description",
"column_break_4",
"is_payable",
"depends_on_payment_days",
"is_tax_applicable",
"deduct_full_tax_on_selected_payroll_date",
"variable_based_on_taxable_salary",
"exempted_from_income_tax",
"round_to_the_nearest_integer",
"statistical_component",
"do_not_include_in_total",
@ -28,8 +30,6 @@
"pay_against_benefit_claim",
"only_tax_impact",
"create_separate_payment_entry_against_benefit_claim",
"section_break_11",
"variable_based_on_taxable_salary",
"section_break_5",
"accounts",
"condition_and_formula",
@ -73,12 +73,6 @@
"fieldtype": "Check",
"label": "Is Tax Applicable"
},
{
"default": "1",
"fieldname": "is_payable",
"fieldtype": "Check",
"label": "Is Payable"
},
{
"default": "1",
"fieldname": "depends_on_payment_days",
@ -94,7 +88,7 @@
},
{
"default": "0",
"depends_on": "is_tax_applicable",
"depends_on": "eval:doc.is_tax_applicable && doc.type=='Earning'",
"fieldname": "deduct_full_tax_on_selected_payroll_date",
"fieldtype": "Check",
"label": "Deduct Full Tax on Selected Payroll Date"
@ -165,13 +159,9 @@
"fieldtype": "Check",
"label": "Create Separate Payment Entry Against Benefit Claim"
},
{
"depends_on": "eval:doc.type=='Deduction'",
"fieldname": "section_break_11",
"fieldtype": "Section Break"
},
{
"default": "0",
"depends_on": "eval:doc.type == \"Deduction\"",
"fieldname": "variable_based_on_taxable_salary",
"fieldtype": "Check",
"label": "Variable Based On Taxable Salary"
@ -233,10 +223,19 @@
"fieldname": "round_to_the_nearest_integer",
"fieldtype": "Check",
"label": "Round to the Nearest Integer"
},
{
"default": "0",
"depends_on": "eval:doc.type == \"Deduction\" && !doc.variable_based_on_taxable_salary",
"description": "If checked, the full amount will be deducted from taxable income before calculating income tax. Otherwise, it can be exempted via Employee Tax Exemption Declaration.",
"fieldname": "exempted_from_income_tax",
"fieldtype": "Check",
"label": "Exempted from Income Tax"
}
],
"icon": "fa fa-flag",
"modified": "2019-06-05 11:34:14.231228",
"links": [],
"modified": "2020-04-24 14:50:28.994054",
"modified_by": "Administrator",
"module": "HR",
"name": "Salary Component",

View File

@ -3,14 +3,12 @@
"doctype": "Salary Component",
"salary_component": "_Test Basic Salary",
"type": "Earning",
"is_payable": 1,
"is_tax_applicable": 1
},
{
"doctype": "Salary Component",
"salary_component": "_Test Allowance",
"type": "Earning",
"is_payable": 1,
"is_tax_applicable": 1
},
{
@ -27,14 +25,12 @@
"doctype": "Salary Component",
"salary_component": "Basic",
"type": "Earning",
"is_payable": 1,
"is_tax_applicable": 1
},
{
"doctype": "Salary Component",
"salary_component": "Leave Encashment",
"type": "Earning",
"is_payable": 1,
"is_tax_applicable": 1
}
]

View File

@ -18,6 +18,5 @@ def create_salary_component(component_name, **args):
"doctype": "Salary Component",
"salary_component": component_name,
"type": args.get("type") or "Earning",
"is_payable": args.get("is_payable") or 1,
"is_tax_applicable": args.get("is_tax_applicable") or 1
}).insert()

View File

@ -12,6 +12,7 @@
"deduct_full_tax_on_selected_payroll_date",
"depends_on_payment_days",
"is_tax_applicable",
"exempted_from_income_tax",
"is_flexible_benefit",
"variable_based_on_taxable_salary",
"section_break_2",
@ -62,6 +63,7 @@
},
{
"default": "0",
"depends_on": "eval:doc.parentfield=='earnings'",
"fetch_from": "salary_component.is_tax_applicable",
"fieldname": "is_tax_applicable",
"fieldtype": "Check",
@ -71,6 +73,7 @@
},
{
"default": "0",
"depends_on": "eval:doc.parentfield=='earnings'",
"fetch_from": "salary_component.is_flexible_benefit",
"fieldname": "is_flexible_benefit",
"fieldtype": "Check",
@ -80,6 +83,7 @@
},
{
"default": "0",
"depends_on": "eval:doc.parentfield=='deductions'",
"fetch_from": "salary_component.variable_based_on_taxable_salary",
"fieldname": "variable_based_on_taxable_salary",
"fieldtype": "Check",
@ -187,11 +191,20 @@
"fieldtype": "HTML",
"label": "Condition and Formula Help",
"options": "<h3>Condition and Formula Help</h3>\n\n<p>Notes:</p>\n\n<ol>\n<li>Use field <code>base</code> for using base salary of the Employee</li>\n<li>Use Salary Component abbreviations in conditions and formulas. <code>BS = Basic Salary</code></li>\n<li>Use field name for employee details in conditions and formulas. <code>Employment Type = employment_type</code><code>Branch = branch</code></li>\n<li>Use field name from Salary Slip in conditions and formulas. <code>Payment Days = payment_days</code><code>Leave without pay = leave_without_pay</code></li>\n<li>Direct Amount can also be entered based on Condtion. See example 3</li></ol>\n\n<h4>Examples</h4>\n<ol>\n<li>Calculating Basic Salary based on <code>base</code>\n<pre><code>Condition: base &lt; 10000</code></pre>\n<pre><code>Formula: base * .2</code></pre></li>\n<li>Calculating HRA based on Basic Salary<code>BS</code> \n<pre><code>Condition: BS &gt; 2000</code></pre>\n<pre><code>Formula: BS * .1</code></pre></li>\n<li>Calculating TDS based on Employment Type<code>employment_type</code> \n<pre><code>Condition: employment_type==\"Intern\"</code></pre>\n<pre><code>Amount: 1000</code></pre></li>\n</ol>"
},
{
"default": "0",
"depends_on": "eval:doc.parentfield=='deductions'",
"fetch_from": "salary_component.exempted_from_income_tax",
"fieldname": "exempted_from_income_tax",
"fieldtype": "Check",
"label": "Exempted from Income Tax",
"read_only": 1
}
],
"istable": 1,
"links": [],
"modified": "2019-12-31 17:15:25.646689",
"modified": "2020-04-24 20:00:16.475295",
"modified_by": "Administrator",
"module": "HR",
"name": "Salary Detail",

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