Merge branch 'develop' into patch-8

This commit is contained in:
Deepesh Garg 2020-04-30 11:37:27 +05:30 committed by GitHub
commit 758f0f0780
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
182 changed files with 3749 additions and 2908 deletions

View File

@ -5,7 +5,7 @@ from __future__ import unicode_literals
import frappe import frappe
import unittest import unittest
from frappe.utils import nowdate from frappe.utils import nowdate, now_datetime
from erpnext.accounts.utils import get_fiscal_year from erpnext.accounts.utils import get_fiscal_year
from erpnext.buying.doctype.purchase_order.test_purchase_order import create_purchase_order from erpnext.buying.doctype.purchase_order.test_purchase_order import create_purchase_order
from erpnext.accounts.doctype.budget.budget import get_actual_expense, BudgetError from erpnext.accounts.doctype.budget.budget import get_actual_expense, BudgetError
@ -13,27 +13,28 @@ from erpnext.accounts.doctype.journal_entry.test_journal_entry import make_journ
class TestBudget(unittest.TestCase): class TestBudget(unittest.TestCase):
def test_monthly_budget_crossed_ignore(self): def test_monthly_budget_crossed_ignore(self):
set_total_expense_zero("2013-02-28", "cost_center") set_total_expense_zero(nowdate(), "cost_center")
budget = make_budget(budget_against="Cost Center") budget = make_budget(budget_against="Cost Center")
jv = make_journal_entry("_Test Account Cost for Goods Sold - _TC", jv = make_journal_entry("_Test Account Cost for Goods Sold - _TC",
"_Test Bank - _TC", 40000, "_Test Cost Center - _TC", posting_date="2013-02-28", submit=True) "_Test Bank - _TC", 40000, "_Test Cost Center - _TC", posting_date=nowdate(), submit=True)
self.assertTrue(frappe.db.get_value("GL Entry", self.assertTrue(frappe.db.get_value("GL Entry",
{"voucher_type": "Journal Entry", "voucher_no": jv.name})) {"voucher_type": "Journal Entry", "voucher_no": jv.name}))
budget.cancel() budget.cancel()
jv.cancel()
def test_monthly_budget_crossed_stop1(self): def test_monthly_budget_crossed_stop1(self):
set_total_expense_zero("2013-02-28", "cost_center") set_total_expense_zero(nowdate(), "cost_center")
budget = make_budget(budget_against="Cost Center") budget = make_budget(budget_against="Cost Center")
frappe.db.set_value("Budget", budget.name, "action_if_accumulated_monthly_budget_exceeded", "Stop") frappe.db.set_value("Budget", budget.name, "action_if_accumulated_monthly_budget_exceeded", "Stop")
jv = make_journal_entry("_Test Account Cost for Goods Sold - _TC", jv = make_journal_entry("_Test Account Cost for Goods Sold - _TC",
"_Test Bank - _TC", 40000, "_Test Cost Center - _TC", posting_date="2013-02-28") "_Test Bank - _TC", 40000, "_Test Cost Center - _TC", posting_date=nowdate())
self.assertRaises(BudgetError, jv.submit) self.assertRaises(BudgetError, jv.submit)
@ -41,14 +42,14 @@ class TestBudget(unittest.TestCase):
budget.cancel() budget.cancel()
def test_exception_approver_role(self): def test_exception_approver_role(self):
set_total_expense_zero("2013-02-28", "cost_center") set_total_expense_zero(nowdate(), "cost_center")
budget = make_budget(budget_against="Cost Center") budget = make_budget(budget_against="Cost Center")
frappe.db.set_value("Budget", budget.name, "action_if_accumulated_monthly_budget_exceeded", "Stop") frappe.db.set_value("Budget", budget.name, "action_if_accumulated_monthly_budget_exceeded", "Stop")
jv = make_journal_entry("_Test Account Cost for Goods Sold - _TC", jv = make_journal_entry("_Test Account Cost for Goods Sold - _TC",
"_Test Bank - _TC", 40000, "_Test Cost Center - _TC", posting_date="2013-03-02") "_Test Bank - _TC", 40000, "_Test Cost Center - _TC", posting_date=nowdate())
self.assertRaises(BudgetError, jv.submit) self.assertRaises(BudgetError, jv.submit)
@ -112,16 +113,17 @@ class TestBudget(unittest.TestCase):
budget.load_from_db() budget.load_from_db()
budget.cancel() budget.cancel()
po.cancel()
def test_monthly_budget_crossed_stop2(self): def test_monthly_budget_crossed_stop2(self):
set_total_expense_zero("2013-02-28", "project") set_total_expense_zero(nowdate(), "project")
budget = make_budget(budget_against="Project") budget = make_budget(budget_against="Project")
frappe.db.set_value("Budget", budget.name, "action_if_accumulated_monthly_budget_exceeded", "Stop") frappe.db.set_value("Budget", budget.name, "action_if_accumulated_monthly_budget_exceeded", "Stop")
jv = make_journal_entry("_Test Account Cost for Goods Sold - _TC", jv = make_journal_entry("_Test Account Cost for Goods Sold - _TC",
"_Test Bank - _TC", 40000, "_Test Cost Center - _TC", project="_Test Project", posting_date="2013-02-28") "_Test Bank - _TC", 40000, "_Test Cost Center - _TC", project="_Test Project", posting_date=nowdate())
self.assertRaises(BudgetError, jv.submit) self.assertRaises(BudgetError, jv.submit)
@ -129,86 +131,76 @@ class TestBudget(unittest.TestCase):
budget.cancel() budget.cancel()
def test_yearly_budget_crossed_stop1(self): def test_yearly_budget_crossed_stop1(self):
set_total_expense_zero("2013-02-28", "cost_center") set_total_expense_zero(nowdate(), "cost_center")
budget = make_budget(budget_against="Cost Center") budget = make_budget(budget_against="Cost Center")
jv = make_journal_entry("_Test Account Cost for Goods Sold - _TC", jv = make_journal_entry("_Test Account Cost for Goods Sold - _TC",
"_Test Bank - _TC", 150000, "_Test Cost Center - _TC", posting_date="2013-03-28") "_Test Bank - _TC", 250000, "_Test Cost Center - _TC", posting_date=nowdate())
self.assertRaises(BudgetError, jv.submit) self.assertRaises(BudgetError, jv.submit)
budget.cancel() budget.cancel()
def test_yearly_budget_crossed_stop2(self): def test_yearly_budget_crossed_stop2(self):
set_total_expense_zero("2013-02-28", "project") set_total_expense_zero(nowdate(), "project")
budget = make_budget(budget_against="Project") budget = make_budget(budget_against="Project")
jv = make_journal_entry("_Test Account Cost for Goods Sold - _TC", jv = make_journal_entry("_Test Account Cost for Goods Sold - _TC",
"_Test Bank - _TC", 150000, "_Test Cost Center - _TC", project="_Test Project", posting_date="2013-03-28") "_Test Bank - _TC", 250000, "_Test Cost Center - _TC", project="_Test Project", posting_date=nowdate())
self.assertRaises(BudgetError, jv.submit) self.assertRaises(BudgetError, jv.submit)
budget.cancel() budget.cancel()
def test_monthly_budget_on_cancellation1(self): def test_monthly_budget_on_cancellation1(self):
set_total_expense_zero("2013-02-28", "cost_center") set_total_expense_zero(nowdate(), "cost_center")
budget = make_budget(budget_against="Cost Center") budget = make_budget(budget_against="Cost Center")
jv1 = make_journal_entry("_Test Account Cost for Goods Sold - _TC", for i in range(now_datetime().month):
"_Test Bank - _TC", 20000, "_Test Cost Center - _TC", posting_date="2013-02-28", submit=True) jv = make_journal_entry("_Test Account Cost for Goods Sold - _TC",
"_Test Bank - _TC", 20000, "_Test Cost Center - _TC", posting_date=nowdate(), submit=True)
self.assertTrue(frappe.db.get_value("GL Entry", self.assertTrue(frappe.db.get_value("GL Entry",
{"voucher_type": "Journal Entry", "voucher_no": jv1.name})) {"voucher_type": "Journal Entry", "voucher_no": jv.name}))
jv2 = make_journal_entry("_Test Account Cost for Goods Sold - _TC",
"_Test Bank - _TC", 20000, "_Test Cost Center - _TC", posting_date="2013-02-28", submit=True)
self.assertTrue(frappe.db.get_value("GL Entry",
{"voucher_type": "Journal Entry", "voucher_no": jv2.name}))
frappe.db.set_value("Budget", budget.name, "action_if_accumulated_monthly_budget_exceeded", "Stop") frappe.db.set_value("Budget", budget.name, "action_if_accumulated_monthly_budget_exceeded", "Stop")
self.assertRaises(BudgetError, jv1.cancel) self.assertRaises(BudgetError, jv.cancel)
budget.load_from_db() budget.load_from_db()
budget.cancel() budget.cancel()
def test_monthly_budget_on_cancellation2(self): def test_monthly_budget_on_cancellation2(self):
set_total_expense_zero("2013-02-28", "project") set_total_expense_zero(nowdate(), "project")
budget = make_budget(budget_against="Project") budget = make_budget(budget_against="Project")
jv1 = make_journal_entry("_Test Account Cost for Goods Sold - _TC", for i in range(now_datetime().month):
"_Test Bank - _TC", 20000, "_Test Cost Center - _TC", posting_date="2013-02-28", submit=True, project="_Test Project") jv = make_journal_entry("_Test Account Cost for Goods Sold - _TC",
"_Test Bank - _TC", 20000, "_Test Cost Center - _TC", posting_date=nowdate(), submit=True, project="_Test Project")
self.assertTrue(frappe.db.get_value("GL Entry", self.assertTrue(frappe.db.get_value("GL Entry",
{"voucher_type": "Journal Entry", "voucher_no": jv1.name})) {"voucher_type": "Journal Entry", "voucher_no": jv.name}))
jv2 = make_journal_entry("_Test Account Cost for Goods Sold - _TC",
"_Test Bank - _TC", 20000, "_Test Cost Center - _TC", posting_date="2013-02-28", submit=True, project="_Test Project")
self.assertTrue(frappe.db.get_value("GL Entry",
{"voucher_type": "Journal Entry", "voucher_no": jv2.name}))
frappe.db.set_value("Budget", budget.name, "action_if_accumulated_monthly_budget_exceeded", "Stop") frappe.db.set_value("Budget", budget.name, "action_if_accumulated_monthly_budget_exceeded", "Stop")
self.assertRaises(BudgetError, jv1.cancel) self.assertRaises(BudgetError, jv.cancel)
budget.load_from_db() budget.load_from_db()
budget.cancel() budget.cancel()
def test_monthly_budget_against_group_cost_center(self): def test_monthly_budget_against_group_cost_center(self):
set_total_expense_zero("2013-02-28", "cost_center") set_total_expense_zero(nowdate(), "cost_center")
set_total_expense_zero("2013-02-28", "cost_center", "_Test Cost Center 2 - _TC") set_total_expense_zero(nowdate(), "cost_center", "_Test Cost Center 2 - _TC")
budget = make_budget(budget_against="Cost Center", cost_center="_Test Company - _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") frappe.db.set_value("Budget", budget.name, "action_if_accumulated_monthly_budget_exceeded", "Stop")
jv = make_journal_entry("_Test Account Cost for Goods Sold - _TC", jv = make_journal_entry("_Test Account Cost for Goods Sold - _TC",
"_Test Bank - _TC", 40000, "_Test Cost Center 2 - _TC", posting_date="2013-02-28") "_Test Bank - _TC", 40000, "_Test Cost Center 2 - _TC", posting_date=nowdate())
self.assertRaises(BudgetError, jv.submit) self.assertRaises(BudgetError, jv.submit)
@ -231,7 +223,7 @@ class TestBudget(unittest.TestCase):
frappe.db.set_value("Budget", budget.name, "action_if_accumulated_monthly_budget_exceeded", "Stop") frappe.db.set_value("Budget", budget.name, "action_if_accumulated_monthly_budget_exceeded", "Stop")
jv = make_journal_entry("_Test Account Cost for Goods Sold - _TC", jv = make_journal_entry("_Test Account Cost for Goods Sold - _TC",
"_Test Bank - _TC", 40000, cost_center, posting_date="2013-02-28") "_Test Bank - _TC", 40000, cost_center, posting_date=nowdate())
self.assertRaises(BudgetError, jv.submit) self.assertRaises(BudgetError, jv.submit)
@ -246,12 +238,14 @@ def set_total_expense_zero(posting_date, budget_against_field=None, budget_again
else: else:
budget_against = budget_against_CC or "_Test Cost Center - _TC" budget_against = budget_against_CC or "_Test Cost Center - _TC"
fiscal_year = get_fiscal_year(nowdate())[0]
args = frappe._dict({ args = frappe._dict({
"account": "_Test Account Cost for Goods Sold - _TC", "account": "_Test Account Cost for Goods Sold - _TC",
"cost_center": "_Test Cost Center - _TC", "cost_center": "_Test Cost Center - _TC",
"monthly_end_date": posting_date, "monthly_end_date": posting_date,
"company": "_Test Company", "company": "_Test Company",
"fiscal_year": "_Test Fiscal Year 2013", "fiscal_year": fiscal_year,
"budget_against_field": budget_against_field, "budget_against_field": budget_against_field,
}) })
@ -263,10 +257,10 @@ def set_total_expense_zero(posting_date, budget_against_field=None, budget_again
if existing_expense: 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", 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) "_Test Bank - _TC", -existing_expense, "_Test Cost Center - _TC", posting_date=nowdate(), submit=True)
elif budget_against_field == "project": elif budget_against_field == "project":
make_journal_entry("_Test Account Cost for Goods Sold - _TC", 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") "_Test Bank - _TC", -existing_expense, "_Test Cost Center - _TC", submit=True, project="_Test Project", posting_date=nowdate())
def make_budget(**args): def make_budget(**args):
args = frappe._dict(args) args = frappe._dict(args)
@ -274,10 +268,13 @@ def make_budget(**args):
budget_against=args.budget_against budget_against=args.budget_against
cost_center=args.cost_center cost_center=args.cost_center
fiscal_year = get_fiscal_year(nowdate())[0]
if budget_against == "Project": if budget_against == "Project":
budget_list = frappe.get_all("Budget", fields=["name"], filters = {"name": ("like", "_Test Project/_Test Fiscal Year 2013%")}) project_name = "{0}%".format("_Test Project/" + fiscal_year)
budget_list = frappe.get_all("Budget", fields=["name"], filters = {"name": ("like", project_name)})
else: else:
cost_center_name = "{0}%".format(cost_center or "_Test Cost Center - _TC/_Test Fiscal Year 2013") cost_center_name = "{0}%".format(cost_center or "_Test Cost Center - _TC/" + fiscal_year)
budget_list = frappe.get_all("Budget", fields=["name"], filters = {"name": ("like", cost_center_name)}) budget_list = frappe.get_all("Budget", fields=["name"], filters = {"name": ("like", cost_center_name)})
for d in budget_list: for d in budget_list:
frappe.db.sql("delete from `tabBudget` where name = %(name)s", d) frappe.db.sql("delete from `tabBudget` where name = %(name)s", d)
@ -290,8 +287,10 @@ def make_budget(**args):
else: else:
budget.cost_center =cost_center or "_Test Cost Center - _TC" budget.cost_center =cost_center or "_Test Cost Center - _TC"
monthly_distribution = frappe.get_doc("Monthly Distribution", "_Test Distribution")
monthly_distribution.fiscal_year = fiscal_year
budget.fiscal_year = "_Test Fiscal Year 2013" budget.fiscal_year = fiscal_year
budget.monthly_distribution = "_Test Distribution" budget.monthly_distribution = "_Test Distribution"
budget.company = "_Test Company" budget.company = "_Test Company"
budget.applicable_on_booking_actual_expenses = 1 budget.applicable_on_booking_actual_expenses = 1
@ -300,7 +299,7 @@ def make_budget(**args):
budget.budget_against = budget_against budget.budget_against = budget_against
budget.append("accounts", { budget.append("accounts", {
"account": "_Test Account Cost for Goods Sold - _TC", "account": "_Test Account Cost for Goods Sold - _TC",
"budget_amount": 100000 "budget_amount": 200000
}) })
if args.applicable_on_material_request: if args.applicable_on_material_request:

View File

@ -18,7 +18,7 @@ frappe.ui.form.on('Cost Center', {
}, },
refresh: function(frm) { refresh: function(frm) {
if (!frm.is_new()) { if (!frm.is_new()) {
frm.add_custom_button(__('Update Cost Center Number'), function () { frm.add_custom_button(__('Update Cost Center Name / Number'), function () {
frm.trigger("update_cost_center_number"); frm.trigger("update_cost_center_number");
}); });
} }
@ -47,35 +47,45 @@ frappe.ui.form.on('Cost Center', {
}, },
update_cost_center_number: function(frm) { update_cost_center_number: function(frm) {
var d = new frappe.ui.Dialog({ var d = new frappe.ui.Dialog({
title: __('Update Cost Center Number'), title: __('Update Cost Center Name / Number'),
fields: [ fields: [
{ {
"label": 'Cost Center Number', "label": "Cost Center Name",
"fieldname": "cost_center_name",
"fieldtype": "Data",
"reqd": 1,
"default": frm.doc.cost_center_name
},
{
"label": "Cost Center Number",
"fieldname": "cost_center_number", "fieldname": "cost_center_number",
"fieldtype": "Data", "fieldtype": "Data",
"reqd": 1 "reqd": 1,
"default": frm.doc.cost_center_number
} }
], ],
primary_action: function() { primary_action: function() {
var data = d.get_values(); var data = d.get_values();
if(data.cost_center_number === frm.doc.cost_center_number) { if(data.cost_center_name === frm.doc.cost_center_name && data.cost_center_number === frm.doc.cost_center_number) {
d.hide(); d.hide();
return; return;
} }
frappe.dom.freeze();
frappe.call({ frappe.call({
method: "erpnext.accounts.utils.update_number_field", method: "erpnext.accounts.utils.update_cost_center",
args: { args: {
doctype_name: frm.doc.doctype, docname: frm.doc.name,
name: frm.doc.name, cost_center_name: data.cost_center_name,
field_name: d.fields[0].fieldname, cost_center_number: data.cost_center_number,
number_value: data.cost_center_number,
company: frm.doc.company company: frm.doc.company
}, },
callback: function(r) { callback: function(r) {
frappe.dom.unfreeze();
if(!r.exc) { if(!r.exc) {
if(r.message) { if(r.message) {
frappe.set_route("Form", "Cost Center", r.message); frappe.set_route("Form", "Cost Center", r.message);
} else { } else {
me.frm.set_value("cost_center_name", data.cost_center_name);
me.frm.set_value("cost_center_number", data.cost_center_number); me.frm.set_value("cost_center_number", data.cost_center_number);
} }
d.hide(); d.hide();

View File

@ -2,7 +2,6 @@
"actions": [], "actions": [],
"allow_copy": 1, "allow_copy": 1,
"allow_import": 1, "allow_import": 1,
"allow_rename": 1,
"creation": "2013-01-23 19:57:17", "creation": "2013-01-23 19:57:17",
"description": "Track separate Income and Expense for product verticals or divisions.", "description": "Track separate Income and Expense for product verticals or divisions.",
"doctype": "DocType", "doctype": "DocType",
@ -126,7 +125,7 @@
"idx": 1, "idx": 1,
"is_tree": 1, "is_tree": 1,
"links": [], "links": [],
"modified": "2020-03-18 17:59:04.321637", "modified": "2020-04-29 16:09:30.025214",
"modified_by": "Administrator", "modified_by": "Administrator",
"module": "Accounts", "module": "Accounts",
"name": "Cost Center", "name": "Cost Center",

View File

@ -30,7 +30,8 @@
"company", "company",
"finance_book", "finance_book",
"to_rename", "to_rename",
"due_date" "due_date",
"is_cancelled"
], ],
"fields": [ "fields": [
{ {
@ -245,12 +246,18 @@
"fieldname": "due_date", "fieldname": "due_date",
"fieldtype": "Date", "fieldtype": "Date",
"label": "Due Date" "label": "Due Date"
},
{
"default": "0",
"fieldname": "is_cancelled",
"fieldtype": "Check",
"label": "Is Cancelled"
} }
], ],
"icon": "fa fa-list", "icon": "fa fa-list",
"idx": 1, "idx": 1,
"in_create": 1, "in_create": 1,
"modified": "2020-03-28 16:22:33.766994", "modified": "2020-04-07 16:22:33.766994",
"modified_by": "Administrator", "modified_by": "Administrator",
"module": "Accounts", "module": "Accounts",
"name": "GL Entry", "name": "GL Entry",

View File

@ -30,23 +30,20 @@ class GLEntry(Document):
self.pl_must_have_cost_center() self.pl_must_have_cost_center()
self.validate_cost_center() self.validate_cost_center()
if not self.flags.from_repost: self.check_pl_account()
self.check_pl_account() self.validate_party()
self.validate_party() self.validate_currency()
self.validate_currency()
def on_update_with_args(self, adv_adj, update_outstanding = 'Yes', from_repost=False): def on_update_with_args(self, adv_adj, update_outstanding = 'Yes'):
if not from_repost: self.validate_account_details(adv_adj)
self.validate_account_details(adv_adj) self.validate_dimensions_for_pl_and_bs()
self.validate_dimensions_for_pl_and_bs()
check_freezing_date(self.posting_date, adv_adj)
validate_frozen_account(self.account, adv_adj) validate_frozen_account(self.account, adv_adj)
validate_balance_type(self.account, adv_adj) validate_balance_type(self.account, adv_adj)
# Update outstanding amt on against voucher # Update outstanding amt on against voucher
if self.against_voucher_type in ['Journal Entry', 'Sales Invoice', 'Purchase Invoice', 'Fees'] \ if self.against_voucher_type in ['Journal Entry', 'Sales Invoice', 'Purchase Invoice', 'Fees'] \
and self.against_voucher and update_outstanding == 'Yes' and not from_repost: and self.against_voucher and update_outstanding == 'Yes':
update_outstanding_amt(self.account, self.party_type, self.party, self.against_voucher_type, update_outstanding_amt(self.account, self.party_type, self.party, self.against_voucher_type,
self.against_voucher) self.against_voucher)
@ -159,7 +156,6 @@ class GLEntry(Document):
if self.party_type and self.party: if self.party_type and self.party:
validate_party_gle_currency(self.party_type, self.party, self.company, self.account_currency) validate_party_gle_currency(self.party_type, self.party, self.company, self.account_currency)
def validate_and_set_fiscal_year(self): def validate_and_set_fiscal_year(self):
if not self.fiscal_year: if not self.fiscal_year:
self.fiscal_year = get_fiscal_year(self.posting_date, company=self.company)[0] self.fiscal_year = get_fiscal_year(self.posting_date, company=self.company)[0]
@ -176,19 +172,6 @@ def validate_balance_type(account, adv_adj=False):
(balance_must_be=="Credit" and flt(balance) > 0): (balance_must_be=="Credit" and flt(balance) > 0):
frappe.throw(_("Balance for Account {0} must always be {1}").format(account, _(balance_must_be))) frappe.throw(_("Balance for Account {0} must always be {1}").format(account, _(balance_must_be)))
def check_freezing_date(posting_date, adv_adj=False):
"""
Nobody can do GL Entries where posting date is before freezing date
except authorized person
"""
if not adv_adj:
acc_frozen_upto = frappe.db.get_value('Accounts Settings', None, 'acc_frozen_upto')
if acc_frozen_upto:
frozen_accounts_modifier = frappe.db.get_value( 'Accounts Settings', None,'frozen_accounts_modifier')
if getdate(posting_date) <= getdate(acc_frozen_upto) \
and not frozen_accounts_modifier in frappe.get_roles():
frappe.throw(_("You are not authorized to add or update entries before {0}").format(formatdate(acc_frozen_upto)))
def update_outstanding_amt(account, party_type, party, against_voucher_type, against_voucher, on_cancel=False): def update_outstanding_amt(account, party_type, party, against_voucher_type, against_voucher, on_cancel=False):
if party_type and party: if party_type and party:
party_condition = " and party_type={0} and party={1}"\ party_condition = " and party_type={0} and party={1}"\

View File

@ -57,6 +57,7 @@ class JournalEntry(AccountsController):
from erpnext.hr.doctype.salary_slip.salary_slip import unlink_ref_doc_from_salary_slip from erpnext.hr.doctype.salary_slip.salary_slip import unlink_ref_doc_from_salary_slip
unlink_ref_doc_from_payment_entries(self) unlink_ref_doc_from_payment_entries(self)
unlink_ref_doc_from_salary_slip(self.name) unlink_ref_doc_from_salary_slip(self.name)
self.ignore_linked_doctypes = ('GL Entry', 'Stock Ledger Entry')
self.make_gl_entries(1) self.make_gl_entries(1)
self.update_advance_paid() self.update_advance_paid()
self.update_expense_claim() self.update_expense_claim()
@ -594,7 +595,7 @@ class JournalEntry(AccountsController):
for d in self.accounts: for d in self.accounts:
if d.reference_type=="Expense Claim" and d.reference_name: if d.reference_type=="Expense Claim" and d.reference_name:
doc = frappe.get_doc("Expense Claim", d.reference_name) doc = frappe.get_doc("Expense Claim", d.reference_name)
update_reimbursed_amount(doc) update_reimbursed_amount(doc, jv=self.name)
def validate_expense_claim(self): def validate_expense_claim(self):

View File

@ -75,6 +75,7 @@ class PaymentEntry(AccountsController):
self.set_status() self.set_status()
def on_cancel(self): def on_cancel(self):
self.ignore_linked_doctypes = ('GL Entry', 'Stock Ledger Entry')
self.setup_party_account_field() self.setup_party_account_field()
self.make_gl_entries(cancel=1) self.make_gl_entries(cancel=1)
self.update_outstanding_amounts() self.update_outstanding_amounts()
@ -571,7 +572,7 @@ class PaymentEntry(AccountsController):
for d in self.get("references"): for d in self.get("references"):
if d.reference_doctype=="Expense Claim" and d.reference_name: if d.reference_doctype=="Expense Claim" and d.reference_name:
doc = frappe.get_doc("Expense Claim", d.reference_name) doc = frappe.get_doc("Expense Claim", d.reference_name)
update_reimbursed_amount(doc) update_reimbursed_amount(doc, self.name)
def on_recurring(self, reference_doc, auto_repeat_doc): def on_recurring(self, reference_doc, auto_repeat_doc):
self.reference_no = reference_doc.name self.reference_no = reference_doc.name

View File

@ -35,8 +35,6 @@ class TestPaymentEntry(unittest.TestCase):
pe.cancel() pe.cancel()
self.assertFalse(self.get_gle(pe.name))
so_advance_paid = frappe.db.get_value("Sales Order", so.name, "advance_paid") so_advance_paid = frappe.db.get_value("Sales Order", so.name, "advance_paid")
self.assertEqual(so_advance_paid, 0) self.assertEqual(so_advance_paid, 0)
@ -124,7 +122,6 @@ class TestPaymentEntry(unittest.TestCase):
self.assertEqual(outstanding_amount, 0) self.assertEqual(outstanding_amount, 0)
pe.cancel() pe.cancel()
self.assertFalse(self.get_gle(pe.name))
outstanding_amount = flt(frappe.db.get_value("Sales Invoice", si.name, "outstanding_amount")) outstanding_amount = flt(frappe.db.get_value("Sales Invoice", si.name, "outstanding_amount"))
self.assertEqual(outstanding_amount, 100) self.assertEqual(outstanding_amount, 100)
@ -381,7 +378,6 @@ class TestPaymentEntry(unittest.TestCase):
self.assertEqual(outstanding_amount, 0) self.assertEqual(outstanding_amount, 0)
pe3.cancel() pe3.cancel()
self.assertFalse(self.get_gle(pe3.name))
outstanding_amount = flt(frappe.db.get_value("Sales Invoice", si1.name, "outstanding_amount")) outstanding_amount = flt(frappe.db.get_value("Sales Invoice", si1.name, "outstanding_amount"))
self.assertEqual(outstanding_amount, -100) self.assertEqual(outstanding_amount, -100)

View File

@ -326,7 +326,7 @@ def make_payment_request(**args):
"reference_doctype": args.dt, "reference_doctype": args.dt,
"reference_name": args.dn, "reference_name": args.dn,
"party_type": args.get("party_type") or "Customer", "party_type": args.get("party_type") or "Customer",
"party": args.get("party") or ref_doc.customer, "party": args.get("party") or ref_doc.get("customer"),
"bank_account": bank_account "bank_account": bank_account
}) })

View File

@ -20,7 +20,7 @@ frappe.ui.form.on('Period Closing Voucher', {
}, },
refresh: function(frm) { refresh: function(frm) {
if(frm.doc.docstatus==1) { if(frm.doc.docstatus > 0) {
frm.add_custom_button(__('Ledger'), function() { frm.add_custom_button(__('Ledger'), function() {
frappe.route_options = { frappe.route_options = {
"voucher_no": frm.doc.name, "voucher_no": frm.doc.name,

View File

@ -17,8 +17,9 @@ class PeriodClosingVoucher(AccountsController):
self.make_gl_entries() self.make_gl_entries()
def on_cancel(self): def on_cancel(self):
frappe.db.sql("""delete from `tabGL Entry` self.ignore_linked_doctypes = ('GL Entry', 'Stock Ledger Entry')
where voucher_type = 'Period Closing Voucher' and voucher_no=%s""", self.name) from erpnext.accounts.general_ledger import make_reverse_gl_entries
make_reverse_gl_entries(voucher_type="Period Closing Voucher", voucher_no=self.name, cancel=True)
def validate_account_head(self): def validate_account_head(self):
closing_account_type = frappe.db.get_value("Account", self.closing_account_head, "root_type") closing_account_type = frappe.db.get_value("Account", self.closing_account_head, "root_type")

View File

@ -261,12 +261,25 @@ erpnext.accounts.PurchaseInvoice = erpnext.buying.BuyingController.extend({
price_list: this.frm.doc.buying_price_list price_list: this.frm.doc.buying_price_list
}, function() { }, function() {
me.apply_pricing_rule(); me.apply_pricing_rule();
me.frm.doc.apply_tds = me.frm.supplier_tds ? 1 : 0; 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("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() { credit_to: function() {
var me = this; var me = this;
if(this.frm.doc.credit_to) { if(this.frm.doc.credit_to) {
@ -369,11 +382,6 @@ function hide_fields(doc) {
cur_frm.refresh_fields(); cur_frm.refresh_fields();
} }
cur_frm.cscript.update_stock = function(doc, dt, dn) {
hide_fields(doc, dt, dn);
this.frm.fields_dict.items.grid.toggle_reqd("item_code", doc.update_stock? true: false)
}
cur_frm.fields_dict.cash_bank_account.get_query = function(doc) { cur_frm.fields_dict.cash_bank_account.get_query = function(doc) {
return { return {
filters: [ filters: [
@ -515,5 +523,10 @@ frappe.ui.form.on("Purchase Invoice", {
erpnext.buying.get_default_bom(frm); erpnext.buying.get_default_bom(frm);
} }
frm.toggle_reqd("supplier_warehouse", frm.doc.is_subcontracted==="Yes"); frm.toggle_reqd("supplier_warehouse", frm.doc.is_subcontracted==="Yes");
},
update_stock: function(frm) {
hide_fields(frm.doc);
frm.fields_dict.items.grid.toggle_reqd("item_code", frm.doc.update_stock? true: false);
} }
}) })

View File

@ -13,6 +13,7 @@
"supplier_name", "supplier_name",
"tax_id", "tax_id",
"due_date", "due_date",
"tax_withholding_category",
"column_break1", "column_break1",
"company", "company",
"posting_date", "posting_date",
@ -1294,13 +1295,21 @@
"fieldtype": "Check", "fieldtype": "Check",
"label": "Is Internal Supplier", "label": "Is Internal Supplier",
"read_only": 1 "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", "icon": "fa fa-file-text",
"idx": 204, "idx": 204,
"is_submittable": 1, "is_submittable": 1,
"links": [], "links": [],
"modified": "2020-04-17 13:05:25.199832", "modified": "2020-04-18 13:05:25.199832",
"modified_by": "Administrator", "modified_by": "Administrator",
"module": "Accounts", "module": "Accounts",
"name": "Purchase Invoice", "name": "Purchase Invoice",

View File

@ -14,7 +14,7 @@ from erpnext.accounts.party import get_party_account, get_due_date
from erpnext.accounts.utils import get_account_currency, get_fiscal_year from erpnext.accounts.utils import get_account_currency, get_fiscal_year
from erpnext.stock.doctype.purchase_receipt.purchase_receipt import update_billed_amount_based_on_po from erpnext.stock.doctype.purchase_receipt.purchase_receipt import update_billed_amount_based_on_po
from erpnext.stock import get_warehouse_account_map from erpnext.stock import get_warehouse_account_map
from erpnext.accounts.general_ledger import make_gl_entries, merge_similar_entries, delete_gl_entries from erpnext.accounts.general_ledger import make_gl_entries, merge_similar_entries, make_reverse_gl_entries
from erpnext.accounts.doctype.gl_entry.gl_entry import update_outstanding_amt from erpnext.accounts.doctype.gl_entry.gl_entry import update_outstanding_amt
from erpnext.buying.utils import check_on_hold_or_closed_status from erpnext.buying.utils import check_on_hold_or_closed_status
from erpnext.accounts.general_ledger import get_round_off_account_and_cost_center from erpnext.accounts.general_ledger import get_round_off_account_and_cost_center
@ -382,7 +382,7 @@ class PurchaseInvoice(BuyingController):
self.update_project() self.update_project()
update_linked_doc(self.doctype, self.name, self.inter_company_invoice_reference) update_linked_doc(self.doctype, self.name, self.inter_company_invoice_reference)
def make_gl_entries(self, gl_entries=None, repost_future_gle=True, from_repost=False): def make_gl_entries(self, gl_entries=None):
if not self.grand_total: if not self.grand_total:
return return
if not gl_entries: if not gl_entries:
@ -391,21 +391,17 @@ class PurchaseInvoice(BuyingController):
if gl_entries: if gl_entries:
update_outstanding = "No" if (cint(self.is_paid) or self.write_off_account) else "Yes" update_outstanding = "No" if (cint(self.is_paid) or self.write_off_account) else "Yes"
make_gl_entries(gl_entries, cancel=(self.docstatus == 2), if self.docstatus == 1:
update_outstanding=update_outstanding, merge_entries=False, from_repost=from_repost) make_gl_entries(gl_entries, update_outstanding=update_outstanding, merge_entries=False)
elif self.docstatus == 2:
make_reverse_gl_entries(voucher_type=self.doctype, voucher_no=self.name)
if update_outstanding == "No": if update_outstanding == "No":
update_outstanding_amt(self.credit_to, "Supplier", self.supplier, update_outstanding_amt(self.credit_to, "Supplier", self.supplier,
self.doctype, self.return_against if cint(self.is_return) and self.return_against else self.name) self.doctype, self.return_against if cint(self.is_return) and self.return_against else self.name)
if (repost_future_gle or self.flags.repost_future_gle) and cint(self.update_stock) and self.auto_accounting_for_stock:
from erpnext.controllers.stock_controller import update_gl_entries_after
items, warehouses = self.get_items_and_warehouses()
update_gl_entries_after(self.posting_date, self.posting_time,
warehouses, items, company = self.company)
elif self.docstatus == 2 and cint(self.update_stock) and self.auto_accounting_for_stock: elif self.docstatus == 2 and cint(self.update_stock) and self.auto_accounting_for_stock:
delete_gl_entries(voucher_type=self.doctype, voucher_no=self.name) make_reverse_gl_entries(voucher_type=self.doctype, voucher_no=self.name)
def get_gl_entries(self, warehouse_account=None): def get_gl_entries(self, warehouse_account=None):
self.auto_accounting_for_stock = erpnext.is_perpetual_inventory_enabled(self.company) self.auto_accounting_for_stock = erpnext.is_perpetual_inventory_enabled(self.company)
@ -934,6 +930,7 @@ class PurchaseInvoice(BuyingController):
frappe.db.set(self, 'status', 'Cancelled') frappe.db.set(self, 'status', 'Cancelled')
unlink_inter_company_doc(self.doctype, self.name, self.inter_company_invoice_reference) unlink_inter_company_doc(self.doctype, self.name, self.inter_company_invoice_reference)
self.ignore_linked_doctypes = ('GL Entry', 'Stock Ledger Entry')
def update_project(self): def update_project(self):
project_list = [] project_list = []
@ -1002,7 +999,7 @@ class PurchaseInvoice(BuyingController):
if not self.apply_tds: if not self.apply_tds:
return 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: if not tax_withholding_details:
return return

View File

@ -138,7 +138,6 @@
"row_id": 7 "row_id": 7
} }
], ],
"posting_date": "2013-02-03",
"supplier": "_Test Supplier", "supplier": "_Test Supplier",
"supplier_name": "_Test Supplier" "supplier_name": "_Test Supplier"
}, },
@ -204,7 +203,6 @@
"tax_amount": 150.0 "tax_amount": 150.0
} }
], ],
"posting_date": "2013-02-03",
"supplier": "_Test Supplier", "supplier": "_Test Supplier",
"supplier_name": "_Test Supplier" "supplier_name": "_Test Supplier"
} }

View File

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

View File

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

View File

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

View File

@ -345,7 +345,7 @@ erpnext.accounts.SalesInvoiceController = erpnext.selling.SellingController.exte
set_dynamic_labels: function() { set_dynamic_labels: function() {
this._super(); this._super();
this.hide_fields(this.frm.doc); this.frm.events.hide_fields(this.frm)
}, },
items_on_form_rendered: function() { items_on_form_rendered: function() {
@ -404,7 +404,7 @@ erpnext.accounts.SalesInvoiceController = erpnext.selling.SellingController.exte
if(r.message && r.message.print_format) { if(r.message && r.message.print_format) {
me.frm.pos_print_format = r.message.print_format; me.frm.pos_print_format = r.message.print_format;
} }
me.frm.script_manager.trigger("update_stock"); me.frm.trigger("update_stock");
if(me.frm.doc.taxes_and_charges) { if(me.frm.doc.taxes_and_charges) {
me.frm.script_manager.trigger("taxes_and_charges"); me.frm.script_manager.trigger("taxes_and_charges");
} }
@ -446,35 +446,6 @@ erpnext.accounts.SalesInvoiceController = erpnext.selling.SellingController.exte
// for backward compatibility: combine new and previous states // for backward compatibility: combine new and previous states
$.extend(cur_frm.cscript, new erpnext.accounts.SalesInvoiceController({frm: cur_frm})); $.extend(cur_frm.cscript, new erpnext.accounts.SalesInvoiceController({frm: cur_frm}));
// Hide Fields
// ------------
cur_frm.cscript.hide_fields = function(doc) {
var parent_fields = ['project', 'due_date', 'is_opening', 'source', 'total_advance', 'get_advances',
'advances', 'from_date', 'to_date'];
if(cint(doc.is_pos) == 1) {
hide_field(parent_fields);
} else {
for (var i in parent_fields) {
var docfield = frappe.meta.docfield_map[doc.doctype][parent_fields[i]];
if(!docfield.hidden) unhide_field(parent_fields[i]);
}
}
// India related fields
if (frappe.boot.sysdefaults.country == 'India') unhide_field(['c_form_applicable', 'c_form_no']);
else hide_field(['c_form_applicable', 'c_form_no']);
this.frm.toggle_enable("write_off_amount", !!!cint(doc.write_off_outstanding_amount_automatically));
cur_frm.refresh_fields();
}
cur_frm.cscript.update_stock = function(doc, dt, dn) {
cur_frm.cscript.hide_fields(doc, dt, dn);
this.frm.fields_dict.items.grid.toggle_reqd("item_code", doc.update_stock? true: false)
}
cur_frm.cscript['Make Delivery Note'] = function() { cur_frm.cscript['Make Delivery Note'] = function() {
frappe.model.open_mapped_doc({ frappe.model.open_mapped_doc({
method: "erpnext.accounts.doctype.sales_invoice.sales_invoice.make_delivery_note", method: "erpnext.accounts.doctype.sales_invoice.sales_invoice.make_delivery_note",
@ -587,7 +558,9 @@ frappe.ui.form.on('Sales Invoice', {
frm.set_query("account_for_change_amount", function() { frm.set_query("account_for_change_amount", function() {
return { return {
filters: { filters: {
account_type: ['in', ["Cash", "Bank"]] account_type: ['in', ["Cash", "Bank"]],
company: frm.doc.company,
is_group: 0
} }
}; };
}); });
@ -668,7 +641,8 @@ frappe.ui.form.on('Sales Invoice', {
frm.fields_dict["loyalty_redemption_account"].get_query = function() { frm.fields_dict["loyalty_redemption_account"].get_query = function() {
return { return {
filters:{ filters:{
"company": frm.doc.company "company": frm.doc.company,
"is_group": 0
} }
} }
}; };
@ -677,7 +651,8 @@ frappe.ui.form.on('Sales Invoice', {
frm.fields_dict["loyalty_redemption_cost_center"].get_query = function() { frm.fields_dict["loyalty_redemption_cost_center"].get_query = function() {
return { return {
filters:{ filters:{
"company": frm.doc.company "company": frm.doc.company,
"is_group": 0
} }
} }
}; };
@ -715,6 +690,12 @@ frappe.ui.form.on('Sales Invoice', {
frm.redemption_conversion_factor = null; frm.redemption_conversion_factor = null;
}, },
update_stock: function(frm, dt, dn) {
frm.events.hide_fields(frm);
frm.fields_dict.items.grid.toggle_reqd("item_code", frm.doc.update_stock);
frm.trigger('reset_posting_time');
},
redeem_loyalty_points: function(frm) { redeem_loyalty_points: function(frm) {
frm.events.get_loyalty_details(frm); frm.events.get_loyalty_details(frm);
}, },
@ -738,6 +719,29 @@ frappe.ui.form.on('Sales Invoice', {
} }
}, },
hide_fields: function(frm) {
let doc = frm.doc;
var parent_fields = ['project', 'due_date', 'is_opening', 'source', 'total_advance', 'get_advances',
'advances', 'from_date', 'to_date'];
if(cint(doc.is_pos) == 1) {
hide_field(parent_fields);
} else {
for (var i in parent_fields) {
var docfield = frappe.meta.docfield_map[doc.doctype][parent_fields[i]];
if(!docfield.hidden) unhide_field(parent_fields[i]);
}
}
// India related fields
if (frappe.boot.sysdefaults.country == 'India') unhide_field(['c_form_applicable', 'c_form_no']);
else hide_field(['c_form_applicable', 'c_form_no']);
frm.toggle_enable("write_off_amount", !!!cint(doc.write_off_outstanding_amount_automatically));
frm.refresh_fields();
},
get_loyalty_details: function(frm) { get_loyalty_details: function(frm) {
if (frm.doc.customer && frm.doc.redeem_loyalty_points) { if (frm.doc.customer && frm.doc.redeem_loyalty_points) {
frappe.call({ frappe.call({

View File

@ -149,9 +149,9 @@
"edit_printing_settings", "edit_printing_settings",
"letter_head", "letter_head",
"group_same_items", "group_same_items",
"language",
"column_break_84",
"select_print_heading", "select_print_heading",
"column_break_84",
"language",
"more_information", "more_information",
"inter_company_invoice_reference", "inter_company_invoice_reference",
"is_internal_customer", "is_internal_customer",
@ -1579,7 +1579,7 @@
"idx": 181, "idx": 181,
"is_submittable": 1, "is_submittable": 1,
"links": [], "links": [],
"modified": "2020-04-17 12:38:41.435728", "modified": "2020-04-29 13:37:09.355300",
"modified_by": "Administrator", "modified_by": "Administrator",
"module": "Accounts", "module": "Accounts",
"name": "Sales Invoice", "name": "Sales Invoice",

View File

@ -7,7 +7,6 @@ import frappe.defaults
from frappe.utils import cint, flt, add_months, today, date_diff, getdate, add_days, cstr, nowdate from frappe.utils import cint, flt, add_months, today, date_diff, getdate, add_days, cstr, nowdate
from frappe import _, msgprint, throw from frappe import _, msgprint, throw
from erpnext.accounts.party import get_party_account, get_due_date from erpnext.accounts.party import get_party_account, get_due_date
from erpnext.controllers.stock_controller import update_gl_entries_after
from frappe.model.mapper import get_mapped_doc from frappe.model.mapper import get_mapped_doc
from erpnext.accounts.doctype.sales_invoice.pos import update_multi_mode_option from erpnext.accounts.doctype.sales_invoice.pos import update_multi_mode_option
@ -282,6 +281,8 @@ class SalesInvoice(SellingController):
if "Healthcare" in active_domains: if "Healthcare" in active_domains:
manage_invoice_submit_cancel(self, "on_cancel") manage_invoice_submit_cancel(self, "on_cancel")
self.ignore_linked_doctypes = ('GL Entry', 'Stock Ledger Entry')
def update_status_updater_args(self): def update_status_updater_args(self):
if cint(self.update_stock): if cint(self.update_stock):
self.status_updater.append({ self.status_updater.append({
@ -717,7 +718,9 @@ class SalesInvoice(SellingController):
if d.delivery_note and frappe.db.get_value("Delivery Note", d.delivery_note, "docstatus") != 1: if d.delivery_note and frappe.db.get_value("Delivery Note", d.delivery_note, "docstatus") != 1:
throw(_("Delivery Note {0} is not submitted").format(d.delivery_note)) throw(_("Delivery Note {0} is not submitted").format(d.delivery_note))
def make_gl_entries(self, gl_entries=None, repost_future_gle=True, from_repost=False): def make_gl_entries(self, gl_entries=None):
from erpnext.accounts.general_ledger import make_reverse_gl_entries
auto_accounting_for_stock = erpnext.is_perpetual_inventory_enabled(self.company) auto_accounting_for_stock = erpnext.is_perpetual_inventory_enabled(self.company)
if not gl_entries: if not gl_entries:
gl_entries = self.get_gl_entries() gl_entries = self.get_gl_entries()
@ -729,23 +732,19 @@ class SalesInvoice(SellingController):
update_outstanding = "No" if (cint(self.is_pos) or self.write_off_account or update_outstanding = "No" if (cint(self.is_pos) or self.write_off_account or
cint(self.redeem_loyalty_points)) else "Yes" cint(self.redeem_loyalty_points)) else "Yes"
make_gl_entries(gl_entries, cancel=(self.docstatus == 2), if self.docstatus == 1:
update_outstanding=update_outstanding, merge_entries=False, from_repost=from_repost) make_gl_entries(gl_entries, update_outstanding=update_outstanding, merge_entries=False)
elif self.docstatus == 2:
make_reverse_gl_entries(voucher_type=self.doctype, voucher_no=self.name)
if update_outstanding == "No": if update_outstanding == "No":
from erpnext.accounts.doctype.gl_entry.gl_entry import update_outstanding_amt from erpnext.accounts.doctype.gl_entry.gl_entry import update_outstanding_amt
update_outstanding_amt(self.debit_to, "Customer", self.customer, update_outstanding_amt(self.debit_to, "Customer", self.customer,
self.doctype, self.return_against if cint(self.is_return) and self.return_against else self.name) self.doctype, self.return_against if cint(self.is_return) and self.return_against else self.name)
if (repost_future_gle or self.flags.repost_future_gle) and cint(self.update_stock) \
and cint(auto_accounting_for_stock):
items, warehouses = self.get_items_and_warehouses()
update_gl_entries_after(self.posting_date, self.posting_time,
warehouses, items, company = self.company)
elif self.docstatus == 2 and cint(self.update_stock) \ elif self.docstatus == 2 and cint(self.update_stock) \
and cint(auto_accounting_for_stock): and cint(auto_accounting_for_stock):
from erpnext.accounts.general_ledger import delete_gl_entries make_reverse_gl_entries(voucher_type=self.doctype, voucher_no=self.name)
delete_gl_entries(voucher_type=self.doctype, voucher_no=self.name)
def get_gl_entries(self, warehouse_account=None): def get_gl_entries(self, warehouse_account=None):
from erpnext.accounts.general_ledger import merge_similar_entries from erpnext.accounts.general_ledger import merge_similar_entries

View File

@ -364,7 +364,7 @@ class TestSalesInvoice(unittest.TestCase):
gle = frappe.db.sql("""select * from `tabGL Entry` gle = frappe.db.sql("""select * from `tabGL Entry`
where voucher_type='Sales Invoice' and voucher_no=%s""", si.name) where voucher_type='Sales Invoice' and voucher_no=%s""", si.name)
self.assertFalse(gle) self.assertTrue(gle)
def test_tax_calculation_with_multiple_items(self): def test_tax_calculation_with_multiple_items(self):
si = create_sales_invoice(qty=84, rate=4.6, do_not_save=True) si = create_sales_invoice(qty=84, rate=4.6, do_not_save=True)
@ -678,14 +678,15 @@ class TestSalesInvoice(unittest.TestCase):
gle = frappe.db.sql("""select * from `tabGL Entry` gle = frappe.db.sql("""select * from `tabGL Entry`
where voucher_type='Sales Invoice' and voucher_no=%s""", si.name) where voucher_type='Sales Invoice' and voucher_no=%s""", si.name)
self.assertFalse(gle) self.assertTrue(gle)
def test_pos_gl_entry_with_perpetual_inventory(self): def test_pos_gl_entry_with_perpetual_inventory(self):
make_pos_profile() make_pos_profile()
pr = make_purchase_receipt(company= "_Test Company with perpetual inventory",supplier_warehouse= "Work In Progress - TCP1", item_code= "_Test FG Item",warehouse= "Stores - TCP1",cost_center= "Main - TCP1") pr = make_purchase_receipt(company= "_Test Company with perpetual inventory", item_code= "_Test FG Item",warehouse= "Stores - TCP1",cost_center= "Main - TCP1")
pos = create_sales_invoice(company= "_Test Company with perpetual inventory", debit_to="Debtors - TCP1", item_code= "_Test FG Item", warehouse="Stores - TCP1", income_account = "Sales - TCP1", expense_account = "Cost of Goods Sold - TCP1", cost_center = "Main - TCP1", do_not_save=True) pos = create_sales_invoice(company= "_Test Company with perpetual inventory", debit_to="Debtors - TCP1", item_code= "_Test FG Item", warehouse="Stores - TCP1",
income_account = "Sales - TCP1", expense_account = "Cost of Goods Sold - TCP1", cost_center = "Main - TCP1", do_not_save=True)
pos.is_pos = 1 pos.is_pos = 1
pos.update_stock = 1 pos.update_stock = 1
@ -766,9 +767,13 @@ class TestSalesInvoice(unittest.TestCase):
def test_pos_change_amount(self): def test_pos_change_amount(self):
make_pos_profile() make_pos_profile()
pr = make_purchase_receipt(company= "_Test Company with perpetual inventory",supplier_warehouse= "Work In Progress - TCP1", item_code= "_Test FG Item",warehouse= "Stores - TCP1",cost_center= "Main - TCP1") pr = make_purchase_receipt(company= "_Test Company with perpetual inventory",
item_code= "_Test FG Item",warehouse= "Stores - TCP1", cost_center= "Main - TCP1")
pos = create_sales_invoice(company= "_Test Company with perpetual inventory", debit_to="Debtors - TCP1", item_code= "_Test FG Item", warehouse="Stores - TCP1", income_account = "Sales - TCP1", expense_account = "Cost of Goods Sold - TCP1", cost_center = "Main - TCP1", do_not_save=True) pos = create_sales_invoice(company= "_Test Company with perpetual inventory",
debit_to="Debtors - TCP1", item_code= "_Test FG Item", warehouse="Stores - TCP1",
income_account = "Sales - TCP1", expense_account = "Cost of Goods Sold - TCP1",
cost_center = "Main - TCP1", do_not_save=True)
pos.is_pos = 1 pos.is_pos = 1
pos.update_stock = 1 pos.update_stock = 1
@ -787,8 +792,15 @@ class TestSalesInvoice(unittest.TestCase):
from erpnext.accounts.doctype.sales_invoice.pos import make_invoice from erpnext.accounts.doctype.sales_invoice.pos import make_invoice
pos_profile = make_pos_profile() pos_profile = make_pos_profile()
pr = make_purchase_receipt(company= "_Test Company with perpetual inventory",supplier_warehouse= "Work In Progress - TCP1", item_code= "_Test FG Item",warehouse= "Stores - TCP1",cost_center= "Main - TCP1")
pos = create_sales_invoice(company= "_Test Company with perpetual inventory", debit_to="Debtors - TCP1", item_code= "_Test FG Item", warehouse="Stores - TCP1", income_account = "Sales - TCP1", expense_account = "Cost of Goods Sold - TCP1", cost_center = "Main - TCP1", do_not_save=True) pr = make_purchase_receipt(company= "_Test Company with perpetual inventory",
item_code= "_Test FG Item",
warehouse= "Stores - TCP1", cost_center= "Main - TCP1")
pos = create_sales_invoice(company= "_Test Company with perpetual inventory",
debit_to="Debtors - TCP1", item_code= "_Test FG Item", warehouse="Stores - TCP1",
income_account = "Sales - TCP1", expense_account = "Cost of Goods Sold - TCP1",
cost_center = "Main - TCP1", do_not_save=True)
pos.is_pos = 1 pos.is_pos = 1
pos.update_stock = 1 pos.update_stock = 1
@ -891,11 +903,9 @@ class TestSalesInvoice(unittest.TestCase):
gle = frappe.db.sql("""select * from `tabGL Entry` gle = frappe.db.sql("""select * from `tabGL Entry`
where voucher_type='Sales Invoice' and voucher_no=%s""", si.name) where voucher_type='Sales Invoice' and voucher_no=%s""", si.name)
self.assertFalse(gle) self.assertTrue(gle)
frappe.db.sql("delete from `tabPOS Profile`") frappe.db.sql("delete from `tabPOS Profile`")
si.delete()
def test_pos_si_without_payment(self): def test_pos_si_without_payment(self):
set_perpetual_inventory() set_perpetual_inventory()
@ -1012,9 +1022,6 @@ class TestSalesInvoice(unittest.TestCase):
si.cancel() si.cancel()
self.assertTrue(not frappe.db.sql("""select name from `tabJournal Entry Account`
where reference_name=%s""", si.name))
def test_serialized(self): def test_serialized(self):
from erpnext.stock.doctype.stock_entry.test_stock_entry import make_serialized_item from erpnext.stock.doctype.stock_entry.test_stock_entry import make_serialized_item
from erpnext.stock.doctype.serial_no.serial_no import get_serial_nos from erpnext.stock.doctype.serial_no.serial_no import get_serial_nos
@ -1230,7 +1237,7 @@ class TestSalesInvoice(unittest.TestCase):
gle = frappe.db.sql("""select name from `tabGL Entry` gle = frappe.db.sql("""select name from `tabGL Entry`
where voucher_type='Sales Invoice' and voucher_no=%s""", si.name) where voucher_type='Sales Invoice' and voucher_no=%s""", si.name)
self.assertFalse(gle) self.assertTrue(gle)
def test_invalid_currency(self): def test_invalid_currency(self):
# Customer currency = USD # Customer currency = USD
@ -1892,7 +1899,7 @@ class TestSalesInvoice(unittest.TestCase):
si.submit() 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['version'], '1.0.1118')
self.assertEqual(data['billLists'][0]['fromGstin'], '27AAECE4835E1ZR') self.assertEqual(data['billLists'][0]['fromGstin'], '27AAECE4835E1ZR')

View File

@ -6,23 +6,42 @@ from __future__ import unicode_literals
import frappe import frappe
from frappe import _ from frappe import _
from frappe.model.document import Document 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 from erpnext.accounts.utils import get_fiscal_year
class TaxWithholdingCategory(Document): class TaxWithholdingCategory(Document):
pass pass
def get_party_tax_withholding_details(ref_doc): def get_party_tax_withholding_details(ref_doc, tax_withholding_category=None):
tax_withholding_category = frappe.db.get_value('Supplier', ref_doc.supplier, 'tax_withholding_category')
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: if not tax_withholding_category:
return 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) 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) tax_details = get_tax_withholding_details(tax_withholding_category, fy[0], ref_doc.company)
if not tax_details: if not tax_details:
frappe.throw(_('Please set associated account in Tax Withholding Category {0} against Company {1}') frappe.throw(_('Please set associated account in Tax Withholding Category {0} against Company {1}')
.format(tax_withholding_category, ref_doc.company)) .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) tax_row = get_tax_row(tax_details, tds_amount)
return tax_row 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.")) frappe.throw(_("No Tax Withholding data found for the current Fiscal Year."))
def get_tax_row(tax_details, tds_amount): def get_tax_row(tax_details, tds_amount):
return { return {
"category": "Total", "category": "Total",
"add_deduct_tax": "Deduct", "add_deduct_tax": "Deduct",
@ -60,25 +80,36 @@ def get_tax_row(tax_details, tds_amount):
"tax_amount": 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 fiscal_year, year_start_date, year_end_date = fiscal_year_details
tds_amount = 0 tds_amount = 0
tds_deducted = 0 tds_deducted = 0
def _get_tds(amount): def _get_tds(amount, rate):
if amount <= 0: if amount <= 0:
return 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(""" entries = frappe.db.sql("""
select voucher_no, credit select voucher_no, credit
from `tabGL Entry` from `tabGL Entry`
where party=%s and fiscal_year=%s and credit > 0 where company = %s and
""", (ref_doc.supplier, fiscal_year), as_dict=1) 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] 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 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 tds_deducted = tds_deducted[0][0] if tds_deducted and tds_deducted[0][0] else 0
if tds_deducted: 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: else:
supplier_credit_amount = frappe.get_all('Purchase Invoice Item', supplier_credit_amount = frappe.get_all('Purchase Invoice Item',
fields = ['sum(net_amount)'], 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)'], fields = ['sum(credit_in_account_currency)'],
filters = { filters = {
'parent': ('in', vouchers), 'docstatus': 1, 'parent': ('in', vouchers), 'docstatus': 1,
'party': ref_doc.supplier, 'party': ('in', suppliers),
'reference_type': ('not in', ['Purchase Invoice']) 'reference_type': ('not in', ['Purchase Invoice'])
}, as_list=1) }, as_list=1)
supplier_credit_amount += (jv_supplier_credit_amt[0][0] supplier_credit_amount += (jv_supplier_credit_amt[0][0]
if jv_supplier_credit_amt and jv_supplier_credit_amt[0][0] else 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 supplier_credit_amount -= debit_note_amount
if ((tax_details.get('threshold', 0) and supplier_credit_amount >= tax_details.threshold) 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)): 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 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 condition = "fiscal_year=%s" % fiscal_year
if company:
condition += "and company =%s" % (company)
if from_date and to_date: 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(""" return frappe.db.sql_list("""
select distinct voucher_no select distinct voucher_no
from `tabGL Entry` from `tabGL Entry`
where party=%s and %s and debit > 0 where party in %s and %s and debit > 0
""", (supplier, condition)) or [] """, (tuple(suppliers), condition)) or []
def get_debit_note_amount(supplier, year_start_date, year_end_date, company=None): def get_debit_note_amount(suppliers, year_start_date, year_end_date, company=None):
condition = "" condition = "and 1=1"
if company: if company:
condition = " and company=%s " % company condition = " and company=%s " % company
if len(suppliers) == 1:
suppliers.append(suppliers[0])
return flt(frappe.db.sql(""" return flt(frappe.db.sql("""
select abs(sum(net_total)) select abs(sum(net_total))
from `tabPurchase Invoice` from `tabPurchase Invoice`
where supplier=%s %s and is_return=1 and docstatus=1 where supplier in %s and is_return=1 and docstatus=1
and posting_date between %s and %s and posting_date between %s and %s %s
""", (supplier, condition, year_start_date, year_end_date))) """, (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

@ -3,7 +3,7 @@
from __future__ import unicode_literals from __future__ import unicode_literals
import frappe, erpnext import frappe, erpnext
from frappe.utils import flt, cstr, cint, comma_and from frappe.utils import flt, cstr, cint, comma_and, today, getdate, formatdate, now
from frappe import _ from frappe import _
from erpnext.accounts.utils import get_stock_and_account_balance from erpnext.accounts.utils import get_stock_and_account_balance
from frappe.model.meta import get_field_precision from frappe.model.meta import get_field_precision
@ -15,17 +15,17 @@ class ClosedAccountingPeriod(frappe.ValidationError): pass
class StockAccountInvalidTransaction(frappe.ValidationError): pass class StockAccountInvalidTransaction(frappe.ValidationError): pass
class StockValueAndAccountBalanceOutOfSync(frappe.ValidationError): pass class StockValueAndAccountBalanceOutOfSync(frappe.ValidationError): pass
def make_gl_entries(gl_map, cancel=False, adv_adj=False, merge_entries=True, update_outstanding='Yes', from_repost=False): def make_gl_entries(gl_map, cancel=False, adv_adj=False, merge_entries=True, update_outstanding='Yes'):
if gl_map: if gl_map:
if not cancel: if not cancel:
validate_accounting_period(gl_map) validate_accounting_period(gl_map)
gl_map = process_gl_map(gl_map, merge_entries) gl_map = process_gl_map(gl_map, merge_entries)
if gl_map and len(gl_map) > 1: if gl_map and len(gl_map) > 1:
save_entries(gl_map, adv_adj, update_outstanding, from_repost) save_entries(gl_map, adv_adj, update_outstanding)
else: else:
frappe.throw(_("Incorrect number of General Ledger Entries found. You might have selected a wrong Account in the transaction.")) frappe.throw(_("Incorrect number of General Ledger Entries found. You might have selected a wrong Account in the transaction."))
else: else:
delete_gl_entries(gl_map, adv_adj=adv_adj, update_outstanding=update_outstanding) make_reverse_gl_entries(gl_map, adv_adj=adv_adj, update_outstanding=update_outstanding)
def validate_accounting_period(gl_map): def validate_accounting_period(gl_map):
accounting_periods = frappe.db.sql(""" SELECT accounting_periods = frappe.db.sql(""" SELECT
@ -119,33 +119,36 @@ def check_if_in_list(gle, gl_map, dimensions=None):
if same_head: if same_head:
return e return e
def save_entries(gl_map, adv_adj, update_outstanding, from_repost=False): def save_entries(gl_map, adv_adj, update_outstanding):
if not from_repost: validate_cwip_accounts(gl_map)
validate_cwip_accounts(gl_map)
round_off_debit_credit(gl_map) round_off_debit_credit(gl_map)
if gl_map:
check_freezing_date(gl_map[0]["posting_date"], adv_adj)
for entry in gl_map: for entry in gl_map:
make_entry(entry, adv_adj, update_outstanding, from_repost) make_entry(entry, adv_adj, update_outstanding)
# check against budget # check against budget
if not from_repost: validate_expense_against_budget(entry)
validate_expense_against_budget(entry)
if not from_repost: validate_account_for_perpetual_inventory(gl_map)
validate_account_for_perpetual_inventory(gl_map)
def make_entry(args, adv_adj, update_outstanding, from_repost=False): def make_entry(args, adv_adj, update_outstanding):
gle = frappe.new_doc("GL Entry") gle = frappe.new_doc("GL Entry")
gle.update(args) gle.update(args)
gle.flags.ignore_permissions = 1 gle.flags.ignore_permissions = 1
gle.flags.from_repost = from_repost
gle.validate() gle.validate()
gle.db_insert() gle.db_insert()
gle.run_method("on_update_with_args", adv_adj, update_outstanding, from_repost) gle.run_method("on_update_with_args", adv_adj, update_outstanding)
gle.flags.ignore_validate = True gle.flags.ignore_validate = True
gle.submit() gle.submit()
# check against budget
validate_expense_against_budget(args)
def validate_account_for_perpetual_inventory(gl_map): def validate_account_for_perpetual_inventory(gl_map):
if cint(erpnext.is_perpetual_inventory_enabled(gl_map[0].company)): if cint(erpnext.is_perpetual_inventory_enabled(gl_map[0].company)):
account_list = [gl_entries.account for gl_entries in gl_map] account_list = [gl_entries.account for gl_entries in gl_map]
@ -169,33 +172,33 @@ def validate_account_for_perpetual_inventory(gl_map):
.format(account), StockAccountInvalidTransaction) .format(account), StockAccountInvalidTransaction)
# This has been comment for a temporary, will add this code again on release of immutable ledger # This has been comment for a temporary, will add this code again on release of immutable ledger
# elif account_bal != stock_bal: elif account_bal != stock_bal:
# precision = get_field_precision(frappe.get_meta("GL Entry").get_field("debit"), precision = get_field_precision(frappe.get_meta("GL Entry").get_field("debit"),
# currency=frappe.get_cached_value('Company', gl_map[0].company, "default_currency")) currency=frappe.get_cached_value('Company', gl_map[0].company, "default_currency"))
# diff = flt(stock_bal - account_bal, precision) diff = flt(stock_bal - account_bal, precision)
# error_reason = _("Stock Value ({0}) and Account Balance ({1}) are out of sync for account {2} and it's linked warehouses.").format( error_reason = _("Stock Value ({0}) and Account Balance ({1}) are out of sync for account {2} and it's linked warehouses.").format(
# stock_bal, account_bal, frappe.bold(account)) stock_bal, account_bal, frappe.bold(account))
# error_resolution = _("Please create adjustment Journal Entry for amount {0} ").format(frappe.bold(diff)) error_resolution = _("Please create adjustment Journal Entry for amount {0} ").format(frappe.bold(diff))
# stock_adjustment_account = frappe.db.get_value("Company",gl_map[0].company,"stock_adjustment_account") stock_adjustment_account = frappe.db.get_value("Company",gl_map[0].company,"stock_adjustment_account")
# db_or_cr_warehouse_account =('credit_in_account_currency' if diff < 0 else 'debit_in_account_currency') db_or_cr_warehouse_account =('credit_in_account_currency' if diff < 0 else 'debit_in_account_currency')
# db_or_cr_stock_adjustment_account = ('debit_in_account_currency' if diff < 0 else 'credit_in_account_currency') db_or_cr_stock_adjustment_account = ('debit_in_account_currency' if diff < 0 else 'credit_in_account_currency')
# journal_entry_args = { journal_entry_args = {
# 'accounts':[ 'accounts':[
# {'account': account, db_or_cr_warehouse_account : abs(diff)}, {'account': account, db_or_cr_warehouse_account : abs(diff)},
# {'account': stock_adjustment_account, db_or_cr_stock_adjustment_account : abs(diff) }] {'account': stock_adjustment_account, db_or_cr_stock_adjustment_account : abs(diff) }]
# } }
# frappe.msgprint(msg="""{0}<br></br>{1}<br></br>""".format(error_reason, error_resolution), frappe.msgprint(msg="""{0}<br></br>{1}<br></br>""".format(error_reason, error_resolution),
# raise_exception=StockValueAndAccountBalanceOutOfSync, raise_exception=StockValueAndAccountBalanceOutOfSync,
# title=_('Values Out Of Sync'), title=_('Values Out Of Sync'),
# primary_action={ primary_action={
# 'label': _('Make Journal Entry'), 'label': _('Make Journal Entry'),
# 'client_action': 'erpnext.route_to_adjustment_jv', 'client_action': 'erpnext.route_to_adjustment_jv',
# 'args': journal_entry_args 'args': journal_entry_args
# }) })
def validate_cwip_accounts(gl_map): def validate_cwip_accounts(gl_map):
cwip_enabled = any([cint(ac.enable_cwip_accounting) for ac in frappe.db.get_all("Asset Category","enable_cwip_accounting")]) cwip_enabled = any([cint(ac.enable_cwip_accounting) for ac in frappe.db.get_all("Asset Category","enable_cwip_accounting")])
@ -282,31 +285,64 @@ def get_round_off_account_and_cost_center(company):
return round_off_account, round_off_cost_center return round_off_account, round_off_cost_center
def delete_gl_entries(gl_entries=None, voucher_type=None, voucher_no=None, def make_reverse_gl_entries(gl_entries=None, voucher_type=None, voucher_no=None,
adv_adj=False, update_outstanding="Yes"): adv_adj=False, update_outstanding="Yes"):
"""
from erpnext.accounts.doctype.gl_entry.gl_entry import validate_balance_type, \ Get original gl entries of the voucher
check_freezing_date, update_outstanding_amt, validate_frozen_account and make reverse gl entries by swapping debit and credit
"""
if not gl_entries: if not gl_entries:
gl_entries = frappe.db.sql(""" gl_entries = frappe.get_all("GL Entry",
select account, posting_date, party_type, party, cost_center, fiscal_year,voucher_type, fields = ["*"],
voucher_no, against_voucher_type, against_voucher, cost_center, company filters = {
from `tabGL Entry` "voucher_type": voucher_type,
where voucher_type=%s and voucher_no=%s""", (voucher_type, voucher_no), as_dict=True) "voucher_no": voucher_no
})
if gl_entries: if gl_entries:
set_as_cancel(gl_entries[0]['voucher_type'], gl_entries[0]['voucher_no'])
check_freezing_date(gl_entries[0]["posting_date"], adv_adj) check_freezing_date(gl_entries[0]["posting_date"], adv_adj)
frappe.db.sql("""delete from `tabGL Entry` where voucher_type=%s and voucher_no=%s""", for entry in gl_entries:
(voucher_type or gl_entries[0]["voucher_type"], voucher_no or gl_entries[0]["voucher_no"])) entry['name'] = None
debit = entry.get('debit', 0)
credit = entry.get('credit', 0)
for entry in gl_entries: debit_in_account_currency = entry.get('debit_in_account_currency', 0)
validate_frozen_account(entry["account"], adv_adj) credit_in_account_currency = entry.get('credit_in_account_currency', 0)
validate_balance_type(entry["account"], adv_adj)
if not adv_adj:
validate_expense_against_budget(entry)
if entry.get("against_voucher") and update_outstanding == 'Yes' and not adv_adj: entry['debit'] = credit
update_outstanding_amt(entry["account"], entry.get("party_type"), entry.get("party"), entry.get("against_voucher_type"), entry['credit'] = debit
entry.get("against_voucher"), on_cancel=True) entry['debit_in_account_currency'] = credit_in_account_currency
entry['credit_in_account_currency'] = debit_in_account_currency
entry['remarks'] = "On cancellation of " + entry['voucher_no']
entry['is_cancelled'] = 1
entry['posting_date'] = today()
if entry['debit'] or entry['credit']:
make_entry(entry, adv_adj, "Yes")
def check_freezing_date(posting_date, adv_adj=False):
"""
Nobody can do GL Entries where posting date is before freezing date
except authorized person
"""
if not adv_adj:
acc_frozen_upto = frappe.db.get_value('Accounts Settings', None, 'acc_frozen_upto')
if acc_frozen_upto:
frozen_accounts_modifier = frappe.db.get_value( 'Accounts Settings', None,'frozen_accounts_modifier')
if getdate(posting_date) <= getdate(acc_frozen_upto) \
and not frozen_accounts_modifier in frappe.get_roles():
frappe.throw(_("You are not authorized to add or update entries before {0}").format(formatdate(acc_frozen_upto)))
def set_as_cancel(voucher_type, voucher_no):
"""
Set is_cancelled=1 in all original gl entries for the voucher
"""
frappe.db.sql("""update `tabGL Entry` set is_cancelled = 1,
modified=%s, modified_by=%s
where voucher_type=%s and voucher_no=%s and is_cancelled = 0""",
(now(), frappe.session.user, voucher_type, voucher_no))

View File

@ -84,6 +84,7 @@ def get_balance_sheet_data(fiscal_year, companies, columns, filters):
def get_profit_loss_data(fiscal_year, companies, columns, filters): def get_profit_loss_data(fiscal_year, companies, columns, filters):
income, expense, net_profit_loss = get_income_expense_data(companies, fiscal_year, filters) income, expense, net_profit_loss = get_income_expense_data(companies, fiscal_year, filters)
company_currency = get_company_currency(filters)
data = [] data = []
data.extend(income or []) data.extend(income or [])
@ -93,7 +94,7 @@ def get_profit_loss_data(fiscal_year, companies, columns, filters):
chart = get_pl_chart_data(filters, columns, income, expense, net_profit_loss) chart = get_pl_chart_data(filters, columns, income, expense, net_profit_loss)
report_summary = get_pl_summary(companies, '', income, expense, net_profit_loss, True) report_summary = get_pl_summary(companies, '', income, expense, net_profit_loss, company_currency, True)
return data, None, chart, report_summary return data, None, chart, report_summary

View File

@ -154,8 +154,12 @@ frappe.query_reports["General Ledger"] = {
{ {
"fieldname": "include_default_book_entries", "fieldname": "include_default_book_entries",
"label": __("Include Default Book Entries"), "label": __("Include Default Book Entries"),
"fieldtype": "Check", "fieldtype": "Check"
"default": 1 },
{
"fieldname": "show_cancelled_entries",
"label": __("Show Cancelled Entries"),
"fieldtype": "Check"
} }
] ]
} }

View File

@ -188,6 +188,9 @@ def get_conditions(filters):
else: else:
conditions.append("finance_book in (%(finance_book)s)") conditions.append("finance_book in (%(finance_book)s)")
if not filters.get("show_cancelled_entries"):
conditions.append("is_cancelled = 0")
from frappe.desk.reportview import build_match_conditions from frappe.desk.reportview import build_match_conditions
match_conditions = build_match_conditions("GL Entry") match_conditions = build_match_conditions("GL Entry")

View File

@ -35,6 +35,12 @@ def execute(filters=None):
}) })
return columns, data return columns, data
# to avoid error eg: gross_income[0] : list index out of range
if not gross_income:
gross_income = [{}]
if not gross_expense:
gross_expense = [{}]
data.append({ data.append({
"account_name": "'" + _("Included in Gross Profit") + "'", "account_name": "'" + _("Included in Gross Profit") + "'",
"account": "'" + _("Included in Gross Profit") + "'" "account": "'" + _("Included in Gross Profit") + "'"

View File

@ -55,27 +55,27 @@ def get_columns(group_wise_columns, filters):
columns = [] columns = []
column_map = frappe._dict({ column_map = frappe._dict({
"parent": _("Sales Invoice") + ":Link/Sales Invoice:120", "parent": _("Sales Invoice") + ":Link/Sales Invoice:120",
"posting_date": _("Posting Date") + ":Date", "posting_date": _("Posting Date") + ":Date:100",
"posting_time": _("Posting Time"), "posting_time": _("Posting Time") + ":Data:100",
"item_code": _("Item Code") + ":Link/Item", "item_code": _("Item Code") + ":Link/Item:100",
"item_name": _("Item Name"), "item_name": _("Item Name") + ":Data:100",
"item_group": _("Item Group") + ":Link/Item Group", "item_group": _("Item Group") + ":Link/Item Group:100",
"brand": _("Brand"), "brand": _("Brand") + ":Link/Brand:100",
"description": _("Description"), "description": _("Description") +":Data:100",
"warehouse": _("Warehouse") + ":Link/Warehouse", "warehouse": _("Warehouse") + ":Link/Warehouse:100",
"qty": _("Qty") + ":Float", "qty": _("Qty") + ":Float:80",
"base_rate": _("Avg. Selling Rate") + ":Currency/currency", "base_rate": _("Avg. Selling Rate") + ":Currency/currency:100",
"buying_rate": _("Valuation Rate") + ":Currency/currency", "buying_rate": _("Valuation Rate") + ":Currency/currency:100",
"base_amount": _("Selling Amount") + ":Currency/currency", "base_amount": _("Selling Amount") + ":Currency/currency:100",
"buying_amount": _("Buying Amount") + ":Currency/currency", "buying_amount": _("Buying Amount") + ":Currency/currency:100",
"gross_profit": _("Gross Profit") + ":Currency/currency", "gross_profit": _("Gross Profit") + ":Currency/currency:100",
"gross_profit_percent": _("Gross Profit %") + ":Percent", "gross_profit_percent": _("Gross Profit %") + ":Percent:100",
"project": _("Project") + ":Link/Project", "project": _("Project") + ":Link/Project:100",
"sales_person": _("Sales person"), "sales_person": _("Sales person"),
"allocated_amount": _("Allocated Amount") + ":Currency/currency", "allocated_amount": _("Allocated Amount") + ":Currency/currency:100",
"customer": _("Customer") + ":Link/Customer", "customer": _("Customer") + ":Link/Customer:100",
"customer_group": _("Customer Group") + ":Link/Customer Group", "customer_group": _("Customer Group") + ":Link/Customer Group:100",
"territory": _("Territory") + ":Link/Territory" "territory": _("Territory") + ":Link/Territory:100"
}) })
for col in group_wise_columns.get(scrub(filters.group_by)): for col in group_wise_columns.get(scrub(filters.group_by)):
@ -85,7 +85,8 @@ def get_columns(group_wise_columns, filters):
"fieldname": "currency", "fieldname": "currency",
"label" : _("Currency"), "label" : _("Currency"),
"fieldtype": "Link", "fieldtype": "Link",
"options": "Currency" "options": "Currency",
"hidden": 1
}) })
return columns return columns
@ -277,7 +278,7 @@ class GrossProfitGenerator(object):
from `tabPurchase Invoice Item` a from `tabPurchase Invoice Item` a
where a.item_code = %s and a.docstatus=1 where a.item_code = %s and a.docstatus=1
and modified <= %s and modified <= %s
order by a.modified desc limit 1""", (item_code,self.filters.to_date)) order by a.modified desc limit 1""", (item_code, self.filters.to_date))
else: else:
last_purchase_rate = frappe.db.sql(""" last_purchase_rate = frappe.db.sql("""
select (a.base_rate / a.conversion_factor) select (a.base_rate / a.conversion_factor)

View File

@ -102,7 +102,7 @@ def _execute(filters=None, additional_table_columns=None, additional_query_colum
data.append(row) data.append(row)
if filters.get('group_by'): if filters.get('group_by') and item_list:
total_row = total_row_map.get(prev_group_by_value or d.get('item_name')) total_row = total_row_map.get(prev_group_by_value or d.get('item_name'))
total_row['percent_gt'] = flt(total_row['total']/grand_total * 100) total_row['percent_gt'] = flt(total_row['total']/grand_total * 100)
data.append(total_row) data.append(total_row)

View File

@ -111,7 +111,7 @@ def _execute(filters=None, additional_table_columns=None, additional_query_colum
data.append(row) data.append(row)
if filters.get('group_by'): if filters.get('group_by') and item_list:
total_row = total_row_map.get(prev_group_by_value or d.get('item_name')) total_row = total_row_map.get(prev_group_by_value or d.get('item_name'))
total_row['percent_gt'] = flt(total_row['total']/grand_total * 100) total_row['percent_gt'] = flt(total_row['total']/grand_total * 100)
data.append(total_row) data.append(total_row)

View File

@ -44,9 +44,14 @@ def get_result(filters):
out = [] out = []
for supplier in filters.supplier: for supplier in filters.supplier:
tds = frappe.get_doc("Tax Withholding Category", supplier.tax_withholding_category) 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: try:
account = [d.account for d in tds.accounts if d.company == filters.company][0] account = [d.account for d in tds.accounts if d.company == filters.company][0]
except IndexError: except IndexError:
account = [] account = []
total_invoiced_amount, tds_deducted = get_invoice_and_tds_amount(supplier.name, 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])) supplier_credit_amount = flt(sum([d.credit for d in entries]))
vouchers = [d.voucher_no 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) from_date=from_date, to_date=to_date)
tds_deducted = 0 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])), """.format(', '.join(["'%s'" % d for d in vouchers])),
(account, from_date, to_date, company))[0][0]) (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 total_invoiced_amount = supplier_credit_amount + tds_deducted - debit_note_amount

View File

@ -817,48 +817,37 @@ def create_payment_gateway_account(gateway):
pass pass
@frappe.whitelist() @frappe.whitelist()
def update_number_field(doctype_name, name, field_name, number_value, company): def update_cost_center(docname, cost_center_name, cost_center_number, company):
''' '''
doctype_name = Name of the DocType
name = Docname being referred
field_name = Name of the field thats holding the 'number' attribute
number_value = Numeric value entered in field_name
Stores the number entered in the dialog to the DocType's field.
Renames the document by adding the number as a prefix to the current name and updates Renames the document by adding the number as a prefix to the current name and updates
all transaction where it was present. all transaction where it was present.
''' '''
doc_title = frappe.db.get_value(doctype_name, name, frappe.scrub(doctype_name)+"_name") validate_field_number("Cost Center", docname, cost_center_number, company, "cost_center_number")
validate_field_number(doctype_name, name, number_value, company, field_name) if cost_center_number:
frappe.db.set_value("Cost Center", docname, "cost_center_number", cost_center_number.strip())
else:
frappe.db.set_value("Cost Center", docname, "cost_center_number", "")
frappe.db.set_value(doctype_name, name, field_name, number_value) frappe.db.set_value("Cost Center", docname, "cost_center_name", cost_center_name.strip())
if doc_title[0].isdigit(): new_name = get_autoname_with_number(cost_center_number, cost_center_name, docname, company)
separator = " - " if " - " in doc_title else " " if docname != new_name:
doc_title = doc_title.split(separator, 1)[1] frappe.rename_doc("Cost Center", docname, new_name, force=1)
frappe.db.set_value(doctype_name, name, frappe.scrub(doctype_name)+"_name", doc_title)
new_name = get_autoname_with_number(number_value, doc_title, name, company)
if name != new_name:
frappe.rename_doc(doctype_name, name, new_name)
return new_name return new_name
def validate_field_number(doctype_name, name, number_value, company, field_name): def validate_field_number(doctype_name, docname, number_value, company, field_name):
''' Validate if the number entered isn't already assigned to some other document. ''' ''' Validate if the number entered isn't already assigned to some other document. '''
if number_value: if number_value:
filters = {field_name: number_value, "name": ["!=", docname]}
if company: if company:
doctype_with_same_number = frappe.db.get_value(doctype_name, filters["company"] = company
{field_name: number_value, "company": company, "name": ["!=", name]})
else: doctype_with_same_number = frappe.db.get_value(doctype_name, filters)
doctype_with_same_number = frappe.db.get_value(doctype_name,
{field_name: number_value, "name": ["!=", name]})
if doctype_with_same_number: if doctype_with_same_number:
frappe.throw(_("{0} Number {1} already used in account {2}") frappe.throw(_("{0} Number {1} is already used in {2} {3}")
.format(doctype_name, number_value, doctype_with_same_number)) .format(doctype_name, number_value, doctype_name.lower(), doctype_with_same_number))
def get_autoname_with_number(number_value, doc_title, name, company): def get_autoname_with_number(number_value, doc_title, name, company):
''' append title with prefix as number and suffix as company's abbreviation separated by '-' ''' ''' append title with prefix as number and suffix as company's abbreviation separated by '-' '''
@ -898,3 +887,59 @@ def get_stock_accounts(company):
"account_type": "Stock", "account_type": "Stock",
"company": company "company": company
}) })
def update_gl_entries_after(posting_date, posting_time, for_warehouses=None, for_items=None,
warehouse_account=None, company=None):
def _delete_gl_entries(voucher_type, voucher_no):
frappe.db.sql("""delete from `tabGL Entry`
where voucher_type=%s and voucher_no=%s""", (voucher_type, voucher_no))
if not warehouse_account:
warehouse_account = get_warehouse_account_map(company)
future_stock_vouchers = get_future_stock_vouchers(posting_date, posting_time, for_warehouses, for_items)
gle = get_voucherwise_gl_entries(future_stock_vouchers, posting_date)
for voucher_type, voucher_no in future_stock_vouchers:
existing_gle = gle.get((voucher_type, voucher_no), [])
voucher_obj = frappe.get_doc(voucher_type, voucher_no)
expected_gle = voucher_obj.get_gl_entries(warehouse_account)
if expected_gle:
if not existing_gle or not compare_existing_and_expected_gle(existing_gle, expected_gle):
_delete_gl_entries(voucher_type, voucher_no)
voucher_obj.make_gl_entries(gl_entries=expected_gle, repost_future_gle=False, from_repost=True)
else:
_delete_gl_entries(voucher_type, voucher_no)
def get_future_stock_vouchers(posting_date, posting_time, for_warehouses=None, for_items=None):
future_stock_vouchers = []
values = []
condition = ""
if for_items:
condition += " and item_code in ({})".format(", ".join(["%s"] * len(for_items)))
values += for_items
if for_warehouses:
condition += " and warehouse in ({})".format(", ".join(["%s"] * len(for_warehouses)))
values += for_warehouses
for d in frappe.db.sql("""select distinct sle.voucher_type, sle.voucher_no
from `tabStock Ledger Entry` sle
where timestamp(sle.posting_date, sle.posting_time) >= timestamp(%s, %s) {condition}
order by timestamp(sle.posting_date, sle.posting_time) asc, creation asc for update""".format(condition=condition),
tuple([posting_date, posting_time] + values), as_dict=True):
future_stock_vouchers.append([d.voucher_type, d.voucher_no])
return future_stock_vouchers
def get_voucherwise_gl_entries(future_stock_vouchers, posting_date):
gl_entries = {}
if future_stock_vouchers:
for d in frappe.db.sql("""select * from `tabGL Entry`
where posting_date >= %s and voucher_no in (%s)""" %
('%s', ', '.join(['%s']*len(future_stock_vouchers))),
tuple([posting_date] + [d[1] for d in future_stock_vouchers]), as_dict=1):
gl_entries.setdefault((d.voucher_type, d.voucher_no), []).append(d)
return gl_entries

View File

@ -11,7 +11,7 @@ from frappe.model.document import Document
from erpnext.assets.doctype.asset_category.asset_category import get_asset_category_account from erpnext.assets.doctype.asset_category.asset_category import get_asset_category_account
from erpnext.assets.doctype.asset.depreciation \ from erpnext.assets.doctype.asset.depreciation \
import get_disposal_account_and_cost_center, get_depreciation_accounts import get_disposal_account_and_cost_center, get_depreciation_accounts
from erpnext.accounts.general_ledger import make_gl_entries, delete_gl_entries from erpnext.accounts.general_ledger import make_gl_entries, make_reverse_gl_entries
from erpnext.accounts.utils import get_account_currency from erpnext.accounts.utils import get_account_currency
from erpnext.controllers.accounts_controller import AccountsController from erpnext.controllers.accounts_controller import AccountsController
@ -41,7 +41,8 @@ class Asset(AccountsController):
self.validate_cancellation() self.validate_cancellation()
self.delete_depreciation_entries() self.delete_depreciation_entries()
self.set_status() self.set_status()
delete_gl_entries(voucher_type='Asset', voucher_no=self.name) self.ignore_linked_doctypes = ('GL Entry', 'Stock Ledger Entry')
make_reverse_gl_entries(voucher_type='Asset', voucher_no=self.name)
self.db_set('booked_fixed_asset', 0) self.db_set('booked_fixed_asset', 0)
def validate_asset_and_reference(self): def validate_asset_and_reference(self):

View File

@ -66,9 +66,6 @@ class TestAsset(unittest.TestCase):
pr.cancel() pr.cancel()
self.assertEqual(asset.docstatus, 2) self.assertEqual(asset.docstatus, 2)
self.assertFalse(frappe.db.get_value("GL Entry",
{"voucher_type": "Purchase Invoice", "voucher_no": pi.name}))
def test_is_fixed_asset_set(self): def test_is_fixed_asset_set(self):
asset = create_asset(is_existing_asset = 1) asset = create_asset(is_existing_asset = 1)
doc = frappe.new_doc('Purchase Invoice') doc = frappe.new_doc('Purchase Invoice')

View File

@ -27,15 +27,6 @@ frappe.ui.form.on("Purchase Order", {
frm.set_indicator_formatter('item_code', frm.set_indicator_formatter('item_code',
function(doc) { return (doc.qty<=doc.received_qty) ? "green" : "orange" }) function(doc) { return (doc.qty<=doc.received_qty) ? "green" : "orange" })
frm.set_query("blanket_order", "items", function() {
return {
filters: {
"company": frm.doc.company,
"docstatus": 1
}
}
});
frm.set_query("expense_account", "items", function() { frm.set_query("expense_account", "items", function() {
return { return {
query: "erpnext.controllers.queries.get_expense_account", query: "erpnext.controllers.queries.get_expense_account",
@ -365,9 +356,7 @@ erpnext.buying.PurchaseOrderController = erpnext.buying.BuyingController.extend(
method: "erpnext.stock.doctype.material_request.material_request.make_purchase_order", method: "erpnext.stock.doctype.material_request.material_request.make_purchase_order",
source_doctype: "Material Request", source_doctype: "Material Request",
target: me.frm, target: me.frm,
setters: { setters: {},
company: me.frm.doc.company
},
get_query_filters: { get_query_filters: {
material_request_type: "Purchase", material_request_type: "Purchase",
docstatus: 1, docstatus: 1,
@ -384,7 +373,7 @@ erpnext.buying.PurchaseOrderController = erpnext.buying.BuyingController.extend(
source_doctype: "Supplier Quotation", source_doctype: "Supplier Quotation",
target: me.frm, target: me.frm,
setters: { setters: {
company: me.frm.doc.company supplier: me.frm.doc.supplier
}, },
get_query_filters: { get_query_filters: {
docstatus: 1, docstatus: 1,

View File

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

View File

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

View File

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

View File

@ -23,11 +23,7 @@ def get_data():
}, },
{ {
'label': _('Payments'), 'label': _('Payments'),
'items': ['Payment Entry'] 'items': ['Payment Entry', 'Bank Account']
},
{
'label': _('Bank'),
'items': ['Bank Account']
}, },
{ {
'label': _('Pricing'), 'label': _('Pricing'),

View File

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

View File

@ -170,6 +170,10 @@ def get_data():
"type": "doctype", "type": "doctype",
"name": "Payroll Period", "name": "Payroll Period",
}, },
{
"type": "doctype",
"name": "Income Tax Slab",
},
{ {
"type": "doctype", "type": "doctype",
"name": "Salary Component", "name": "Salary Component",
@ -209,6 +213,10 @@ def get_data():
"name": "Employee Tax Exemption Proof Submission", "name": "Employee Tax Exemption Proof Submission",
"dependencies": ["Employee"] "dependencies": ["Employee"]
}, },
{
"type": "doctype",
"name": "Employee Other Income",
},
{ {
"type": "doctype", "type": "doctype",
"name": "Employee Benefit Application", "name": "Employee Benefit Application",

View File

@ -664,23 +664,26 @@ class AccountsController(TransactionBase):
def set_total_advance_paid(self): def set_total_advance_paid(self):
if self.doctype == "Sales Order": if self.doctype == "Sales Order":
dr_or_cr = "credit_in_account_currency" dr_or_cr = "credit_in_account_currency"
rev_dr_or_cr = "debit_in_account_currency"
party = self.customer party = self.customer
else: else:
dr_or_cr = "debit_in_account_currency" dr_or_cr = "debit_in_account_currency"
rev_dr_or_cr = "credit_in_account_currency"
party = self.supplier party = self.supplier
advance = frappe.db.sql(""" advance = frappe.db.sql("""
select select
account_currency, sum({dr_or_cr}) as amount account_currency, sum({dr_or_cr}) - sum({rev_dr_cr}) as amount
from from
`tabGL Entry` `tabGL Entry`
where where
against_voucher_type = %s and against_voucher = %s and party=%s against_voucher_type = %s and against_voucher = %s and party=%s
and docstatus = 1 and docstatus = 1
""".format(dr_or_cr=dr_or_cr), (self.doctype, self.name, party), as_dict=1) """.format(dr_or_cr=dr_or_cr, rev_dr_cr=rev_dr_or_cr), (self.doctype, self.name, party), as_dict=1) #nosec
if advance: if advance:
advance = advance[0] advance = advance[0]
advance_paid = flt(advance.amount, self.precision("advance_paid")) advance_paid = flt(advance.amount, self.precision("advance_paid"))
formatted_advance_paid = fmt_money(advance_paid, precision=self.precision("advance_paid"), formatted_advance_paid = fmt_money(advance_paid, precision=self.precision("advance_paid"),
currency=advance.account_currency) currency=advance.account_currency)

View File

@ -371,6 +371,19 @@ def get_account_list(doctype, txt, searchfield, start, page_len, filters):
fields = ["name", "parent_account"], fields = ["name", "parent_account"],
limit_start=start, limit_page_length=page_len, as_list=True) limit_start=start, limit_page_length=page_len, as_list=True)
def get_blanket_orders(doctype, txt, searchfield, start, page_len, filters):
return frappe.db.sql("""select distinct bo.name, bo.blanket_order_type, bo.to_date
from `tabBlanket Order` bo, `tabBlanket Order Item` boi
where
boi.parent = bo.name
and boi.item_code = {item_code}
and bo.blanket_order_type = '{blanket_order_type}'
and bo.company = {company}
and bo.docstatus = 1"""
.format(item_code = frappe.db.escape(filters.get("item")),
blanket_order_type = filters.get("blanket_order_type"),
company = frappe.db.escape(filters.get("company"))
))
@frappe.whitelist() @frappe.whitelist()
def get_income_account(doctype, txt, searchfield, start, page_len, filters): def get_income_account(doctype, txt, searchfield, start, page_len, filters):

View File

@ -3,11 +3,11 @@
from __future__ import unicode_literals from __future__ import unicode_literals
import frappe, erpnext import frappe, erpnext
from frappe.utils import cint, flt, cstr from frappe.utils import cint, flt, cstr, get_link_to_form, today, getdate
from frappe import _ from frappe import _
import frappe.defaults import frappe.defaults
from erpnext.accounts.utils import get_fiscal_year from erpnext.accounts.utils import get_fiscal_year
from erpnext.accounts.general_ledger import make_gl_entries, delete_gl_entries, process_gl_map from erpnext.accounts.general_ledger import make_gl_entries, make_reverse_gl_entries, process_gl_map
from erpnext.controllers.accounts_controller import AccountsController from erpnext.controllers.accounts_controller import AccountsController
from erpnext.stock.stock_ledger import get_valuation_rate from erpnext.stock.stock_ledger import get_valuation_rate
from erpnext.stock import get_warehouse_account_map from erpnext.stock import get_warehouse_account_map
@ -23,9 +23,9 @@ class StockController(AccountsController):
self.validate_serialized_batch() self.validate_serialized_batch()
self.validate_customer_provided_item() self.validate_customer_provided_item()
def make_gl_entries(self, gl_entries=None, repost_future_gle=True, from_repost=False): def make_gl_entries(self, gl_entries=None):
if self.docstatus == 2: if self.docstatus == 2:
delete_gl_entries(voucher_type=self.doctype, voucher_no=self.name) make_reverse_gl_entries(voucher_type=self.doctype, voucher_no=self.name)
if cint(erpnext.is_perpetual_inventory_enabled(self.company)): if cint(erpnext.is_perpetual_inventory_enabled(self.company)):
warehouse_account = get_warehouse_account_map(self.company) warehouse_account = get_warehouse_account_map(self.company)
@ -33,16 +33,12 @@ class StockController(AccountsController):
if self.docstatus==1: if self.docstatus==1:
if not gl_entries: if not gl_entries:
gl_entries = self.get_gl_entries(warehouse_account) gl_entries = self.get_gl_entries(warehouse_account)
make_gl_entries(gl_entries, from_repost=from_repost) make_gl_entries(gl_entries)
if (repost_future_gle or self.flags.repost_future_gle):
items, warehouses = self.get_items_and_warehouses()
update_gl_entries_after(self.posting_date, self.posting_time, warehouses, items,
warehouse_account, company=self.company)
elif self.doctype in ['Purchase Receipt', 'Purchase Invoice'] and self.docstatus == 1: elif self.doctype in ['Purchase Receipt', 'Purchase Invoice'] and self.docstatus == 1:
gl_entries = [] gl_entries = []
gl_entries = self.get_asset_gl_entry(gl_entries) gl_entries = self.get_asset_gl_entry(gl_entries)
make_gl_entries(gl_entries, from_repost=from_repost) make_gl_entries(gl_entries)
def validate_serialized_batch(self): def validate_serialized_batch(self):
from erpnext.stock.doctype.serial_no.serial_no import get_serial_nos from erpnext.stock.doctype.serial_no.serial_no import get_serial_nos
@ -55,6 +51,13 @@ class StockController(AccountsController):
frappe.throw(_("Row #{0}: Serial No {1} does not belong to Batch {2}") frappe.throw(_("Row #{0}: Serial No {1} does not belong to Batch {2}")
.format(d.idx, serial_no_data.name, d.batch_no)) .format(d.idx, serial_no_data.name, d.batch_no))
if d.qty > 0 and d.get("batch_no") and self.get("posting_date") and self.docstatus < 2:
expiry_date = frappe.get_cached_value("Batch", d.get("batch_no"), "expiry_date")
if expiry_date and getdate(expiry_date) < getdate(self.posting_date):
frappe.throw(_("Row #{0}: The batch {1} has already expired.")
.format(d.idx, get_link_to_form("Batch", d.get("batch_no"))))
def get_gl_entries(self, warehouse_account=None, default_expense_account=None, def get_gl_entries(self, warehouse_account=None, default_expense_account=None,
default_cost_center=None): default_cost_center=None):
@ -267,21 +270,21 @@ class StockController(AccountsController):
"batch_no": cstr(d.get("batch_no")).strip(), "batch_no": cstr(d.get("batch_no")).strip(),
"serial_no": d.get("serial_no"), "serial_no": d.get("serial_no"),
"project": d.get("project") or self.get('project'), "project": d.get("project") or self.get('project'),
"is_cancelled": self.docstatus==2 and "Yes" or "No" "is_cancelled": 1 if self.docstatus==2 else 0
}) })
sl_dict.update(args) sl_dict.update(args)
return sl_dict return sl_dict
def make_sl_entries(self, sl_entries, is_amended=None, allow_negative_stock=False, def make_sl_entries(self, sl_entries, allow_negative_stock=False,
via_landed_cost_voucher=False): via_landed_cost_voucher=False):
from erpnext.stock.stock_ledger import make_sl_entries from erpnext.stock.stock_ledger import make_sl_entries
make_sl_entries(sl_entries, is_amended, allow_negative_stock, via_landed_cost_voucher) make_sl_entries(sl_entries, allow_negative_stock, via_landed_cost_voucher)
def make_gl_entries_on_cancel(self, repost_future_gle=True): def make_gl_entries_on_cancel(self):
if frappe.db.sql("""select name from `tabGL Entry` where voucher_type=%s if frappe.db.sql("""select name from `tabGL Entry` where voucher_type=%s
and voucher_no=%s""", (self.doctype, self.name)): and voucher_no=%s""", (self.doctype, self.name)):
self.make_gl_entries(repost_future_gle=repost_future_gle) self.make_gl_entries()
def get_serialized_items(self): def get_serialized_items(self):
serialized_items = [] serialized_items = []
@ -384,29 +387,6 @@ class StockController(AccountsController):
if frappe.db.get_value('Item', d.item_code, 'is_customer_provided_item'): if frappe.db.get_value('Item', d.item_code, 'is_customer_provided_item'):
d.allow_zero_valuation_rate = 1 d.allow_zero_valuation_rate = 1
def update_gl_entries_after(posting_date, posting_time, for_warehouses=None, for_items=None,
warehouse_account=None, company=None):
def _delete_gl_entries(voucher_type, voucher_no):
frappe.db.sql("""delete from `tabGL Entry`
where voucher_type=%s and voucher_no=%s""", (voucher_type, voucher_no))
if not warehouse_account:
warehouse_account = get_warehouse_account_map(company)
future_stock_vouchers = get_future_stock_vouchers(posting_date, posting_time, for_warehouses, for_items)
gle = get_voucherwise_gl_entries(future_stock_vouchers, posting_date)
for voucher_type, voucher_no in future_stock_vouchers:
existing_gle = gle.get((voucher_type, voucher_no), [])
voucher_obj = frappe.get_doc(voucher_type, voucher_no)
expected_gle = voucher_obj.get_gl_entries(warehouse_account)
if expected_gle:
if not existing_gle or not compare_existing_and_expected_gle(existing_gle, expected_gle):
_delete_gl_entries(voucher_type, voucher_no)
voucher_obj.make_gl_entries(gl_entries=expected_gle, repost_future_gle=False, from_repost=True)
else:
_delete_gl_entries(voucher_type, voucher_no)
def compare_existing_and_expected_gle(existing_gle, expected_gle): def compare_existing_and_expected_gle(existing_gle, expected_gle):
matched = True matched = True
for entry in expected_gle: for entry in expected_gle:
@ -423,36 +403,3 @@ def compare_existing_and_expected_gle(existing_gle, expected_gle):
matched = False matched = False
break break
return matched return matched
def get_future_stock_vouchers(posting_date, posting_time, for_warehouses=None, for_items=None):
future_stock_vouchers = []
values = []
condition = ""
if for_items:
condition += " and item_code in ({})".format(", ".join(["%s"] * len(for_items)))
values += for_items
if for_warehouses:
condition += " and warehouse in ({})".format(", ".join(["%s"] * len(for_warehouses)))
values += for_warehouses
for d in frappe.db.sql("""select distinct sle.voucher_type, sle.voucher_no
from `tabStock Ledger Entry` sle
where timestamp(sle.posting_date, sle.posting_time) >= timestamp(%s, %s) {condition}
order by timestamp(sle.posting_date, sle.posting_time) asc, creation asc for update""".format(condition=condition),
tuple([posting_date, posting_time] + values), as_dict=True):
future_stock_vouchers.append([d.voucher_type, d.voucher_no])
return future_stock_vouchers
def get_voucherwise_gl_entries(future_stock_vouchers, posting_date):
gl_entries = {}
if future_stock_vouchers:
for d in frappe.db.sql("""select * from `tabGL Entry`
where posting_date >= %s and voucher_no in (%s)""" %
('%s', ', '.join(['%s']*len(future_stock_vouchers))),
tuple([posting_date] + [d[1] for d in future_stock_vouchers]), as_dict=1):
gl_entries.setdefault((d.voucher_type, d.voucher_no), []).append(d)
return gl_entries

View File

@ -12,13 +12,18 @@
}, },
{ {
"hidden": 0, "hidden": 0,
"label": "Settings", "label": "Maintenance",
"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]" "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, "hidden": 0,
"label": "Maintenance", "label": "Campaign",
"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]" "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", "category": "Modules",
@ -33,7 +38,7 @@
"idx": 0, "idx": 0,
"is_standard": 1, "is_standard": 1,
"label": "CRM", "label": "CRM",
"modified": "2020-04-01 11:28:51.219999", "modified": "2020-04-27 22:32:26.682911",
"modified_by": "Administrator", "modified_by": "Administrator",
"module": "CRM", "module": "CRM",
"name": "CRM", "name": "CRM",
@ -42,7 +47,7 @@
"pin_to_top": 0, "pin_to_top": 0,
"shortcuts": [ "shortcuts": [
{ {
"format": "Open", "format": "{} Open",
"label": "Lead", "label": "Lead",
"link_to": "Lead", "link_to": "Lead",
"stats_filter": "{\"status\":\"Open\"}", "stats_filter": "{\"status\":\"Open\"}",

View File

@ -16,23 +16,27 @@ frappe.ui.form.on('Twitter Settings', {
} }
}, },
refresh: function(frm){ refresh: function(frm){
let msg,color; let msg, color, flag=false;
if (frm.doc.session_status == "Active"){ if (frm.doc.session_status == "Active"){
msg = __("Session Active"); msg = __("Session Active");
color = 'green'; color = 'green';
flag = true;
} }
else { else if(frm.doc.consumer_key && frm.doc.consumer_secret) {
msg = __("Session Not Active. Save doc to login."); msg = __("Session Not Active. Save doc to login.");
color = 'red'; color = 'red';
flag = true;
} }
frm.dashboard.set_headline_alert( if (flag){
`<div class="row"> frm.dashboard.set_headline_alert(
<div class="col-xs-12"> `<div class="row">
<span class="indicator whitespace-nowrap ${color}"><span class="hidden-xs">${msg}</span></span> <div class="col-xs-12">
</div> <span class="indicator whitespace-nowrap ${color}"><span class="hidden-xs">${msg}</span></span>
</div>` </div>
); </div>`
);
}
}, },
login: function(frm){ login: function(frm){
if (frm.doc.consumer_key && frm.doc.consumer_secret){ if (frm.doc.consumer_key && frm.doc.consumer_secret){

24
erpnext/crm/utils.py Normal file
View File

@ -0,0 +1,24 @@
import frappe
def update_lead_phone_numbers(contact, method):
if contact.phone_nos:
contact_lead = contact.get_link_for("Lead")
if contact_lead:
phone = mobile_no = contact.phone_nos[0].phone
if len(contact.phone_nos) > 1:
# get the default phone number
primary_phones = [phone_doc.phone for phone_doc in contact.phone_nos if phone_doc.is_primary_phone]
if primary_phones:
phone = primary_phones[0]
# get the default mobile number
primary_mobile_nos = [phone_doc.phone for phone_doc in contact.phone_nos if phone_doc.is_primary_mobile_no]
if primary_mobile_nos:
mobile_no = primary_mobile_nos[0]
lead = frappe.get_doc("Lead", contact_lead)
lead.phone = phone
lead.mobile_no = mobile_no
lead.save()

View File

@ -112,6 +112,8 @@ frappe.ui.form.on("Fees", {
args: { args: {
"dt": frm.doc.doctype, "dt": frm.doc.doctype,
"dn": frm.doc.name, "dn": frm.doc.name,
"party_type": "Student",
"party": frm.doc.student,
"recipient_id": frm.doc.student_email "recipient_id": frm.doc.student_email
}, },
callback: function(r) { callback: function(r) {

View File

@ -10,7 +10,7 @@ from frappe.utils import money_in_words
from erpnext.accounts.doctype.payment_request.payment_request import make_payment_request from erpnext.accounts.doctype.payment_request.payment_request import make_payment_request
from frappe.utils.csvutils import getlink from frappe.utils.csvutils import getlink
from erpnext.controllers.accounts_controller import AccountsController from erpnext.controllers.accounts_controller import AccountsController
from erpnext.accounts.general_ledger import delete_gl_entries from erpnext.accounts.general_ledger import make_reverse_gl_entries
class Fees(AccountsController): class Fees(AccountsController):
@ -75,12 +75,14 @@ class Fees(AccountsController):
self.make_gl_entries() self.make_gl_entries()
if self.send_payment_request and self.student_email: if self.send_payment_request and self.student_email:
pr = make_payment_request(dt="Fees", dn=self.name, recipient_id=self.student_email, pr = make_payment_request(party_type="Student", party=self.student, dt="Fees",
dn=self.name, recipient_id=self.student_email,
submit_doc=True, use_dummy_message=True) submit_doc=True, use_dummy_message=True)
frappe.msgprint(_("Payment request {0} created").format(getlink("Payment Request", pr.name))) frappe.msgprint(_("Payment request {0} created").format(getlink("Payment Request", pr.name)))
def on_cancel(self): def on_cancel(self):
delete_gl_entries(voucher_type=self.doctype, voucher_no=self.name) self.ignore_linked_doctypes = ('GL Entry', 'Stock Ledger Entry')
make_reverse_gl_entries(voucher_type=self.doctype, voucher_no=self.name, cancel=True)
# frappe.db.set(self, 'status', 'Cancelled') # frappe.db.set(self, 'status', 'Cancelled')

View File

@ -6,6 +6,9 @@ def get_data():
'heatmap': True, 'heatmap': True,
'heatmap_message': _('This is based on the attendance of this Student'), 'heatmap_message': _('This is based on the attendance of this Student'),
'fieldname': 'student', 'fieldname': 'student',
'non_standard_fieldnames': {
'Bank Account': 'party'
},
'transactions': [ 'transactions': [
{ {
'label': _('Admission'), 'label': _('Admission'),
@ -29,7 +32,7 @@ def get_data():
}, },
{ {
'label': _('Fee'), 'label': _('Fee'),
'items': ['Fees'] 'items': ['Fees', 'Bank Account']
} }
] ]
} }

View File

@ -80,6 +80,7 @@ frappe.ui.form.on('Clinical Procedure', {
frappe.call({ frappe.call({
method: 'complete_procedure', method: 'complete_procedure',
doc: frm.doc, doc: frm.doc,
freeze: true,
callback: function(r) { callback: function(r) {
if (r.message) { if (r.message) {
frappe.show_alert({ frappe.show_alert({
@ -87,8 +88,8 @@ frappe.ui.form.on('Clinical Procedure', {
['<a class="bold" href="#Form/Stock Entry/'+ r.message + '">' + r.message + '</a>']), ['<a class="bold" href="#Form/Stock Entry/'+ r.message + '">' + r.message + '</a>']),
indicator: 'green' indicator: 'green'
}); });
frm.reload_doc();
} }
frm.reload_doc();
} }
}); });
} }
@ -111,9 +112,10 @@ frappe.ui.form.on('Clinical Procedure', {
frappe.call({ frappe.call({
doc: frm.doc, doc: frm.doc,
method: 'make_material_receipt', method: 'make_material_receipt',
freeze: true,
callback: function(r) { callback: function(r) {
if (!r.exc) { if (!r.exc) {
cur_frm.reload_doc(); frm.reload_doc();
let doclist = frappe.model.sync(r.message); let doclist = frappe.model.sync(r.message);
frappe.set_route('Form', doclist[0].doctype, doclist[0].name); frappe.set_route('Form', doclist[0].doctype, doclist[0].name);
} }
@ -122,7 +124,7 @@ frappe.ui.form.on('Clinical Procedure', {
} }
); );
} else { } else {
cur_frm.reload_doc(); frm.reload_doc();
} }
} }
} }

View File

@ -87,7 +87,8 @@ class ClinicalProcedure(Document):
else: else:
frappe.throw(_('Please set Customer in Patient {0}').format(frappe.bold(self.patient)), title=_('Customer Not Found')) frappe.throw(_('Please set Customer in Patient {0}').format(frappe.bold(self.patient)), title=_('Customer Not Found'))
frappe.db.set_value('Clinical Procedure', self.name, 'status', 'Completed') self.db_set('status', 'Completed')
if self.consume_stock and self.items: if self.consume_stock and self.items:
return stock_entry return stock_entry
@ -245,9 +246,9 @@ def make_procedure(source_name, target_doc=None):
def insert_clinical_procedure_to_medical_record(doc): def insert_clinical_procedure_to_medical_record(doc):
subject = cstr(doc.procedure_template) subject = frappe.bold(_("Clinical Procedure conducted: ")) + cstr(doc.procedure_template) + "<br>"
if doc.practitioner: if doc.practitioner:
subject += ' ' + doc.practitioner subject += frappe.bold(_('Healthcare Practitioner: ')) + doc.practitioner
if subject and doc.notes: if subject and doc.notes:
subject += '<br/>' + doc.notes subject += '<br/>' + doc.notes

View File

@ -24,6 +24,8 @@ erpnext.ExerciseEditor = Class.extend({
this.exercise_cards = $('<div class="exercise-cards"></div>').appendTo(this.wrapper); this.exercise_cards = $('<div class="exercise-cards"></div>').appendTo(this.wrapper);
this.row = $('<div class="exercise-row"></div>').appendTo(this.wrapper);
let me = this; let me = this;
this.exercise_toolbar.find(".btn-add") this.exercise_toolbar.find(".btn-add")
@ -32,7 +34,7 @@ erpnext.ExerciseEditor = Class.extend({
me.show_add_card_dialog(frm); me.show_add_card_dialog(frm);
}); });
if (frm.doc.steps_table.length > 0) { if (frm.doc.steps_table && frm.doc.steps_table.length > 0) {
this.make_cards(frm); this.make_cards(frm);
this.make_buttons(frm); this.make_buttons(frm);
} }
@ -41,7 +43,6 @@ erpnext.ExerciseEditor = Class.extend({
make_cards: function(frm) { make_cards: function(frm) {
var me = this; var me = this;
$(me.exercise_cards).empty(); $(me.exercise_cards).empty();
this.row = $('<div class="exercise-row"></div>').appendTo(me.exercise_cards);
$.each(frm.doc.steps_table, function(i, step) { $.each(frm.doc.steps_table, function(i, step) {
$(repl(` $(repl(`
@ -78,6 +79,7 @@ erpnext.ExerciseEditor = Class.extend({
frm.doc.steps_table.pop(id); frm.doc.steps_table.pop(id);
frm.refresh_field('steps_table'); frm.refresh_field('steps_table');
$('#col-'+id).remove(); $('#col-'+id).remove();
frm.dirty();
}, 300); }, 300);
}); });
}, },
@ -106,7 +108,10 @@ erpnext.ExerciseEditor = Class.extend({
], ],
primary_action: function() { primary_action: function() {
let data = d.get_values(); let data = d.get_values();
let i = frm.doc.steps_table.length; let i = 0;
if (frm.doc.steps_table) {
i = frm.doc.steps_table.length;
}
$(repl(` $(repl(`
<div class="exercise-col col-sm-4" id="%(col_id)s"> <div class="exercise-col col-sm-4" id="%(col_id)s">
<div class="card h-100 exercise-card" id="%(card_id)s"> <div class="card h-100 exercise-card" id="%(card_id)s">
@ -165,9 +170,10 @@ erpnext.ExerciseEditor = Class.extend({
frm.doc.steps_table[id].image = data.image; frm.doc.steps_table[id].image = data.image;
frm.doc.steps_table[id].description = data.step_description; frm.doc.steps_table[id].description = data.step_description;
refresh_field('steps_table'); refresh_field('steps_table');
frm.dirty();
new_dialog.hide(); new_dialog.hide();
}, },
primary_action_label: __("Save"), primary_action_label: __("Edit"),
}); });
new_dialog.set_values({ new_dialog.set_values({

View File

@ -288,23 +288,23 @@ def insert_lab_test_to_medical_record(doc):
table_row = False table_row = False
subject = cstr(doc.lab_test_name) subject = cstr(doc.lab_test_name)
if doc.practitioner: if doc.practitioner:
subject += " "+ doc.practitioner subject += frappe.bold(_("Healthcare Practitioner: "))+ doc.practitioner + "<br>"
if doc.normal_test_items: if doc.normal_test_items:
item = doc.normal_test_items[0] item = doc.normal_test_items[0]
comment = "" comment = ""
if item.lab_test_comment: if item.lab_test_comment:
comment = str(item.lab_test_comment) comment = str(item.lab_test_comment)
table_row = item.lab_test_name table_row = frappe.bold(_("Lab Test Conducted: ")) + item.lab_test_name
if item.lab_test_event: if item.lab_test_event:
table_row += " " + item.lab_test_event table_row += frappe.bold(_("Lab Test Event: ")) + item.lab_test_event
if item.result_value: if item.result_value:
table_row += " " + item.result_value table_row += " " + frappe.bold(_("Lab Test Result: ")) + item.result_value
if item.normal_range: if item.normal_range:
table_row += " normal_range("+item.normal_range+")" table_row += " " + _("Normal Range:") + item.normal_range
table_row += " "+comment table_row += " " + comment
elif doc.special_test_items: elif doc.special_test_items:
item = doc.special_test_items[0] item = doc.special_test_items[0]
@ -316,12 +316,12 @@ def insert_lab_test_to_medical_record(doc):
item = doc.sensitivity_test_items[0] item = doc.sensitivity_test_items[0]
if item.antibiotic and item.antibiotic_sensitivity: if item.antibiotic and item.antibiotic_sensitivity:
table_row = item.antibiotic +" "+ item.antibiotic_sensitivity table_row = item.antibiotic + " " + item.antibiotic_sensitivity
if table_row: if table_row:
subject += "<br/>"+table_row subject += "<br>" + table_row
if doc.lab_test_comment: if doc.lab_test_comment:
subject += "<br/>"+ cstr(doc.lab_test_comment) subject += "<br>" + cstr(doc.lab_test_comment)
medical_record = frappe.new_doc("Patient Medical Record") medical_record = frappe.new_doc("Patient Medical Record")
medical_record.patient = doc.patient medical_record.patient = doc.patient

View File

@ -18,6 +18,9 @@ class PatientEncounter(Document):
def after_insert(self): def after_insert(self):
insert_encounter_to_medical_record(self) insert_encounter_to_medical_record(self)
def on_submit(self):
update_encounter_medical_record(self)
def on_cancel(self): def on_cancel(self):
if self.appointment: if self.appointment:
frappe.db.set_value('Patient Appointment', self.appointment, 'status', 'Open') frappe.db.set_value('Patient Appointment', self.appointment, 'status', 'Open')
@ -66,22 +69,26 @@ def delete_medical_record(encounter):
frappe.db.delete_doc_if_exists('Patient Medical Record', 'reference_name', encounter.name) frappe.db.delete_doc_if_exists('Patient Medical Record', 'reference_name', encounter.name)
def set_subject_field(encounter): def set_subject_field(encounter):
subject = encounter.practitioner + '\n' subject = frappe.bold(_('Healthcare Practitioner: ')) + encounter.practitioner + '<br>'
if encounter.symptoms: if encounter.symptoms:
subject += _('Symptoms: ') + cstr(encounter.symptoms) + '\n' subject += frappe.bold(_('Symptoms: ')) + '<br>'
for entry in encounter.symptoms:
subject += cstr(entry.complaint) + '<br>'
else: else:
subject += _('No Symptoms') + '\n' subject += frappe.bold(_('No Symptoms')) + '<br>'
if encounter.diagnosis: if encounter.diagnosis:
subject += _('Diagnosis: ') + cstr(encounter.diagnosis) + '\n' subject += frappe.bold(_('Diagnosis: ')) + '<br>'
for entry in encounter.diagnosis:
subject += cstr(entry.diagnosis) + '<br>'
else: else:
subject += _('No Diagnosis') + '\n' subject += frappe.bold(_('No Diagnosis')) + '<br>'
if encounter.drug_prescription: if encounter.drug_prescription:
subject += '\n' + _('Drug(s) Prescribed.') subject += '<br>' + _('Drug(s) Prescribed.')
if encounter.lab_test_prescription: if encounter.lab_test_prescription:
subject += '\n' + _('Test(s) Prescribed.') subject += '<br>' + _('Test(s) Prescribed.')
if encounter.procedure_prescription: if encounter.procedure_prescription:
subject += '\n' + _('Procedure(s) Prescribed.') subject += '<br>' + _('Procedure(s) Prescribed.')
return subject return subject

View File

@ -57,7 +57,7 @@
}, },
{ {
"fieldname": "subject", "fieldname": "subject",
"fieldtype": "Small Text", "fieldtype": "Text Editor",
"ignore_xss_filter": 1, "ignore_xss_filter": 1,
"label": "Subject" "label": "Subject"
}, },
@ -125,7 +125,7 @@
], ],
"in_create": 1, "in_create": 1,
"links": [], "links": [],
"modified": "2020-03-23 19:26:59.308383", "modified": "2020-04-29 12:26:57.679402",
"modified_by": "Administrator", "modified_by": "Administrator",
"module": "Healthcare", "module": "Healthcare",
"name": "Patient Medical Record", "name": "Patient Medical Record",

View File

@ -21,8 +21,14 @@ class TherapyPlan(Document):
self.status = 'Completed' self.status = 'Completed'
def set_totals(self): def set_totals(self):
total_sessions = sum([int(d.no_of_sessions) for d in self.get('therapy_plan_details')]) total_sessions = 0
total_sessions_completed = sum([int(d.sessions_completed) for d in self.get('therapy_plan_details')]) total_sessions_completed = 0
for entry in self.therapy_plan_details:
if entry.no_of_sessions:
total_sessions += entry.no_of_sessions
if entry.sessions_completed:
total_sessions_completed += entry.sessions_completed
self.db_set('total_sessions', total_sessions) self.db_set('total_sessions', total_sessions)
self.db_set('total_sessions_completed', total_sessions_completed) self.db_set('total_sessions_completed', total_sessions_completed)

View File

@ -13,23 +13,92 @@ frappe.ui.form.on('Therapy Session', {
refresh: function(frm) { refresh: function(frm) {
if (!frm.doc.__islocal) { if (!frm.doc.__islocal) {
let target = 0; frm.dashboard.add_indicator(__('Counts Targeted: {0}', [frm.doc.total_counts_targeted]), 'blue');
let completed = 0; frm.dashboard.add_indicator(__('Counts Completed: {0}', [frm.doc.total_counts_completed]),
$.each(frm.doc.exercises, function(_i, e) { (frm.doc.total_counts_completed < frm.doc.total_counts_targeted) ? 'orange' : 'green');
target += e.counts_target;
completed += e.counts_completed;
});
frm.dashboard.add_indicator(__('Counts Targetted: {0}', [target]), 'blue');
frm.dashboard.add_indicator(__('Counts Completed: {0}', [completed]), (completed < target) ? 'orange' : 'green');
} }
if (frm.doc.docstatus === 1) { if (frm.doc.docstatus === 1) {
frm.add_custom_button(__('Patient Assessment'),function() { frm.add_custom_button(__('Patient Assessment'), function() {
frappe.model.open_mapped_doc({ frappe.model.open_mapped_doc({
method: 'erpnext.healthcare.doctype.patient_assessment.patient_assessment.create_patient_assessment', method: 'erpnext.healthcare.doctype.patient_assessment.patient_assessment.create_patient_assessment',
frm: frm, frm: frm,
}) })
}, 'Create'); }, 'Create');
frm.add_custom_button(__('Sales Invoice'), function() {
frappe.model.open_mapped_doc({
method: 'erpnext.healthcare.doctype.therapy_session.therapy_session.invoice_therapy_session',
frm: frm,
})
}, 'Create');
}
},
patient: function(frm) {
if (frm.doc.patient) {
frappe.call({
'method': 'erpnext.healthcare.doctype.patient.patient.get_patient_detail',
args: {
patient: frm.doc.patient
},
callback: function (data) {
let age = '';
if (data.message.dob) {
age = calculate_age(data.message.dob);
} else if (data.message.age) {
age = data.message.age;
if (data.message.age_as_on) {
age = __('{0} as on {1}', [age, data.message.age_as_on]);
}
}
frm.set_value('patient_age', age);
frm.set_value('gender', data.message.sex);
frm.set_value('patient_name', data.message.patient_name);
}
});
} else {
frm.set_value('patient_age', '');
frm.set_value('gender', '');
frm.set_value('patient_name', '');
}
},
appointment: function(frm) {
if (frm.doc.appointment) {
frappe.call({
'method': 'frappe.client.get',
args: {
doctype: 'Patient Appointment',
name: frm.doc.appointment
},
callback: function(data) {
let values = {
'patient':data.message.patient,
'therapy_type': data.message.therapy_type,
'therapy_plan': data.message.therapy_plan,
'practitioner': data.message.practitioner,
'department': data.message.department,
'start_date': data.message.appointment_date,
'start_time': data.message.appointment_time,
'service_unit': data.message.service_unit,
'company': data.message.company
};
frm.set_value(values);
}
});
} else {
let values = {
'patient': '',
'therapy_type': '',
'therapy_plan': '',
'practitioner': '',
'department': '',
'start_date': '',
'start_time': '',
'service_unit': '',
};
frm.set_value(values);
} }
}, },
@ -44,6 +113,8 @@ frappe.ui.form.on('Therapy Session', {
callback: function(data) { callback: function(data) {
frm.set_value('duration', data.message.default_duration); frm.set_value('duration', data.message.default_duration);
frm.set_value('rate', data.message.rate); frm.set_value('rate', data.message.rate);
frm.set_value('service_unit', data.message.healthcare_service_unit);
frm.set_value('department', data.message.medical_department);
frm.doc.exercises = []; frm.doc.exercises = [];
$.each(data.message.exercises, function(_i, e) { $.each(data.message.exercises, function(_i, e) {
let exercise = frm.add_child('exercises'); let exercise = frm.add_child('exercises');

View File

@ -9,9 +9,11 @@
"naming_series", "naming_series",
"appointment", "appointment",
"patient", "patient",
"patient_name",
"patient_age", "patient_age",
"gender", "gender",
"column_break_5", "column_break_5",
"company",
"therapy_plan", "therapy_plan",
"therapy_type", "therapy_type",
"practitioner", "practitioner",
@ -20,7 +22,6 @@
"duration", "duration",
"rate", "rate",
"location", "location",
"company",
"column_break_12", "column_break_12",
"service_unit", "service_unit",
"start_date", "start_date",
@ -28,6 +29,10 @@
"invoiced", "invoiced",
"exercises_section", "exercises_section",
"exercises", "exercises",
"section_break_23",
"total_counts_targeted",
"column_break_25",
"total_counts_completed",
"amended_from" "amended_from"
], ],
"fields": [ "fields": [
@ -159,7 +164,8 @@
"fieldname": "company", "fieldname": "company",
"fieldtype": "Link", "fieldtype": "Link",
"label": "Company", "label": "Company",
"options": "Company" "options": "Company",
"reqd": 1
}, },
{ {
"default": "0", "default": "0",
@ -173,11 +179,38 @@
"fieldtype": "Data", "fieldtype": "Data",
"label": "Patient Age", "label": "Patient Age",
"read_only": 1 "read_only": 1
},
{
"fieldname": "total_counts_targeted",
"fieldtype": "Int",
"label": "Total Counts Targeted",
"read_only": 1
},
{
"fieldname": "total_counts_completed",
"fieldtype": "Int",
"label": "Total Counts Completed",
"read_only": 1
},
{
"fieldname": "section_break_23",
"fieldtype": "Section Break"
},
{
"fieldname": "column_break_25",
"fieldtype": "Column Break"
},
{
"fetch_from": "patient.patient_name",
"fieldname": "patient_name",
"fieldtype": "Data",
"label": "Patient Name",
"read_only": 1
} }
], ],
"is_submittable": 1, "is_submittable": 1,
"links": [], "links": [],
"modified": "2020-04-21 13:16:46.378798", "modified": "2020-04-29 16:49:16.286006",
"modified_by": "Administrator", "modified_by": "Administrator",
"module": "Healthcare", "module": "Healthcare",
"name": "Therapy Session", "name": "Therapy Session",

View File

@ -6,10 +6,17 @@ from __future__ import unicode_literals
import frappe import frappe
from frappe.model.document import Document from frappe.model.document import Document
from frappe.model.mapper import get_mapped_doc from frappe.model.mapper import get_mapped_doc
from frappe import _
from frappe.utils import cstr, getdate
from erpnext.healthcare.doctype.healthcare_settings.healthcare_settings import get_receivable_account, get_income_account
class TherapySession(Document): class TherapySession(Document):
def validate(self):
self.set_total_counts()
def on_submit(self): def on_submit(self):
self.update_sessions_count_in_therapy_plan() self.update_sessions_count_in_therapy_plan()
insert_session_medical_record(self)
def on_cancel(self): def on_cancel(self):
self.update_sessions_count_in_therapy_plan(on_cancel=True) self.update_sessions_count_in_therapy_plan(on_cancel=True)
@ -24,6 +31,18 @@ class TherapySession(Document):
entry.sessions_completed += 1 entry.sessions_completed += 1
therapy_plan.save() therapy_plan.save()
def set_total_counts(self):
target_total = 0
counts_completed = 0
for entry in self.exercises:
if entry.counts_target:
target_total += entry.counts_target
if entry.counts_completed:
counts_completed += entry.counts_completed
self.db_set('total_counts_targeted', target_total)
self.db_set('total_counts_completed', counts_completed)
@frappe.whitelist() @frappe.whitelist()
def create_therapy_session(source_name, target_doc=None): def create_therapy_session(source_name, target_doc=None):
@ -53,3 +72,61 @@ def create_therapy_session(source_name, target_doc=None):
}, target_doc, set_missing_values) }, target_doc, set_missing_values)
return doc return doc
@frappe.whitelist()
def invoice_therapy_session(source_name, target_doc=None):
def set_missing_values(source, target):
target.customer = frappe.db.get_value('Patient', source.patient, 'customer')
target.due_date = getdate()
target.debit_to = get_receivable_account(source.company)
item = target.append('items', {})
item = get_therapy_item(source, item)
target.set_missing_values(for_validate=True)
doc = get_mapped_doc('Therapy Session', source_name, {
'Therapy Session': {
'doctype': 'Sales Invoice',
'field_map': [
['patient', 'patient'],
['referring_practitioner', 'practitioner'],
['company', 'company'],
['due_date', 'start_date']
]
}
}, target_doc, set_missing_values)
return doc
def get_therapy_item(therapy, item):
item.item_code = frappe.db.get_value('Therapy Type', therapy.therapy_type, 'item')
item.description = _('Therapy Session Charges: {0}').format(therapy.practitioner)
item.income_account = get_income_account(therapy.practitioner, therapy.company)
item.cost_center = frappe.get_cached_value('Company', therapy.company, 'cost_center')
item.rate = therapy.rate
item.amount = therapy.rate
item.qty = 1
item.reference_dt = 'Therapy Session'
item.reference_dn = therapy.name
return item
def insert_session_medical_record(doc):
subject = frappe.bold(_('Therapy: ')) + cstr(doc.therapy_type) + '<br>'
if doc.therapy_plan:
subject += frappe.bold(_('Therapy Plan: ')) + cstr(doc.therapy_plan) + '<br>'
if doc.practitioner:
subject += frappe.bold(_('Healthcare Practitioner: ')) + doc.practitioner
subject += frappe.bold(_('Total Counts Targeted: ')) + cstr(doc.total_counts_targeted) + '<br>'
subject += frappe.bold(_('Total Counts Completed: ')) + cstr(doc.total_counts_completed) + '<br>'
medical_record = frappe.new_doc('Patient Medical Record')
medical_record.patient = doc.patient
medical_record.subject = subject
medical_record.status = 'Open'
medical_record.communication_date = doc.start_date
medical_record.reference_doctype = 'Therapy Session'
medical_record.reference_name = doc.name
medical_record.reference_owner = doc.owner
medical_record.save(ignore_permissions=True)

View File

@ -35,17 +35,17 @@ def delete_vital_signs_from_medical_record(doc):
def set_subject_field(doc): def set_subject_field(doc):
subject = '' subject = ''
if(doc.temperature): if doc.temperature:
subject += _('Temperature: ') + '\n'+ cstr(doc.temperature) + '. ' subject += frappe.bold(_('Temperature: ')) + cstr(doc.temperature) + '<br>'
if(doc.pulse): if doc.pulse:
subject += _('Pulse: ') + '\n' + cstr(doc.pulse) + '. ' subject += frappe.bold(_('Pulse: ')) + cstr(doc.pulse) + '<br>'
if(doc.respiratory_rate): if doc.respiratory_rate:
subject += _('Respiratory Rate: ') + '\n' + cstr(doc.respiratory_rate) + '. ' subject += frappe.bold(_('Respiratory Rate: ')) + cstr(doc.respiratory_rate) + '<br>'
if(doc.bp): if doc.bp:
subject += _('BP: ') + '\n' + cstr(doc.bp) + '. ' subject += frappe.bold(_('BP: ')) + cstr(doc.bp) + '<br>'
if(doc.bmi): if doc.bmi:
subject += _('BMI: ') + '\n' + cstr(doc.bmi) + '. ' subject += frappe.bold(_('BMI: ')) + cstr(doc.bmi) + '<br>'
if(doc.nutrition_note): if doc.nutrition_note:
subject += _('Note: ') + '\n' + cstr(doc.nutrition_note) + '. ' subject += frappe.bold(_('Note: ')) + cstr(doc.nutrition_note) + '<br>'
return subject return subject

View File

@ -43,7 +43,7 @@ def validate_customer_created(patient):
def get_fee_validity(patient_appointments): def get_fee_validity(patient_appointments):
if not frappe.db.get_single_value('Healthcare Settings', 'enable_free_follow_ups'): if not frappe.db.get_single_value('Healthcare Settings', 'enable_free_follow_ups'):
return return []
items_to_invoice = [] items_to_invoice = []
for appointment in patient_appointments: for appointment in patient_appointments:
@ -110,7 +110,7 @@ def get_lab_tests_to_invoice(patient):
filters={'patient': patient.name, 'invoiced': False, 'docstatus': 1} filters={'patient': patient.name, 'invoiced': False, 'docstatus': 1}
) )
for lab_test in lab_tests: 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: if is_billable:
lab_tests_to_invoice.append({ lab_tests_to_invoice.append({
'reference_type': 'Lab Test', 'reference_type': 'Lab Test',

View File

@ -250,7 +250,8 @@ doc_events = {
}, },
"Contact": { "Contact": {
"on_trash": "erpnext.support.doctype.issue.issue.update_issue", "on_trash": "erpnext.support.doctype.issue.issue.update_issue",
"after_insert": "erpnext.communication.doctype.call_log.call_log.set_caller_information" "after_insert": "erpnext.communication.doctype.call_log.call_log.set_caller_information",
"validate": "erpnext.crm.utils.update_lead_phone_numbers"
}, },
"Lead": { "Lead": {
"after_insert": "erpnext.communication.doctype.call_log.call_log.set_caller_information" "after_insert": "erpnext.communication.doctype.call_log.call_log.set_caller_information"

View File

@ -23,7 +23,7 @@
{ {
"hidden": 0, "hidden": 0,
"label": "Payroll", "label": "Payroll",
"links": "[\n {\n \"label\": \"Salary Structure\",\n \"name\": \"Salary Structure\",\n \"onboard\": 1,\n \"type\": \"doctype\"\n },\n {\n \"dependencies\": [\n \"Salary Structure\",\n \"Employee\"\n ],\n \"label\": \"Salary Structure Assignment\",\n \"name\": \"Salary Structure Assignment\",\n \"onboard\": 1,\n \"type\": \"doctype\"\n },\n {\n \"label\": \"Payroll Entry\",\n \"name\": \"Payroll Entry\",\n \"onboard\": 1,\n \"type\": \"doctype\"\n },\n {\n \"label\": \"Salary Slip\",\n \"name\": \"Salary Slip\",\n \"onboard\": 1,\n \"type\": \"doctype\"\n },\n {\n \"label\": \"Payroll Period\",\n \"name\": \"Payroll Period\",\n \"type\": \"doctype\"\n },\n {\n \"label\": \"Salary Component\",\n \"name\": \"Salary Component\",\n \"type\": \"doctype\"\n },\n {\n \"label\": \"Additional Salary\",\n \"name\": \"Additional Salary\",\n \"type\": \"doctype\"\n },\n {\n \"dependencies\": [\n \"Employee\"\n ],\n \"label\": \"Retention Bonus\",\n \"name\": \"Retention Bonus\",\n \"type\": \"doctype\"\n },\n {\n \"dependencies\": [\n \"Employee\"\n ],\n \"label\": \"Employee Incentive\",\n \"name\": \"Employee Incentive\",\n \"type\": \"doctype\"\n },\n {\n \"dependencies\": [\n \"Salary Slip\"\n ],\n \"doctype\": \"Salary Slip\",\n \"is_query_report\": true,\n \"label\": \"Salary Register\",\n \"name\": \"Salary Register\",\n \"type\": \"report\"\n }\n]" "links": "[\n {\n \"label\": \"Salary Structure\",\n \"name\": \"Salary Structure\",\n \"onboard\": 1,\n \"type\": \"doctype\"\n },\n {\n \"dependencies\": [\n \"Salary Structure\",\n \"Employee\"\n ],\n \"label\": \"Salary Structure Assignment\",\n \"name\": \"Salary Structure Assignment\",\n \"onboard\": 1,\n \"type\": \"doctype\"\n },\n {\n \"label\": \"Payroll Entry\",\n \"name\": \"Payroll Entry\",\n \"onboard\": 1,\n \"type\": \"doctype\"\n },\n {\n \"label\": \"Salary Slip\",\n \"name\": \"Salary Slip\",\n \"onboard\": 1,\n \"type\": \"doctype\"\n },\n {\n \"label\": \"Payroll Period\",\n \"name\": \"Payroll Period\",\n \"type\": \"doctype\"\n },\n {\n \"label\": \"Income Tax Slab\",\n \"name\": \"Income Tax Slab\",\n \"type\": \"doctype\"\n },\n {\n \"label\": \"Salary Component\",\n \"name\": \"Salary Component\",\n \"type\": \"doctype\"\n },\n {\n \"label\": \"Additional Salary\",\n \"name\": \"Additional Salary\",\n \"type\": \"doctype\"\n },\n {\n \"dependencies\": [\n \"Employee\"\n ],\n \"label\": \"Retention Bonus\",\n \"name\": \"Retention Bonus\",\n \"type\": \"doctype\"\n },\n {\n \"dependencies\": [\n \"Employee\"\n ],\n \"label\": \"Employee Incentive\",\n \"name\": \"Employee Incentive\",\n \"type\": \"doctype\"\n },\n {\n \"dependencies\": [\n \"Salary Slip\"\n ],\n \"doctype\": \"Salary Slip\",\n \"is_query_report\": true,\n \"label\": \"Salary Register\",\n \"name\": \"Salary Register\",\n \"type\": \"report\"\n }\n]"
}, },
{ {
"hidden": 0, "hidden": 0,
@ -73,7 +73,7 @@
{ {
"hidden": 0, "hidden": 0,
"label": "Employee Tax and Benefits", "label": "Employee Tax and Benefits",
"links": "[\n {\n \"dependencies\": [\n \"Employee\"\n ],\n \"label\": \"Employee Tax Exemption Declaration\",\n \"name\": \"Employee Tax Exemption Declaration\",\n \"type\": \"doctype\"\n },\n {\n \"dependencies\": [\n \"Employee\"\n ],\n \"label\": \"Employee Tax Exemption Proof Submission\",\n \"name\": \"Employee Tax Exemption Proof Submission\",\n \"type\": \"doctype\"\n },\n {\n \"dependencies\": [\n \"Employee\"\n ],\n \"label\": \"Employee Benefit Application\",\n \"name\": \"Employee Benefit Application\",\n \"type\": \"doctype\"\n },\n {\n \"dependencies\": [\n \"Employee\"\n ],\n \"label\": \"Employee Benefit Claim\",\n \"name\": \"Employee Benefit Claim\",\n \"type\": \"doctype\"\n },\n {\n \"dependencies\": [\n \"Employee\"\n ],\n \"label\": \"Employee Tax Exemption Category\",\n \"name\": \"Employee Tax Exemption Category\",\n \"type\": \"doctype\"\n },\n {\n \"dependencies\": [\n \"Employee\"\n ],\n \"label\": \"Employee Tax Exemption Sub Category\",\n \"name\": \"Employee Tax Exemption Sub Category\",\n \"type\": \"doctype\"\n }\n]" "links": "[\n {\n \"dependencies\": [\n \"Employee\"\n ],\n \"label\": \"Employee Tax Exemption Declaration\",\n \"name\": \"Employee Tax Exemption Declaration\",\n \"type\": \"doctype\"\n },\n {\n \"dependencies\": [\n \"Employee\"\n ],\n \"label\": \"Employee Tax Exemption Proof Submission\",\n \"name\": \"Employee Tax Exemption Proof Submission\",\n \"type\": \"doctype\"\n },\n {\n \"dependencies\": [\n \"Employee\",\n \"Payroll Period\"\n ],\n \"label\": \"Employee Other Income\",\n \"name\": \"Employee Other Income\",\n \"type\": \"doctype\"\n },\n {\n \"dependencies\": [\n \"Employee\"\n ],\n \"label\": \"Employee Benefit Application\",\n \"name\": \"Employee Benefit Application\",\n \"type\": \"doctype\"\n },\n {\n \"dependencies\": [\n \"Employee\"\n ],\n \"label\": \"Employee Benefit Claim\",\n \"name\": \"Employee Benefit Claim\",\n \"type\": \"doctype\"\n },\n {\n \"dependencies\": [\n \"Employee\"\n ],\n \"label\": \"Employee Tax Exemption Category\",\n \"name\": \"Employee Tax Exemption Category\",\n \"type\": \"doctype\"\n },\n {\n \"dependencies\": [\n \"Employee\"\n ],\n \"label\": \"Employee Tax Exemption Sub Category\",\n \"name\": \"Employee Tax Exemption Sub Category\",\n \"type\": \"doctype\"\n }\n]"
} }
], ],
"category": "Modules", "category": "Modules",
@ -88,7 +88,7 @@
"idx": 0, "idx": 0,
"is_standard": 1, "is_standard": 1,
"label": "HR", "label": "HR",
"modified": "2020-04-01 11:28:50.860012", "modified": "2020-04-29 20:29:22.114309",
"modified_by": "Administrator", "modified_by": "Administrator",
"module": "HR", "module": "HR",
"name": "HR", "name": "HR",

View File

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

View File

@ -7,33 +7,15 @@ import frappe
from frappe.utils import getdate, nowdate from frappe.utils import getdate, nowdate
from frappe import _ from frappe import _
from frappe.model.document import Document from frappe.model.document import Document
from frappe.utils import cstr, get_datetime, get_datetime_str from frappe.utils import cstr, get_datetime, formatdate
from frappe.utils import update_progress_bar
class Attendance(Document): class Attendance(Document):
def validate_duplicate_record(self): def validate(self):
res = frappe.db.sql("""select name from `tabAttendance` where employee = %s and attendance_date = %s from erpnext.controllers.status_updater import validate_status
and name != %s and docstatus != 2""", validate_status(self.status, ["Present", "Absent", "On Leave", "Half Day", "Work From Home"])
(self.employee, getdate(self.attendance_date), self.name)) self.validate_attendance_date()
if res: self.validate_duplicate_record()
frappe.throw(_("Attendance for employee {0} is already marked").format(self.employee)) self.check_leave_record()
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_attendance_date(self): def validate_attendance_date(self):
date_of_joining = frappe.db.get_value("Employee", self.employee, "date_of_joining") 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): 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")) 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): def validate_employee(self):
emp = frappe.db.sql("select name from `tabEmployee` where name = %s and status = 'Active'", emp = frappe.db.sql("select name from `tabEmployee` where name = %s and status = 'Active'",
self.employee) self.employee)
if not emp: if not emp:
frappe.throw(_("Employee {0} is not active or does not exist").format(self.employee)) 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() @frappe.whitelist()
def get_events(start, end, filters=None): def get_events(start, end, filters=None):
events = [] events = []
@ -90,18 +105,20 @@ def add_attendance(events, start, end, conditions=None):
if e not in events: if e not in events:
events.append(e) events.append(e)
def mark_attendance(employee, attendance_date, status, shift=None): def mark_attendance(employee, attendance_date, status, shift=None, leave_type=None, ignore_validate=False):
employee_doc = frappe.get_doc('Employee', employee)
if not frappe.db.exists('Attendance', {'employee':employee, 'attendance_date':attendance_date, 'docstatus':('!=', '2')}): 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', 'doctype': 'Attendance',
'employee': employee, 'employee': employee,
'attendance_date': attendance_date, 'attendance_date': attendance_date,
'status': status, 'status': status,
'company': employee_doc.company, 'company': company,
'shift': shift 'shift': shift,
} 'leave_type': leave_type
attendance = frappe.get_doc(doc_dict).insert() })
attendance.flags.ignore_validate = ignore_validate
attendance.insert()
attendance.submit() attendance.submit()
return attendance.name return attendance.name

View File

@ -6,6 +6,9 @@ def get_data():
'heatmap': True, 'heatmap': True,
'heatmap_message': _('This is based on the attendance of this Employee'), 'heatmap_message': _('This is based on the attendance of this Employee'),
'fieldname': 'employee', 'fieldname': 'employee',
'non_standard_fieldnames': {
'Bank Account': 'party'
},
'transactions': [ 'transactions': [
{ {
'label': _('Leave and Attendance'), 'label': _('Leave and Attendance'),
@ -33,7 +36,7 @@ def get_data():
}, },
{ {
'label': _('Payroll'), 'label': _('Payroll'),
'items': ['Salary Structure Assignment', 'Salary Slip', 'Additional Salary', 'Timesheet','Employee Incentive', 'Retention Bonus'] 'items': ['Salary Structure Assignment', 'Salary Slip', 'Additional Salary', 'Timesheet','Employee Incentive', 'Retention Bonus', 'Bank Account']
}, },
{ {
'label': _('Training'), 'label': _('Training'),

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

View File

@ -8,31 +8,17 @@ from frappe.model.document import Document
from frappe import _ from frappe import _
from frappe.utils import flt from frappe.utils import flt
from frappe.model.mapper import get_mapped_doc 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 from erpnext.hr.utils import validate_tax_declaration, get_total_exemption_amount, \
calculate_annual_eligible_hra_exemption, validate_duplicate_exemption_for_payroll_period
class DuplicateDeclarationError(frappe.ValidationError): pass
class EmployeeTaxExemptionDeclaration(Document): class EmployeeTaxExemptionDeclaration(Document):
def validate(self): def validate(self):
validate_tax_declaration(self.declarations) 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_declared_amount()
self.set_total_exemption_amount() self.set_total_exemption_amount()
self.calculate_hra_exemption() 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): def set_total_declared_amount(self):
self.total_declared_amount = 0.0 self.total_declared_amount = 0.0
for d in self.declarations: for d in self.declarations:

View File

@ -6,7 +6,7 @@ from __future__ import unicode_literals
import frappe, erpnext import frappe, erpnext
import unittest import unittest
from erpnext.hr.doctype.employee.test_employee import make_employee 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): class TestEmployeeTaxExemptionDeclaration(unittest.TestCase):
def setUp(self): def setUp(self):

View File

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

View File

@ -7,7 +7,8 @@ import frappe
from frappe.model.document import Document from frappe.model.document import Document
from frappe import _ from frappe import _
from frappe.utils import flt 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): class EmployeeTaxExemptionProofSubmission(Document):
def validate(self): def validate(self):
@ -15,6 +16,7 @@ class EmployeeTaxExemptionProofSubmission(Document):
self.set_total_actual_amount() self.set_total_actual_amount()
self.set_total_exemption_amount() self.set_total_exemption_amount()
self.calculate_hra_exemption() 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): def set_total_actual_amount(self):
self.total_actual_amount = flt(self.get("house_rent_payment_amount")) self.total_actual_amount = flt(self.get("house_rent_payment_amount"))

View File

@ -17,7 +17,7 @@ erpnext.hr.ExpenseClaimController = frappe.ui.form.Controller.extend({
return; return;
} }
return frappe.call({ return frappe.call({
method: "erpnext.hr.doctype.expense_claim.expense_claim.get_expense_claim_account", method: "erpnext.hr.doctype.expense_claim.expense_claim.get_expense_claim_account_and_cost_center",
args: { args: {
"expense_claim_type": d.expense_type, "expense_claim_type": d.expense_type,
"company": doc.company "company": doc.company
@ -25,6 +25,7 @@ erpnext.hr.ExpenseClaimController = frappe.ui.form.Controller.extend({
callback: function(r) { callback: function(r) {
if (r.message) { if (r.message) {
d.default_account = r.message.account; d.default_account = r.message.account;
d.cost_center = r.message.cost_center;
} }
} }
}); });

View File

@ -2,9 +2,9 @@
# License: GNU General Public License v3. See license.txt # License: GNU General Public License v3. See license.txt
from __future__ import unicode_literals from __future__ import unicode_literals
import frappe import frappe, erpnext
from frappe import _ from frappe import _
from frappe.utils import get_fullname, flt, cstr from frappe.utils import get_fullname, flt, cstr, get_link_to_form
from frappe.model.document import Document from frappe.model.document import Document
from erpnext.hr.utils import set_employee_name from erpnext.hr.utils import set_employee_name
from erpnext.accounts.party import get_party_account from erpnext.accounts.party import get_party_account
@ -76,6 +76,7 @@ class ExpenseClaim(AccountsController):
def on_cancel(self): def on_cancel(self):
self.update_task_and_project() self.update_task_and_project()
self.ignore_linked_doctypes = ('GL Entry', 'Stock Ledger Entry')
if self.payable_account: if self.payable_account:
self.make_gl_entries(cancel=True) self.make_gl_entries(cancel=True)
@ -192,7 +193,8 @@ class ExpenseClaim(AccountsController):
def validate_account_details(self): def validate_account_details(self):
for data in self.expenses: for data in self.expenses:
if not data.cost_center: if not data.cost_center:
frappe.throw(_("Cost center is required to book an expense claim")) frappe.throw(_("Row {0}: {1} is required in the expenses table to book an expense claim.")
.format(data.idx, frappe.bold("Cost Center")))
if self.is_paid: if self.is_paid:
if not self.mode_of_payment: if not self.mode_of_payment:
@ -259,10 +261,17 @@ class ExpenseClaim(AccountsController):
if not expense.default_account or not validate: if not expense.default_account or not validate:
expense.default_account = get_expense_claim_account(expense.expense_type, self.company)["account"] expense.default_account = get_expense_claim_account(expense.expense_type, self.company)["account"]
def update_reimbursed_amount(doc): def update_reimbursed_amount(doc, jv=None):
amt = frappe.db.sql("""select ifnull(sum(debit_in_account_currency), 0) as amt
condition = ""
if jv:
condition += "and voucher_no = '{0}'".format(jv)
amt = frappe.db.sql("""select ifnull(sum(debit_in_account_currency), 0) - ifnull(sum(credit_in_account_currency), 0)as amt
from `tabGL Entry` where against_voucher_type = 'Expense Claim' and against_voucher = %s from `tabGL Entry` where against_voucher_type = 'Expense Claim' and against_voucher = %s
and party = %s """, (doc.name, doc.employee) ,as_dict=1)[0].amt and party = %s {condition}""".format(condition=condition), #nosec
(doc.name, doc.employee) ,as_dict=1)[0].amt
doc.total_amount_reimbursed = amt doc.total_amount_reimbursed = amt
frappe.db.set_value("Expense Claim", doc.name , "total_amount_reimbursed", amt) frappe.db.set_value("Expense Claim", doc.name , "total_amount_reimbursed", amt)
@ -308,13 +317,23 @@ def make_bank_entry(dt, dn):
return je.as_dict() return je.as_dict()
@frappe.whitelist()
def get_expense_claim_account_and_cost_center(expense_claim_type, company):
data = get_expense_claim_account(expense_claim_type, company)
cost_center = erpnext.get_default_cost_center(company)
return {
"account": data.get("account"),
"cost_center": cost_center
}
@frappe.whitelist() @frappe.whitelist()
def get_expense_claim_account(expense_claim_type, company): def get_expense_claim_account(expense_claim_type, company):
account = frappe.db.get_value("Expense Claim Account", account = frappe.db.get_value("Expense Claim Account",
{"parent": expense_claim_type, "company": company}, "default_account") {"parent": expense_claim_type, "company": company}, "default_account")
if not account: if not account:
frappe.throw(_("Please set default account in Expense Claim Type {0}") frappe.throw(_("Set the default account for the {0} {1}")
.format(expense_claim_type)) .format(frappe.bold("Expense Claim Type"), get_link_to_form("Expense Claim Type", expense_claim_type)))
return { return {
"account": account "account": account

View File

@ -13,10 +13,12 @@
"stop_birthday_reminders", "stop_birthday_reminders",
"expense_approver_mandatory_in_expense_claim", "expense_approver_mandatory_in_expense_claim",
"payroll_settings", "payroll_settings",
"payroll_based_on",
"max_working_hours_against_timesheet",
"include_holidays_in_total_working_days", "include_holidays_in_total_working_days",
"disable_rounded_total", "disable_rounded_total",
"max_working_hours_against_timesheet",
"column_break_11", "column_break_11",
"daily_wages_fraction_for_half_day",
"email_salary_slip_to_employee", "email_salary_slip_to_employee",
"encrypt_salary_slips_in_emails", "encrypt_salary_slips_in_emails",
"password_policy", "password_policy",
@ -184,13 +186,27 @@
"fieldtype": "Link", "fieldtype": "Link",
"label": "Role Allowed to Create Backdated Leave Application", "label": "Role Allowed to Create Backdated Leave Application",
"options": "Role" "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", "icon": "fa fa-cog",
"idx": 1, "idx": 1,
"issingle": 1, "issingle": 1,
"links": [], "links": [],
"modified": "2020-01-06 18:46:30.189815", "modified": "2020-04-13 21:20:59.382394",
"modified_by": "Administrator", "modified_by": "Administrator",
"module": "HR", "module": "HR",
"name": "HR Settings", "name": "HR Settings",

View File

@ -15,6 +15,9 @@ class HRSettings(Document):
self.set_naming_series() self.set_naming_series()
self.validate_password_policy() 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): def set_naming_series(self):
from erpnext.setup.doctype.naming_series.naming_series import set_by_naming_series from erpnext.setup.doctype.naming_series.naming_series import set_by_naming_series
set_by_naming_series("Employee", "employee_number", 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,152 @@
{
"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,
"collapsible_depends_on": "other_taxes_and_charges",
"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-29 15:08:21.436120",
"modified_by": "Administrator",
"module": "HR",
"name": "Income Tax Slab",
"owner": "Administrator",
"permissions": [
{
"amend": 1,
"cancel": 1,
"create": 1,
"delete": 1,
"email": 1,
"export": 1,
"print": 1,
"read": 1,
"report": 1,
"role": "System Manager",
"share": 1,
"submit": 1,
"write": 1
},
{
"amend": 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
},
{
"amend": 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
}
],
"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 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,16 +30,16 @@ class LeaveAllocation(Document):
def validate_leave_allocation_days(self): def validate_leave_allocation_days(self):
company = frappe.db.get_value("Employee", self.employee, "company") company = frappe.db.get_value("Employee", self.employee, "company")
leave_period = get_leave_period(self.from_date, self.to_date, 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: if max_leaves_allowed > 0:
leave_allocated = 0 leave_allocated = 0
if leave_period: if leave_period:
leave_allocated = get_leave_allocation_for_period(self.employee, self.leave_type, leave_allocated = get_leave_allocation_for_period(self.employee, self.leave_type,
leave_period[0].from_date, leave_period[0].to_date) 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: 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") 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)) .format(self.leave_type, self.employee))
def on_submit(self): def on_submit(self):
self.create_leave_ledger_entry() self.create_leave_ledger_entry()

View File

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

View File

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

View File

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

View File

@ -45,8 +45,9 @@ class PayrollPeriod(Document):
+ _(") for {0}").format(self.company) + _(") for {0}").format(self.company)
frappe.throw(msg) 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):
company = frappe.db.get_value("Employee", employee, "company") if not company:
company = frappe.db.get_value("Employee", employee, "company")
payroll_period = frappe.db.sql(""" payroll_period = frappe.db.sql("""
select name, start_date, end_date select name, start_date, end_date
from `tabPayroll Period` from `tabPayroll Period`

View File

@ -1,264 +1,263 @@
{ {
"allow_import": 1, "actions": [],
"allow_rename": 1, "allow_import": 1,
"autoname": "field:salary_component", "allow_rename": 1,
"creation": "2016-06-30 15:42:43.631931", "autoname": "field:salary_component",
"doctype": "DocType", "creation": "2016-06-30 15:42:43.631931",
"document_type": "Setup", "doctype": "DocType",
"editable_grid": 1, "document_type": "Setup",
"engine": "InnoDB", "editable_grid": 1,
"field_order": [ "engine": "InnoDB",
"salary_component", "field_order": [
"salary_component_abbr", "salary_component",
"type", "salary_component_abbr",
"description", "type",
"column_break_4", "description",
"is_payable", "column_break_4",
"depends_on_payment_days", "depends_on_payment_days",
"is_tax_applicable", "is_tax_applicable",
"deduct_full_tax_on_selected_payroll_date", "deduct_full_tax_on_selected_payroll_date",
"round_to_the_nearest_integer", "variable_based_on_taxable_salary",
"statistical_component", "exempted_from_income_tax",
"do_not_include_in_total", "round_to_the_nearest_integer",
"disabled", "statistical_component",
"flexible_benefits", "do_not_include_in_total",
"is_flexible_benefit", "disabled",
"max_benefit_amount", "flexible_benefits",
"column_break_9", "is_flexible_benefit",
"pay_against_benefit_claim", "max_benefit_amount",
"only_tax_impact", "column_break_9",
"create_separate_payment_entry_against_benefit_claim", "pay_against_benefit_claim",
"section_break_11", "only_tax_impact",
"variable_based_on_taxable_salary", "create_separate_payment_entry_against_benefit_claim",
"section_break_5", "section_break_5",
"accounts", "accounts",
"condition_and_formula", "condition_and_formula",
"condition", "condition",
"amount", "amount",
"amount_based_on_formula", "amount_based_on_formula",
"formula", "formula",
"column_break_28", "column_break_28",
"help" "help"
], ],
"fields": [ "fields": [
{ {
"fieldname": "salary_component", "fieldname": "salary_component",
"fieldtype": "Data", "fieldtype": "Data",
"in_list_view": 1, "in_list_view": 1,
"label": "Name", "label": "Name",
"reqd": 1, "reqd": 1,
"unique": 1 "unique": 1
}, },
{ {
"fieldname": "salary_component_abbr", "fieldname": "salary_component_abbr",
"fieldtype": "Data", "fieldtype": "Data",
"in_list_view": 1, "in_list_view": 1,
"label": "Abbr", "label": "Abbr",
"print_width": "120px", "print_width": "120px",
"reqd": 1, "reqd": 1,
"width": "120px" "width": "120px"
}, },
{ {
"fieldname": "type", "fieldname": "type",
"fieldtype": "Select", "fieldtype": "Select",
"in_standard_filter": 1, "in_standard_filter": 1,
"label": "Type", "label": "Type",
"options": "Earning\nDeduction", "options": "Earning\nDeduction",
"reqd": 1 "reqd": 1
}, },
{ {
"default": "1", "default": "1",
"depends_on": "eval:doc.type == \"Earning\"", "depends_on": "eval:doc.type == \"Earning\"",
"fieldname": "is_tax_applicable", "fieldname": "is_tax_applicable",
"fieldtype": "Check", "fieldtype": "Check",
"label": "Is Tax Applicable" "label": "Is Tax Applicable"
}, },
{ {
"default": "1", "default": "1",
"fieldname": "is_payable", "fieldname": "depends_on_payment_days",
"fieldtype": "Check", "fieldtype": "Check",
"label": "Is Payable" "label": "Depends on Payment Days",
}, "print_hide": 1
{ },
"default": "1", {
"fieldname": "depends_on_payment_days", "default": "0",
"fieldtype": "Check", "fieldname": "do_not_include_in_total",
"label": "Depends on Payment Days", "fieldtype": "Check",
"print_hide": 1 "label": "Do Not Include in Total"
}, },
{ {
"default": "0", "default": "0",
"fieldname": "do_not_include_in_total", "depends_on": "eval:doc.is_tax_applicable && doc.type=='Earning'",
"fieldtype": "Check", "fieldname": "deduct_full_tax_on_selected_payroll_date",
"label": "Do Not Include in Total" "fieldtype": "Check",
}, "label": "Deduct Full Tax on Selected Payroll Date"
{ },
"default": "0", {
"depends_on": "is_tax_applicable", "fieldname": "column_break_4",
"fieldname": "deduct_full_tax_on_selected_payroll_date", "fieldtype": "Column Break"
"fieldtype": "Check", },
"label": "Deduct Full Tax on Selected Payroll Date" {
}, "default": "0",
{ "fieldname": "disabled",
"fieldname": "column_break_4", "fieldtype": "Check",
"fieldtype": "Column Break" "label": "Disabled"
}, },
{ {
"default": "0", "fieldname": "description",
"fieldname": "disabled", "fieldtype": "Small Text",
"fieldtype": "Check", "in_list_view": 1,
"label": "Disabled" "label": "Description"
}, },
{ {
"fieldname": "description", "default": "0",
"fieldtype": "Small Text", "description": "If selected, the value specified or calculated in this component will not contribute to the earnings or deductions. However, it's value can be referenced by other components that can be added or deducted. ",
"in_list_view": 1, "fieldname": "statistical_component",
"label": "Description" "fieldtype": "Check",
}, "label": "Statistical Component"
{ },
"default": "0", {
"description": "If selected, the value specified or calculated in this component will not contribute to the earnings or deductions. However, it's value can be referenced by other components that can be added or deducted. ", "depends_on": "eval:doc.type==\"Earning\" && doc.statistical_component!=1",
"fieldname": "statistical_component", "fieldname": "flexible_benefits",
"fieldtype": "Check", "fieldtype": "Section Break",
"label": "Statistical Component" "label": "Flexible Benefits"
}, },
{ {
"depends_on": "eval:doc.type==\"Earning\" && doc.statistical_component!=1", "default": "0",
"fieldname": "flexible_benefits", "fieldname": "is_flexible_benefit",
"fieldtype": "Section Break", "fieldtype": "Check",
"label": "Flexible Benefits" "label": "Is Flexible Benefit"
}, },
{ {
"default": "0", "depends_on": "is_flexible_benefit",
"fieldname": "is_flexible_benefit", "fieldname": "max_benefit_amount",
"fieldtype": "Check", "fieldtype": "Currency",
"label": "Is Flexible Benefit" "label": "Max Benefit Amount (Yearly)"
}, },
{ {
"depends_on": "is_flexible_benefit", "fieldname": "column_break_9",
"fieldname": "max_benefit_amount", "fieldtype": "Column Break"
"fieldtype": "Currency", },
"label": "Max Benefit Amount (Yearly)" {
}, "default": "0",
{ "depends_on": "is_flexible_benefit",
"fieldname": "column_break_9", "fieldname": "pay_against_benefit_claim",
"fieldtype": "Column Break" "fieldtype": "Check",
}, "label": "Pay Against Benefit Claim"
{ },
"default": "0", {
"depends_on": "is_flexible_benefit", "default": "0",
"fieldname": "pay_against_benefit_claim", "depends_on": "eval:doc.is_flexible_benefit == 1 & doc.create_separate_payment_entry_against_benefit_claim !=1",
"fieldtype": "Check", "fieldname": "only_tax_impact",
"label": "Pay Against Benefit Claim" "fieldtype": "Check",
}, "label": "Only Tax Impact (Cannot Claim But Part of Taxable Income)"
{ },
"default": "0", {
"depends_on": "eval:doc.is_flexible_benefit == 1 & doc.create_separate_payment_entry_against_benefit_claim !=1", "default": "0",
"fieldname": "only_tax_impact", "depends_on": "eval:doc.is_flexible_benefit == 1 & doc.only_tax_impact !=1",
"fieldtype": "Check", "fieldname": "create_separate_payment_entry_against_benefit_claim",
"label": "Only Tax Impact (Cannot Claim But Part of Taxable Income)" "fieldtype": "Check",
}, "label": "Create Separate Payment Entry Against Benefit Claim"
{ },
"default": "0", {
"depends_on": "eval:doc.is_flexible_benefit == 1 & doc.only_tax_impact !=1", "default": "0",
"fieldname": "create_separate_payment_entry_against_benefit_claim", "depends_on": "eval:doc.type == \"Deduction\"",
"fieldtype": "Check", "fieldname": "variable_based_on_taxable_salary",
"label": "Create Separate Payment Entry Against Benefit Claim" "fieldtype": "Check",
}, "label": "Variable Based On Taxable Salary"
{ },
"depends_on": "eval:doc.type=='Deduction'", {
"fieldname": "section_break_11", "depends_on": "eval:doc.statistical_component != 1",
"fieldtype": "Section Break" "fieldname": "section_break_5",
}, "fieldtype": "Section Break",
{ "label": "Accounts"
"default": "0", },
"fieldname": "variable_based_on_taxable_salary", {
"fieldtype": "Check", "fieldname": "accounts",
"label": "Variable Based On Taxable Salary" "fieldtype": "Table",
}, "label": "Accounts",
{ "options": "Salary Component Account"
"depends_on": "eval:doc.statistical_component != 1", },
"fieldname": "section_break_5", {
"fieldtype": "Section Break", "collapsible": 1,
"label": "Accounts" "depends_on": "eval:doc.is_flexible_benefit != 1 && doc.variable_based_on_taxable_salary != 1",
}, "fieldname": "condition_and_formula",
{ "fieldtype": "Section Break",
"fieldname": "accounts", "label": "Condition and Formula"
"fieldtype": "Table", },
"label": "Accounts", {
"options": "Salary Component Account" "fieldname": "condition",
}, "fieldtype": "Code",
{ "label": "Condition"
"collapsible": 1, },
"depends_on": "eval:doc.is_flexible_benefit != 1 && doc.variable_based_on_taxable_salary != 1", {
"fieldname": "condition_and_formula", "default": "0",
"fieldtype": "Section Break", "fieldname": "amount_based_on_formula",
"label": "Condition and Formula" "fieldtype": "Check",
}, "label": "Amount based on formula"
{ },
"fieldname": "condition", {
"fieldtype": "Code", "depends_on": "amount_based_on_formula",
"label": "Condition" "fieldname": "formula",
}, "fieldtype": "Code",
{ "label": "Formula"
"default": "0", },
"fieldname": "amount_based_on_formula", {
"fieldtype": "Check", "depends_on": "eval:doc.amount_based_on_formula!==1",
"label": "Amount based on formula" "fieldname": "amount",
}, "fieldtype": "Currency",
{ "label": "Amount"
"depends_on": "amount_based_on_formula", },
"fieldname": "formula", {
"fieldtype": "Code", "fieldname": "column_break_28",
"label": "Formula" "fieldtype": "Column Break"
}, },
{ {
"depends_on": "eval:doc.amount_based_on_formula!==1", "fieldname": "help",
"fieldname": "amount", "fieldtype": "HTML",
"fieldtype": "Currency", "label": "Help",
"label": "Amount" "options": "<h3>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>"
}, },
{ {
"fieldname": "column_break_28", "default": "0",
"fieldtype": "Column Break" "fieldname": "round_to_the_nearest_integer",
}, "fieldtype": "Check",
{ "label": "Round to the Nearest Integer"
"fieldname": "help", },
"fieldtype": "HTML", {
"label": "Help", "default": "0",
"options": "<h3>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>" "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 without any declaration or proof submission.",
{ "fieldname": "exempted_from_income_tax",
"default": "0", "fieldtype": "Check",
"fieldname": "round_to_the_nearest_integer", "label": "Exempted from Income Tax"
"fieldtype": "Check", }
"label": "Round to the Nearest Integer" ],
} "icon": "fa fa-flag",
], "links": [],
"icon": "fa fa-flag", "modified": "2020-04-28 15:46:45.252945",
"modified": "2019-06-05 11:34:14.231228", "modified_by": "Administrator",
"modified_by": "Administrator", "module": "HR",
"module": "HR", "name": "Salary Component",
"name": "Salary Component", "owner": "Administrator",
"owner": "Administrator", "permissions": [
"permissions": [ {
{ "create": 1,
"create": 1, "delete": 1,
"delete": 1, "email": 1,
"email": 1, "export": 1,
"export": 1, "print": 1,
"print": 1, "read": 1,
"read": 1, "report": 1,
"report": 1, "role": "HR User",
"role": "HR User", "share": 1,
"share": 1, "write": 1
"write": 1 },
}, {
{ "read": 1,
"read": 1, "role": "Employee"
"role": "Employee" }
} ],
], "sort_field": "modified",
"sort_field": "modified", "sort_order": "DESC"
"sort_order": "DESC" }
}

View File

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

View File

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

View File

@ -12,6 +12,7 @@
"deduct_full_tax_on_selected_payroll_date", "deduct_full_tax_on_selected_payroll_date",
"depends_on_payment_days", "depends_on_payment_days",
"is_tax_applicable", "is_tax_applicable",
"exempted_from_income_tax",
"is_flexible_benefit", "is_flexible_benefit",
"variable_based_on_taxable_salary", "variable_based_on_taxable_salary",
"section_break_2", "section_break_2",
@ -62,6 +63,7 @@
}, },
{ {
"default": "0", "default": "0",
"depends_on": "eval:doc.parentfield=='earnings'",
"fetch_from": "salary_component.is_tax_applicable", "fetch_from": "salary_component.is_tax_applicable",
"fieldname": "is_tax_applicable", "fieldname": "is_tax_applicable",
"fieldtype": "Check", "fieldtype": "Check",
@ -71,6 +73,7 @@
}, },
{ {
"default": "0", "default": "0",
"depends_on": "eval:doc.parentfield=='earnings'",
"fetch_from": "salary_component.is_flexible_benefit", "fetch_from": "salary_component.is_flexible_benefit",
"fieldname": "is_flexible_benefit", "fieldname": "is_flexible_benefit",
"fieldtype": "Check", "fieldtype": "Check",
@ -80,6 +83,7 @@
}, },
{ {
"default": "0", "default": "0",
"depends_on": "eval:doc.parentfield=='deductions'",
"fetch_from": "salary_component.variable_based_on_taxable_salary", "fetch_from": "salary_component.variable_based_on_taxable_salary",
"fieldname": "variable_based_on_taxable_salary", "fieldname": "variable_based_on_taxable_salary",
"fieldtype": "Check", "fieldtype": "Check",
@ -187,11 +191,20 @@
"fieldtype": "HTML", "fieldtype": "HTML",
"label": "Condition and Formula Help", "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>" "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, "istable": 1,
"links": [], "links": [],
"modified": "2019-12-31 17:15:25.646689", "modified": "2020-04-24 20:00:16.475295",
"modified_by": "Administrator", "modified_by": "Administrator",
"module": "HR", "module": "HR",
"name": "Salary Detail", "name": "Salary Detail",

View File

@ -51,7 +51,7 @@ frappe.ui.form.on("Salary Slip", {
}, },
end_date: function(frm) { end_date: function(frm) {
frm.events.get_emp_and_leave_details(frm); frm.events.get_emp_and_working_day_details(frm);
}, },
set_end_date: function(frm){ set_end_date: function(frm){
@ -86,7 +86,7 @@ frappe.ui.form.on("Salary Slip", {
salary_slip_based_on_timesheet: function(frm) { salary_slip_based_on_timesheet: function(frm) {
frm.trigger("toggle_fields"); frm.trigger("toggle_fields");
frm.events.get_emp_and_leave_details(frm); frm.events.get_emp_and_working_day_details(frm);
}, },
payroll_frequency: function(frm) { payroll_frequency: function(frm) {
@ -95,15 +95,14 @@ frappe.ui.form.on("Salary Slip", {
}, },
employee: function(frm) { employee: function(frm) {
frm.events.get_emp_and_leave_details(frm); frm.events.get_emp_and_working_day_details(frm);
}, },
leave_without_pay: function(frm){ leave_without_pay: function(frm){
if (frm.doc.employee && frm.doc.start_date && frm.doc.end_date) { if (frm.doc.employee && frm.doc.start_date && frm.doc.end_date) {
return frappe.call({ return frappe.call({
method: 'process_salary_based_on_leave', method: 'process_salary_based_on_working_days',
doc: frm.doc, doc: frm.doc,
args: {"lwp": frm.doc.leave_without_pay},
callback: function(r, rt) { callback: function(r, rt) {
frm.refresh(); frm.refresh();
} }
@ -115,12 +114,12 @@ frappe.ui.form.on("Salary Slip", {
frm.toggle_display(['hourly_wages', 'timesheets'], cint(frm.doc.salary_slip_based_on_timesheet)===1); frm.toggle_display(['hourly_wages', 'timesheets'], cint(frm.doc.salary_slip_based_on_timesheet)===1);
frm.toggle_display(['payment_days', 'total_working_days', 'leave_without_pay'], frm.toggle_display(['payment_days', 'total_working_days', 'leave_without_pay'],
frm.doc.payroll_frequency!=""); frm.doc.payroll_frequency != "");
}, },
get_emp_and_leave_details: function(frm) { get_emp_and_working_day_details: function(frm) {
return frappe.call({ return frappe.call({
method: 'get_emp_and_leave_details', method: 'get_emp_and_working_day_details',
doc: frm.doc, doc: frm.doc,
callback: function(r, rt) { callback: function(r, rt) {
frm.refresh(); frm.refresh();

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