mirror of
https://github.com/frappe/erpnext.git
synced 2026-05-13 10:11:20 +00:00
Merge branch 'develop' into e-commerce-refactor
This commit is contained in:
@@ -63,17 +63,21 @@
|
|||||||
"Gewinnermittlung \u00a74/3 nicht Ergebniswirksam": {
|
"Gewinnermittlung \u00a74/3 nicht Ergebniswirksam": {
|
||||||
"account_number": "1371"
|
"account_number": "1371"
|
||||||
},
|
},
|
||||||
"Abziehbare VSt. 7%": {
|
"Abziehbare Vorsteuer": {
|
||||||
"account_number": "1571"
|
"account_type": "Tax",
|
||||||
},
|
"is_group": 1,
|
||||||
"Abziehbare VSt. 19%": {
|
"Abziehbare Vorsteuer 7%": {
|
||||||
"account_number": "1576"
|
"account_number": "1571"
|
||||||
},
|
},
|
||||||
"Abziehbare VStr. nach \u00a713b UStG 19%": {
|
"Abziehbare Vorsteuer 19%": {
|
||||||
"account_number": "1577"
|
"account_number": "1576"
|
||||||
},
|
},
|
||||||
"Leistungen \u00a713b UStG 19% Vorsteuer, 19% Umsatzsteuer": {
|
"Abziehbare Vorsteuer nach \u00a713b UStG 19%": {
|
||||||
"account_number": "3120"
|
"account_number": "1577"
|
||||||
|
},
|
||||||
|
"Leistungen \u00a713b UStG 19% Vorsteuer, 19% Umsatzsteuer": {
|
||||||
|
"account_number": "3120"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"III. Wertpapiere": {
|
"III. Wertpapiere": {
|
||||||
@@ -196,6 +200,7 @@
|
|||||||
},
|
},
|
||||||
"Umsatzsteuer": {
|
"Umsatzsteuer": {
|
||||||
"is_group": 1,
|
"is_group": 1,
|
||||||
|
"account_type": "Tax",
|
||||||
"Umsatzsteuer 7%": {
|
"Umsatzsteuer 7%": {
|
||||||
"account_number": "1771"
|
"account_number": "1771"
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -292,18 +292,21 @@
|
|||||||
"Umsatzsteuerforderungen fr\u00fchere Jahre": {}
|
"Umsatzsteuerforderungen fr\u00fchere Jahre": {}
|
||||||
},
|
},
|
||||||
"Sonstige Verm\u00f6gensgegenst\u00e4nde oder sonstige Verbindlichkeiten": {
|
"Sonstige Verm\u00f6gensgegenst\u00e4nde oder sonstige Verbindlichkeiten": {
|
||||||
"Abziehbare Vorsteuer": {},
|
"Abziehbare Vorsteuer": {
|
||||||
"Abziehbare Vorsteuer 16%": {},
|
"account_type": "Tax",
|
||||||
"Abziehbare Vorsteuer 19%": {},
|
"is_group": 1,
|
||||||
"Abziehbare Vorsteuer 7%": {},
|
"Abziehbare Vorsteuer 16%": {},
|
||||||
"Abziehbare Vorsteuer aus der Auslagerung von Gegenst\u00e4nden aus einem Unsatzsteuerlager": {},
|
"Abziehbare Vorsteuer 19%": {},
|
||||||
"Abziehbare Vorsteuer aus innergemeinschaftlichem Erwerb": {},
|
"Abziehbare Vorsteuer 7%": {},
|
||||||
"Abziehbare Vorsteuer aus innergemeinschaftlichem Erwerb 16%": {},
|
"Abziehbare Vorsteuer aus der Auslagerung von Gegenst\u00e4nden aus einem Unsatzsteuerlager": {},
|
||||||
"Abziehbare Vorsteuer aus innergemeinschaftlichem Erwerb 19%": {},
|
"Abziehbare Vorsteuer aus innergemeinschaftlichem Erwerb": {},
|
||||||
"Abziehbare Vorsteuer aus innergemeinschaftlichem Erwerb von Neufahrzeugen von Lieferanten ohne Ust-Identifikationsnummer": {},
|
"Abziehbare Vorsteuer aus innergemeinschaftlichem Erwerb 16%": {},
|
||||||
"Abziehbare Vorsteuer nach \u00a7 13b UStG ": {},
|
"Abziehbare Vorsteuer aus innergemeinschaftlichem Erwerb 19%": {},
|
||||||
"Abziehbare Vorsteuer nach \u00a7 13b UStG 16%": {},
|
"Abziehbare Vorsteuer aus innergemeinschaftlichem Erwerb von Neufahrzeugen von Lieferanten ohne Ust-Identifikationsnummer": {},
|
||||||
"Abziehbare Vorsteuer nach \u00a7 13b UStG 19%": {},
|
"Abziehbare Vorsteuer nach \u00a7 13b UStG ": {},
|
||||||
|
"Abziehbare Vorsteuer nach \u00a7 13b UStG 16%": {},
|
||||||
|
"Abziehbare Vorsteuer nach \u00a7 13b UStG 19%": {}
|
||||||
|
},
|
||||||
"Aufl\u00f6sung Vorsteuer aus Vorjahr \u00a7 4/3 EStG": {},
|
"Aufl\u00f6sung Vorsteuer aus Vorjahr \u00a7 4/3 EStG": {},
|
||||||
"Aufzuteilende Vorsteuer": {},
|
"Aufzuteilende Vorsteuer": {},
|
||||||
"Aufzuteilende Vorsteuer 16%": {},
|
"Aufzuteilende Vorsteuer 16%": {},
|
||||||
@@ -673,23 +676,26 @@
|
|||||||
"Sonstige Verrechnungskonten (Interimskonto)": {
|
"Sonstige Verrechnungskonten (Interimskonto)": {
|
||||||
"account_type": "Stock Received But Not Billed"
|
"account_type": "Stock Received But Not Billed"
|
||||||
},
|
},
|
||||||
"Umsatzsteuer": {},
|
"Umsatzsteuer": {
|
||||||
"Umsatzsteuer 16%": {},
|
"account_type": "Tax",
|
||||||
"Umsatzsteuer 19%": {},
|
"is_group": 1,
|
||||||
"Umsatzsteuer 7%": {},
|
"Umsatzsteuer 16%": {},
|
||||||
"Umsatzsteuer Vorjahr": {},
|
"Umsatzsteuer 19%": {},
|
||||||
"Umsatzsteuer aus der Auslagerung von Gegenst\u00e4nden aus einem Umsatzsteuerlager": {},
|
"Umsatzsteuer 7%": {},
|
||||||
"Umsatzsteuer aus im Inland steuerpflichtigen EG-Lieferungen": {},
|
"Umsatzsteuer Vorjahr": {},
|
||||||
"Umsatzsteuer aus im Inland steuerpflichtigen EG-Lieferungen 19%": {},
|
"Umsatzsteuer aus der Auslagerung von Gegenst\u00e4nden aus einem Umsatzsteuerlager": {},
|
||||||
"Umsatzsteuer aus innergemeinschaftlichem Erwerb ": {},
|
"Umsatzsteuer aus im Inland steuerpflichtigen EG-Lieferungen": {},
|
||||||
"Umsatzsteuer aus innergemeinschaftlichem Erwerb 16%": {},
|
"Umsatzsteuer aus im Inland steuerpflichtigen EG-Lieferungen 19%": {},
|
||||||
"Umsatzsteuer aus innergemeinschaftlichem Erwerb 19%": {},
|
"Umsatzsteuer aus innergemeinschaftlichem Erwerb ": {},
|
||||||
"Umsatzsteuer aus innergemeinschaftlichem Erwerb ohne Vorsteuerabzug": {},
|
"Umsatzsteuer aus innergemeinschaftlichem Erwerb 16%": {},
|
||||||
"Umsatzsteuer fr\u00fchere Jahre": {},
|
"Umsatzsteuer aus innergemeinschaftlichem Erwerb 19%": {},
|
||||||
"Umsatzsteuer laufendes Jahr": {},
|
"Umsatzsteuer aus innergemeinschaftlichem Erwerb ohne Vorsteuerabzug": {},
|
||||||
"Umsatzsteuer nach \u00a713b UStG": {},
|
"Umsatzsteuer fr\u00fchere Jahre": {},
|
||||||
"Umsatzsteuer nach \u00a713b UStG 16%": {},
|
"Umsatzsteuer laufendes Jahr": {},
|
||||||
"Umsatzsteuer nach \u00a713b UStG 19%": {},
|
"Umsatzsteuer nach \u00a713b UStG": {},
|
||||||
|
"Umsatzsteuer nach \u00a713b UStG 16%": {},
|
||||||
|
"Umsatzsteuer nach \u00a713b UStG 19%": {}
|
||||||
|
},
|
||||||
"Umsatzsteuer- Vorauszahlungen": {},
|
"Umsatzsteuer- Vorauszahlungen": {},
|
||||||
"Umsatzsteuer- Vorauszahlungen 1/11": {},
|
"Umsatzsteuer- Vorauszahlungen 1/11": {},
|
||||||
"Verbindlichkeiten aus Lohn- und Kirchensteuer": {}
|
"Verbindlichkeiten aus Lohn- und Kirchensteuer": {}
|
||||||
|
|||||||
@@ -659,6 +659,7 @@
|
|||||||
},
|
},
|
||||||
"Abziehbare Vorsteuer (Gruppe)": {
|
"Abziehbare Vorsteuer (Gruppe)": {
|
||||||
"is_group": 1,
|
"is_group": 1,
|
||||||
|
"account_type": "Tax",
|
||||||
"Abziehbare Vorsteuer": {
|
"Abziehbare Vorsteuer": {
|
||||||
"account_number": "1400"
|
"account_number": "1400"
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
{
|
{
|
||||||
|
"actions": [],
|
||||||
"allow_import": 1,
|
"allow_import": 1,
|
||||||
"allow_rename": 1,
|
"allow_rename": 1,
|
||||||
"autoname": "field:title",
|
|
||||||
"creation": "2018-11-22 22:45:00.370913",
|
"creation": "2018-11-22 22:45:00.370913",
|
||||||
"doctype": "DocType",
|
"doctype": "DocType",
|
||||||
"document_type": "Setup",
|
"document_type": "Setup",
|
||||||
@@ -20,8 +20,7 @@
|
|||||||
"in_list_view": 1,
|
"in_list_view": 1,
|
||||||
"label": "Title",
|
"label": "Title",
|
||||||
"no_copy": 1,
|
"no_copy": 1,
|
||||||
"reqd": 1,
|
"reqd": 1
|
||||||
"unique": 1
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"fieldname": "taxes",
|
"fieldname": "taxes",
|
||||||
@@ -33,12 +32,14 @@
|
|||||||
{
|
{
|
||||||
"fieldname": "company",
|
"fieldname": "company",
|
||||||
"fieldtype": "Link",
|
"fieldtype": "Link",
|
||||||
|
"in_list_view": 1,
|
||||||
"label": "Company",
|
"label": "Company",
|
||||||
"options": "Company",
|
"options": "Company",
|
||||||
"reqd": 1
|
"reqd": 1
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"modified": "2020-09-18 17:26:09.703215",
|
"links": [],
|
||||||
|
"modified": "2021-03-08 19:50:21.416513",
|
||||||
"modified_by": "Administrator",
|
"modified_by": "Administrator",
|
||||||
"module": "Accounts",
|
"module": "Accounts",
|
||||||
"name": "Item Tax Template",
|
"name": "Item Tax Template",
|
||||||
@@ -81,5 +82,6 @@
|
|||||||
"show_name_in_global_search": 1,
|
"show_name_in_global_search": 1,
|
||||||
"sort_field": "modified",
|
"sort_field": "modified",
|
||||||
"sort_order": "DESC",
|
"sort_order": "DESC",
|
||||||
|
"title_field": "title",
|
||||||
"track_changes": 1
|
"track_changes": 1
|
||||||
}
|
}
|
||||||
@@ -11,6 +11,11 @@ class ItemTaxTemplate(Document):
|
|||||||
def validate(self):
|
def validate(self):
|
||||||
self.validate_tax_accounts()
|
self.validate_tax_accounts()
|
||||||
|
|
||||||
|
def autoname(self):
|
||||||
|
if self.company and self.title:
|
||||||
|
abbr = frappe.get_cached_value('Company', self.company, 'abbr')
|
||||||
|
self.name = '{0} - {1}'.format(self.title, abbr)
|
||||||
|
|
||||||
def validate_tax_accounts(self):
|
def validate_tax_accounts(self):
|
||||||
"""Check whether Tax Rate is not entered twice for same Tax Type"""
|
"""Check whether Tax Rate is not entered twice for same Tax Type"""
|
||||||
check_list = []
|
check_list = []
|
||||||
|
|||||||
@@ -198,6 +198,7 @@ def start_import(invoices):
|
|||||||
try:
|
try:
|
||||||
publish(idx, len(invoices), d.doctype)
|
publish(idx, len(invoices), d.doctype)
|
||||||
doc = frappe.get_doc(d)
|
doc = frappe.get_doc(d)
|
||||||
|
doc.flags.ignore_mandatory = True
|
||||||
doc.insert()
|
doc.insert()
|
||||||
doc.submit()
|
doc.submit()
|
||||||
frappe.db.commit()
|
frappe.db.commit()
|
||||||
|
|||||||
@@ -92,14 +92,16 @@ frappe.ui.form.on('Payment Entry', {
|
|||||||
});
|
});
|
||||||
|
|
||||||
frm.set_query("reference_doctype", "references", function() {
|
frm.set_query("reference_doctype", "references", function() {
|
||||||
if (frm.doc.party_type=="Customer") {
|
if (frm.doc.party_type == "Customer") {
|
||||||
var doctypes = ["Sales Order", "Sales Invoice", "Journal Entry", "Dunning"];
|
var doctypes = ["Sales Order", "Sales Invoice", "Journal Entry", "Dunning"];
|
||||||
} else if (frm.doc.party_type=="Supplier") {
|
} else if (frm.doc.party_type == "Supplier") {
|
||||||
var doctypes = ["Purchase Order", "Purchase Invoice", "Journal Entry"];
|
var doctypes = ["Purchase Order", "Purchase Invoice", "Journal Entry"];
|
||||||
} else if (frm.doc.party_type=="Employee") {
|
} else if (frm.doc.party_type == "Employee") {
|
||||||
var doctypes = ["Expense Claim", "Journal Entry"];
|
var doctypes = ["Expense Claim", "Journal Entry"];
|
||||||
} else if (frm.doc.party_type=="Student") {
|
} else if (frm.doc.party_type == "Student") {
|
||||||
var doctypes = ["Fees"];
|
var doctypes = ["Fees"];
|
||||||
|
} else if (frm.doc.party_type == "Donor") {
|
||||||
|
var doctypes = ["Donation"];
|
||||||
} else {
|
} else {
|
||||||
var doctypes = ["Journal Entry"];
|
var doctypes = ["Journal Entry"];
|
||||||
}
|
}
|
||||||
@@ -128,7 +130,7 @@ frappe.ui.form.on('Payment Entry', {
|
|||||||
const child = locals[cdt][cdn];
|
const child = locals[cdt][cdn];
|
||||||
const filters = {"docstatus": 1, "company": doc.company};
|
const filters = {"docstatus": 1, "company": doc.company};
|
||||||
const party_type_doctypes = ['Sales Invoice', 'Sales Order', 'Purchase Invoice',
|
const party_type_doctypes = ['Sales Invoice', 'Sales Order', 'Purchase Invoice',
|
||||||
'Purchase Order', 'Expense Claim', 'Fees', 'Dunning'];
|
'Purchase Order', 'Expense Claim', 'Fees', 'Dunning', 'Donation'];
|
||||||
|
|
||||||
if (in_list(party_type_doctypes, child.reference_doctype)) {
|
if (in_list(party_type_doctypes, child.reference_doctype)) {
|
||||||
filters[doc.party_type.toLowerCase()] = doc.party;
|
filters[doc.party_type.toLowerCase()] = doc.party;
|
||||||
@@ -281,7 +283,7 @@ frappe.ui.form.on('Payment Entry', {
|
|||||||
let party_types = Object.keys(frappe.boot.party_account_types);
|
let party_types = Object.keys(frappe.boot.party_account_types);
|
||||||
if(frm.doc.party_type && !party_types.includes(frm.doc.party_type)){
|
if(frm.doc.party_type && !party_types.includes(frm.doc.party_type)){
|
||||||
frm.set_value("party_type", "");
|
frm.set_value("party_type", "");
|
||||||
frappe.throw(__("Party can only be one of "+ party_types.join(", ")));
|
frappe.throw(__("Party can only be one of {0}", [party_types.join(", ")]));
|
||||||
}
|
}
|
||||||
|
|
||||||
frm.set_query("party", function() {
|
frm.set_query("party", function() {
|
||||||
@@ -705,7 +707,8 @@ frappe.ui.form.on('Payment Entry', {
|
|||||||
(frm.doc.payment_type=="Receive" && frm.doc.party_type=="Customer") ||
|
(frm.doc.payment_type=="Receive" && frm.doc.party_type=="Customer") ||
|
||||||
(frm.doc.payment_type=="Pay" && frm.doc.party_type=="Supplier") ||
|
(frm.doc.payment_type=="Pay" && frm.doc.party_type=="Supplier") ||
|
||||||
(frm.doc.payment_type=="Pay" && frm.doc.party_type=="Employee") ||
|
(frm.doc.payment_type=="Pay" && frm.doc.party_type=="Employee") ||
|
||||||
(frm.doc.payment_type=="Receive" && frm.doc.party_type=="Student")
|
(frm.doc.payment_type=="Receive" && frm.doc.party_type=="Student") ||
|
||||||
|
(frm.doc.payment_type=="Receive" && frm.doc.party_type=="Donor")
|
||||||
) {
|
) {
|
||||||
if(total_positive_outstanding > total_negative_outstanding)
|
if(total_positive_outstanding > total_negative_outstanding)
|
||||||
if (!frm.doc.paid_amount)
|
if (!frm.doc.paid_amount)
|
||||||
@@ -748,7 +751,8 @@ frappe.ui.form.on('Payment Entry', {
|
|||||||
(frm.doc.payment_type=="Receive" && frm.doc.party_type=="Customer") ||
|
(frm.doc.payment_type=="Receive" && frm.doc.party_type=="Customer") ||
|
||||||
(frm.doc.payment_type=="Pay" && frm.doc.party_type=="Supplier") ||
|
(frm.doc.payment_type=="Pay" && frm.doc.party_type=="Supplier") ||
|
||||||
(frm.doc.payment_type=="Pay" && frm.doc.party_type=="Employee") ||
|
(frm.doc.payment_type=="Pay" && frm.doc.party_type=="Employee") ||
|
||||||
(frm.doc.payment_type=="Receive" && frm.doc.party_type=="Student")
|
(frm.doc.payment_type=="Receive" && frm.doc.party_type=="Student") ||
|
||||||
|
(frm.doc.payment_type=="Receive" && frm.doc.party_type=="Donor")
|
||||||
) {
|
) {
|
||||||
if(total_positive_outstanding_including_order > paid_amount) {
|
if(total_positive_outstanding_including_order > paid_amount) {
|
||||||
var remaining_outstanding = total_positive_outstanding_including_order - paid_amount;
|
var remaining_outstanding = total_positive_outstanding_including_order - paid_amount;
|
||||||
@@ -905,6 +909,12 @@ frappe.ui.form.on('Payment Entry', {
|
|||||||
frappe.msgprint(__("Row #{0}: Reference Document Type must be one of Expense Claim or Journal Entry", [row.idx]));
|
frappe.msgprint(__("Row #{0}: Reference Document Type must be one of Expense Claim or Journal Entry", [row.idx]));
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (frm.doc.party_type == "Donor" && row.reference_doctype != "Donation") {
|
||||||
|
frappe.model.set_value(row.doctype, row.name, "reference_doctype", null);
|
||||||
|
frappe.msgprint(__("Row #{0}: Reference Document Type must be Donation", [row.idx]));
|
||||||
|
return false;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (row) {
|
if (row) {
|
||||||
|
|||||||
@@ -536,7 +536,8 @@
|
|||||||
"fieldtype": "Data",
|
"fieldtype": "Data",
|
||||||
"hidden": 1,
|
"hidden": 1,
|
||||||
"label": "Title",
|
"label": "Title",
|
||||||
"print_hide": 1
|
"print_hide": 1,
|
||||||
|
"read_only": 1
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"depends_on": "party",
|
"depends_on": "party",
|
||||||
@@ -588,7 +589,7 @@
|
|||||||
"index_web_pages_for_search": 1,
|
"index_web_pages_for_search": 1,
|
||||||
"is_submittable": 1,
|
"is_submittable": 1,
|
||||||
"links": [],
|
"links": [],
|
||||||
"modified": "2020-10-30 13:56:20.007336",
|
"modified": "2021-03-08 13:05:16.958866",
|
||||||
"modified_by": "Administrator",
|
"modified_by": "Administrator",
|
||||||
"module": "Accounts",
|
"module": "Accounts",
|
||||||
"name": "Payment Entry",
|
"name": "Payment Entry",
|
||||||
|
|||||||
@@ -72,6 +72,7 @@ class PaymentEntry(AccountsController):
|
|||||||
self.update_outstanding_amounts()
|
self.update_outstanding_amounts()
|
||||||
self.update_advance_paid()
|
self.update_advance_paid()
|
||||||
self.update_expense_claim()
|
self.update_expense_claim()
|
||||||
|
self.update_donation()
|
||||||
self.update_payment_schedule()
|
self.update_payment_schedule()
|
||||||
self.set_status()
|
self.set_status()
|
||||||
|
|
||||||
@@ -82,6 +83,7 @@ class PaymentEntry(AccountsController):
|
|||||||
self.update_outstanding_amounts()
|
self.update_outstanding_amounts()
|
||||||
self.update_advance_paid()
|
self.update_advance_paid()
|
||||||
self.update_expense_claim()
|
self.update_expense_claim()
|
||||||
|
self.update_donation(cancel=1)
|
||||||
self.delink_advance_entry_references()
|
self.delink_advance_entry_references()
|
||||||
self.update_payment_schedule(cancel=1)
|
self.update_payment_schedule(cancel=1)
|
||||||
self.set_payment_req_status()
|
self.set_payment_req_status()
|
||||||
@@ -242,9 +244,11 @@ class PaymentEntry(AccountsController):
|
|||||||
elif self.party_type == "Supplier":
|
elif self.party_type == "Supplier":
|
||||||
valid_reference_doctypes = ("Purchase Order", "Purchase Invoice", "Journal Entry")
|
valid_reference_doctypes = ("Purchase Order", "Purchase Invoice", "Journal Entry")
|
||||||
elif self.party_type == "Employee":
|
elif self.party_type == "Employee":
|
||||||
valid_reference_doctypes = ("Expense Claim", "Journal Entry", "Employee Advance")
|
valid_reference_doctypes = ("Expense Claim", "Journal Entry", "Employee Advance", "Gratuity")
|
||||||
elif self.party_type == "Shareholder":
|
elif self.party_type == "Shareholder":
|
||||||
valid_reference_doctypes = ("Journal Entry")
|
valid_reference_doctypes = ("Journal Entry")
|
||||||
|
elif self.party_type == "Donor":
|
||||||
|
valid_reference_doctypes = ("Donation")
|
||||||
|
|
||||||
for d in self.get("references"):
|
for d in self.get("references"):
|
||||||
if not d.allocated_amount:
|
if not d.allocated_amount:
|
||||||
@@ -455,6 +459,10 @@ class PaymentEntry(AccountsController):
|
|||||||
.format(total_negative_outstanding), InvalidPaymentEntry)
|
.format(total_negative_outstanding), InvalidPaymentEntry)
|
||||||
|
|
||||||
def set_title(self):
|
def set_title(self):
|
||||||
|
if frappe.flags.in_import and self.title:
|
||||||
|
# do not set title dynamically if title exists during data import.
|
||||||
|
return
|
||||||
|
|
||||||
if self.payment_type in ("Receive", "Pay"):
|
if self.payment_type in ("Receive", "Pay"):
|
||||||
self.title = self.party
|
self.title = self.party
|
||||||
else:
|
else:
|
||||||
@@ -604,7 +612,7 @@ class PaymentEntry(AccountsController):
|
|||||||
if self.payment_type in ("Receive", "Pay") and self.party:
|
if self.payment_type in ("Receive", "Pay") and self.party:
|
||||||
for d in self.get("references"):
|
for d in self.get("references"):
|
||||||
if d.allocated_amount \
|
if d.allocated_amount \
|
||||||
and d.reference_doctype in ("Sales Order", "Purchase Order", "Employee Advance"):
|
and d.reference_doctype in ("Sales Order", "Purchase Order", "Employee Advance", "Gratuity"):
|
||||||
frappe.get_doc(d.reference_doctype, d.reference_name).set_total_advance_paid()
|
frappe.get_doc(d.reference_doctype, d.reference_name).set_total_advance_paid()
|
||||||
|
|
||||||
def update_expense_claim(self):
|
def update_expense_claim(self):
|
||||||
@@ -614,6 +622,13 @@ class PaymentEntry(AccountsController):
|
|||||||
doc = frappe.get_doc("Expense Claim", d.reference_name)
|
doc = frappe.get_doc("Expense Claim", d.reference_name)
|
||||||
update_reimbursed_amount(doc, self.name)
|
update_reimbursed_amount(doc, self.name)
|
||||||
|
|
||||||
|
def update_donation(self, cancel=0):
|
||||||
|
if self.payment_type == "Receive" and self.party_type == "Donor" and self.party:
|
||||||
|
for d in self.get("references"):
|
||||||
|
if d.reference_doctype=="Donation" and d.reference_name:
|
||||||
|
is_paid = 0 if cancel else 1
|
||||||
|
frappe.db.set_value("Donation", d.reference_name, "paid", is_paid)
|
||||||
|
|
||||||
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
|
||||||
self.reference_date = nowdate()
|
self.reference_date = nowdate()
|
||||||
@@ -913,6 +928,9 @@ def get_reference_details(reference_doctype, reference_name, party_account_curre
|
|||||||
total_amount = ref_doc.get("grand_total")
|
total_amount = ref_doc.get("grand_total")
|
||||||
exchange_rate = 1
|
exchange_rate = 1
|
||||||
outstanding_amount = ref_doc.get("outstanding_amount")
|
outstanding_amount = ref_doc.get("outstanding_amount")
|
||||||
|
elif reference_doctype == "Donation":
|
||||||
|
total_amount = ref_doc.get("amount")
|
||||||
|
exchange_rate = 1
|
||||||
elif reference_doctype == "Dunning":
|
elif reference_doctype == "Dunning":
|
||||||
total_amount = ref_doc.get("dunning_amount")
|
total_amount = ref_doc.get("dunning_amount")
|
||||||
exchange_rate = 1
|
exchange_rate = 1
|
||||||
@@ -932,6 +950,8 @@ def get_reference_details(reference_doctype, reference_name, party_account_curre
|
|||||||
exchange_rate = ref_doc.get("exchange_rate")
|
exchange_rate = ref_doc.get("exchange_rate")
|
||||||
if party_account_currency != ref_doc.currency:
|
if party_account_currency != ref_doc.currency:
|
||||||
total_amount = flt(total_amount) * flt(exchange_rate)
|
total_amount = flt(total_amount) * flt(exchange_rate)
|
||||||
|
elif ref_doc.doctype == "Gratuity":
|
||||||
|
total_amount = ref_doc.amount
|
||||||
if not total_amount:
|
if not total_amount:
|
||||||
if party_account_currency == company_currency:
|
if party_account_currency == company_currency:
|
||||||
total_amount = ref_doc.base_grand_total
|
total_amount = ref_doc.base_grand_total
|
||||||
@@ -955,6 +975,8 @@ def get_reference_details(reference_doctype, reference_name, party_account_curre
|
|||||||
outstanding_amount = flt(outstanding_amount) * flt(exchange_rate)
|
outstanding_amount = flt(outstanding_amount) * flt(exchange_rate)
|
||||||
if party_account_currency == company_currency:
|
if party_account_currency == company_currency:
|
||||||
exchange_rate = 1
|
exchange_rate = 1
|
||||||
|
elif reference_doctype == "Gratuity":
|
||||||
|
outstanding_amount = ref_doc.amount - flt(ref_doc.paid_amount)
|
||||||
else:
|
else:
|
||||||
outstanding_amount = flt(total_amount) - flt(ref_doc.advance_paid)
|
outstanding_amount = flt(total_amount) - flt(ref_doc.advance_paid)
|
||||||
else:
|
else:
|
||||||
@@ -1160,10 +1182,12 @@ def set_party_type(dt):
|
|||||||
party_type = "Customer"
|
party_type = "Customer"
|
||||||
elif dt in ("Purchase Invoice", "Purchase Order"):
|
elif dt in ("Purchase Invoice", "Purchase Order"):
|
||||||
party_type = "Supplier"
|
party_type = "Supplier"
|
||||||
elif dt in ("Expense Claim", "Employee Advance"):
|
elif dt in ("Expense Claim", "Employee Advance", "Gratuity"):
|
||||||
party_type = "Employee"
|
party_type = "Employee"
|
||||||
elif dt in ("Fees"):
|
elif dt == "Fees":
|
||||||
party_type = "Student"
|
party_type = "Student"
|
||||||
|
elif dt == "Donation":
|
||||||
|
party_type = "Donor"
|
||||||
return party_type
|
return party_type
|
||||||
|
|
||||||
def set_party_account(dt, dn, doc, party_type):
|
def set_party_account(dt, dn, doc, party_type):
|
||||||
@@ -1177,6 +1201,8 @@ def set_party_account(dt, dn, doc, party_type):
|
|||||||
party_account = doc.advance_account
|
party_account = doc.advance_account
|
||||||
elif dt == "Expense Claim":
|
elif dt == "Expense Claim":
|
||||||
party_account = doc.payable_account
|
party_account = doc.payable_account
|
||||||
|
elif dt == "Gratuity":
|
||||||
|
party_account = doc.payable_account
|
||||||
else:
|
else:
|
||||||
party_account = get_party_account(party_type, doc.get(party_type.lower()), doc.company)
|
party_account = get_party_account(party_type, doc.get(party_type.lower()), doc.company)
|
||||||
return party_account
|
return party_account
|
||||||
@@ -1189,7 +1215,7 @@ def set_party_account_currency(dt, party_account, doc):
|
|||||||
return party_account_currency
|
return party_account_currency
|
||||||
|
|
||||||
def set_payment_type(dt, doc):
|
def set_payment_type(dt, doc):
|
||||||
if (dt == "Sales Order" or (dt in ("Sales Invoice", "Fees", "Dunning") and doc.outstanding_amount > 0)) \
|
if (dt in ("Sales Order", "Donation") or (dt in ("Sales Invoice", "Fees", "Dunning") and doc.outstanding_amount > 0)) \
|
||||||
or (dt=="Purchase Invoice" and doc.outstanding_amount < 0):
|
or (dt=="Purchase Invoice" and doc.outstanding_amount < 0):
|
||||||
payment_type = "Receive"
|
payment_type = "Receive"
|
||||||
else:
|
else:
|
||||||
@@ -1222,6 +1248,12 @@ def set_grand_total_and_outstanding_amount(party_amount, dt, party_account_curre
|
|||||||
elif dt == "Dunning":
|
elif dt == "Dunning":
|
||||||
grand_total = doc.grand_total
|
grand_total = doc.grand_total
|
||||||
outstanding_amount = doc.grand_total
|
outstanding_amount = doc.grand_total
|
||||||
|
elif dt == "Donation":
|
||||||
|
grand_total = doc.amount
|
||||||
|
outstanding_amount = doc.amount
|
||||||
|
elif dt == "Gratuity":
|
||||||
|
grand_total = doc.amount
|
||||||
|
outstanding_amount = flt(doc.amount) - flt(doc.paid_amount)
|
||||||
else:
|
else:
|
||||||
if party_account_currency == doc.company_currency:
|
if party_account_currency == doc.company_currency:
|
||||||
grand_total = flt(doc.get("base_rounded_total") or doc.base_grand_total)
|
grand_total = flt(doc.get("base_rounded_total") or doc.base_grand_total)
|
||||||
|
|||||||
@@ -99,10 +99,10 @@ class TestPOSInvoice(unittest.TestCase):
|
|||||||
item_row = inv.get("items")[0]
|
item_row = inv.get("items")[0]
|
||||||
|
|
||||||
add_items = [
|
add_items = [
|
||||||
(54, '_Test Account Excise Duty @ 12'),
|
(54, '_Test Account Excise Duty @ 12 - _TC'),
|
||||||
(288, '_Test Account Excise Duty @ 15'),
|
(288, '_Test Account Excise Duty @ 15 - _TC'),
|
||||||
(144, '_Test Account Excise Duty @ 20'),
|
(144, '_Test Account Excise Duty @ 20 - _TC'),
|
||||||
(430, '_Test Item Tax Template 1')
|
(430, '_Test Item Tax Template 1 - _TC')
|
||||||
]
|
]
|
||||||
for qty, item_tax_template in add_items:
|
for qty, item_tax_template in add_items:
|
||||||
item_row_copy = copy.deepcopy(item_row)
|
item_row_copy = copy.deepcopy(item_row)
|
||||||
|
|||||||
@@ -58,6 +58,7 @@
|
|||||||
"rejected_warehouse",
|
"rejected_warehouse",
|
||||||
"col_break_warehouse",
|
"col_break_warehouse",
|
||||||
"set_from_warehouse",
|
"set_from_warehouse",
|
||||||
|
"supplier_warehouse",
|
||||||
"is_subcontracted",
|
"is_subcontracted",
|
||||||
"items_section",
|
"items_section",
|
||||||
"update_stock",
|
"update_stock",
|
||||||
@@ -1350,7 +1351,7 @@
|
|||||||
"options": "Company"
|
"options": "Company"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"depends_on": "eval:doc.update_stock && (doc.is_subcontracted==\"Yes\" || doc.is_internal_supplier)",
|
"depends_on": "eval:doc.update_stock && doc.is_internal_supplier",
|
||||||
"description": "Sets 'From Warehouse' in each row of the items table.",
|
"description": "Sets 'From Warehouse' in each row of the items table.",
|
||||||
"fieldname": "set_from_warehouse",
|
"fieldname": "set_from_warehouse",
|
||||||
"fieldtype": "Link",
|
"fieldtype": "Link",
|
||||||
@@ -1360,13 +1361,24 @@
|
|||||||
"print_hide": 1,
|
"print_hide": 1,
|
||||||
"print_width": "50px",
|
"print_width": "50px",
|
||||||
"width": "50px"
|
"width": "50px"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"depends_on": "eval:doc.update_stock && doc.is_subcontracted==\"Yes\"",
|
||||||
|
"fieldname": "supplier_warehouse",
|
||||||
|
"fieldtype": "Link",
|
||||||
|
"label": "Supplier Warehouse",
|
||||||
|
"no_copy": 1,
|
||||||
|
"options": "Warehouse",
|
||||||
|
"print_hide": 1,
|
||||||
|
"print_width": "50px",
|
||||||
|
"width": "50px"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"icon": "fa fa-file-text",
|
"icon": "fa fa-file-text",
|
||||||
"idx": 204,
|
"idx": 204,
|
||||||
"is_submittable": 1,
|
"is_submittable": 1,
|
||||||
"links": [],
|
"links": [],
|
||||||
"modified": "2020-12-26 20:49:03.305063",
|
"modified": "2021-03-09 21:12:30.422084",
|
||||||
"modified_by": "Administrator",
|
"modified_by": "Administrator",
|
||||||
"module": "Accounts",
|
"module": "Accounts",
|
||||||
"name": "Purchase Invoice",
|
"name": "Purchase Invoice",
|
||||||
|
|||||||
@@ -968,7 +968,7 @@ class PurchaseInvoice(BuyingController):
|
|||||||
# base_rounding_adjustment may become zero due to small precision
|
# base_rounding_adjustment may become zero due to small precision
|
||||||
# eg: rounding_adjustment = 0.01 and exchange rate = 0.05 and precision of base_rounding_adjustment is 2
|
# eg: rounding_adjustment = 0.01 and exchange rate = 0.05 and precision of base_rounding_adjustment is 2
|
||||||
# then base_rounding_adjustment becomes zero and error is thrown in GL Entry
|
# then base_rounding_adjustment becomes zero and error is thrown in GL Entry
|
||||||
if self.rounding_adjustment and self.base_rounding_adjustment:
|
if not self.is_internal_transfer() and self.rounding_adjustment and self.base_rounding_adjustment:
|
||||||
round_off_account, round_off_cost_center = \
|
round_off_account, round_off_cost_center = \
|
||||||
get_round_off_account_and_cost_center(self.company)
|
get_round_off_account_and_cost_center(self.company)
|
||||||
|
|
||||||
|
|||||||
@@ -18,7 +18,7 @@
|
|||||||
"expense_account": "_Test Account Cost for Goods Sold - _TC",
|
"expense_account": "_Test Account Cost for Goods Sold - _TC",
|
||||||
"item_code": "_Test Item Home Desktop 100",
|
"item_code": "_Test Item Home Desktop 100",
|
||||||
"item_name": "_Test Item Home Desktop 100",
|
"item_name": "_Test Item Home Desktop 100",
|
||||||
"item_tax_template": "_Test Account Excise Duty @ 10",
|
"item_tax_template": "_Test Account Excise Duty @ 10 - _TC",
|
||||||
"parentfield": "items",
|
"parentfield": "items",
|
||||||
"qty": 10,
|
"qty": 10,
|
||||||
"rate": 50,
|
"rate": 50,
|
||||||
|
|||||||
@@ -148,7 +148,7 @@
|
|||||||
"expense_account": "_Test Account Cost for Goods Sold - _TC",
|
"expense_account": "_Test Account Cost for Goods Sold - _TC",
|
||||||
"item_code": "_Test Item Home Desktop 100",
|
"item_code": "_Test Item Home Desktop 100",
|
||||||
"item_name": "_Test Item Home Desktop 100",
|
"item_name": "_Test Item Home Desktop 100",
|
||||||
"item_tax_template": "_Test Account Excise Duty @ 10",
|
"item_tax_template": "_Test Account Excise Duty @ 10 - _TC",
|
||||||
"parentfield": "items",
|
"parentfield": "items",
|
||||||
"price_list_rate": 50,
|
"price_list_rate": 50,
|
||||||
"qty": 10,
|
"qty": 10,
|
||||||
@@ -276,7 +276,7 @@
|
|||||||
"expense_account": "_Test Account Cost for Goods Sold - _TC",
|
"expense_account": "_Test Account Cost for Goods Sold - _TC",
|
||||||
"item_code": "_Test Item Home Desktop 100",
|
"item_code": "_Test Item Home Desktop 100",
|
||||||
"item_name": "_Test Item Home Desktop 100",
|
"item_name": "_Test Item Home Desktop 100",
|
||||||
"item_tax_template": "_Test Account Excise Duty @ 10",
|
"item_tax_template": "_Test Account Excise Duty @ 10 - _TC",
|
||||||
"parentfield": "items",
|
"parentfield": "items",
|
||||||
"price_list_rate": 62.5,
|
"price_list_rate": 62.5,
|
||||||
"qty": 10,
|
"qty": 10,
|
||||||
|
|||||||
@@ -405,10 +405,10 @@ class TestSalesInvoice(unittest.TestCase):
|
|||||||
item_row = si.get("items")[0]
|
item_row = si.get("items")[0]
|
||||||
|
|
||||||
add_items = [
|
add_items = [
|
||||||
(54, '_Test Account Excise Duty @ 12'),
|
(54, '_Test Account Excise Duty @ 12 - _TC'),
|
||||||
(288, '_Test Account Excise Duty @ 15'),
|
(288, '_Test Account Excise Duty @ 15 - _TC'),
|
||||||
(144, '_Test Account Excise Duty @ 20'),
|
(144, '_Test Account Excise Duty @ 20 - _TC'),
|
||||||
(430, '_Test Item Tax Template 1')
|
(430, '_Test Item Tax Template 1 - _TC')
|
||||||
]
|
]
|
||||||
for qty, item_tax_template in add_items:
|
for qty, item_tax_template in add_items:
|
||||||
item_row_copy = copy.deepcopy(item_row)
|
item_row_copy = copy.deepcopy(item_row)
|
||||||
@@ -2077,14 +2077,14 @@ def check_gl_entries(doc, voucher_no, expected_gle, posting_date):
|
|||||||
item.save()
|
item.save()
|
||||||
|
|
||||||
item.append("taxes", {
|
item.append("taxes", {
|
||||||
"item_tax_template": "_Test Item Tax Template 1",
|
"item_tax_template": "_Test Item Tax Template 1 - _TC",
|
||||||
"valid_from": add_days(nowdate(), 1)
|
"valid_from": add_days(nowdate(), 1)
|
||||||
})
|
})
|
||||||
|
|
||||||
item.save()
|
item.save()
|
||||||
|
|
||||||
sales_invoice = create_sales_invoice(item = "_Test Item 2", do_not_save=1)
|
sales_invoice = create_sales_invoice(item = "_Test Item 2", do_not_save=1)
|
||||||
sales_invoice.items[0].item_tax_template = "_Test Item Tax Template 1"
|
sales_invoice.items[0].item_tax_template = "_Test Item Tax Template 1 - _TC"
|
||||||
self.assertRaises(frappe.ValidationError, sales_invoice.save)
|
self.assertRaises(frappe.ValidationError, sales_invoice.save)
|
||||||
|
|
||||||
item.taxes = []
|
item.taxes = []
|
||||||
|
|||||||
@@ -240,8 +240,7 @@ def get_company_currency(filters=None):
|
|||||||
def calculate_values(accounts_by_name, gl_entries_by_account, companies, start_date, filters):
|
def calculate_values(accounts_by_name, gl_entries_by_account, companies, start_date, filters):
|
||||||
for entries in gl_entries_by_account.values():
|
for entries in gl_entries_by_account.values():
|
||||||
for entry in entries:
|
for entry in entries:
|
||||||
key = entry.account_number or entry.account_name
|
d = accounts_by_name.get(entry.account_name)
|
||||||
d = accounts_by_name.get(key)
|
|
||||||
if d:
|
if d:
|
||||||
for company in companies:
|
for company in companies:
|
||||||
# check if posting date is within the period
|
# check if posting date is within the period
|
||||||
@@ -256,7 +255,8 @@ def accumulate_values_into_parents(accounts, accounts_by_name, companies):
|
|||||||
"""accumulate children's values in parent accounts"""
|
"""accumulate children's values in parent accounts"""
|
||||||
for d in reversed(accounts):
|
for d in reversed(accounts):
|
||||||
if d.parent_account:
|
if d.parent_account:
|
||||||
account = d.parent_account.split(' - ')[0].strip()
|
account = d.parent_account_name
|
||||||
|
|
||||||
if not accounts_by_name.get(account):
|
if not accounts_by_name.get(account):
|
||||||
continue
|
continue
|
||||||
|
|
||||||
@@ -267,16 +267,34 @@ def accumulate_values_into_parents(accounts, accounts_by_name, companies):
|
|||||||
accounts_by_name[account]["opening_balance"] = \
|
accounts_by_name[account]["opening_balance"] = \
|
||||||
accounts_by_name[account].get("opening_balance", 0.0) + d.get("opening_balance", 0.0)
|
accounts_by_name[account].get("opening_balance", 0.0) + d.get("opening_balance", 0.0)
|
||||||
|
|
||||||
|
|
||||||
def get_account_heads(root_type, companies, filters):
|
def get_account_heads(root_type, companies, filters):
|
||||||
accounts = get_accounts(root_type, filters)
|
accounts = get_accounts(root_type, filters)
|
||||||
|
|
||||||
if not accounts:
|
if not accounts:
|
||||||
return None, None
|
return None, None
|
||||||
|
|
||||||
|
accounts = update_parent_account_names(accounts)
|
||||||
|
|
||||||
accounts, accounts_by_name, parent_children_map = filter_accounts(accounts)
|
accounts, accounts_by_name, parent_children_map = filter_accounts(accounts)
|
||||||
|
|
||||||
return accounts, accounts_by_name
|
return accounts, accounts_by_name
|
||||||
|
|
||||||
|
def update_parent_account_names(accounts):
|
||||||
|
"""Update parent_account_name in accounts list.
|
||||||
|
|
||||||
|
parent_name is `name` of parent account which could have other prefix
|
||||||
|
of account_number and suffix of company abbr. This function adds key called
|
||||||
|
`parent_account_name` which does not have such prefix/suffix.
|
||||||
|
"""
|
||||||
|
name_to_account_map = { d.name : d.account_name for d in accounts }
|
||||||
|
|
||||||
|
for account in accounts:
|
||||||
|
if account.parent_account:
|
||||||
|
account["parent_account_name"] = name_to_account_map[account.parent_account]
|
||||||
|
|
||||||
|
return accounts
|
||||||
|
|
||||||
def get_companies(filters):
|
def get_companies(filters):
|
||||||
companies = {}
|
companies = {}
|
||||||
all_companies = get_subsidiary_companies(filters.get('company'))
|
all_companies = get_subsidiary_companies(filters.get('company'))
|
||||||
@@ -381,9 +399,9 @@ def set_gl_entries_by_account(from_date, to_date, root_lft, root_rgt, filters, g
|
|||||||
convert_to_presentation_currency(gl_entries, currency_info, filters.get('company'))
|
convert_to_presentation_currency(gl_entries, currency_info, filters.get('company'))
|
||||||
|
|
||||||
for entry in gl_entries:
|
for entry in gl_entries:
|
||||||
key = entry.account_number or entry.account_name
|
account_name = entry.account_name
|
||||||
validate_entries(key, entry, accounts_by_name, accounts)
|
validate_entries(account_name, entry, accounts_by_name, accounts)
|
||||||
gl_entries_by_account.setdefault(key, []).append(entry)
|
gl_entries_by_account.setdefault(account_name, []).append(entry)
|
||||||
|
|
||||||
return gl_entries_by_account
|
return gl_entries_by_account
|
||||||
|
|
||||||
@@ -452,8 +470,7 @@ def filter_accounts(accounts, depth=10):
|
|||||||
parent_children_map = {}
|
parent_children_map = {}
|
||||||
accounts_by_name = {}
|
accounts_by_name = {}
|
||||||
for d in accounts:
|
for d in accounts:
|
||||||
key = d.account_number or d.account_name
|
accounts_by_name[d.account_name] = d
|
||||||
accounts_by_name[key] = d
|
|
||||||
parent_children_map.setdefault(d.parent_account or None, []).append(d)
|
parent_children_map.setdefault(d.parent_account or None, []).append(d)
|
||||||
|
|
||||||
filtered_accounts = []
|
filtered_accounts = []
|
||||||
|
|||||||
@@ -129,6 +129,9 @@ def get_gl_entries(filters, accounting_dimensions):
|
|||||||
|
|
||||||
order_by_statement = "order by posting_date, account, creation"
|
order_by_statement = "order by posting_date, account, creation"
|
||||||
|
|
||||||
|
if filters.get("include_dimensions"):
|
||||||
|
order_by_statement = "order by posting_date, creation"
|
||||||
|
|
||||||
if filters.get("group_by") == _("Group by Voucher"):
|
if filters.get("group_by") == _("Group by Voucher"):
|
||||||
order_by_statement = "order by posting_date, voucher_type, voucher_no"
|
order_by_statement = "order by posting_date, voucher_type, voucher_no"
|
||||||
|
|
||||||
@@ -142,7 +145,9 @@ def get_gl_entries(filters, accounting_dimensions):
|
|||||||
|
|
||||||
distributed_cost_center_query = ""
|
distributed_cost_center_query = ""
|
||||||
if filters and filters.get('cost_center'):
|
if filters and filters.get('cost_center'):
|
||||||
select_fields_with_percentage = """, debit*(DCC_allocation.percentage_allocation/100) as debit, credit*(DCC_allocation.percentage_allocation/100) as credit, debit_in_account_currency*(DCC_allocation.percentage_allocation/100) as debit_in_account_currency,
|
select_fields_with_percentage = """, debit*(DCC_allocation.percentage_allocation/100) as debit,
|
||||||
|
credit*(DCC_allocation.percentage_allocation/100) as credit,
|
||||||
|
debit_in_account_currency*(DCC_allocation.percentage_allocation/100) as debit_in_account_currency,
|
||||||
credit_in_account_currency*(DCC_allocation.percentage_allocation/100) as credit_in_account_currency """
|
credit_in_account_currency*(DCC_allocation.percentage_allocation/100) as credit_in_account_currency """
|
||||||
|
|
||||||
distributed_cost_center_query = """
|
distributed_cost_center_query = """
|
||||||
@@ -200,7 +205,7 @@ def get_gl_entries(filters, accounting_dimensions):
|
|||||||
|
|
||||||
def get_conditions(filters):
|
def get_conditions(filters):
|
||||||
conditions = []
|
conditions = []
|
||||||
if filters.get("account"):
|
if filters.get("account") and not filters.get("include_dimensions"):
|
||||||
lft, rgt = frappe.db.get_value("Account", filters["account"], ["lft", "rgt"])
|
lft, rgt = frappe.db.get_value("Account", filters["account"], ["lft", "rgt"])
|
||||||
conditions.append("""account in (select name from tabAccount
|
conditions.append("""account in (select name from tabAccount
|
||||||
where lft>=%s and rgt<=%s and docstatus<2)""" % (lft, rgt))
|
where lft>=%s and rgt<=%s and docstatus<2)""" % (lft, rgt))
|
||||||
@@ -245,17 +250,19 @@ def get_conditions(filters):
|
|||||||
if match_conditions:
|
if match_conditions:
|
||||||
conditions.append(match_conditions)
|
conditions.append(match_conditions)
|
||||||
|
|
||||||
accounting_dimensions = get_accounting_dimensions(as_list=False)
|
if filters.get("include_dimensions"):
|
||||||
|
accounting_dimensions = get_accounting_dimensions(as_list=False)
|
||||||
|
|
||||||
if accounting_dimensions:
|
if accounting_dimensions:
|
||||||
for dimension in accounting_dimensions:
|
for dimension in accounting_dimensions:
|
||||||
if filters.get(dimension.fieldname):
|
if not dimension.disabled:
|
||||||
if frappe.get_cached_value('DocType', dimension.document_type, 'is_tree'):
|
if filters.get(dimension.fieldname):
|
||||||
filters[dimension.fieldname] = get_dimension_with_children(dimension.document_type,
|
if frappe.get_cached_value('DocType', dimension.document_type, 'is_tree'):
|
||||||
filters.get(dimension.fieldname))
|
filters[dimension.fieldname] = get_dimension_with_children(dimension.document_type,
|
||||||
conditions.append("{0} in %({0})s".format(dimension.fieldname))
|
filters.get(dimension.fieldname))
|
||||||
else:
|
conditions.append("{0} in %({0})s".format(dimension.fieldname))
|
||||||
conditions.append("{0} in (%({0})s)".format(dimension.fieldname))
|
else:
|
||||||
|
conditions.append("{0} in (%({0})s)".format(dimension.fieldname))
|
||||||
|
|
||||||
return "and {}".format(" and ".join(conditions)) if conditions else ""
|
return "and {}".format(" and ".join(conditions)) if conditions else ""
|
||||||
|
|
||||||
|
|||||||
@@ -55,7 +55,7 @@ def get_result(filters):
|
|||||||
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,
|
||||||
filters.company, filters.from_date, filters.to_date)
|
filters.company, filters.from_date, filters.to_date, filters.fiscal_year)
|
||||||
|
|
||||||
if total_invoiced_amount or tds_deducted:
|
if total_invoiced_amount or tds_deducted:
|
||||||
row = [supplier.pan, supplier.name]
|
row = [supplier.pan, supplier.name]
|
||||||
@@ -68,7 +68,7 @@ def get_result(filters):
|
|||||||
|
|
||||||
return out
|
return out
|
||||||
|
|
||||||
def get_invoice_and_tds_amount(supplier, account, company, from_date, to_date):
|
def get_invoice_and_tds_amount(supplier, account, company, from_date, to_date, fiscal_year):
|
||||||
''' calculate total invoice amount and total tds deducted for given supplier '''
|
''' calculate total invoice amount and total tds deducted for given supplier '''
|
||||||
|
|
||||||
entries = frappe.db.sql("""
|
entries = frappe.db.sql("""
|
||||||
@@ -94,7 +94,9 @@ 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)
|
date_range_filter = [fiscal_year, from_date, to_date]
|
||||||
|
|
||||||
|
debit_note_amount = get_debit_note_amount([supplier], date_range_filter, company=company)
|
||||||
|
|
||||||
total_invoiced_amount = supplier_credit_amount + tds_deducted - debit_note_amount
|
total_invoiced_amount = supplier_credit_amount + tds_deducted - debit_note_amount
|
||||||
|
|
||||||
|
|||||||
@@ -231,12 +231,12 @@ class TestPurchaseOrder(unittest.TestCase):
|
|||||||
new_item_with_tax = frappe.get_doc("Item", "Test Item with Tax")
|
new_item_with_tax = frappe.get_doc("Item", "Test Item with Tax")
|
||||||
|
|
||||||
new_item_with_tax.append("taxes", {
|
new_item_with_tax.append("taxes", {
|
||||||
"item_tax_template": "Test Update Items Template",
|
"item_tax_template": "Test Update Items Template - _TC",
|
||||||
"valid_from": nowdate()
|
"valid_from": nowdate()
|
||||||
})
|
})
|
||||||
new_item_with_tax.save()
|
new_item_with_tax.save()
|
||||||
|
|
||||||
tax_template = "_Test Account Excise Duty @ 10"
|
tax_template = "_Test Account Excise Duty @ 10 - _TC"
|
||||||
item = "_Test Item Home Desktop 100"
|
item = "_Test Item Home Desktop 100"
|
||||||
if not frappe.db.exists("Item Tax", {"parent":item, "item_tax_template":tax_template}):
|
if not frappe.db.exists("Item Tax", {"parent":item, "item_tax_template":tax_template}):
|
||||||
item_doc = frappe.get_doc("Item", item)
|
item_doc = frappe.get_doc("Item", item)
|
||||||
@@ -287,7 +287,7 @@ class TestPurchaseOrder(unittest.TestCase):
|
|||||||
po.cancel()
|
po.cancel()
|
||||||
po.delete()
|
po.delete()
|
||||||
new_item_with_tax.delete()
|
new_item_with_tax.delete()
|
||||||
frappe.get_doc("Item Tax Template", "Test Update Items Template").delete()
|
frappe.get_doc("Item Tax Template", "Test Update Items Template - _TC").delete()
|
||||||
|
|
||||||
def test_update_child_uom_conv_factor_change(self):
|
def test_update_child_uom_conv_factor_change(self):
|
||||||
po = create_purchase_order(item_code="_Test FG Item", is_subcontracted="Yes")
|
po = create_purchase_order(item_code="_Test FG Item", is_subcontracted="Yes")
|
||||||
|
|||||||
@@ -278,7 +278,7 @@ class BuyingController(StockController):
|
|||||||
|
|
||||||
if self.is_subcontracted == "Yes":
|
if self.is_subcontracted == "Yes":
|
||||||
if self.doctype in ["Purchase Receipt", "Purchase Invoice"] and not self.supplier_warehouse:
|
if self.doctype in ["Purchase Receipt", "Purchase Invoice"] and not self.supplier_warehouse:
|
||||||
frappe.throw(_("Supplier Warehouse mandatory for sub-contracted Purchase Receipt"))
|
frappe.throw(_("Supplier Warehouse mandatory for sub-contracted {0}").format(self.doctype))
|
||||||
|
|
||||||
for item in self.get("items"):
|
for item in self.get("items"):
|
||||||
if item in self.sub_contracted_items and not item.bom:
|
if item in self.sub_contracted_items and not item.bom:
|
||||||
|
|||||||
@@ -406,7 +406,8 @@ class StockController(AccountsController):
|
|||||||
def set_rate_of_stock_uom(self):
|
def set_rate_of_stock_uom(self):
|
||||||
if self.doctype in ["Purchase Receipt", "Purchase Invoice", "Purchase Order", "Sales Invoice", "Sales Order", "Delivery Note", "Quotation"]:
|
if self.doctype in ["Purchase Receipt", "Purchase Invoice", "Purchase Order", "Sales Invoice", "Sales Order", "Delivery Note", "Quotation"]:
|
||||||
for d in self.get("items"):
|
for d in self.get("items"):
|
||||||
d.stock_uom_rate = d.rate / d.conversion_factor
|
if d.conversion_factor:
|
||||||
|
d.stock_uom_rate = d.rate / d.conversion_factor
|
||||||
|
|
||||||
def validate_internal_transfer(self):
|
def validate_internal_transfer(self):
|
||||||
if self.doctype in ('Sales Invoice', 'Delivery Note', 'Purchase Invoice', 'Purchase Receipt') \
|
if self.doctype in ('Sales Invoice', 'Delivery Note', 'Purchase Invoice', 'Purchase Receipt') \
|
||||||
|
|||||||
@@ -36,6 +36,7 @@ def enroll_student(source_name):
|
|||||||
student.save()
|
student.save()
|
||||||
program_enrollment = frappe.new_doc("Program Enrollment")
|
program_enrollment = frappe.new_doc("Program Enrollment")
|
||||||
program_enrollment.student = student.name
|
program_enrollment.student = student.name
|
||||||
|
program_enrollment.student_category = student.student_category
|
||||||
program_enrollment.student_name = student.title
|
program_enrollment.student_name = student.title
|
||||||
program_enrollment.program = frappe.db.get_value("Student Applicant", source_name, "program")
|
program_enrollment.program = frappe.db.get_value("Student Applicant", source_name, "program")
|
||||||
frappe.publish_realtime('enroll_student_progress', {"progress": [2, 4]}, user=frappe.session.user)
|
frappe.publish_realtime('enroll_student_progress', {"progress": [2, 4]}, user=frappe.session.user)
|
||||||
|
|||||||
@@ -30,7 +30,7 @@ class ProgramEnrollmentTool(Document):
|
|||||||
.format(condition), self.as_dict(), as_dict=1)
|
.format(condition), self.as_dict(), as_dict=1)
|
||||||
elif self.get_students_from == "Program Enrollment":
|
elif self.get_students_from == "Program Enrollment":
|
||||||
condition2 = 'and student_batch_name=%(student_batch)s' if self.student_batch else " "
|
condition2 = 'and student_batch_name=%(student_batch)s' if self.student_batch else " "
|
||||||
students = frappe.db.sql('''select student, student_name, student_batch_name from `tabProgram Enrollment`
|
students = frappe.db.sql('''select student, student_name, student_batch_name, student_category from `tabProgram Enrollment`
|
||||||
where program=%(program)s and academic_year=%(academic_year)s {0} {1} and docstatus != 2'''
|
where program=%(program)s and academic_year=%(academic_year)s {0} {1} and docstatus != 2'''
|
||||||
.format(condition, condition2), self.as_dict(), as_dict=1)
|
.format(condition, condition2), self.as_dict(), as_dict=1)
|
||||||
|
|
||||||
@@ -57,6 +57,7 @@ class ProgramEnrollmentTool(Document):
|
|||||||
prog_enrollment = frappe.new_doc("Program Enrollment")
|
prog_enrollment = frappe.new_doc("Program Enrollment")
|
||||||
prog_enrollment.student = stud.student
|
prog_enrollment.student = stud.student
|
||||||
prog_enrollment.student_name = stud.student_name
|
prog_enrollment.student_name = stud.student_name
|
||||||
|
prog_enrollment.student_category = stud.student_category
|
||||||
prog_enrollment.program = self.new_program
|
prog_enrollment.program = self.new_program
|
||||||
prog_enrollment.academic_year = self.new_academic_year
|
prog_enrollment.academic_year = self.new_academic_year
|
||||||
prog_enrollment.academic_term = self.new_academic_term
|
prog_enrollment.academic_term = self.new_academic_term
|
||||||
|
|||||||
@@ -11,6 +11,7 @@
|
|||||||
"middle_name",
|
"middle_name",
|
||||||
"last_name",
|
"last_name",
|
||||||
"program",
|
"program",
|
||||||
|
"student_category",
|
||||||
"lms_only",
|
"lms_only",
|
||||||
"paid",
|
"paid",
|
||||||
"column_break_8",
|
"column_break_8",
|
||||||
@@ -257,12 +258,18 @@
|
|||||||
"options": "Student Applicant",
|
"options": "Student Applicant",
|
||||||
"print_hide": 1,
|
"print_hide": 1,
|
||||||
"read_only": 1
|
"read_only": 1
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "student_category",
|
||||||
|
"fieldtype": "Link",
|
||||||
|
"label": "Student Category",
|
||||||
|
"options": "Student Category"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"image_field": "image",
|
"image_field": "image",
|
||||||
"is_submittable": 1,
|
"is_submittable": 1,
|
||||||
"links": [],
|
"links": [],
|
||||||
"modified": "2020-10-05 13:59:45.631647",
|
"modified": "2021-03-01 23:00:25.119241",
|
||||||
"modified_by": "Administrator",
|
"modified_by": "Administrator",
|
||||||
"module": "Education",
|
"module": "Education",
|
||||||
"name": "Student Applicant",
|
"name": "Student Applicant",
|
||||||
|
|||||||
@@ -364,7 +364,7 @@ let calculate_age = function(birth) {
|
|||||||
let age = new Date();
|
let age = new Date();
|
||||||
age.setTime(ageMS);
|
age.setTime(ageMS);
|
||||||
let years = age.getFullYear() - 1970;
|
let years = age.getFullYear() - 1970;
|
||||||
return years + ' Year(s) ' + age.getMonth() + ' Month(s) ' + age.getDate() + ' Day(s)';
|
return `${years} ${__('Years(s)')} ${age.getMonth()} ${__('Month(s)')} ${age.getDate()} ${__('Day(s)')}`;
|
||||||
};
|
};
|
||||||
|
|
||||||
// List Stock items
|
// List Stock items
|
||||||
|
|||||||
@@ -258,5 +258,5 @@ var calculate_age = function (dob) {
|
|||||||
var age = new Date();
|
var age = new Date();
|
||||||
age.setTime(ageMS);
|
age.setTime(ageMS);
|
||||||
var years = age.getFullYear() - 1970;
|
var years = age.getFullYear() - 1970;
|
||||||
return years + ' Year(s) ' + age.getMonth() + ' Month(s) ' + age.getDate() + ' Day(s)';
|
return `${years} ${__('Years(s)')} ${age.getMonth()} ${__('Month(s)')} ${age.getDate()} ${__('Day(s)')}`;
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -46,11 +46,11 @@ frappe.ui.form.on('Patient', {
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
onload: function (frm) {
|
onload: function (frm) {
|
||||||
if(!frm.doc.dob){
|
if (!frm.doc.dob) {
|
||||||
$(frm.fields_dict['age_html'].wrapper).html('');
|
$(frm.fields_dict['age_html'].wrapper).html('');
|
||||||
}
|
}
|
||||||
if(frm.doc.dob){
|
if (frm.doc.dob) {
|
||||||
$(frm.fields_dict['age_html'].wrapper).html('AGE : ' + get_age(frm.doc.dob));
|
$(frm.fields_dict['age_html'].wrapper).html(`${__('AGE')} : ${get_age(frm.doc.dob)}`);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
@@ -65,7 +65,7 @@ frappe.ui.form.on('Patient', 'dob', function(frm) {
|
|||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
let age_str = get_age(frm.doc.dob);
|
let age_str = get_age(frm.doc.dob);
|
||||||
$(frm.fields_dict['age_html'].wrapper).html('AGE : ' + age_str);
|
$(frm.fields_dict['age_html'].wrapper).html(`${__('AGE')} : ${age_str}`);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
|
|||||||
@@ -108,7 +108,7 @@ class Patient(Document):
|
|||||||
if self.dob:
|
if self.dob:
|
||||||
dob = getdate(self.dob)
|
dob = getdate(self.dob)
|
||||||
age = dateutil.relativedelta.relativedelta(getdate(), dob)
|
age = dateutil.relativedelta.relativedelta(getdate(), dob)
|
||||||
age_str = str(age.years) + ' year(s) ' + str(age.months) + ' month(s) ' + str(age.days) + ' day(s)'
|
age_str = str(age.years) + ' ' + _("Years(s)") + ' ' + str(age.months) + ' ' + _("Month(s)") + ' ' + str(age.days) + ' ' + _("Day(s)")
|
||||||
return age_str
|
return age_str
|
||||||
|
|
||||||
def invoice_patient_registration(self):
|
def invoice_patient_registration(self):
|
||||||
|
|||||||
@@ -596,5 +596,5 @@ let calculate_age = function(birth) {
|
|||||||
let age = new Date();
|
let age = new Date();
|
||||||
age.setTime(ageMS);
|
age.setTime(ageMS);
|
||||||
let years = age.getFullYear() - 1970;
|
let years = age.getFullYear() - 1970;
|
||||||
return years + ' Year(s) ' + age.getMonth() + ' Month(s) ' + age.getDate() + ' Day(s)';
|
return `${years} ${__('Years(s)')} ${age.getMonth()} ${__('Month(s)')} ${age.getDate()} ${__('Day(s)')}`;
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -358,5 +358,5 @@ let calculate_age = function(birth) {
|
|||||||
let age = new Date();
|
let age = new Date();
|
||||||
age.setTime(ageMS);
|
age.setTime(ageMS);
|
||||||
let years = age.getFullYear() - 1970;
|
let years = age.getFullYear() - 1970;
|
||||||
return years + ' Year(s) ' + age.getMonth() + ' Month(s) ' + age.getDate() + ' Day(s)';
|
return `${years} ${__('Years(s)')} ${age.getMonth()} ${__('Month(s)')} ${age.getDate()} ${__('Day(s)')}`;
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -36,5 +36,5 @@ var calculate_age = function(birth) {
|
|||||||
var age = new Date();
|
var age = new Date();
|
||||||
age.setTime(ageMS);
|
age.setTime(ageMS);
|
||||||
var years = age.getFullYear() - 1970;
|
var years = age.getFullYear() - 1970;
|
||||||
return years + ' Year(s) ' + age.getMonth() + ' Month(s) ' + age.getDate() + ' Day(s)';
|
return `${years} ${__('Years(s)')} ${age.getMonth()} ${__('Month(s)')} ${age.getDate()} ${__('Day(s)')}`;
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -131,6 +131,10 @@ def mark_bulk_attendance(data):
|
|||||||
data = json.loads(data)
|
data = json.loads(data)
|
||||||
data = frappe._dict(data)
|
data = frappe._dict(data)
|
||||||
company = frappe.get_value('Employee', data.employee, 'company')
|
company = frappe.get_value('Employee', data.employee, 'company')
|
||||||
|
if not data.unmarked_days:
|
||||||
|
frappe.throw(_("Please select a date."))
|
||||||
|
return
|
||||||
|
|
||||||
for date in data.unmarked_days:
|
for date in data.unmarked_days:
|
||||||
doc_dict = {
|
doc_dict = {
|
||||||
'doctype': 'Attendance',
|
'doctype': 'Attendance',
|
||||||
|
|||||||
@@ -12,7 +12,7 @@ frappe.listview_settings['Attendance'] = {
|
|||||||
onload: function(list_view) {
|
onload: function(list_view) {
|
||||||
let me = this;
|
let me = this;
|
||||||
const months = moment.months()
|
const months = moment.months()
|
||||||
list_view.page.add_inner_button( __("Mark Attendance"), function(){
|
list_view.page.add_inner_button( __("Mark Attendance"), function() {
|
||||||
let dialog = new frappe.ui.Dialog({
|
let dialog = new frappe.ui.Dialog({
|
||||||
title: __("Mark Attendance"),
|
title: __("Mark Attendance"),
|
||||||
fields: [
|
fields: [
|
||||||
@@ -22,11 +22,12 @@ frappe.listview_settings['Attendance'] = {
|
|||||||
fieldtype: 'Link',
|
fieldtype: 'Link',
|
||||||
options: 'Employee',
|
options: 'Employee',
|
||||||
reqd: 1,
|
reqd: 1,
|
||||||
onchange: function(){
|
onchange: function() {
|
||||||
dialog.set_df_property("unmarked_days", "hidden", 1);
|
dialog.set_df_property("unmarked_days", "hidden", 1);
|
||||||
dialog.set_df_property("status", "hidden", 1);
|
dialog.set_df_property("status", "hidden", 1);
|
||||||
dialog.set_df_property("month", "value", '');
|
dialog.set_df_property("month", "value", '');
|
||||||
dialog.set_df_property("unmarked_days", "options", []);
|
dialog.set_df_property("unmarked_days", "options", []);
|
||||||
|
dialog.no_unmarked_days_left = false;
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@@ -35,13 +36,18 @@ frappe.listview_settings['Attendance'] = {
|
|||||||
fieldname: "month",
|
fieldname: "month",
|
||||||
options: months,
|
options: months,
|
||||||
reqd: 1,
|
reqd: 1,
|
||||||
onchange: function(){
|
onchange: function() {
|
||||||
if(dialog.fields_dict.employee.value && dialog.fields_dict.month.value) {
|
if(dialog.fields_dict.employee.value && dialog.fields_dict.month.value) {
|
||||||
dialog.set_df_property("status", "hidden", 0);
|
dialog.set_df_property("status", "hidden", 0);
|
||||||
dialog.set_df_property("unmarked_days", "options", []);
|
dialog.set_df_property("unmarked_days", "options", []);
|
||||||
|
dialog.no_unmarked_days_left = false;
|
||||||
me.get_multi_select_options(dialog.fields_dict.employee.value, dialog.fields_dict.month.value).then(options =>{
|
me.get_multi_select_options(dialog.fields_dict.employee.value, dialog.fields_dict.month.value).then(options =>{
|
||||||
dialog.set_df_property("unmarked_days", "hidden", 0);
|
if (options.length > 0) {
|
||||||
dialog.set_df_property("unmarked_days", "options", options);
|
dialog.set_df_property("unmarked_days", "hidden", 0);
|
||||||
|
dialog.set_df_property("unmarked_days", "options", options);
|
||||||
|
} else {
|
||||||
|
dialog.no_unmarked_days_left = true;
|
||||||
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -64,21 +70,25 @@ frappe.listview_settings['Attendance'] = {
|
|||||||
hidden: 1
|
hidden: 1
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
primary_action(data){
|
primary_action(data) {
|
||||||
frappe.confirm(__('Mark attendance as <b>' + data.status + '</b> for <b>' + data.month +'</b>' + ' on selected dates?'), () => {
|
if (cur_dialog.no_unmarked_days_left) {
|
||||||
frappe.call({
|
frappe.msgprint(__("Attendance for the month of {0} , has already been marked for the Employee {1}",[dialog.fields_dict.month.value, dialog.fields_dict.employee.value]));
|
||||||
method: "erpnext.hr.doctype.attendance.attendance.mark_bulk_attendance",
|
} else {
|
||||||
args: {
|
frappe.confirm(__('Mark attendance as {0} for {1} on selected dates?', [data.status,data.month]), () => {
|
||||||
data : data
|
frappe.call({
|
||||||
},
|
method: "erpnext.hr.doctype.attendance.attendance.mark_bulk_attendance",
|
||||||
callback: function(r) {
|
args: {
|
||||||
if(r.message === 1) {
|
data: data
|
||||||
frappe.show_alert({message:__("Attendance Marked"), indicator:'blue'});
|
},
|
||||||
cur_dialog.hide();
|
callback: function(r) {
|
||||||
|
if (r.message === 1) {
|
||||||
|
frappe.show_alert({message: __("Attendance Marked"), indicator: 'blue'});
|
||||||
|
cur_dialog.hide();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
});
|
||||||
});
|
});
|
||||||
});
|
}
|
||||||
dialog.hide();
|
dialog.hide();
|
||||||
list_view.refresh();
|
list_view.refresh();
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -813,7 +813,7 @@
|
|||||||
"idx": 24,
|
"idx": 24,
|
||||||
"image_field": "image",
|
"image_field": "image",
|
||||||
"links": [],
|
"links": [],
|
||||||
"modified": "2021-01-01 16:54:33.477439",
|
"modified": "2021-01-02 16:54:33.477439",
|
||||||
"modified_by": "Administrator",
|
"modified_by": "Administrator",
|
||||||
"module": "HR",
|
"module": "HR",
|
||||||
"name": "Employee",
|
"name": "Employee",
|
||||||
|
|||||||
@@ -48,6 +48,7 @@ class TestEmployee(unittest.TestCase):
|
|||||||
self.assertRaises(EmployeeLeftValidationError, employee1_doc.save)
|
self.assertRaises(EmployeeLeftValidationError, employee1_doc.save)
|
||||||
|
|
||||||
def make_employee(user, company=None, **kwargs):
|
def make_employee(user, company=None, **kwargs):
|
||||||
|
""
|
||||||
if not frappe.db.get_value("User", user):
|
if not frappe.db.get_value("User", user):
|
||||||
frappe.get_doc({
|
frappe.get_doc({
|
||||||
"doctype": "User",
|
"doctype": "User",
|
||||||
|
|||||||
@@ -1,16 +1,19 @@
|
|||||||
# Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors
|
# Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors
|
||||||
# License: GNU General Public License v3. See license.txt
|
# License: GNU General Public License v3. See license.txt
|
||||||
|
|
||||||
from __future__ import unicode_literals
|
import erpnext
|
||||||
import frappe, erpnext
|
import frappe
|
||||||
from frappe import _
|
|
||||||
from frappe.utils import formatdate, format_datetime, getdate, get_datetime, nowdate, flt, cstr, add_days, today
|
|
||||||
from frappe.model.document import Document
|
|
||||||
from frappe.desk.form import assign_to
|
|
||||||
from erpnext.hr.doctype.employee.employee import get_holiday_list_for_employee
|
from erpnext.hr.doctype.employee.employee import get_holiday_list_for_employee
|
||||||
|
from frappe import _
|
||||||
|
from frappe.desk.form import assign_to
|
||||||
|
from frappe.model.document import Document
|
||||||
|
from frappe.utils import (add_days, cstr, flt, format_datetime, formatdate,
|
||||||
|
get_datetime, getdate, nowdate, today, unique)
|
||||||
|
|
||||||
|
|
||||||
class DuplicateDeclarationError(frappe.ValidationError): pass
|
class DuplicateDeclarationError(frappe.ValidationError): pass
|
||||||
|
|
||||||
|
|
||||||
class EmployeeBoardingController(Document):
|
class EmployeeBoardingController(Document):
|
||||||
'''
|
'''
|
||||||
Create the project and the task for the boarding process
|
Create the project and the task for the boarding process
|
||||||
@@ -48,27 +51,38 @@ class EmployeeBoardingController(Document):
|
|||||||
continue
|
continue
|
||||||
|
|
||||||
task = frappe.get_doc({
|
task = frappe.get_doc({
|
||||||
"doctype": "Task",
|
"doctype": "Task",
|
||||||
"project": self.project,
|
"project": self.project,
|
||||||
"subject": activity.activity_name + " : " + self.employee_name,
|
"subject": activity.activity_name + " : " + self.employee_name,
|
||||||
"description": activity.description,
|
"description": activity.description,
|
||||||
"department": self.department,
|
"department": self.department,
|
||||||
"company": self.company,
|
"company": self.company,
|
||||||
"task_weight": activity.task_weight
|
"task_weight": activity.task_weight
|
||||||
}).insert(ignore_permissions=True)
|
}).insert(ignore_permissions=True)
|
||||||
activity.db_set("task", task.name)
|
activity.db_set("task", task.name)
|
||||||
|
|
||||||
users = [activity.user] if activity.user else []
|
users = [activity.user] if activity.user else []
|
||||||
if activity.role:
|
if activity.role:
|
||||||
user_list = frappe.db.sql_list('''select distinct(parent) from `tabHas Role`
|
user_list = frappe.db.sql_list('''
|
||||||
where parenttype='User' and role=%s''', activity.role)
|
SELECT
|
||||||
users = users + user_list
|
DISTINCT(has_role.parent)
|
||||||
|
FROM
|
||||||
|
`tabHas Role` has_role
|
||||||
|
LEFT JOIN `tabUser` user
|
||||||
|
ON has_role.parent = user.name
|
||||||
|
WHERE
|
||||||
|
has_role.parenttype = 'User'
|
||||||
|
AND user.enabled = 1
|
||||||
|
AND has_role.role = %s
|
||||||
|
''', activity.role)
|
||||||
|
users = unique(users + user_list)
|
||||||
|
|
||||||
if "Administrator" in users:
|
if "Administrator" in users:
|
||||||
users.remove("Administrator")
|
users.remove("Administrator")
|
||||||
|
|
||||||
# assign the task the users
|
# assign the task the users
|
||||||
if users:
|
if users:
|
||||||
self.assign_task_to_users(task, set(users))
|
self.assign_task_to_users(task, users)
|
||||||
|
|
||||||
def assign_task_to_users(self, task, users):
|
def assign_task_to_users(self, task, users):
|
||||||
for user in users:
|
for user in users:
|
||||||
|
|||||||
26
erpnext/non_profit/doctype/donation/donation.js
Normal file
26
erpnext/non_profit/doctype/donation/donation.js
Normal file
@@ -0,0 +1,26 @@
|
|||||||
|
// Copyright (c) 2021, Frappe Technologies Pvt. Ltd. and contributors
|
||||||
|
// For license information, please see license.txt
|
||||||
|
|
||||||
|
frappe.ui.form.on('Donation', {
|
||||||
|
refresh: function(frm) {
|
||||||
|
if (frm.doc.docstatus === 1 && !frm.doc.paid) {
|
||||||
|
frm.add_custom_button(__('Create Payment Entry'), function() {
|
||||||
|
frm.events.make_payment_entry(frm);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
make_payment_entry: function(frm) {
|
||||||
|
return frappe.call({
|
||||||
|
method: 'erpnext.accounts.doctype.payment_entry.payment_entry.get_payment_entry',
|
||||||
|
args: {
|
||||||
|
'dt': frm.doc.doctype,
|
||||||
|
'dn': frm.doc.name
|
||||||
|
},
|
||||||
|
callback: function(r) {
|
||||||
|
var doc = frappe.model.sync(r.message);
|
||||||
|
frappe.set_route('Form', doc[0].doctype, doc[0].name);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
},
|
||||||
|
});
|
||||||
156
erpnext/non_profit/doctype/donation/donation.json
Normal file
156
erpnext/non_profit/doctype/donation/donation.json
Normal file
@@ -0,0 +1,156 @@
|
|||||||
|
{
|
||||||
|
"actions": [],
|
||||||
|
"autoname": "naming_series:",
|
||||||
|
"creation": "2021-02-17 10:28:52.645731",
|
||||||
|
"doctype": "DocType",
|
||||||
|
"editable_grid": 1,
|
||||||
|
"engine": "InnoDB",
|
||||||
|
"field_order": [
|
||||||
|
"naming_series",
|
||||||
|
"donor",
|
||||||
|
"donor_name",
|
||||||
|
"email",
|
||||||
|
"column_break_4",
|
||||||
|
"company",
|
||||||
|
"date",
|
||||||
|
"payment_details_section",
|
||||||
|
"paid",
|
||||||
|
"amount",
|
||||||
|
"mode_of_payment",
|
||||||
|
"razorpay_payment_id",
|
||||||
|
"amended_from"
|
||||||
|
],
|
||||||
|
"fields": [
|
||||||
|
{
|
||||||
|
"fieldname": "donor",
|
||||||
|
"fieldtype": "Link",
|
||||||
|
"label": "Donor",
|
||||||
|
"options": "Donor",
|
||||||
|
"reqd": 1
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fetch_from": "donor.donor_name",
|
||||||
|
"fieldname": "donor_name",
|
||||||
|
"fieldtype": "Data",
|
||||||
|
"in_list_view": 1,
|
||||||
|
"in_standard_filter": 1,
|
||||||
|
"label": "Donor Name",
|
||||||
|
"read_only": 1
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fetch_from": "donor.email",
|
||||||
|
"fieldname": "email",
|
||||||
|
"fieldtype": "Data",
|
||||||
|
"in_list_view": 1,
|
||||||
|
"in_standard_filter": 1,
|
||||||
|
"label": "Email",
|
||||||
|
"read_only": 1
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "column_break_4",
|
||||||
|
"fieldtype": "Column Break"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "date",
|
||||||
|
"fieldtype": "Date",
|
||||||
|
"label": "Date",
|
||||||
|
"reqd": 1
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "payment_details_section",
|
||||||
|
"fieldtype": "Section Break",
|
||||||
|
"label": "Payment Details"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "amount",
|
||||||
|
"fieldtype": "Currency",
|
||||||
|
"label": "Amount",
|
||||||
|
"reqd": 1
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "mode_of_payment",
|
||||||
|
"fieldtype": "Link",
|
||||||
|
"label": "Mode of Payment",
|
||||||
|
"options": "Mode of Payment"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "razorpay_payment_id",
|
||||||
|
"fieldtype": "Data",
|
||||||
|
"label": "Razorpay Payment ID",
|
||||||
|
"read_only": 1
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "naming_series",
|
||||||
|
"fieldtype": "Select",
|
||||||
|
"label": "Naming Series",
|
||||||
|
"options": "NPO-DTN-.YYYY.-"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"default": "0",
|
||||||
|
"fieldname": "paid",
|
||||||
|
"fieldtype": "Check",
|
||||||
|
"in_list_view": 1,
|
||||||
|
"in_standard_filter": 1,
|
||||||
|
"label": "Paid"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "company",
|
||||||
|
"fieldtype": "Link",
|
||||||
|
"label": "Company",
|
||||||
|
"options": "Company",
|
||||||
|
"reqd": 1
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "amended_from",
|
||||||
|
"fieldtype": "Link",
|
||||||
|
"label": "Amended From",
|
||||||
|
"no_copy": 1,
|
||||||
|
"options": "Donation",
|
||||||
|
"print_hide": 1,
|
||||||
|
"read_only": 1
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"index_web_pages_for_search": 1,
|
||||||
|
"is_submittable": 1,
|
||||||
|
"links": [],
|
||||||
|
"modified": "2021-03-11 10:53:11.269005",
|
||||||
|
"modified_by": "Administrator",
|
||||||
|
"module": "Non Profit",
|
||||||
|
"name": "Donation",
|
||||||
|
"owner": "Administrator",
|
||||||
|
"permissions": [
|
||||||
|
{
|
||||||
|
"create": 1,
|
||||||
|
"delete": 1,
|
||||||
|
"email": 1,
|
||||||
|
"export": 1,
|
||||||
|
"print": 1,
|
||||||
|
"read": 1,
|
||||||
|
"report": 1,
|
||||||
|
"role": "System Manager",
|
||||||
|
"select": 1,
|
||||||
|
"share": 1,
|
||||||
|
"submit": 1,
|
||||||
|
"write": 1
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"create": 1,
|
||||||
|
"delete": 1,
|
||||||
|
"email": 1,
|
||||||
|
"export": 1,
|
||||||
|
"print": 1,
|
||||||
|
"read": 1,
|
||||||
|
"report": 1,
|
||||||
|
"role": "Non Profit Manager",
|
||||||
|
"select": 1,
|
||||||
|
"share": 1,
|
||||||
|
"submit": 1,
|
||||||
|
"write": 1
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"search_fields": "donor_name, email",
|
||||||
|
"sort_field": "modified",
|
||||||
|
"sort_order": "DESC",
|
||||||
|
"title_field": "donor_name",
|
||||||
|
"track_changes": 1
|
||||||
|
}
|
||||||
215
erpnext/non_profit/doctype/donation/donation.py
Normal file
215
erpnext/non_profit/doctype/donation/donation.py
Normal file
@@ -0,0 +1,215 @@
|
|||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
# Copyright (c) 2021, Frappe Technologies Pvt. Ltd. and contributors
|
||||||
|
# For license information, please see license.txt
|
||||||
|
|
||||||
|
from __future__ import unicode_literals
|
||||||
|
import frappe
|
||||||
|
import six
|
||||||
|
import json
|
||||||
|
from frappe.model.document import Document
|
||||||
|
from frappe import _
|
||||||
|
from frappe.utils import getdate, flt, get_link_to_form
|
||||||
|
from frappe.email import sendmail_to_system_managers
|
||||||
|
from erpnext.non_profit.doctype.membership.membership import verify_signature
|
||||||
|
|
||||||
|
class Donation(Document):
|
||||||
|
def validate(self):
|
||||||
|
if not self.donor or not frappe.db.exists('Donor', self.donor):
|
||||||
|
# for web forms
|
||||||
|
user_type = frappe.db.get_value('User', frappe.session.user, 'user_type')
|
||||||
|
if user_type == 'Website User':
|
||||||
|
self.create_donor_for_website_user()
|
||||||
|
else:
|
||||||
|
frappe.throw(_('Please select a Member'))
|
||||||
|
|
||||||
|
def create_donor_for_website_user(self):
|
||||||
|
donor_name = frappe.get_value('Donor', dict(email=frappe.session.user))
|
||||||
|
|
||||||
|
if not donor_name:
|
||||||
|
user = frappe.get_doc('User', frappe.session.user)
|
||||||
|
donor = frappe.get_doc(dict(
|
||||||
|
doctype='Donor',
|
||||||
|
donor_type=self.get('donor_type'),
|
||||||
|
email=frappe.session.user,
|
||||||
|
member_name=user.get_fullname()
|
||||||
|
)).insert(ignore_permissions=True)
|
||||||
|
donor_name = donor.name
|
||||||
|
|
||||||
|
if self.get('__islocal'):
|
||||||
|
self.donor = donor_name
|
||||||
|
|
||||||
|
def on_payment_authorized(self, *args, **kwargs):
|
||||||
|
self.load_from_db()
|
||||||
|
self.create_payment_entry()
|
||||||
|
|
||||||
|
def create_payment_entry(self):
|
||||||
|
settings = frappe.get_doc('Non Profit Settings')
|
||||||
|
if not settings.automate_donation_payment_entries:
|
||||||
|
return
|
||||||
|
|
||||||
|
if not settings.donation_payment_account:
|
||||||
|
frappe.throw(_('You need to set <b>Payment Account</b> for Donation in {0}').format(
|
||||||
|
get_link_to_form('Non Profit Settings', 'Non Profit Settings')))
|
||||||
|
|
||||||
|
from erpnext.accounts.doctype.payment_entry.payment_entry import get_payment_entry
|
||||||
|
|
||||||
|
frappe.flags.ignore_account_permission = True
|
||||||
|
pe = get_payment_entry(dt=self.doctype, dn=self.name)
|
||||||
|
frappe.flags.ignore_account_permission = False
|
||||||
|
pe.paid_from = settings.donation_debit_account
|
||||||
|
pe.paid_to = settings.donation_payment_account
|
||||||
|
pe.reference_no = self.name
|
||||||
|
pe.reference_date = getdate()
|
||||||
|
pe.flags.ignore_mandatory = True
|
||||||
|
pe.insert()
|
||||||
|
pe.submit()
|
||||||
|
|
||||||
|
|
||||||
|
@frappe.whitelist(allow_guest=True)
|
||||||
|
def capture_razorpay_donations(*args, **kwargs):
|
||||||
|
"""
|
||||||
|
Creates Donation from Razorpay Webhook Request Data on payment.captured event
|
||||||
|
Creates Donor from email if not found
|
||||||
|
"""
|
||||||
|
data = frappe.request.get_data(as_text=True)
|
||||||
|
|
||||||
|
try:
|
||||||
|
verify_signature(data, endpoint='Donation')
|
||||||
|
except Exception as e:
|
||||||
|
log = frappe.log_error(e, 'Donation Webhook Verification Error')
|
||||||
|
notify_failure(log)
|
||||||
|
return { 'status': 'Failed', 'reason': e }
|
||||||
|
|
||||||
|
if isinstance(data, six.string_types):
|
||||||
|
data = json.loads(data)
|
||||||
|
data = frappe._dict(data)
|
||||||
|
|
||||||
|
payment = data.payload.get('payment', {}).get('entity', {})
|
||||||
|
payment = frappe._dict(payment)
|
||||||
|
|
||||||
|
try:
|
||||||
|
if not data.event == 'payment.captured':
|
||||||
|
return
|
||||||
|
|
||||||
|
donor = get_donor(payment.email)
|
||||||
|
if not donor:
|
||||||
|
donor = create_donor(payment)
|
||||||
|
|
||||||
|
donation = create_donation(donor, payment)
|
||||||
|
donation.run_method('create_payment_entry')
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
message = '{0}\n\n{1}\n\n{2}: {3}'.format(e, frappe.get_traceback(), _('Payment ID'), payment.id)
|
||||||
|
log = frappe.log_error(message, _('Error creating donation entry for {0}').format(donor.name))
|
||||||
|
notify_failure(log)
|
||||||
|
return { 'status': 'Failed', 'reason': e }
|
||||||
|
|
||||||
|
return { 'status': 'Success' }
|
||||||
|
|
||||||
|
|
||||||
|
def create_donation(donor, payment):
|
||||||
|
if not frappe.db.exists('Mode of Payment', payment.method):
|
||||||
|
create_mode_of_payment(payment.method)
|
||||||
|
|
||||||
|
company = get_company_for_donations()
|
||||||
|
donation = frappe.get_doc({
|
||||||
|
'doctype': 'Donation',
|
||||||
|
'company': company,
|
||||||
|
'donor': donor.name,
|
||||||
|
'donor_name': donor.donor_name,
|
||||||
|
'email': donor.email,
|
||||||
|
'date': getdate(),
|
||||||
|
'amount': flt(payment.amount),
|
||||||
|
'mode_of_payment': payment.method,
|
||||||
|
'razorpay_payment_id': payment.id
|
||||||
|
}).insert(ignore_mandatory=True)
|
||||||
|
|
||||||
|
donation.submit()
|
||||||
|
return donation
|
||||||
|
|
||||||
|
|
||||||
|
def get_donor(email):
|
||||||
|
donors = frappe.get_all('Donor',
|
||||||
|
filters={'email': email},
|
||||||
|
order_by='creation desc')
|
||||||
|
|
||||||
|
try:
|
||||||
|
return frappe.get_doc('Donor', donors[0]['name'])
|
||||||
|
except Exception:
|
||||||
|
return None
|
||||||
|
|
||||||
|
|
||||||
|
@frappe.whitelist()
|
||||||
|
def create_donor(payment):
|
||||||
|
donor_details = frappe._dict(payment)
|
||||||
|
donor_type = frappe.db.get_single_value('Non Profit Settings', 'default_donor_type')
|
||||||
|
|
||||||
|
donor = frappe.new_doc('Donor')
|
||||||
|
donor.update({
|
||||||
|
'donor_name': donor_details.email,
|
||||||
|
'donor_type': donor_type,
|
||||||
|
'email': donor_details.email,
|
||||||
|
'contact': donor_details.contact
|
||||||
|
})
|
||||||
|
|
||||||
|
if donor_details.get('notes'):
|
||||||
|
donor = get_additional_notes(donor, donor_details)
|
||||||
|
|
||||||
|
donor.insert(ignore_mandatory=True)
|
||||||
|
return donor
|
||||||
|
|
||||||
|
|
||||||
|
def get_company_for_donations():
|
||||||
|
company = frappe.db.get_single_value('Non Profit Settings', 'donation_company')
|
||||||
|
if not company:
|
||||||
|
from erpnext.healthcare.setup import get_company
|
||||||
|
company = get_company()
|
||||||
|
return company
|
||||||
|
|
||||||
|
|
||||||
|
def get_additional_notes(donor, donor_details):
|
||||||
|
if type(donor_details.notes) == dict:
|
||||||
|
for k, v in donor_details.notes.items():
|
||||||
|
notes = '\n'.join('{}: {}'.format(k, v))
|
||||||
|
|
||||||
|
# extract donor name from notes
|
||||||
|
if 'name' in k.lower():
|
||||||
|
donor.update({
|
||||||
|
'donor_name': donor_details.notes.get(k)
|
||||||
|
})
|
||||||
|
|
||||||
|
# extract pan from notes
|
||||||
|
if 'pan' in k.lower():
|
||||||
|
donor.update({
|
||||||
|
'pan_number': donor_details.notes.get(k)
|
||||||
|
})
|
||||||
|
|
||||||
|
donor.add_comment('Comment', notes)
|
||||||
|
|
||||||
|
elif type(donor_details.notes) == str:
|
||||||
|
donor.add_comment('Comment', donor_details.notes)
|
||||||
|
|
||||||
|
return donor
|
||||||
|
|
||||||
|
|
||||||
|
def create_mode_of_payment(method):
|
||||||
|
frappe.get_doc({
|
||||||
|
'doctype': 'Mode of Payment',
|
||||||
|
'mode_of_payment': method
|
||||||
|
}).insert(ignore_mandatory=True)
|
||||||
|
|
||||||
|
|
||||||
|
def notify_failure(log):
|
||||||
|
try:
|
||||||
|
content = '''
|
||||||
|
Dear System Manager,
|
||||||
|
Razorpay webhook for creating donation failed due to some reason.
|
||||||
|
Please check the error log linked below
|
||||||
|
Error Log: {0}
|
||||||
|
Regards, Administrator
|
||||||
|
'''.format(get_link_to_form('Error Log', log.name))
|
||||||
|
|
||||||
|
sendmail_to_system_managers(_('[Important] [ERPNext] Razorpay donation webhook failed, please check.'), content)
|
||||||
|
except Exception:
|
||||||
|
pass
|
||||||
|
|
||||||
16
erpnext/non_profit/doctype/donation/donation_dashboard.py
Normal file
16
erpnext/non_profit/doctype/donation/donation_dashboard.py
Normal file
@@ -0,0 +1,16 @@
|
|||||||
|
from __future__ import unicode_literals
|
||||||
|
from frappe import _
|
||||||
|
|
||||||
|
def get_data():
|
||||||
|
return {
|
||||||
|
'fieldname': 'donation',
|
||||||
|
'non_standard_fieldnames': {
|
||||||
|
'Payment Entry': 'reference_name'
|
||||||
|
},
|
||||||
|
'transactions': [
|
||||||
|
{
|
||||||
|
'label': _('Payment'),
|
||||||
|
'items': ['Payment Entry']
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
76
erpnext/non_profit/doctype/donation/test_donation.py
Normal file
76
erpnext/non_profit/doctype/donation/test_donation.py
Normal file
@@ -0,0 +1,76 @@
|
|||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
# Copyright (c) 2021, Frappe Technologies Pvt. Ltd. and Contributors
|
||||||
|
# See license.txt
|
||||||
|
from __future__ import unicode_literals
|
||||||
|
|
||||||
|
import frappe
|
||||||
|
import unittest
|
||||||
|
from erpnext.non_profit.doctype.donation.donation import create_donation
|
||||||
|
|
||||||
|
class TestDonation(unittest.TestCase):
|
||||||
|
def setUp(self):
|
||||||
|
create_donor_type()
|
||||||
|
settings = frappe.get_doc('Non Profit Settings')
|
||||||
|
settings.company = '_Test Company'
|
||||||
|
settings.donation_company = '_Test Company'
|
||||||
|
settings.default_donor_type = '_Test Donor'
|
||||||
|
settings.automate_donation_payment_entries = 1
|
||||||
|
settings.donation_debit_account = 'Debtors - _TC'
|
||||||
|
settings.donation_payment_account = 'Cash - _TC'
|
||||||
|
settings.creation_user = 'Administrator'
|
||||||
|
settings.flags.ignore_permissions = True
|
||||||
|
settings.save()
|
||||||
|
|
||||||
|
def test_payment_entry_for_donations(self):
|
||||||
|
donor = create_donor()
|
||||||
|
create_mode_of_payment()
|
||||||
|
payment = frappe._dict({
|
||||||
|
'amount': 100,
|
||||||
|
'method': 'Debit Card',
|
||||||
|
'id': 'pay_MeXAmsgeKOhq7O'
|
||||||
|
})
|
||||||
|
donation = create_donation(donor, payment)
|
||||||
|
|
||||||
|
self.assertTrue(donation.name)
|
||||||
|
|
||||||
|
# Naive test to check if at all payment entry is generated
|
||||||
|
# This method is actually triggered from Payment Gateway
|
||||||
|
# In any case if details were missing, this would throw an error
|
||||||
|
donation.on_payment_authorized()
|
||||||
|
donation.reload()
|
||||||
|
|
||||||
|
self.assertEquals(donation.paid, 1)
|
||||||
|
self.assertTrue(frappe.db.exists('Payment Entry', {'reference_no': donation.name}))
|
||||||
|
|
||||||
|
|
||||||
|
def create_donor_type():
|
||||||
|
if not frappe.db.exists('Donor Type', '_Test Donor'):
|
||||||
|
frappe.get_doc({
|
||||||
|
'doctype': 'Donor Type',
|
||||||
|
'donor_type': '_Test Donor'
|
||||||
|
}).insert()
|
||||||
|
|
||||||
|
|
||||||
|
def create_donor():
|
||||||
|
donor = frappe.db.exists('Donor', 'donor@test.com')
|
||||||
|
if donor:
|
||||||
|
return frappe.get_doc('Donor', 'donor@test.com')
|
||||||
|
else:
|
||||||
|
return frappe.get_doc({
|
||||||
|
'doctype': 'Donor',
|
||||||
|
'donor_name': '_Test Donor',
|
||||||
|
'donor_type': '_Test Donor',
|
||||||
|
'email': 'donor@test.com'
|
||||||
|
}).insert()
|
||||||
|
|
||||||
|
|
||||||
|
def create_mode_of_payment():
|
||||||
|
if not frappe.db.exists('Mode of Payment', 'Debit Card'):
|
||||||
|
frappe.get_doc({
|
||||||
|
'doctype': 'Mode of Payment',
|
||||||
|
'mode_of_payment': 'Debit Card',
|
||||||
|
'accounts': [{
|
||||||
|
'company': '_Test Company',
|
||||||
|
'default_account': 'Cash - _TC'
|
||||||
|
}]
|
||||||
|
}).insert()
|
||||||
@@ -76,8 +76,13 @@
|
|||||||
}
|
}
|
||||||
],
|
],
|
||||||
"image_field": "image",
|
"image_field": "image",
|
||||||
"links": [],
|
"links": [
|
||||||
"modified": "2020-09-16 23:46:04.083274",
|
{
|
||||||
|
"link_doctype": "Donation",
|
||||||
|
"link_fieldname": "donor"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"modified": "2021-02-17 16:36:33.470731",
|
||||||
"modified_by": "Administrator",
|
"modified_by": "Administrator",
|
||||||
"module": "Non Profit",
|
"module": "Non Profit",
|
||||||
"name": "Donor",
|
"name": "Donor",
|
||||||
|
|||||||
@@ -11,3 +11,8 @@ class Donor(Document):
|
|||||||
"""Load address and contacts in `__onload`"""
|
"""Load address and contacts in `__onload`"""
|
||||||
load_address_and_contact(self)
|
load_address_and_contact(self)
|
||||||
|
|
||||||
|
def validate(self):
|
||||||
|
from frappe.utils import validate_email_address
|
||||||
|
if self.email:
|
||||||
|
validate_email_address(self.email.strip(), True)
|
||||||
|
|
||||||
|
|||||||
@@ -3,7 +3,7 @@
|
|||||||
|
|
||||||
frappe.ui.form.on('Member', {
|
frappe.ui.form.on('Member', {
|
||||||
setup: function(frm) {
|
setup: function(frm) {
|
||||||
frappe.db.get_single_value("Membership Settings", "enable_razorpay").then(val => {
|
frappe.db.get_single_value('Non Profit Settings', 'enable_razorpay_for_memberships').then(val => {
|
||||||
if (val && (frm.doc.subscription_id || frm.doc.customer_id)) {
|
if (val && (frm.doc.subscription_id || frm.doc.customer_id)) {
|
||||||
frm.set_df_property('razorpay_details_section', 'hidden', false);
|
frm.set_df_property('razorpay_details_section', 'hidden', false);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -7,7 +7,7 @@ import frappe
|
|||||||
from frappe import _
|
from frappe import _
|
||||||
from frappe.model.document import Document
|
from frappe.model.document import Document
|
||||||
from frappe.contacts.address_and_contact import load_address_and_contact
|
from frappe.contacts.address_and_contact import load_address_and_contact
|
||||||
from frappe.utils import cint
|
from frappe.utils import cint, get_link_to_form
|
||||||
from frappe.integrations.utils import get_payment_gateway_controller
|
from frappe.integrations.utils import get_payment_gateway_controller
|
||||||
from erpnext.non_profit.doctype.membership_type.membership_type import get_membership_type
|
from erpnext.non_profit.doctype.membership_type.membership_type import get_membership_type
|
||||||
|
|
||||||
@@ -26,9 +26,10 @@ class Member(Document):
|
|||||||
validate_email_address(email.strip(), True)
|
validate_email_address(email.strip(), True)
|
||||||
|
|
||||||
def setup_subscription(self):
|
def setup_subscription(self):
|
||||||
membership_settings = frappe.get_doc("Membership Settings")
|
non_profit_settings = frappe.get_doc('Non Profit Settings')
|
||||||
if not membership_settings.enable_razorpay:
|
if not non_profit_settings.enable_razorpay_for_memberships:
|
||||||
frappe.throw("Please enable Razorpay to setup subscription")
|
frappe.throw('Please check Enable Razorpay for Memberships in {0} to setup subscription').format(
|
||||||
|
get_link_to_form('Non Profit Settings', 'Non Profit Settings'))
|
||||||
|
|
||||||
controller = get_payment_gateway_controller("Razorpay")
|
controller = get_payment_gateway_controller("Razorpay")
|
||||||
settings = controller.get_settings({})
|
settings = controller.get_settings({})
|
||||||
@@ -40,7 +41,7 @@ class Member(Document):
|
|||||||
|
|
||||||
subscription_details = {
|
subscription_details = {
|
||||||
"plan_id": plan_id,
|
"plan_id": plan_id,
|
||||||
"billing_frequency": cint(membership_settings.billing_frequency),
|
"billing_frequency": cint(non_profit_settings.billing_frequency),
|
||||||
"customer_notify": 1
|
"customer_notify": 1
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -3,7 +3,7 @@
|
|||||||
|
|
||||||
frappe.ui.form.on('Membership', {
|
frappe.ui.form.on('Membership', {
|
||||||
setup: function(frm) {
|
setup: function(frm) {
|
||||||
frappe.db.get_single_value("Membership Settings", "enable_razorpay").then(val => {
|
frappe.db.get_single_value("Non Profit Settings", "enable_razorpay_for_memberships").then(val => {
|
||||||
if (val) frm.set_df_property("razorpay_details_section", "hidden", false);
|
if (val) frm.set_df_property("razorpay_details_section", "hidden", false);
|
||||||
})
|
})
|
||||||
},
|
},
|
||||||
@@ -26,7 +26,7 @@ frappe.ui.form.on('Membership', {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
frappe.db.get_single_value("Membership Settings", "send_email").then(val => {
|
frappe.db.get_single_value("Non Profit Settings", "send_email").then(val => {
|
||||||
if (val) frm.add_custom_button("Send Acknowledgement", () => {
|
if (val) frm.add_custom_button("Send Acknowledgement", () => {
|
||||||
frm.call("send_acknowlement").then(() => {
|
frm.call("send_acknowlement").then(() => {
|
||||||
frm.reload_doc();
|
frm.reload_doc();
|
||||||
|
|||||||
@@ -10,6 +10,7 @@
|
|||||||
"member_name",
|
"member_name",
|
||||||
"membership_type",
|
"membership_type",
|
||||||
"column_break_3",
|
"column_break_3",
|
||||||
|
"company",
|
||||||
"membership_status",
|
"membership_status",
|
||||||
"membership_validity_section",
|
"membership_validity_section",
|
||||||
"from_date",
|
"from_date",
|
||||||
@@ -132,11 +133,18 @@
|
|||||||
"fieldtype": "Data",
|
"fieldtype": "Data",
|
||||||
"label": "Member Name",
|
"label": "Member Name",
|
||||||
"read_only": 1
|
"read_only": 1
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "company",
|
||||||
|
"fieldtype": "Link",
|
||||||
|
"label": "Company",
|
||||||
|
"options": "Company",
|
||||||
|
"reqd": 1
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"index_web_pages_for_search": 1,
|
"index_web_pages_for_search": 1,
|
||||||
"links": [],
|
"links": [],
|
||||||
"modified": "2021-01-21 16:31:20.032656",
|
"modified": "2021-02-19 14:33:44.925122",
|
||||||
"modified_by": "Administrator",
|
"modified_by": "Administrator",
|
||||||
"module": "Non Profit",
|
"module": "Non Profit",
|
||||||
"name": "Membership",
|
"name": "Membership",
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ from __future__ import unicode_literals
|
|||||||
import json
|
import json
|
||||||
import frappe
|
import frappe
|
||||||
import six
|
import six
|
||||||
|
import os
|
||||||
from datetime import datetime
|
from datetime import datetime
|
||||||
from frappe.model.document import Document
|
from frappe.model.document import Document
|
||||||
from frappe.email import sendmail_to_system_managers
|
from frappe.email import sendmail_to_system_managers
|
||||||
@@ -58,7 +59,7 @@ class Membership(Document):
|
|||||||
else:
|
else:
|
||||||
self.from_date = nowdate()
|
self.from_date = nowdate()
|
||||||
|
|
||||||
if frappe.db.get_single_value("Membership Settings", "billing_cycle") == "Yearly":
|
if frappe.db.get_single_value("Non Profit Settings", "billing_cycle") == "Yearly":
|
||||||
self.to_date = add_years(self.from_date, 1)
|
self.to_date = add_years(self.from_date, 1)
|
||||||
else:
|
else:
|
||||||
self.to_date = add_months(self.from_date, 1)
|
self.to_date = add_months(self.from_date, 1)
|
||||||
@@ -68,9 +69,9 @@ class Membership(Document):
|
|||||||
return
|
return
|
||||||
self.load_from_db()
|
self.load_from_db()
|
||||||
self.db_set("paid", 1)
|
self.db_set("paid", 1)
|
||||||
settings = frappe.get_doc("Membership Settings")
|
settings = frappe.get_doc("Non Profit Settings")
|
||||||
if settings.enable_invoicing and settings.create_for_web_forms:
|
if settings.allow_invoicing and settings.automate_membership_invoicing:
|
||||||
self.generate_invoice(with_payment_entry=settings.make_payment_entry, save=True)
|
self.generate_invoice(with_payment_entry=settings.automate_membership_payment_entries, save=True)
|
||||||
|
|
||||||
|
|
||||||
def generate_invoice(self, save=True, with_payment_entry=False):
|
def generate_invoice(self, save=True, with_payment_entry=False):
|
||||||
@@ -85,7 +86,7 @@ class Membership(Document):
|
|||||||
frappe.throw(_("No customer linked to member {0}").format(frappe.bold(self.member)))
|
frappe.throw(_("No customer linked to member {0}").format(frappe.bold(self.member)))
|
||||||
|
|
||||||
plan = frappe.get_doc("Membership Type", self.membership_type)
|
plan = frappe.get_doc("Membership Type", self.membership_type)
|
||||||
settings = frappe.get_doc("Membership Settings")
|
settings = frappe.get_doc("Non Profit Settings")
|
||||||
self.validate_membership_type_and_settings(plan, settings)
|
self.validate_membership_type_and_settings(plan, settings)
|
||||||
|
|
||||||
invoice = make_invoice(self, member, plan, settings)
|
invoice = make_invoice(self, member, plan, settings)
|
||||||
@@ -102,7 +103,7 @@ class Membership(Document):
|
|||||||
def validate_membership_type_and_settings(self, plan, settings):
|
def validate_membership_type_and_settings(self, plan, settings):
|
||||||
settings_link = get_link_to_form("Membership Type", self.membership_type)
|
settings_link = get_link_to_form("Membership Type", self.membership_type)
|
||||||
|
|
||||||
if not settings.debit_account:
|
if not settings.membership_debit_account:
|
||||||
frappe.throw(_("You need to set <b>Debit Account</b> in {0}").format(settings_link))
|
frappe.throw(_("You need to set <b>Debit Account</b> in {0}").format(settings_link))
|
||||||
|
|
||||||
if not settings.company:
|
if not settings.company:
|
||||||
@@ -113,25 +114,26 @@ class Membership(Document):
|
|||||||
get_link_to_form("Membership Type", self.membership_type)))
|
get_link_to_form("Membership Type", self.membership_type)))
|
||||||
|
|
||||||
def make_payment_entry(self, settings, invoice):
|
def make_payment_entry(self, settings, invoice):
|
||||||
if not settings.payment_account:
|
if not settings.membership_payment_account:
|
||||||
frappe.throw(_("You need to set <b>Payment Account</b> in {0}").format(
|
frappe.throw(_("You need to set <b>Payment Account</b> for Membership in {0}").format(
|
||||||
get_link_to_form("Membership Type", self.membership_type)))
|
get_link_to_form("Non Profit Settings", "Non Profit Settings")))
|
||||||
|
|
||||||
from erpnext.accounts.doctype.payment_entry.payment_entry import get_payment_entry
|
from erpnext.accounts.doctype.payment_entry.payment_entry import get_payment_entry
|
||||||
frappe.flags.ignore_account_permission = True
|
frappe.flags.ignore_account_permission = True
|
||||||
pe = get_payment_entry(dt="Sales Invoice", dn=invoice.name, bank_amount=invoice.grand_total)
|
pe = get_payment_entry(dt="Sales Invoice", dn=invoice.name, bank_amount=invoice.grand_total)
|
||||||
frappe.flags.ignore_account_permission=False
|
frappe.flags.ignore_account_permission=False
|
||||||
pe.paid_to = settings.payment_account
|
pe.paid_to = settings.membership_payment_account
|
||||||
pe.reference_no = self.name
|
pe.reference_no = self.name
|
||||||
pe.reference_date = getdate()
|
pe.reference_date = getdate()
|
||||||
pe.save(ignore_permissions=True)
|
pe.flags.ignore_mandatory = True
|
||||||
|
pe.save()
|
||||||
pe.submit()
|
pe.submit()
|
||||||
|
|
||||||
def send_acknowlement(self):
|
def send_acknowlement(self):
|
||||||
settings = frappe.get_doc("Membership Settings")
|
settings = frappe.get_doc("Non Profit Settings")
|
||||||
if not settings.send_email:
|
if not settings.send_email:
|
||||||
frappe.throw(_("You need to enable <b>Send Acknowledge Email</b> in {0}").format(
|
frappe.throw(_("You need to enable <b>Send Acknowledge Email</b> in {0}").format(
|
||||||
get_link_to_form("Membership Settings", "Membership Settings")))
|
get_link_to_form("Non Profit Settings", "Non Profit Settings")))
|
||||||
|
|
||||||
member = frappe.get_doc("Member", self.member)
|
member = frappe.get_doc("Member", self.member)
|
||||||
if not member.email_id:
|
if not member.email_id:
|
||||||
@@ -170,7 +172,7 @@ def make_invoice(membership, member, plan, settings):
|
|||||||
invoice = frappe.get_doc({
|
invoice = frappe.get_doc({
|
||||||
"doctype": "Sales Invoice",
|
"doctype": "Sales Invoice",
|
||||||
"customer": member.customer,
|
"customer": member.customer,
|
||||||
"debit_to": settings.debit_account,
|
"debit_to": settings.membership_debit_account,
|
||||||
"currency": membership.currency,
|
"currency": membership.currency,
|
||||||
"company": settings.company,
|
"company": settings.company,
|
||||||
"is_pos": 0,
|
"is_pos": 0,
|
||||||
@@ -183,7 +185,7 @@ def make_invoice(membership, member, plan, settings):
|
|||||||
]
|
]
|
||||||
})
|
})
|
||||||
invoice.set_missing_values()
|
invoice.set_missing_values()
|
||||||
invoice.insert(ignore_permissions=True)
|
invoice.insert()
|
||||||
invoice.submit()
|
invoice.submit()
|
||||||
|
|
||||||
frappe.msgprint(_("Sales Invoice created successfully"))
|
frappe.msgprint(_("Sales Invoice created successfully"))
|
||||||
@@ -203,17 +205,18 @@ def get_member_based_on_subscription(subscription_id, email):
|
|||||||
return None
|
return None
|
||||||
|
|
||||||
|
|
||||||
def verify_signature(data):
|
def verify_signature(data, endpoint="Membership"):
|
||||||
if frappe.flags.in_test:
|
if frappe.flags.in_test or os.environ.get("CI"):
|
||||||
return True
|
return True
|
||||||
signature = frappe.request.headers.get("X-Razorpay-Signature")
|
signature = frappe.request.headers.get("X-Razorpay-Signature")
|
||||||
|
|
||||||
settings = frappe.get_doc("Membership Settings")
|
settings = frappe.get_doc("Non Profit Settings")
|
||||||
key = settings.get_webhook_secret()
|
key = settings.get_webhook_secret(endpoint)
|
||||||
|
|
||||||
controller = frappe.get_doc("Razorpay Settings")
|
controller = frappe.get_doc("Razorpay Settings")
|
||||||
|
|
||||||
controller.verify_signature(data, signature, key)
|
controller.verify_signature(data, signature, key)
|
||||||
|
frappe.set_user(settings.creation_user)
|
||||||
|
|
||||||
|
|
||||||
@frappe.whitelist(allow_guest=True)
|
@frappe.whitelist(allow_guest=True)
|
||||||
@@ -222,7 +225,7 @@ def trigger_razorpay_subscription(*args, **kwargs):
|
|||||||
try:
|
try:
|
||||||
verify_signature(data)
|
verify_signature(data)
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
log = frappe.log_error(e, "Webhook Verification Error")
|
log = frappe.log_error(e, "Membership Webhook Verification Error")
|
||||||
notify_failure(log)
|
notify_failure(log)
|
||||||
return { "status": "Failed", "reason": e}
|
return { "status": "Failed", "reason": e}
|
||||||
|
|
||||||
@@ -250,16 +253,15 @@ def trigger_razorpay_subscription(*args, **kwargs):
|
|||||||
|
|
||||||
member.subscription_id = subscription.id
|
member.subscription_id = subscription.id
|
||||||
member.customer_id = payment.customer_id
|
member.customer_id = payment.customer_id
|
||||||
if subscription.notes and type(subscription.notes) == dict:
|
|
||||||
notes = "\n".join("{}: {}".format(k, v) for k, v in subscription.notes.items())
|
|
||||||
member.add_comment("Comment", notes)
|
|
||||||
elif subscription.notes and type(subscription.notes) == str:
|
|
||||||
member.add_comment("Comment", subscription.notes)
|
|
||||||
|
|
||||||
|
if subscription.get("notes"):
|
||||||
|
member = get_additional_notes(member, subscription)
|
||||||
|
|
||||||
|
company = get_company_for_memberships()
|
||||||
# Update Membership
|
# Update Membership
|
||||||
membership = frappe.new_doc("Membership")
|
membership = frappe.new_doc("Membership")
|
||||||
membership.update({
|
membership.update({
|
||||||
|
"company": company,
|
||||||
"member": member.name,
|
"member": member.name,
|
||||||
"membership_status": "Current",
|
"membership_status": "Current",
|
||||||
"membership_type": member.membership_type,
|
"membership_type": member.membership_type,
|
||||||
@@ -270,13 +272,20 @@ def trigger_razorpay_subscription(*args, **kwargs):
|
|||||||
"to_date": datetime.fromtimestamp(subscription.current_end),
|
"to_date": datetime.fromtimestamp(subscription.current_end),
|
||||||
"amount": payment.amount / 100 # Convert to rupees from paise
|
"amount": payment.amount / 100 # Convert to rupees from paise
|
||||||
})
|
})
|
||||||
membership.insert(ignore_permissions=True)
|
membership.flags.ignore_mandatory = True
|
||||||
|
membership.insert()
|
||||||
|
|
||||||
# Update membership values
|
# Update membership values
|
||||||
member.subscription_start = datetime.fromtimestamp(subscription.start_at)
|
member.subscription_start = datetime.fromtimestamp(subscription.start_at)
|
||||||
member.subscription_end = datetime.fromtimestamp(subscription.end_at)
|
member.subscription_end = datetime.fromtimestamp(subscription.end_at)
|
||||||
member.subscription_activated = 1
|
member.subscription_activated = 1
|
||||||
member.save(ignore_permissions=True)
|
member.flags.ignore_mandatory = True
|
||||||
|
member.save()
|
||||||
|
|
||||||
|
settings = frappe.get_doc("Non Profit Settings")
|
||||||
|
if settings.allow_invoicing and settings.automate_membership_invoicing:
|
||||||
|
membership.generate_invoice(with_payment_entry=settings.automate_membership_payment_entries, save=True)
|
||||||
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
message = "{0}\n\n{1}\n\n{2}: {3}".format(e, frappe.get_traceback(), __("Payment ID"), payment.id)
|
message = "{0}\n\n{1}\n\n{2}: {3}".format(e, frappe.get_traceback(), __("Payment ID"), payment.id)
|
||||||
log = frappe.log_error(message, _("Error creating membership entry for {0}").format(member.name))
|
log = frappe.log_error(message, _("Error creating membership entry for {0}").format(member.name))
|
||||||
@@ -286,6 +295,39 @@ def trigger_razorpay_subscription(*args, **kwargs):
|
|||||||
return { "status": "Success" }
|
return { "status": "Success" }
|
||||||
|
|
||||||
|
|
||||||
|
def get_company_for_memberships():
|
||||||
|
company = frappe.db.get_single_value("Non Profit Settings", "company")
|
||||||
|
if not company:
|
||||||
|
from erpnext.healthcare.setup import get_company
|
||||||
|
company = get_company()
|
||||||
|
return company
|
||||||
|
|
||||||
|
|
||||||
|
def get_additional_notes(member, subscription):
|
||||||
|
if type(subscription.notes) == dict:
|
||||||
|
for k, v in subscription.notes.items():
|
||||||
|
notes = "\n".join("{}: {}".format(k, v))
|
||||||
|
|
||||||
|
# extract member name from notes
|
||||||
|
if "name" in k.lower():
|
||||||
|
member.update({
|
||||||
|
"member_name": subscription.notes.get(k)
|
||||||
|
})
|
||||||
|
|
||||||
|
# extract pan number from notes
|
||||||
|
if "pan" in k.lower():
|
||||||
|
member.update({
|
||||||
|
"pan_number": subscription.notes.get(k)
|
||||||
|
})
|
||||||
|
|
||||||
|
member.add_comment("Comment", notes)
|
||||||
|
|
||||||
|
elif type(subscription.notes) == str:
|
||||||
|
member.add_comment("Comment", subscription.notes)
|
||||||
|
|
||||||
|
return member
|
||||||
|
|
||||||
|
|
||||||
def notify_failure(log):
|
def notify_failure(log):
|
||||||
try:
|
try:
|
||||||
content = """
|
content = """
|
||||||
|
|||||||
@@ -10,33 +10,7 @@ from frappe.utils import nowdate, add_months
|
|||||||
|
|
||||||
class TestMembership(unittest.TestCase):
|
class TestMembership(unittest.TestCase):
|
||||||
def setUp(self):
|
def setUp(self):
|
||||||
# Get default company
|
plan = setup_membership()
|
||||||
company = frappe.get_doc("Company", erpnext.get_default_company())
|
|
||||||
|
|
||||||
# update membership settings
|
|
||||||
settings = frappe.get_doc("Membership Settings")
|
|
||||||
# Enable razorpay
|
|
||||||
settings.enable_razorpay = 1
|
|
||||||
settings.billing_cycle = "Monthly"
|
|
||||||
settings.billing_frequency = 24
|
|
||||||
# Enable invoicing
|
|
||||||
settings.enable_invoicing = 1
|
|
||||||
settings.make_payment_entry = 1
|
|
||||||
settings.company = company.name
|
|
||||||
settings.payment_account = company.default_cash_account
|
|
||||||
settings.debit_account = company.default_receivable_account
|
|
||||||
settings.save()
|
|
||||||
|
|
||||||
# make test plan
|
|
||||||
if not frappe.db.exists("Membership Type", "_rzpy_test_milythm"):
|
|
||||||
plan = frappe.new_doc("Membership Type")
|
|
||||||
plan.membership_type = "_rzpy_test_milythm"
|
|
||||||
plan.amount = 100
|
|
||||||
plan.razorpay_plan_id = "_rzpy_test_milythm"
|
|
||||||
plan.linked_item = create_item("_Test Item for Non Profit Membership").name
|
|
||||||
plan.insert()
|
|
||||||
else:
|
|
||||||
plan = frappe.get_doc("Membership Type", "_rzpy_test_milythm")
|
|
||||||
|
|
||||||
# make test member
|
# make test member
|
||||||
self.member_doc = create_member(frappe._dict({
|
self.member_doc = create_member(frappe._dict({
|
||||||
@@ -78,7 +52,7 @@ class TestMembership(unittest.TestCase):
|
|||||||
})
|
})
|
||||||
|
|
||||||
def set_config(key, value):
|
def set_config(key, value):
|
||||||
frappe.db.set_value("Membership Settings", None, key, value)
|
frappe.db.set_value("Non Profit Settings", None, key, value)
|
||||||
|
|
||||||
def make_membership(member, payload={}):
|
def make_membership(member, payload={}):
|
||||||
data = {
|
data = {
|
||||||
@@ -109,3 +83,36 @@ def create_item(item_code):
|
|||||||
else:
|
else:
|
||||||
item = frappe.get_doc("Item", item_code)
|
item = frappe.get_doc("Item", item_code)
|
||||||
return item
|
return item
|
||||||
|
|
||||||
|
def setup_membership():
|
||||||
|
# Get default company
|
||||||
|
company = frappe.get_doc("Company", erpnext.get_default_company())
|
||||||
|
|
||||||
|
# update non profit settings
|
||||||
|
settings = frappe.get_doc("Non Profit Settings")
|
||||||
|
# Enable razorpay
|
||||||
|
settings.enable_razorpay_for_memberships = 1
|
||||||
|
settings.billing_cycle = "Monthly"
|
||||||
|
settings.billing_frequency = 24
|
||||||
|
# Enable invoicing
|
||||||
|
settings.allow_invoicing = 1
|
||||||
|
settings.automate_membership_payment_entries = 1
|
||||||
|
settings.company = company.name
|
||||||
|
settings.donation_company = company.name
|
||||||
|
settings.membership_payment_account = company.default_cash_account
|
||||||
|
settings.membership_debit_account = company.default_receivable_account
|
||||||
|
settings.flags.ignore_mandatory = True
|
||||||
|
settings.save()
|
||||||
|
|
||||||
|
# make test plan
|
||||||
|
if not frappe.db.exists("Membership Type", "_rzpy_test_milythm"):
|
||||||
|
plan = frappe.new_doc("Membership Type")
|
||||||
|
plan.membership_type = "_rzpy_test_milythm"
|
||||||
|
plan.amount = 100
|
||||||
|
plan.razorpay_plan_id = "_rzpy_test_milythm"
|
||||||
|
plan.linked_item = create_item("_Test Item for Non Profit Membership").name
|
||||||
|
plan.insert()
|
||||||
|
else:
|
||||||
|
plan = frappe.get_doc("Membership Type", "_rzpy_test_milythm")
|
||||||
|
|
||||||
|
return plan
|
||||||
@@ -1,192 +0,0 @@
|
|||||||
{
|
|
||||||
"actions": [],
|
|
||||||
"creation": "2020-03-29 12:57:03.005120",
|
|
||||||
"doctype": "DocType",
|
|
||||||
"editable_grid": 1,
|
|
||||||
"engine": "InnoDB",
|
|
||||||
"field_order": [
|
|
||||||
"enable_razorpay",
|
|
||||||
"razorpay_settings_section",
|
|
||||||
"billing_cycle",
|
|
||||||
"billing_frequency",
|
|
||||||
"webhook_secret",
|
|
||||||
"column_break_6",
|
|
||||||
"enable_invoicing",
|
|
||||||
"create_for_web_forms",
|
|
||||||
"make_payment_entry",
|
|
||||||
"company",
|
|
||||||
"debit_account",
|
|
||||||
"payment_account",
|
|
||||||
"column_break_9",
|
|
||||||
"send_email",
|
|
||||||
"send_invoice",
|
|
||||||
"membership_print_format",
|
|
||||||
"inv_print_format",
|
|
||||||
"email_template"
|
|
||||||
],
|
|
||||||
"fields": [
|
|
||||||
{
|
|
||||||
"fieldname": "billing_cycle",
|
|
||||||
"fieldtype": "Select",
|
|
||||||
"label": "Billing Cycle",
|
|
||||||
"options": "Monthly\nYearly"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"default": "0",
|
|
||||||
"fieldname": "enable_razorpay",
|
|
||||||
"fieldtype": "Check",
|
|
||||||
"label": "Enable RazorPay For Memberships"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"depends_on": "eval:doc.enable_razorpay",
|
|
||||||
"fieldname": "razorpay_settings_section",
|
|
||||||
"fieldtype": "Section Break",
|
|
||||||
"label": "RazorPay Settings"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"description": "The number of billing cycles for which the customer should be charged. For example, if a customer is buying a 1-year membership that should be billed on a monthly basis, this value should be 12.",
|
|
||||||
"fieldname": "billing_frequency",
|
|
||||||
"fieldtype": "Int",
|
|
||||||
"label": "Billing Frequency"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"fieldname": "webhook_secret",
|
|
||||||
"fieldtype": "Password",
|
|
||||||
"label": "Webhook Secret",
|
|
||||||
"read_only": 1
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"fieldname": "column_break_6",
|
|
||||||
"fieldtype": "Section Break",
|
|
||||||
"label": "Invoicing"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"depends_on": "eval:doc.enable_invoicing",
|
|
||||||
"fieldname": "debit_account",
|
|
||||||
"fieldtype": "Link",
|
|
||||||
"label": "Debit Account",
|
|
||||||
"mandatory_depends_on": "eval:doc.enable_auto_invoicing",
|
|
||||||
"options": "Account"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"fieldname": "column_break_9",
|
|
||||||
"fieldtype": "Column Break"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"depends_on": "eval:doc.enable_invoicing",
|
|
||||||
"fieldname": "company",
|
|
||||||
"fieldtype": "Link",
|
|
||||||
"label": "Company",
|
|
||||||
"mandatory_depends_on": "eval:doc.enable_auto_invoicing",
|
|
||||||
"options": "Company"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"default": "0",
|
|
||||||
"depends_on": "eval:doc.enable_invoicing && doc.send_email",
|
|
||||||
"fieldname": "send_invoice",
|
|
||||||
"fieldtype": "Check",
|
|
||||||
"label": "Send Invoice with Email"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"default": "0",
|
|
||||||
"fieldname": "send_email",
|
|
||||||
"fieldtype": "Check",
|
|
||||||
"label": "Send Membership Acknowledgement"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"depends_on": "eval: doc.send_invoice",
|
|
||||||
"fieldname": "inv_print_format",
|
|
||||||
"fieldtype": "Link",
|
|
||||||
"label": "Invoice Print Format",
|
|
||||||
"mandatory_depends_on": "eval: doc.send_invoice",
|
|
||||||
"options": "Print Format"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"depends_on": "eval:doc.send_email",
|
|
||||||
"fieldname": "membership_print_format",
|
|
||||||
"fieldtype": "Link",
|
|
||||||
"label": "Membership Print Format",
|
|
||||||
"options": "Print Format"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"depends_on": "eval:doc.send_email",
|
|
||||||
"fieldname": "email_template",
|
|
||||||
"fieldtype": "Link",
|
|
||||||
"label": "Email Template",
|
|
||||||
"mandatory_depends_on": "eval:doc.send_email",
|
|
||||||
"options": "Email Template"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"default": "0",
|
|
||||||
"fieldname": "enable_invoicing",
|
|
||||||
"fieldtype": "Check",
|
|
||||||
"label": "Enable Invoicing",
|
|
||||||
"mandatory_depends_on": "eval:doc.send_invoice || doc.make_payment_entry"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"default": "0",
|
|
||||||
"depends_on": "eval:doc.enable_invoicing",
|
|
||||||
"description": "Auto creates Payment Entry for Sales Invoices created for Membership from web forms.",
|
|
||||||
"fieldname": "make_payment_entry",
|
|
||||||
"fieldtype": "Check",
|
|
||||||
"label": "Make Payment Entry"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"depends_on": "eval:doc.make_payment_entry",
|
|
||||||
"fieldname": "payment_account",
|
|
||||||
"fieldtype": "Link",
|
|
||||||
"label": "Payment To",
|
|
||||||
"mandatory_depends_on": "eval:doc.make_payment_entry",
|
|
||||||
"options": "Account"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"default": "0",
|
|
||||||
"depends_on": "eval:doc.enable_invoicing",
|
|
||||||
"description": "Automatically create an invoice when payment is authorized from a web form entry",
|
|
||||||
"fieldname": "create_for_web_forms",
|
|
||||||
"fieldtype": "Check",
|
|
||||||
"label": "Auto Create Invoice for Web Forms"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"index_web_pages_for_search": 1,
|
|
||||||
"issingle": 1,
|
|
||||||
"links": [],
|
|
||||||
"modified": "2021-01-21 19:57:53.213286",
|
|
||||||
"modified_by": "Administrator",
|
|
||||||
"module": "Non Profit",
|
|
||||||
"name": "Membership Settings",
|
|
||||||
"owner": "Administrator",
|
|
||||||
"permissions": [
|
|
||||||
{
|
|
||||||
"create": 1,
|
|
||||||
"delete": 1,
|
|
||||||
"email": 1,
|
|
||||||
"print": 1,
|
|
||||||
"read": 1,
|
|
||||||
"role": "System Manager",
|
|
||||||
"share": 1,
|
|
||||||
"write": 1
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"create": 1,
|
|
||||||
"delete": 1,
|
|
||||||
"email": 1,
|
|
||||||
"print": 1,
|
|
||||||
"read": 1,
|
|
||||||
"role": "Non Profit Manager",
|
|
||||||
"share": 1,
|
|
||||||
"write": 1
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"email": 1,
|
|
||||||
"print": 1,
|
|
||||||
"read": 1,
|
|
||||||
"role": "Non Profit Member",
|
|
||||||
"share": 1
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"quick_entry": 1,
|
|
||||||
"sort_field": "modified",
|
|
||||||
"sort_order": "DESC",
|
|
||||||
"track_changes": 1
|
|
||||||
}
|
|
||||||
@@ -1,33 +0,0 @@
|
|||||||
# -*- 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 import _
|
|
||||||
from frappe.integrations.utils import get_payment_gateway_controller
|
|
||||||
from frappe.model.document import Document
|
|
||||||
|
|
||||||
class MembershipSettings(Document):
|
|
||||||
def generate_webhook_key(self):
|
|
||||||
key = frappe.generate_hash(length=20)
|
|
||||||
self.webhook_secret = key
|
|
||||||
self.save()
|
|
||||||
|
|
||||||
frappe.msgprint(
|
|
||||||
_("Here is your webhook secret, this will be shown to you only once.") + "<br><br>" + key,
|
|
||||||
_("Webhook Secret")
|
|
||||||
);
|
|
||||||
|
|
||||||
def revoke_key(self):
|
|
||||||
self.webhook_secret = None;
|
|
||||||
self.save()
|
|
||||||
|
|
||||||
def get_webhook_secret(self):
|
|
||||||
return self.get_password(fieldname="webhook_secret", raise_exception=False)
|
|
||||||
|
|
||||||
@frappe.whitelist()
|
|
||||||
def get_plans_for_membership(*args, **kwargs):
|
|
||||||
controller = get_payment_gateway_controller("Razorpay")
|
|
||||||
plans = controller.get_plans()
|
|
||||||
return [plan.get("item") for plan in plans.get("items")]
|
|
||||||
@@ -3,11 +3,11 @@
|
|||||||
|
|
||||||
frappe.ui.form.on('Membership Type', {
|
frappe.ui.form.on('Membership Type', {
|
||||||
refresh: function (frm) {
|
refresh: function (frm) {
|
||||||
frappe.db.get_single_value('Membership Settings', 'enable_razorpay').then(val => {
|
frappe.db.get_single_value('Non Profit Settings', 'enable_razorpay_for_memberships').then(val => {
|
||||||
if (val) frm.set_df_property('razorpay_plan_id', 'hidden', false);
|
if (val) frm.set_df_property('razorpay_plan_id', 'hidden', false);
|
||||||
});
|
});
|
||||||
|
|
||||||
frappe.db.get_single_value('Membership Settings', 'enable_invoicing').then(val => {
|
frappe.db.get_single_value('Non Profit Settings', 'allow_invoicing').then(val => {
|
||||||
if (val) frm.set_df_property('linked_item', 'hidden', false);
|
if (val) frm.set_df_property('linked_item', 'hidden', false);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@@ -1,16 +1,8 @@
|
|||||||
// Copyright (c) 2020, Frappe Technologies Pvt. Ltd. and contributors
|
// Copyright (c) 2020, Frappe Technologies Pvt. Ltd. and contributors
|
||||||
// For license information, please see license.txt
|
// For license information, please see license.txt
|
||||||
|
|
||||||
frappe.ui.form.on("Membership Settings", {
|
frappe.ui.form.on("Non Profit Settings", {
|
||||||
refresh: function(frm) {
|
refresh: function(frm) {
|
||||||
if (frm.doc.webhook_secret) {
|
|
||||||
frm.add_custom_button(__("Revoke <Key></Key>"), () => {
|
|
||||||
frm.call("revoke_key").then(() => {
|
|
||||||
frm.refresh();
|
|
||||||
})
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
frm.set_query("inv_print_format", function() {
|
frm.set_query("inv_print_format", function() {
|
||||||
return {
|
return {
|
||||||
filters: {
|
filters: {
|
||||||
@@ -37,7 +29,7 @@ frappe.ui.form.on("Membership Settings", {
|
|||||||
};
|
};
|
||||||
});
|
});
|
||||||
|
|
||||||
frm.set_query("payment_account", function () {
|
frm.set_query("membership_payment_account", function () {
|
||||||
var account_types = ["Bank", "Cash"];
|
var account_types = ["Bank", "Cash"];
|
||||||
return {
|
return {
|
||||||
filters: {
|
filters: {
|
||||||
@@ -51,31 +43,70 @@ frappe.ui.form.on("Membership Settings", {
|
|||||||
let docs_url = "https://docs.erpnext.com/docs/user/manual/en/non_profit/membership";
|
let docs_url = "https://docs.erpnext.com/docs/user/manual/en/non_profit/membership";
|
||||||
|
|
||||||
frm.set_intro(__("You can learn more about memberships in the manual. ") + `<a href='${docs_url}'>${__('ERPNext Docs')}</a>`, true);
|
frm.set_intro(__("You can learn more about memberships in the manual. ") + `<a href='${docs_url}'>${__('ERPNext Docs')}</a>`, true);
|
||||||
|
frm.trigger("setup_buttons_for_membership");
|
||||||
frm.trigger("add_generate_button");
|
frm.trigger("setup_buttons_for_donation");
|
||||||
frm.trigger("add_copy_buttonn");
|
|
||||||
},
|
},
|
||||||
|
|
||||||
add_generate_button: function(frm) {
|
setup_buttons_for_membership: function(frm) {
|
||||||
let label;
|
let label;
|
||||||
|
|
||||||
if (frm.doc.webhook_secret) {
|
if (frm.doc.membership_webhook_secret) {
|
||||||
|
|
||||||
|
frm.add_custom_button(__("Copy Webhook URL"), () => {
|
||||||
|
frappe.utils.copy_to_clipboard(`https://${frappe.boot.sitename}/api/method/erpnext.non_profit.doctype.membership.membership.trigger_razorpay_subscription`);
|
||||||
|
}, __("Memberships"));
|
||||||
|
|
||||||
|
frm.add_custom_button(__("Revoke Key"), () => {
|
||||||
|
frm.call("revoke_key", {
|
||||||
|
key: "membership_webhook_secret"
|
||||||
|
}).then(() => {
|
||||||
|
frm.refresh();
|
||||||
|
});
|
||||||
|
}, __("Memberships"));
|
||||||
|
|
||||||
label = __("Regenerate Webhook Secret");
|
label = __("Regenerate Webhook Secret");
|
||||||
|
|
||||||
} else {
|
} else {
|
||||||
label = __("Generate Webhook Secret");
|
label = __("Generate Webhook Secret");
|
||||||
}
|
}
|
||||||
|
|
||||||
frm.add_custom_button(label, () => {
|
frm.add_custom_button(label, () => {
|
||||||
frm.call("generate_webhook_key").then(() => {
|
frm.call("generate_webhook_secret", {
|
||||||
|
field: "membership_webhook_secret"
|
||||||
|
}).then(() => {
|
||||||
frm.refresh();
|
frm.refresh();
|
||||||
});
|
});
|
||||||
});
|
}, __("Memberships"));
|
||||||
},
|
},
|
||||||
|
|
||||||
add_copy_buttonn: function(frm) {
|
setup_buttons_for_donation: function(frm) {
|
||||||
if (frm.doc.webhook_secret) {
|
let label;
|
||||||
|
|
||||||
|
if (frm.doc.donation_webhook_secret) {
|
||||||
|
label = __("Regenerate Webhook Secret");
|
||||||
|
|
||||||
frm.add_custom_button(__("Copy Webhook URL"), () => {
|
frm.add_custom_button(__("Copy Webhook URL"), () => {
|
||||||
frappe.utils.copy_to_clipboard(`https://${frappe.boot.sitename}/api/method/erpnext.non_profit.doctype.membership.membership.trigger_razorpay_subscription`);
|
frappe.utils.copy_to_clipboard(`https://${frappe.boot.sitename}/api/method/erpnext.non_profit.doctype.donation.donation.capture_razorpay_donations`);
|
||||||
});
|
}, __("Donations"));
|
||||||
|
|
||||||
|
frm.add_custom_button(__("Revoke Key"), () => {
|
||||||
|
frm.call("revoke_key", {
|
||||||
|
key: "donation_webhook_secret"
|
||||||
|
}).then(() => {
|
||||||
|
frm.refresh();
|
||||||
|
});
|
||||||
|
}, __("Donations"));
|
||||||
|
|
||||||
|
} else {
|
||||||
|
label = __("Generate Webhook Secret");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
frm.add_custom_button(label, () => {
|
||||||
|
frm.call("generate_webhook_secret", {
|
||||||
|
field: "donation_webhook_secret"
|
||||||
|
}).then(() => {
|
||||||
|
frm.refresh();
|
||||||
|
});
|
||||||
|
}, __("Donations"));
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
@@ -0,0 +1,273 @@
|
|||||||
|
{
|
||||||
|
"actions": [],
|
||||||
|
"creation": "2020-03-29 12:57:03.005120",
|
||||||
|
"doctype": "DocType",
|
||||||
|
"editable_grid": 1,
|
||||||
|
"engine": "InnoDB",
|
||||||
|
"field_order": [
|
||||||
|
"enable_razorpay_for_memberships",
|
||||||
|
"razorpay_settings_section",
|
||||||
|
"billing_cycle",
|
||||||
|
"billing_frequency",
|
||||||
|
"membership_webhook_secret",
|
||||||
|
"column_break_6",
|
||||||
|
"allow_invoicing",
|
||||||
|
"automate_membership_invoicing",
|
||||||
|
"automate_membership_payment_entries",
|
||||||
|
"company",
|
||||||
|
"membership_debit_account",
|
||||||
|
"membership_payment_account",
|
||||||
|
"column_break_9",
|
||||||
|
"send_email",
|
||||||
|
"send_invoice",
|
||||||
|
"membership_print_format",
|
||||||
|
"inv_print_format",
|
||||||
|
"email_template",
|
||||||
|
"donation_settings_section",
|
||||||
|
"donation_company",
|
||||||
|
"default_donor_type",
|
||||||
|
"donation_webhook_secret",
|
||||||
|
"column_break_22",
|
||||||
|
"automate_donation_payment_entries",
|
||||||
|
"donation_debit_account",
|
||||||
|
"donation_payment_account",
|
||||||
|
"section_break_27",
|
||||||
|
"creation_user"
|
||||||
|
],
|
||||||
|
"fields": [
|
||||||
|
{
|
||||||
|
"fieldname": "billing_cycle",
|
||||||
|
"fieldtype": "Select",
|
||||||
|
"label": "Billing Cycle",
|
||||||
|
"options": "Monthly\nYearly"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"depends_on": "eval:doc.enable_razorpay_for_memberships",
|
||||||
|
"fieldname": "razorpay_settings_section",
|
||||||
|
"fieldtype": "Section Break",
|
||||||
|
"label": "RazorPay Settings for Memberships"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"description": "The number of billing cycles for which the customer should be charged. For example, if a customer is buying a 1-year membership that should be billed on a monthly basis, this value should be 12.",
|
||||||
|
"fieldname": "billing_frequency",
|
||||||
|
"fieldtype": "Int",
|
||||||
|
"label": "Billing Frequency"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "column_break_6",
|
||||||
|
"fieldtype": "Section Break",
|
||||||
|
"label": "Membership Invoicing"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "column_break_9",
|
||||||
|
"fieldtype": "Column Break"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"description": "This company will be set for the Memberships created via webhook.",
|
||||||
|
"fieldname": "company",
|
||||||
|
"fieldtype": "Link",
|
||||||
|
"in_list_view": 1,
|
||||||
|
"label": "Company",
|
||||||
|
"options": "Company",
|
||||||
|
"reqd": 1
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"default": "0",
|
||||||
|
"depends_on": "eval:doc.allow_invoicing && doc.send_email",
|
||||||
|
"fieldname": "send_invoice",
|
||||||
|
"fieldtype": "Check",
|
||||||
|
"label": "Send Invoice with Email"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"default": "0",
|
||||||
|
"fieldname": "send_email",
|
||||||
|
"fieldtype": "Check",
|
||||||
|
"label": "Send Membership Acknowledgement"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"depends_on": "eval: doc.send_invoice",
|
||||||
|
"fieldname": "inv_print_format",
|
||||||
|
"fieldtype": "Link",
|
||||||
|
"label": "Invoice Print Format",
|
||||||
|
"mandatory_depends_on": "eval: doc.send_invoice",
|
||||||
|
"options": "Print Format"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"depends_on": "eval:doc.send_email",
|
||||||
|
"fieldname": "membership_print_format",
|
||||||
|
"fieldtype": "Link",
|
||||||
|
"label": "Membership Print Format",
|
||||||
|
"options": "Print Format"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"depends_on": "eval:doc.send_email",
|
||||||
|
"fieldname": "email_template",
|
||||||
|
"fieldtype": "Link",
|
||||||
|
"label": "Email Template",
|
||||||
|
"mandatory_depends_on": "eval:doc.send_email",
|
||||||
|
"options": "Email Template"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"default": "0",
|
||||||
|
"fieldname": "allow_invoicing",
|
||||||
|
"fieldtype": "Check",
|
||||||
|
"label": "Allow Invoicing for Memberships",
|
||||||
|
"mandatory_depends_on": "eval:doc.send_invoice || doc.make_payment_entry"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"default": "0",
|
||||||
|
"depends_on": "eval:doc.allow_invoicing",
|
||||||
|
"description": "Automatically create an invoice when payment is authorized from a web form entry",
|
||||||
|
"fieldname": "automate_membership_invoicing",
|
||||||
|
"fieldtype": "Check",
|
||||||
|
"label": "Automate Invoicing for Web Forms"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"default": "0",
|
||||||
|
"depends_on": "eval:doc.allow_invoicing",
|
||||||
|
"description": "Auto creates Payment Entry for Sales Invoices created for Membership from web forms.",
|
||||||
|
"fieldname": "automate_membership_payment_entries",
|
||||||
|
"fieldtype": "Check",
|
||||||
|
"label": "Automate Payment Entry Creation"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"default": "0",
|
||||||
|
"fieldname": "enable_razorpay_for_memberships",
|
||||||
|
"fieldtype": "Check",
|
||||||
|
"label": "Enable RazorPay For Memberships"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"depends_on": "eval:doc.automate_membership_payment_entries",
|
||||||
|
"description": "Account for accepting membership payments",
|
||||||
|
"fieldname": "membership_payment_account",
|
||||||
|
"fieldtype": "Link",
|
||||||
|
"label": "Membership Payment To",
|
||||||
|
"mandatory_depends_on": "eval:doc.automate_membership_payment_entries",
|
||||||
|
"options": "Account"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "membership_webhook_secret",
|
||||||
|
"fieldtype": "Password",
|
||||||
|
"label": "Membership Webhook Secret",
|
||||||
|
"read_only": 1
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "donation_webhook_secret",
|
||||||
|
"fieldtype": "Password",
|
||||||
|
"label": "Donation Webhook Secret",
|
||||||
|
"read_only": 1
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"depends_on": "automate_donation_payment_entries",
|
||||||
|
"description": "Account for accepting donation payments",
|
||||||
|
"fieldname": "donation_payment_account",
|
||||||
|
"fieldtype": "Link",
|
||||||
|
"label": "Donation Payment To",
|
||||||
|
"mandatory_depends_on": "automate_donation_payment_entries",
|
||||||
|
"options": "Account"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"default": "0",
|
||||||
|
"description": "Auto creates Payment Entry for Donations created from web forms.",
|
||||||
|
"fieldname": "automate_donation_payment_entries",
|
||||||
|
"fieldtype": "Check",
|
||||||
|
"label": "Automate Donation Payment Entries"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"depends_on": "eval:doc.allow_invoicing",
|
||||||
|
"fieldname": "membership_debit_account",
|
||||||
|
"fieldtype": "Link",
|
||||||
|
"label": "Debit Account",
|
||||||
|
"mandatory_depends_on": "eval:doc.allow_invoicing",
|
||||||
|
"options": "Account"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"depends_on": "automate_donation_payment_entries",
|
||||||
|
"fieldname": "donation_debit_account",
|
||||||
|
"fieldtype": "Link",
|
||||||
|
"label": "Debit Account",
|
||||||
|
"mandatory_depends_on": "automate_donation_payment_entries",
|
||||||
|
"options": "Account"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"description": "This company will be set for the Donations created via webhook.",
|
||||||
|
"fieldname": "donation_company",
|
||||||
|
"fieldtype": "Link",
|
||||||
|
"in_list_view": 1,
|
||||||
|
"label": "Company",
|
||||||
|
"options": "Company",
|
||||||
|
"reqd": 1
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "donation_settings_section",
|
||||||
|
"fieldtype": "Section Break",
|
||||||
|
"label": "Donation Settings"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "column_break_22",
|
||||||
|
"fieldtype": "Column Break"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"description": "This Donor Type will be set for the Donor created via Donation web form entry.",
|
||||||
|
"fieldname": "default_donor_type",
|
||||||
|
"fieldtype": "Link",
|
||||||
|
"in_list_view": 1,
|
||||||
|
"label": "Default Donor Type",
|
||||||
|
"options": "Donor Type",
|
||||||
|
"reqd": 1
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "section_break_27",
|
||||||
|
"fieldtype": "Section Break"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"description": "The user that will be used to create Donations, Memberships, Invoices, and Payment Entries. This user should have the relevant permissions.",
|
||||||
|
"fieldname": "creation_user",
|
||||||
|
"fieldtype": "Link",
|
||||||
|
"label": "Creation User",
|
||||||
|
"options": "User",
|
||||||
|
"reqd": 1
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"index_web_pages_for_search": 1,
|
||||||
|
"issingle": 1,
|
||||||
|
"links": [],
|
||||||
|
"modified": "2021-03-11 10:43:38.124240",
|
||||||
|
"modified_by": "Administrator",
|
||||||
|
"module": "Non Profit",
|
||||||
|
"name": "Non Profit Settings",
|
||||||
|
"owner": "Administrator",
|
||||||
|
"permissions": [
|
||||||
|
{
|
||||||
|
"create": 1,
|
||||||
|
"delete": 1,
|
||||||
|
"email": 1,
|
||||||
|
"print": 1,
|
||||||
|
"read": 1,
|
||||||
|
"role": "System Manager",
|
||||||
|
"share": 1,
|
||||||
|
"write": 1
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"create": 1,
|
||||||
|
"delete": 1,
|
||||||
|
"email": 1,
|
||||||
|
"print": 1,
|
||||||
|
"read": 1,
|
||||||
|
"role": "Non Profit Manager",
|
||||||
|
"share": 1,
|
||||||
|
"write": 1
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"email": 1,
|
||||||
|
"print": 1,
|
||||||
|
"read": 1,
|
||||||
|
"role": "Non Profit Member",
|
||||||
|
"share": 1
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"quick_entry": 1,
|
||||||
|
"sort_field": "modified",
|
||||||
|
"sort_order": "DESC",
|
||||||
|
"track_changes": 1
|
||||||
|
}
|
||||||
@@ -0,0 +1,36 @@
|
|||||||
|
# -*- 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 import _
|
||||||
|
from frappe.integrations.utils import get_payment_gateway_controller
|
||||||
|
from frappe.model.document import Document
|
||||||
|
|
||||||
|
class NonProfitSettings(Document):
|
||||||
|
def generate_webhook_secret(self, field="membership_webhook_secret"):
|
||||||
|
key = frappe.generate_hash(length=20)
|
||||||
|
self.set(field, key)
|
||||||
|
self.save()
|
||||||
|
|
||||||
|
secret_for = "Membership" if field == "membership_webhook_secret" else "Donation"
|
||||||
|
|
||||||
|
frappe.msgprint(
|
||||||
|
_("Here is your webhook secret for {0} API, this will be shown to you only once.").format(secret_for) + "<br><br>" + key,
|
||||||
|
_("Webhook Secret")
|
||||||
|
)
|
||||||
|
|
||||||
|
def revoke_key(self, key):
|
||||||
|
self.set(key, None)
|
||||||
|
self.save()
|
||||||
|
|
||||||
|
def get_webhook_secret(self, endpoint="Membership"):
|
||||||
|
fieldname = "membership_webhook_secret" if endpoint == "Membership" else "donation_webhook_secret"
|
||||||
|
return self.get_password(fieldname=fieldname, raise_exception=False)
|
||||||
|
|
||||||
|
@frappe.whitelist()
|
||||||
|
def get_plans_for_membership(*args, **kwargs):
|
||||||
|
controller = get_payment_gateway_controller("Razorpay")
|
||||||
|
plans = controller.get_plans()
|
||||||
|
return [plan.get("item") for plan in plans.get("items")]
|
||||||
@@ -6,5 +6,5 @@ from __future__ import unicode_literals
|
|||||||
# import frappe
|
# import frappe
|
||||||
import unittest
|
import unittest
|
||||||
|
|
||||||
class TestMembershipSettings(unittest.TestCase):
|
class TestNonProfitSettings(unittest.TestCase):
|
||||||
pass
|
pass
|
||||||
@@ -10,6 +10,7 @@
|
|||||||
"hide_custom": 0,
|
"hide_custom": 0,
|
||||||
"icon": "non-profit",
|
"icon": "non-profit",
|
||||||
"idx": 0,
|
"idx": 0,
|
||||||
|
"is_default": 0,
|
||||||
"is_standard": 1,
|
"is_standard": 1,
|
||||||
"label": "Non Profit",
|
"label": "Non Profit",
|
||||||
"links": [
|
"links": [
|
||||||
@@ -109,7 +110,7 @@
|
|||||||
"hidden": 0,
|
"hidden": 0,
|
||||||
"is_query_report": 0,
|
"is_query_report": 0,
|
||||||
"label": "Membership Settings",
|
"label": "Membership Settings",
|
||||||
"link_to": "Membership Settings",
|
"link_to": "Non Profit Settings",
|
||||||
"link_type": "DocType",
|
"link_type": "DocType",
|
||||||
"onboard": 0,
|
"onboard": 0,
|
||||||
"type": "Link"
|
"type": "Link"
|
||||||
@@ -161,7 +162,7 @@
|
|||||||
{
|
{
|
||||||
"hidden": 0,
|
"hidden": 0,
|
||||||
"is_query_report": 0,
|
"is_query_report": 0,
|
||||||
"label": "Donor",
|
"label": "Donation",
|
||||||
"onboard": 0,
|
"onboard": 0,
|
||||||
"type": "Card Break"
|
"type": "Card Break"
|
||||||
},
|
},
|
||||||
@@ -184,9 +185,35 @@
|
|||||||
"link_type": "DocType",
|
"link_type": "DocType",
|
||||||
"onboard": 0,
|
"onboard": 0,
|
||||||
"type": "Link"
|
"type": "Link"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"hidden": 0,
|
||||||
|
"is_query_report": 0,
|
||||||
|
"label": "Donation",
|
||||||
|
"link_to": "Donation",
|
||||||
|
"link_type": "DocType",
|
||||||
|
"onboard": 0,
|
||||||
|
"type": "Link"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"hidden": 0,
|
||||||
|
"is_query_report": 0,
|
||||||
|
"label": "Tax Exemption Certification (India)",
|
||||||
|
"link_type": "DocType",
|
||||||
|
"onboard": 0,
|
||||||
|
"type": "Card Break"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"hidden": 0,
|
||||||
|
"is_query_report": 0,
|
||||||
|
"label": "Tax Exemption 80G Certificate",
|
||||||
|
"link_to": "Tax Exemption 80G Certificate",
|
||||||
|
"link_type": "DocType",
|
||||||
|
"onboard": 0,
|
||||||
|
"type": "Link"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"modified": "2020-12-01 13:38:38.351409",
|
"modified": "2021-03-11 11:38:09.140655",
|
||||||
"modified_by": "Administrator",
|
"modified_by": "Administrator",
|
||||||
"module": "Non Profit",
|
"module": "Non Profit",
|
||||||
"name": "Non Profit",
|
"name": "Non Profit",
|
||||||
@@ -201,8 +228,8 @@
|
|||||||
"type": "DocType"
|
"type": "DocType"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"label": "Membership Settings",
|
"label": "Non Profit Settings",
|
||||||
"link_to": "Membership Settings",
|
"link_to": "Non Profit Settings",
|
||||||
"type": "DocType"
|
"type": "DocType"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -756,6 +756,9 @@ erpnext.patches.v12_0.add_state_code_for_ladakh
|
|||||||
erpnext.patches.v13_0.item_reposting_for_incorrect_sl_and_gl
|
erpnext.patches.v13_0.item_reposting_for_incorrect_sl_and_gl
|
||||||
erpnext.patches.v13_0.delete_old_bank_reconciliation_doctypes
|
erpnext.patches.v13_0.delete_old_bank_reconciliation_doctypes
|
||||||
erpnext.patches.v13_0.update_vehicle_no_reqd_condition
|
erpnext.patches.v13_0.update_vehicle_no_reqd_condition
|
||||||
|
erpnext.patches.v13_0.setup_fields_for_80g_certificate_and_donation
|
||||||
|
erpnext.patches.v13_0.rename_membership_settings_to_non_profit_settings
|
||||||
|
erpnext.patches.v13_0.setup_gratuity_rule_for_india_and_uae
|
||||||
erpnext.patches.v13_0.create_website_items
|
erpnext.patches.v13_0.create_website_items
|
||||||
erpnext.patches.v13_0.populate_e_commerce_settings
|
erpnext.patches.v13_0.populate_e_commerce_settings
|
||||||
erpnext.patches.v13_0.make_homepage_products_website_items
|
erpnext.patches.v13_0.make_homepage_products_website_items
|
||||||
|
|||||||
@@ -0,0 +1,22 @@
|
|||||||
|
from __future__ import unicode_literals
|
||||||
|
import frappe
|
||||||
|
from frappe.model.utils.rename_field import rename_field
|
||||||
|
|
||||||
|
def execute():
|
||||||
|
if frappe.db.table_exists("Membership Settings"):
|
||||||
|
frappe.rename_doc("DocType", "Membership Settings", "Non Profit Settings")
|
||||||
|
frappe.reload_doctype("Non Profit Settings", force=True)
|
||||||
|
|
||||||
|
if frappe.db.table_exists("Non Profit Settings"):
|
||||||
|
rename_fields_map = {
|
||||||
|
"enable_invoicing": "allow_invoicing",
|
||||||
|
"create_for_web_forms": "automate_membership_invoicing",
|
||||||
|
"make_payment_entry": "automate_membership_payment_entries",
|
||||||
|
"enable_razorpay": "enable_razorpay_for_memberships",
|
||||||
|
"debit_account": "membership_debit_account",
|
||||||
|
"payment_account": "membership_payment_account",
|
||||||
|
"webhook_secret": "membership_webhook_secret"
|
||||||
|
}
|
||||||
|
|
||||||
|
for old_name, new_name in rename_fields_map.items():
|
||||||
|
rename_field("Non Profit Settings", old_name, new_name)
|
||||||
@@ -0,0 +1,16 @@
|
|||||||
|
import frappe
|
||||||
|
from erpnext.regional.india.setup import make_custom_fields
|
||||||
|
|
||||||
|
def execute():
|
||||||
|
company = frappe.get_all('Company', filters = {'country': 'India'})
|
||||||
|
if not company:
|
||||||
|
return
|
||||||
|
|
||||||
|
make_custom_fields()
|
||||||
|
|
||||||
|
if not frappe.db.exists('Party Type', 'Donor'):
|
||||||
|
frappe.get_doc({
|
||||||
|
'doctype': 'Party Type',
|
||||||
|
'party_type': 'Donor',
|
||||||
|
'account_type': 'Receivable'
|
||||||
|
}).insert(ignore_permissions=True)
|
||||||
@@ -0,0 +1,16 @@
|
|||||||
|
# Copyright (c) 2019, Frappe and Contributors
|
||||||
|
# License: GNU General Public License v3. See license.txt
|
||||||
|
|
||||||
|
from __future__ import unicode_literals
|
||||||
|
import frappe
|
||||||
|
|
||||||
|
def execute():
|
||||||
|
frappe.reload_doc('payroll', 'doctype', 'gratuity_rule')
|
||||||
|
frappe.reload_doc('payroll', 'doctype', 'gratuity_rule_slab')
|
||||||
|
frappe.reload_doc('payroll', 'doctype', 'gratuity_applicable_component')
|
||||||
|
if frappe.db.exists("Company", {"country": "India"}):
|
||||||
|
from erpnext.regional.india.setup import create_gratuity_rule
|
||||||
|
create_gratuity_rule()
|
||||||
|
if frappe.db.exists("Company", {"country": "United Arab Emirates"}):
|
||||||
|
from erpnext.regional.united_arab_emirates.setup import create_gratuity_rule
|
||||||
|
create_gratuity_rule()
|
||||||
0
erpnext/payroll/doctype/gratuity/__init__.py
Normal file
0
erpnext/payroll/doctype/gratuity/__init__.py
Normal file
72
erpnext/payroll/doctype/gratuity/gratuity.js
Normal file
72
erpnext/payroll/doctype/gratuity/gratuity.js
Normal file
@@ -0,0 +1,72 @@
|
|||||||
|
// Copyright (c) 2020, Frappe Technologies Pvt. Ltd. and contributors
|
||||||
|
// For license information, please see license.txt
|
||||||
|
|
||||||
|
frappe.ui.form.on('Gratuity', {
|
||||||
|
setup: function (frm) {
|
||||||
|
frm.set_query('salary_component', function () {
|
||||||
|
return {
|
||||||
|
filters: {
|
||||||
|
type: "Earning"
|
||||||
|
}
|
||||||
|
};
|
||||||
|
});
|
||||||
|
frm.set_query("expense_account", function () {
|
||||||
|
return {
|
||||||
|
filters: {
|
||||||
|
"root_type": "Expense",
|
||||||
|
"is_group": 0,
|
||||||
|
"company": frm.doc.company
|
||||||
|
}
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
|
frm.set_query("payable_account", function () {
|
||||||
|
return {
|
||||||
|
filters: {
|
||||||
|
"root_type": "Liability",
|
||||||
|
"is_group": 0,
|
||||||
|
"company": frm.doc.company
|
||||||
|
}
|
||||||
|
};
|
||||||
|
});
|
||||||
|
},
|
||||||
|
refresh: function (frm) {
|
||||||
|
if (frm.doc.docstatus === 1 && frm.doc.pay_via_salary_slip === 0 && frm.doc.status === "Unpaid") {
|
||||||
|
frm.add_custom_button(__("Create Payment Entry"), function () {
|
||||||
|
return frappe.call({
|
||||||
|
method: 'erpnext.accounts.doctype.payment_entry.payment_entry.get_payment_entry',
|
||||||
|
args: {
|
||||||
|
"dt": frm.doc.doctype,
|
||||||
|
"dn": frm.doc.name
|
||||||
|
},
|
||||||
|
callback: function (r) {
|
||||||
|
var doclist = frappe.model.sync(r.message);
|
||||||
|
frappe.set_route("Form", doclist[0].doctype, doclist[0].name);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
},
|
||||||
|
employee: function (frm) {
|
||||||
|
frm.events.calculate_work_experience_and_amount(frm);
|
||||||
|
},
|
||||||
|
gratuity_rule: function (frm) {
|
||||||
|
frm.events.calculate_work_experience_and_amount(frm);
|
||||||
|
},
|
||||||
|
calculate_work_experience_and_amount: function (frm) {
|
||||||
|
|
||||||
|
if (frm.doc.employee && frm.doc.gratuity_rule) {
|
||||||
|
frappe.call({
|
||||||
|
method: "erpnext.payroll.doctype.gratuity.gratuity.calculate_work_experience_and_amount",
|
||||||
|
args: {
|
||||||
|
employee: frm.doc.employee,
|
||||||
|
gratuity_rule: frm.doc.gratuity_rule
|
||||||
|
}
|
||||||
|
}).then((r) => {
|
||||||
|
frm.set_value("current_work_experience", r.message['current_work_experience']);
|
||||||
|
frm.set_value("amount", r.message['amount']);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
});
|
||||||
232
erpnext/payroll/doctype/gratuity/gratuity.json
Normal file
232
erpnext/payroll/doctype/gratuity/gratuity.json
Normal file
@@ -0,0 +1,232 @@
|
|||||||
|
{
|
||||||
|
"actions": [],
|
||||||
|
"autoname": "HR-GRA-PAY-.#####",
|
||||||
|
"creation": "2020-08-05 20:52:13.024683",
|
||||||
|
"doctype": "DocType",
|
||||||
|
"editable_grid": 1,
|
||||||
|
"engine": "InnoDB",
|
||||||
|
"field_order": [
|
||||||
|
"employee",
|
||||||
|
"employee_name",
|
||||||
|
"department",
|
||||||
|
"designation",
|
||||||
|
"column_break_3",
|
||||||
|
"posting_date",
|
||||||
|
"status",
|
||||||
|
"company",
|
||||||
|
"gratuity_rule",
|
||||||
|
"section_break_5",
|
||||||
|
"pay_via_salary_slip",
|
||||||
|
"payroll_date",
|
||||||
|
"salary_component",
|
||||||
|
"payable_account",
|
||||||
|
"expense_account",
|
||||||
|
"mode_of_payment",
|
||||||
|
"cost_center",
|
||||||
|
"column_break_15",
|
||||||
|
"current_work_experience",
|
||||||
|
"amount",
|
||||||
|
"paid_amount",
|
||||||
|
"amended_from"
|
||||||
|
],
|
||||||
|
"fields": [
|
||||||
|
{
|
||||||
|
"fieldname": "employee",
|
||||||
|
"fieldtype": "Link",
|
||||||
|
"in_global_search": 1,
|
||||||
|
"in_list_view": 1,
|
||||||
|
"label": "Employee",
|
||||||
|
"options": "Employee",
|
||||||
|
"reqd": 1,
|
||||||
|
"search_index": 1
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fetch_from": "employee.company",
|
||||||
|
"fieldname": "company",
|
||||||
|
"fieldtype": "Link",
|
||||||
|
"label": "Company",
|
||||||
|
"options": "Company",
|
||||||
|
"read_only": 1,
|
||||||
|
"reqd": 1
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"default": "1",
|
||||||
|
"fieldname": "pay_via_salary_slip",
|
||||||
|
"fieldtype": "Check",
|
||||||
|
"label": "Pay via Salary Slip"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "posting_date",
|
||||||
|
"fieldtype": "Date",
|
||||||
|
"label": "Posting date",
|
||||||
|
"reqd": 1
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"depends_on": "eval: doc.pay_via_salary_slip == 1",
|
||||||
|
"fieldname": "salary_component",
|
||||||
|
"fieldtype": "Link",
|
||||||
|
"label": "Salary Component",
|
||||||
|
"mandatory_depends_on": "eval: doc.pay_via_salary_slip == 1",
|
||||||
|
"options": "Salary Component"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"default": "0",
|
||||||
|
"fieldname": "current_work_experience",
|
||||||
|
"fieldtype": "Int",
|
||||||
|
"label": "Current Work Experience",
|
||||||
|
"read_only": 1
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"default": "0",
|
||||||
|
"fieldname": "amount",
|
||||||
|
"fieldtype": "Currency",
|
||||||
|
"label": "Total Amount",
|
||||||
|
"read_only": 1,
|
||||||
|
"reqd": 1
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"default": "Draft",
|
||||||
|
"fieldname": "status",
|
||||||
|
"fieldtype": "Select",
|
||||||
|
"in_list_view": 1,
|
||||||
|
"label": "Status",
|
||||||
|
"options": "Draft\nUnpaid\nPaid",
|
||||||
|
"read_only": 1,
|
||||||
|
"reqd": 1
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"depends_on": "eval: doc.pay_via_salary_slip == 0",
|
||||||
|
"fieldname": "expense_account",
|
||||||
|
"fieldtype": "Link",
|
||||||
|
"label": "Expense Account",
|
||||||
|
"mandatory_depends_on": "eval: doc.pay_via_salary_slip == 0",
|
||||||
|
"options": "Account"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"depends_on": "eval: doc.pay_via_salary_slip == 0",
|
||||||
|
"fieldname": "mode_of_payment",
|
||||||
|
"fieldtype": "Link",
|
||||||
|
"label": "Mode of Payment",
|
||||||
|
"mandatory_depends_on": "eval: doc.pay_via_salary_slip == 0",
|
||||||
|
"options": "Mode of Payment"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "gratuity_rule",
|
||||||
|
"fieldtype": "Link",
|
||||||
|
"label": "Gratuity Rule",
|
||||||
|
"options": "Gratuity Rule",
|
||||||
|
"reqd": 1
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "section_break_5",
|
||||||
|
"fieldtype": "Section Break",
|
||||||
|
"label": "Payment Configuration"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fetch_from": "employee.employee_name",
|
||||||
|
"fieldname": "employee_name",
|
||||||
|
"fieldtype": "Data",
|
||||||
|
"label": "Employee Name",
|
||||||
|
"read_only": 1
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "column_break_3",
|
||||||
|
"fieldtype": "Column Break"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fetch_from": "employee.department",
|
||||||
|
"fieldname": "department",
|
||||||
|
"fieldtype": "Link",
|
||||||
|
"label": "Department",
|
||||||
|
"options": "Department",
|
||||||
|
"read_only": 1
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fetch_from": "employee.designation",
|
||||||
|
"fieldname": "designation",
|
||||||
|
"fieldtype": "Data",
|
||||||
|
"label": "Designation",
|
||||||
|
"read_only": 1
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "amended_from",
|
||||||
|
"fieldtype": "Link",
|
||||||
|
"label": "Amended From",
|
||||||
|
"no_copy": 1,
|
||||||
|
"options": "Gratuity",
|
||||||
|
"print_hide": 1,
|
||||||
|
"read_only": 1
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "column_break_15",
|
||||||
|
"fieldtype": "Column Break"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"depends_on": "eval: doc.pay_via_salary_slip == 1",
|
||||||
|
"fieldname": "payroll_date",
|
||||||
|
"fieldtype": "Date",
|
||||||
|
"label": "Payroll Date",
|
||||||
|
"mandatory_depends_on": "eval: doc.pay_via_salary_slip == 1"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"default": "0",
|
||||||
|
"depends_on": "eval:doc.pay_via_salary_slip == 0",
|
||||||
|
"fieldname": "paid_amount",
|
||||||
|
"fieldtype": "Currency",
|
||||||
|
"label": "Paid Amount",
|
||||||
|
"read_only": 1
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"depends_on": "eval: doc.pay_via_salary_slip == 0",
|
||||||
|
"fieldname": "payable_account",
|
||||||
|
"fieldtype": "Link",
|
||||||
|
"label": "Payable Account",
|
||||||
|
"mandatory_depends_on": "eval: doc.pay_via_salary_slip == 0",
|
||||||
|
"options": "Account"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"depends_on": "eval: doc.pay_via_salary_slip == 0",
|
||||||
|
"fieldname": "cost_center",
|
||||||
|
"fieldtype": "Link",
|
||||||
|
"label": "Cost Center",
|
||||||
|
"mandatory_depends_on": "eval: doc.pay_via_salary_slip == 0",
|
||||||
|
"options": "Cost Center"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"index_web_pages_for_search": 1,
|
||||||
|
"is_submittable": 1,
|
||||||
|
"links": [],
|
||||||
|
"modified": "2020-11-02 18:21:11.971488",
|
||||||
|
"modified_by": "Administrator",
|
||||||
|
"module": "Payroll",
|
||||||
|
"name": "Gratuity",
|
||||||
|
"owner": "Administrator",
|
||||||
|
"permissions": [
|
||||||
|
{
|
||||||
|
"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
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"sort_field": "modified",
|
||||||
|
"sort_order": "DESC"
|
||||||
|
}
|
||||||
249
erpnext/payroll/doctype/gratuity/gratuity.py
Normal file
249
erpnext/payroll/doctype/gratuity/gratuity.py
Normal file
@@ -0,0 +1,249 @@
|
|||||||
|
# -*- 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 import _, bold
|
||||||
|
from frappe.utils import flt, get_datetime, get_link_to_form
|
||||||
|
from erpnext.accounts.general_ledger import make_gl_entries
|
||||||
|
from erpnext.controllers.accounts_controller import AccountsController
|
||||||
|
from math import floor
|
||||||
|
|
||||||
|
class Gratuity(AccountsController):
|
||||||
|
def validate(self):
|
||||||
|
data = calculate_work_experience_and_amount(self.employee, self.gratuity_rule)
|
||||||
|
self.current_work_experience = data["current_work_experience"]
|
||||||
|
self.amount = data["amount"]
|
||||||
|
if self.docstatus == 1:
|
||||||
|
self.status = "Unpaid"
|
||||||
|
|
||||||
|
def on_submit(self):
|
||||||
|
if self.pay_via_salary_slip:
|
||||||
|
self.create_additional_salary()
|
||||||
|
else:
|
||||||
|
self.create_gl_entries()
|
||||||
|
|
||||||
|
def on_cancel(self):
|
||||||
|
self.ignore_linked_doctypes = ['GL Entry']
|
||||||
|
self.create_gl_entries(cancel=True)
|
||||||
|
|
||||||
|
def create_gl_entries(self, cancel=False):
|
||||||
|
gl_entries = self.get_gl_entries()
|
||||||
|
make_gl_entries(gl_entries, cancel)
|
||||||
|
|
||||||
|
def get_gl_entries(self):
|
||||||
|
gl_entry = []
|
||||||
|
# payable entry
|
||||||
|
if self.amount:
|
||||||
|
gl_entry.append(
|
||||||
|
self.get_gl_dict({
|
||||||
|
"account": self.payable_account,
|
||||||
|
"credit": self.amount,
|
||||||
|
"credit_in_account_currency": self.amount,
|
||||||
|
"against": self.expense_account,
|
||||||
|
"party_type": "Employee",
|
||||||
|
"party": self.employee,
|
||||||
|
"against_voucher_type": self.doctype,
|
||||||
|
"against_voucher": self.name,
|
||||||
|
"cost_center": self.cost_center
|
||||||
|
}, item=self)
|
||||||
|
)
|
||||||
|
|
||||||
|
# expense entries
|
||||||
|
gl_entry.append(
|
||||||
|
self.get_gl_dict({
|
||||||
|
"account": self.expense_account,
|
||||||
|
"debit": self.amount,
|
||||||
|
"debit_in_account_currency": self.amount,
|
||||||
|
"against": self.payable_account,
|
||||||
|
"cost_center": self.cost_center
|
||||||
|
}, item=self)
|
||||||
|
)
|
||||||
|
else:
|
||||||
|
frappe.throw(_("Total Amount can not be zero"))
|
||||||
|
|
||||||
|
return gl_entry
|
||||||
|
|
||||||
|
def create_additional_salary(self):
|
||||||
|
if self.pay_via_salary_slip:
|
||||||
|
additional_salary = frappe.new_doc('Additional Salary')
|
||||||
|
additional_salary.employee = self.employee
|
||||||
|
additional_salary.salary_component = self.salary_component
|
||||||
|
additional_salary.overwrite_salary_structure_amount = 0
|
||||||
|
additional_salary.amount = self.amount
|
||||||
|
additional_salary.payroll_date = self.payroll_date
|
||||||
|
additional_salary.company = self.company
|
||||||
|
additional_salary.ref_doctype = self.doctype
|
||||||
|
additional_salary.ref_docname = self.name
|
||||||
|
additional_salary.submit()
|
||||||
|
|
||||||
|
def set_total_advance_paid(self):
|
||||||
|
paid_amount = frappe.db.sql("""
|
||||||
|
select ifnull(sum(debit_in_account_currency), 0) as paid_amount
|
||||||
|
from `tabGL Entry`
|
||||||
|
where against_voucher_type = 'Gratuity'
|
||||||
|
and against_voucher = %s
|
||||||
|
and party_type = 'Employee'
|
||||||
|
and party = %s
|
||||||
|
""", (self.name, self.employee), as_dict=1)[0].paid_amount
|
||||||
|
|
||||||
|
if flt(paid_amount) > self.amount:
|
||||||
|
frappe.throw(_("Row {0}# Paid Amount cannot be greater than Total amount"))
|
||||||
|
|
||||||
|
|
||||||
|
self.db_set("paid_amount", paid_amount)
|
||||||
|
if self.amount == self.paid_amount:
|
||||||
|
self.db_set("status", "Paid")
|
||||||
|
|
||||||
|
|
||||||
|
@frappe.whitelist()
|
||||||
|
def calculate_work_experience_and_amount(employee, gratuity_rule):
|
||||||
|
current_work_experience = calculate_work_experience(employee, gratuity_rule) or 0
|
||||||
|
gratuity_amount = calculate_gratuity_amount(employee, gratuity_rule, current_work_experience) or 0
|
||||||
|
|
||||||
|
return {'current_work_experience': current_work_experience, "amount": gratuity_amount}
|
||||||
|
|
||||||
|
def calculate_work_experience(employee, gratuity_rule):
|
||||||
|
|
||||||
|
total_working_days_per_year, minimum_year_for_gratuity = frappe.db.get_value("Gratuity Rule", gratuity_rule, ["total_working_days_per_year", "minimum_year_for_gratuity"])
|
||||||
|
|
||||||
|
date_of_joining, relieving_date = frappe.db.get_value('Employee', employee, ['date_of_joining', 'relieving_date'])
|
||||||
|
if not relieving_date:
|
||||||
|
frappe.throw(_("Please set Relieving Date for employee: {0}").format(bold(get_link_to_form("Employee", employee))))
|
||||||
|
|
||||||
|
method = frappe.db.get_value("Gratuity Rule", gratuity_rule, "work_experience_calculation_function")
|
||||||
|
employee_total_workings_days = calculate_employee_total_workings_days(employee, date_of_joining, relieving_date)
|
||||||
|
|
||||||
|
current_work_experience = employee_total_workings_days/total_working_days_per_year or 1
|
||||||
|
current_work_experience = get_work_experience_using_method(method, current_work_experience, minimum_year_for_gratuity, employee)
|
||||||
|
return current_work_experience
|
||||||
|
|
||||||
|
def calculate_employee_total_workings_days(employee, date_of_joining, relieving_date ):
|
||||||
|
employee_total_workings_days = (get_datetime(relieving_date) - get_datetime(date_of_joining)).days
|
||||||
|
|
||||||
|
payroll_based_on = frappe.db.get_value("Payroll Settings", None, "payroll_based_on") or "Leave"
|
||||||
|
if payroll_based_on == "Leave":
|
||||||
|
total_lwp = get_non_working_days(employee, relieving_date, "On Leave")
|
||||||
|
employee_total_workings_days -= total_lwp
|
||||||
|
elif payroll_based_on == "Attendance":
|
||||||
|
total_absents = get_non_working_days(employee, relieving_date, "Absent")
|
||||||
|
employee_total_workings_days -= total_absents
|
||||||
|
|
||||||
|
return employee_total_workings_days
|
||||||
|
|
||||||
|
def get_work_experience_using_method(method, current_work_experience, minimum_year_for_gratuity, employee):
|
||||||
|
if method == "Round off Work Experience":
|
||||||
|
current_work_experience = round(current_work_experience)
|
||||||
|
else:
|
||||||
|
current_work_experience = floor(current_work_experience)
|
||||||
|
|
||||||
|
if current_work_experience < minimum_year_for_gratuity:
|
||||||
|
frappe.throw(_("Employee: {0} have to complete minimum {1} years for gratuity").format(bold(employee), minimum_year_for_gratuity))
|
||||||
|
return current_work_experience
|
||||||
|
|
||||||
|
def get_non_working_days(employee, relieving_date, status):
|
||||||
|
|
||||||
|
filters={
|
||||||
|
"docstatus": 1,
|
||||||
|
"status": status,
|
||||||
|
"employee": employee,
|
||||||
|
"attendance_date": ("<=", get_datetime(relieving_date))
|
||||||
|
}
|
||||||
|
|
||||||
|
if status == "On Leave":
|
||||||
|
lwp_leave_types = frappe.get_list("Leave Type", filters = {"is_lwp":1})
|
||||||
|
lwp_leave_types = [leave_type.name for leave_type in lwp_leave_types]
|
||||||
|
filters["leave_type"] = ("IN", lwp_leave_types)
|
||||||
|
|
||||||
|
|
||||||
|
record = frappe.get_all("Attendance", filters=filters, fields = ["COUNT(name) as total_lwp"])
|
||||||
|
return record[0].total_lwp if len(record) else 0
|
||||||
|
|
||||||
|
def calculate_gratuity_amount(employee, gratuity_rule, experience):
|
||||||
|
applicable_earnings_component = get_applicable_components(gratuity_rule)
|
||||||
|
total_applicable_components_amount = get_total_applicable_component_amount(employee, applicable_earnings_component, gratuity_rule)
|
||||||
|
|
||||||
|
calculate_gratuity_amount_based_on = frappe.db.get_value("Gratuity Rule", gratuity_rule, "calculate_gratuity_amount_based_on")
|
||||||
|
gratuity_amount = 0
|
||||||
|
slabs = get_gratuity_rule_slabs(gratuity_rule)
|
||||||
|
slab_found = False
|
||||||
|
year_left = experience
|
||||||
|
|
||||||
|
for slab in slabs:
|
||||||
|
if calculate_gratuity_amount_based_on == "Current Slab":
|
||||||
|
slab_found, gratuity_amount = calculate_amount_based_on_current_slab(slab.from_year, slab.to_year,
|
||||||
|
experience, total_applicable_components_amount, slab.fraction_of_applicable_earnings)
|
||||||
|
if slab_found:
|
||||||
|
break
|
||||||
|
|
||||||
|
elif calculate_gratuity_amount_based_on == "Sum of all previous slabs":
|
||||||
|
if slab.to_year == 0 and slab.from_year == 0:
|
||||||
|
gratuity_amount += year_left * total_applicable_components_amount * slab.fraction_of_applicable_earnings
|
||||||
|
slab_found = True
|
||||||
|
break
|
||||||
|
|
||||||
|
if experience > slab.to_year and experience > slab.from_year and slab.to_year !=0:
|
||||||
|
gratuity_amount += (slab.to_year - slab.from_year) * total_applicable_components_amount * slab.fraction_of_applicable_earnings
|
||||||
|
year_left -= (slab.to_year - slab.from_year)
|
||||||
|
slab_found = True
|
||||||
|
elif slab.from_year <= experience and (experience < slab.to_year or slab.to_year == 0):
|
||||||
|
gratuity_amount += year_left * total_applicable_components_amount * slab.fraction_of_applicable_earnings
|
||||||
|
slab_found = True
|
||||||
|
|
||||||
|
if not slab_found:
|
||||||
|
frappe.throw(_("No Suitable Slab found for Calculation of gratuity amount in Gratuity Rule: {0}").format(bold(gratuity_rule)))
|
||||||
|
return gratuity_amount
|
||||||
|
|
||||||
|
def get_applicable_components(gratuity_rule):
|
||||||
|
applicable_earnings_component = frappe.get_all("Gratuity Applicable Component", filters= {'parent': gratuity_rule}, fields=["salary_component"])
|
||||||
|
if len(applicable_earnings_component) == 0:
|
||||||
|
frappe.throw(_("No Applicable Earnings Component found for Gratuity Rule: {0}").format(bold(get_link_to_form("Gratuity Rule",gratuity_rule))))
|
||||||
|
applicable_earnings_component = [component.salary_component for component in applicable_earnings_component]
|
||||||
|
|
||||||
|
return applicable_earnings_component
|
||||||
|
|
||||||
|
def get_total_applicable_component_amount(employee, applicable_earnings_component, gratuity_rule):
|
||||||
|
sal_slip = get_last_salary_slip(employee)
|
||||||
|
if not sal_slip:
|
||||||
|
frappe.throw(_("No Salary Slip is found for Employee: {0}").format(bold(employee)))
|
||||||
|
component_and_amounts = frappe.get_list("Salary Detail",
|
||||||
|
filters={
|
||||||
|
"docstatus": 1,
|
||||||
|
'parent': sal_slip,
|
||||||
|
"parentfield": "earnings",
|
||||||
|
'salary_component': ('in', applicable_earnings_component)
|
||||||
|
},
|
||||||
|
fields=["amount"])
|
||||||
|
total_applicable_components_amount = 0
|
||||||
|
if not len(component_and_amounts):
|
||||||
|
frappe.throw(_("No Applicable Component is present in last month salary slip"))
|
||||||
|
for data in component_and_amounts:
|
||||||
|
total_applicable_components_amount += data.amount
|
||||||
|
return total_applicable_components_amount
|
||||||
|
|
||||||
|
def calculate_amount_based_on_current_slab(from_year, to_year, experience, total_applicable_components_amount, fraction_of_applicable_earnings):
|
||||||
|
slab_found = False; gratuity_amount = 0
|
||||||
|
if experience >= from_year and (to_year == 0 or experience < to_year):
|
||||||
|
gratuity_amount = total_applicable_components_amount * experience * fraction_of_applicable_earnings
|
||||||
|
if fraction_of_applicable_earnings:
|
||||||
|
slab_found = True
|
||||||
|
|
||||||
|
return slab_found, gratuity_amount
|
||||||
|
|
||||||
|
def get_gratuity_rule_slabs(gratuity_rule):
|
||||||
|
return frappe.get_all("Gratuity Rule Slab", filters= {'parent': gratuity_rule}, fields = ["*"], order_by="idx")
|
||||||
|
|
||||||
|
def get_salary_structure(employee):
|
||||||
|
return frappe.get_list("Salary Structure Assignment", filters = {
|
||||||
|
"employee": employee, 'docstatus': 1
|
||||||
|
},
|
||||||
|
fields=["from_date", "salary_structure"],
|
||||||
|
order_by = "from_date desc")[0].salary_structure
|
||||||
|
|
||||||
|
def get_last_salary_slip(employee):
|
||||||
|
return frappe.get_list("Salary Slip", filters = {
|
||||||
|
"employee": employee, 'docstatus': 1
|
||||||
|
},
|
||||||
|
order_by = "start_date desc")[0].name
|
||||||
|
|
||||||
20
erpnext/payroll/doctype/gratuity/gratuity_dashboard.py
Normal file
20
erpnext/payroll/doctype/gratuity/gratuity_dashboard.py
Normal file
@@ -0,0 +1,20 @@
|
|||||||
|
from __future__ import unicode_literals
|
||||||
|
from frappe import _
|
||||||
|
|
||||||
|
def get_data():
|
||||||
|
return {
|
||||||
|
'fieldname': 'reference_name',
|
||||||
|
'non_standard_fieldnames': {
|
||||||
|
'Additional Salary': 'ref_docname',
|
||||||
|
},
|
||||||
|
'transactions': [
|
||||||
|
{
|
||||||
|
'label': _('Payment'),
|
||||||
|
'items': ['Payment Entry']
|
||||||
|
},
|
||||||
|
{
|
||||||
|
'label': _('Additional Salary'),
|
||||||
|
'items': ['Additional Salary']
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
192
erpnext/payroll/doctype/gratuity/test_gratuity.py
Normal file
192
erpnext/payroll/doctype/gratuity/test_gratuity.py
Normal file
@@ -0,0 +1,192 @@
|
|||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
# Copyright (c) 2020, Frappe Technologies Pvt. Ltd. and Contributors
|
||||||
|
# See license.txt
|
||||||
|
from __future__ import unicode_literals
|
||||||
|
|
||||||
|
import frappe
|
||||||
|
import unittest
|
||||||
|
from erpnext.hr.doctype.employee.test_employee import make_employee
|
||||||
|
from erpnext.payroll.doctype.salary_slip.test_salary_slip import make_employee_salary_slip, make_earning_salary_component, \
|
||||||
|
make_deduction_salary_component
|
||||||
|
from erpnext.payroll.doctype.gratuity.gratuity import get_last_salary_slip
|
||||||
|
from erpnext.regional.united_arab_emirates.setup import create_gratuity_rule
|
||||||
|
from erpnext.hr.doctype.expense_claim.test_expense_claim import get_payable_account
|
||||||
|
from frappe.utils import getdate, add_days, get_datetime, flt
|
||||||
|
|
||||||
|
test_dependencies = ["Salary Component", "Salary Slip", "Account"]
|
||||||
|
class TestGratuity(unittest.TestCase):
|
||||||
|
def setUp(self):
|
||||||
|
make_earning_salary_component(setup=True, test_tax=True, company_list=['_Test Company'])
|
||||||
|
make_deduction_salary_component(setup=True, test_tax=True, company_list=['_Test Company'])
|
||||||
|
frappe.db.sql("DELETE FROM `tabGratuity`")
|
||||||
|
frappe.db.sql("DELETE FROM `tabAdditional Salary` WHERE ref_doctype = 'Gratuity'")
|
||||||
|
|
||||||
|
def test_check_gratuity_amount_based_on_current_slab_and_additional_salary_creation(self):
|
||||||
|
employee, sal_slip = create_employee_and_get_last_salary_slip()
|
||||||
|
|
||||||
|
rule = get_gratuity_rule("Rule Under Unlimited Contract on termination (UAE)")
|
||||||
|
|
||||||
|
gratuity = create_gratuity(pay_via_salary_slip = 1, employee=employee, rule=rule.name)
|
||||||
|
|
||||||
|
#work experience calculation
|
||||||
|
date_of_joining, relieving_date = frappe.db.get_value('Employee', employee, ['date_of_joining', 'relieving_date'])
|
||||||
|
employee_total_workings_days = (get_datetime(relieving_date) - get_datetime(date_of_joining)).days
|
||||||
|
|
||||||
|
experience = employee_total_workings_days/rule.total_working_days_per_year
|
||||||
|
gratuity.reload()
|
||||||
|
from math import floor
|
||||||
|
self.assertEqual(floor(experience), gratuity.current_work_experience)
|
||||||
|
|
||||||
|
#amount Calculation
|
||||||
|
component_amount = frappe.get_list("Salary Detail",
|
||||||
|
filters={
|
||||||
|
"docstatus": 1,
|
||||||
|
'parent': sal_slip,
|
||||||
|
"parentfield": "earnings",
|
||||||
|
'salary_component': "Basic Salary"
|
||||||
|
},
|
||||||
|
fields=["amount"])
|
||||||
|
|
||||||
|
''' 5 - 0 fraction is 1 '''
|
||||||
|
|
||||||
|
gratuity_amount = component_amount[0].amount * experience
|
||||||
|
gratuity.reload()
|
||||||
|
|
||||||
|
self.assertEqual(flt(gratuity_amount, 2), flt(gratuity.amount, 2))
|
||||||
|
|
||||||
|
#additional salary creation (Pay via salary slip)
|
||||||
|
self.assertTrue(frappe.db.exists("Additional Salary", {"ref_docname": gratuity.name}))
|
||||||
|
|
||||||
|
def test_check_gratuity_amount_based_on_all_previous_slabs(self):
|
||||||
|
employee, sal_slip = create_employee_and_get_last_salary_slip()
|
||||||
|
rule = get_gratuity_rule("Rule Under Limited Contract (UAE)")
|
||||||
|
set_mode_of_payment_account()
|
||||||
|
|
||||||
|
gratuity = create_gratuity(expense_account = 'Payment Account - _TC', mode_of_payment='Cash', employee=employee)
|
||||||
|
|
||||||
|
#work experience calculation
|
||||||
|
date_of_joining, relieving_date = frappe.db.get_value('Employee', employee, ['date_of_joining', 'relieving_date'])
|
||||||
|
employee_total_workings_days = (get_datetime(relieving_date) - get_datetime(date_of_joining)).days
|
||||||
|
|
||||||
|
experience = employee_total_workings_days/rule.total_working_days_per_year
|
||||||
|
|
||||||
|
gratuity.reload()
|
||||||
|
|
||||||
|
from math import floor
|
||||||
|
|
||||||
|
self.assertEqual(floor(experience), gratuity.current_work_experience)
|
||||||
|
|
||||||
|
#amount Calculation
|
||||||
|
component_amount = frappe.get_list("Salary Detail",
|
||||||
|
filters={
|
||||||
|
"docstatus": 1,
|
||||||
|
'parent': sal_slip,
|
||||||
|
"parentfield": "earnings",
|
||||||
|
'salary_component': "Basic Salary"
|
||||||
|
},
|
||||||
|
fields=["amount"])
|
||||||
|
|
||||||
|
''' range | Fraction
|
||||||
|
0-1 | 0
|
||||||
|
1-5 | 0.7
|
||||||
|
5-0 | 1
|
||||||
|
'''
|
||||||
|
|
||||||
|
gratuity_amount = ((0 * 1) + (4 * 0.7) + (1 * 1)) * component_amount[0].amount
|
||||||
|
gratuity.reload()
|
||||||
|
|
||||||
|
self.assertEqual(flt(gratuity_amount, 2), flt(gratuity.amount, 2))
|
||||||
|
self.assertEqual(gratuity.status, "Unpaid")
|
||||||
|
|
||||||
|
from erpnext.accounts.doctype.payment_entry.payment_entry import get_payment_entry
|
||||||
|
pay_entry = get_payment_entry("Gratuity", gratuity.name)
|
||||||
|
pay_entry.reference_no = "123467"
|
||||||
|
pay_entry.reference_date = getdate()
|
||||||
|
pay_entry.save()
|
||||||
|
pay_entry.submit()
|
||||||
|
gratuity.reload()
|
||||||
|
|
||||||
|
self.assertEqual(gratuity.status, "Paid")
|
||||||
|
self.assertEqual(flt(gratuity.paid_amount,2), flt(gratuity.amount, 2))
|
||||||
|
|
||||||
|
def tearDown(self):
|
||||||
|
frappe.db.sql("DELETE FROM `tabGratuity`")
|
||||||
|
frappe.db.sql("DELETE FROM `tabAdditional Salary` WHERE ref_doctype = 'Gratuity'")
|
||||||
|
|
||||||
|
def get_gratuity_rule(name):
|
||||||
|
rule = frappe.db.exists("Gratuity Rule", name)
|
||||||
|
if not rule:
|
||||||
|
create_gratuity_rule()
|
||||||
|
rule = frappe.get_doc("Gratuity Rule", name)
|
||||||
|
rule.applicable_earnings_component = []
|
||||||
|
rule.append("applicable_earnings_component", {
|
||||||
|
"salary_component": "Basic Salary"
|
||||||
|
})
|
||||||
|
rule.save()
|
||||||
|
rule.reload()
|
||||||
|
|
||||||
|
return rule
|
||||||
|
|
||||||
|
def create_gratuity(**args):
|
||||||
|
if args:
|
||||||
|
args = frappe._dict(args)
|
||||||
|
gratuity = frappe.new_doc("Gratuity")
|
||||||
|
gratuity.employee = args.employee
|
||||||
|
gratuity.posting_date = getdate()
|
||||||
|
gratuity.gratuity_rule = args.rule or "Rule Under Limited Contract (UAE)"
|
||||||
|
gratuity.pay_via_salary_slip = args.pay_via_salary_slip or 0
|
||||||
|
if gratuity.pay_via_salary_slip:
|
||||||
|
gratuity.payroll_date = getdate()
|
||||||
|
gratuity.salary_component = "Performance Bonus"
|
||||||
|
else:
|
||||||
|
gratuity.expense_account = args.expense_account or 'Payment Account - _TC'
|
||||||
|
gratuity.payable_account = args.payable_account or get_payable_account("_Test Company")
|
||||||
|
gratuity.mode_of_payment = args.mode_of_payment or 'Cash'
|
||||||
|
|
||||||
|
gratuity.save()
|
||||||
|
gratuity.submit()
|
||||||
|
|
||||||
|
return gratuity
|
||||||
|
|
||||||
|
def set_mode_of_payment_account():
|
||||||
|
if not frappe.db.exists("Account", "Payment Account - _TC"):
|
||||||
|
mode_of_payment = create_account()
|
||||||
|
|
||||||
|
mode_of_payment = frappe.get_doc("Mode of Payment", "Cash")
|
||||||
|
|
||||||
|
mode_of_payment.accounts = []
|
||||||
|
mode_of_payment.append("accounts", {
|
||||||
|
"company": "_Test Company",
|
||||||
|
"default_account": "_Test Bank - _TC"
|
||||||
|
})
|
||||||
|
mode_of_payment.save()
|
||||||
|
|
||||||
|
def create_account():
|
||||||
|
return frappe.get_doc({
|
||||||
|
"doctype": "Account",
|
||||||
|
"company": "_Test Company",
|
||||||
|
"account_name": "Payment Account",
|
||||||
|
"root_type": "Asset",
|
||||||
|
"report_type": "Balance Sheet",
|
||||||
|
"currency": "INR",
|
||||||
|
"parent_account": "Bank Accounts - _TC",
|
||||||
|
"account_type": "Bank",
|
||||||
|
}).insert(ignore_permissions=True)
|
||||||
|
|
||||||
|
def create_employee_and_get_last_salary_slip():
|
||||||
|
employee = make_employee("test_employee@salary.com", company='_Test Company')
|
||||||
|
frappe.db.set_value("Employee", employee, "relieving_date", getdate())
|
||||||
|
frappe.db.set_value("Employee", employee, "date_of_joining", add_days(getdate(), - (6*365)))
|
||||||
|
if not frappe.db.exists("Salary Slip", {"employee":employee}):
|
||||||
|
salary_slip = make_employee_salary_slip("test_employee@salary.com", "Monthly")
|
||||||
|
salary_slip.submit()
|
||||||
|
salary_slip = salary_slip.name
|
||||||
|
else:
|
||||||
|
salary_slip = get_last_salary_slip(employee)
|
||||||
|
|
||||||
|
if not frappe.db.get_value("Employee", "test_employee@salary.com", "holiday_list"):
|
||||||
|
from erpnext.payroll.doctype.salary_slip.test_salary_slip import make_holiday_list
|
||||||
|
make_holiday_list()
|
||||||
|
frappe.db.set_value("Company", '_Test Company', "default_holiday_list", "Salary Slip Test Holiday List")
|
||||||
|
|
||||||
|
return employee, salary_slip
|
||||||
@@ -0,0 +1,32 @@
|
|||||||
|
{
|
||||||
|
"actions": [],
|
||||||
|
"creation": "2020-08-05 19:00:28.097265",
|
||||||
|
"doctype": "DocType",
|
||||||
|
"editable_grid": 1,
|
||||||
|
"engine": "InnoDB",
|
||||||
|
"field_order": [
|
||||||
|
"salary_component"
|
||||||
|
],
|
||||||
|
"fields": [
|
||||||
|
{
|
||||||
|
"fieldname": "salary_component",
|
||||||
|
"fieldtype": "Link",
|
||||||
|
"in_list_view": 1,
|
||||||
|
"label": "Salary Component ",
|
||||||
|
"options": "Salary Component",
|
||||||
|
"reqd": 1
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"istable": 1,
|
||||||
|
"links": [],
|
||||||
|
"modified": "2020-08-05 20:17:13.855035",
|
||||||
|
"modified_by": "Administrator",
|
||||||
|
"module": "Payroll",
|
||||||
|
"name": "Gratuity Applicable Component",
|
||||||
|
"owner": "Administrator",
|
||||||
|
"permissions": [],
|
||||||
|
"quick_entry": 1,
|
||||||
|
"sort_field": "modified",
|
||||||
|
"sort_order": "DESC",
|
||||||
|
"track_changes": 1
|
||||||
|
}
|
||||||
@@ -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 GratuityApplicableComponent(Document):
|
||||||
|
pass
|
||||||
0
erpnext/payroll/doctype/gratuity_rule/__init__.py
Normal file
0
erpnext/payroll/doctype/gratuity_rule/__init__.py
Normal file
40
erpnext/payroll/doctype/gratuity_rule/gratuity_rule.js
Normal file
40
erpnext/payroll/doctype/gratuity_rule/gratuity_rule.js
Normal file
@@ -0,0 +1,40 @@
|
|||||||
|
// Copyright (c) 2020, Frappe Technologies Pvt. Ltd. and contributors
|
||||||
|
// For license information, please see license.txt
|
||||||
|
|
||||||
|
frappe.ui.form.on('Gratuity Rule', {
|
||||||
|
// refresh: function(frm) {
|
||||||
|
|
||||||
|
// }
|
||||||
|
});
|
||||||
|
|
||||||
|
frappe.ui.form.on('Gratuity Rule Slab', {
|
||||||
|
|
||||||
|
/*
|
||||||
|
Slabs should be in order like
|
||||||
|
|
||||||
|
from | to | fraction
|
||||||
|
0 | 4 | 0.5
|
||||||
|
4 | 6 | 0.7
|
||||||
|
|
||||||
|
So, on row addition setting current_row.from = previous row.to.
|
||||||
|
On to_year insert we have to check that it is not less than from_year
|
||||||
|
|
||||||
|
Wrong order may lead to Wrong Calculation
|
||||||
|
*/
|
||||||
|
|
||||||
|
gratuity_rule_slabs_add(frm, cdt, cdn) {
|
||||||
|
let row = locals[cdt][cdn];
|
||||||
|
let array_idx = row.idx - 1;
|
||||||
|
if (array_idx > 0) {
|
||||||
|
row.from_year = cur_frm.doc.gratuity_rule_slabs[array_idx - 1].to_year;
|
||||||
|
frm.refresh();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
to_year(frm, cdt, cdn) {
|
||||||
|
let row = locals[cdt][cdn];
|
||||||
|
if (row.to_year <= row.from_year && row.to_year === 0) {
|
||||||
|
frappe.throw(__("To(Year) year can not be less than From(year) "));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
114
erpnext/payroll/doctype/gratuity_rule/gratuity_rule.json
Normal file
114
erpnext/payroll/doctype/gratuity_rule/gratuity_rule.json
Normal file
@@ -0,0 +1,114 @@
|
|||||||
|
{
|
||||||
|
"actions": [],
|
||||||
|
"autoname": "Prompt",
|
||||||
|
"creation": "2020-08-05 19:00:36.103500",
|
||||||
|
"doctype": "DocType",
|
||||||
|
"editable_grid": 1,
|
||||||
|
"engine": "InnoDB",
|
||||||
|
"field_order": [
|
||||||
|
"applicable_earnings_component",
|
||||||
|
"work_experience_calculation_function",
|
||||||
|
"total_working_days_per_year",
|
||||||
|
"column_break_3",
|
||||||
|
"disable",
|
||||||
|
"calculate_gratuity_amount_based_on",
|
||||||
|
"minimum_year_for_gratuity",
|
||||||
|
"gratuity_rules_section",
|
||||||
|
"gratuity_rule_slabs"
|
||||||
|
],
|
||||||
|
"fields": [
|
||||||
|
{
|
||||||
|
"default": "0",
|
||||||
|
"fieldname": "disable",
|
||||||
|
"fieldtype": "Check",
|
||||||
|
"label": "Disable"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "calculate_gratuity_amount_based_on",
|
||||||
|
"fieldtype": "Select",
|
||||||
|
"in_list_view": 1,
|
||||||
|
"label": "Calculate Gratuity Amount Based On",
|
||||||
|
"options": "Current Slab\nSum of all previous slabs",
|
||||||
|
"reqd": 1
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"description": "Salary components should be part of the Salary Structure.",
|
||||||
|
"fieldname": "applicable_earnings_component",
|
||||||
|
"fieldtype": "Table MultiSelect",
|
||||||
|
"label": "Applicable Earnings Component",
|
||||||
|
"options": "Gratuity Applicable Component",
|
||||||
|
"reqd": 1
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "column_break_3",
|
||||||
|
"fieldtype": "Column Break"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "gratuity_rules_section",
|
||||||
|
"fieldtype": "Section Break",
|
||||||
|
"label": "Gratuity Rules"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"description": "Leave <b>From</b> and <b>To</b> 0 for no upper and lower limit.",
|
||||||
|
"fieldname": "gratuity_rule_slabs",
|
||||||
|
"fieldtype": "Table",
|
||||||
|
"label": "Current Work Experience",
|
||||||
|
"options": "Gratuity Rule Slab",
|
||||||
|
"reqd": 1
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"default": "Round off Work Experience",
|
||||||
|
"fieldname": "work_experience_calculation_function",
|
||||||
|
"fieldtype": "Select",
|
||||||
|
"label": "Work Experience Calculation method",
|
||||||
|
"options": "Round off Work Experience\nTake Exact Completed Years"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"default": "365",
|
||||||
|
"fieldname": "total_working_days_per_year",
|
||||||
|
"fieldtype": "Int",
|
||||||
|
"label": "Total working Days Per Year"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "minimum_year_for_gratuity",
|
||||||
|
"fieldtype": "Int",
|
||||||
|
"label": "Minimum Year for Gratuity"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"index_web_pages_for_search": 1,
|
||||||
|
"links": [],
|
||||||
|
"modified": "2020-12-03 17:08:27.891535",
|
||||||
|
"modified_by": "Administrator",
|
||||||
|
"module": "Payroll",
|
||||||
|
"name": "Gratuity Rule",
|
||||||
|
"owner": "Administrator",
|
||||||
|
"permissions": [
|
||||||
|
{
|
||||||
|
"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
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"sort_field": "modified",
|
||||||
|
"sort_order": "DESC",
|
||||||
|
"track_changes": 1
|
||||||
|
}
|
||||||
33
erpnext/payroll/doctype/gratuity_rule/gratuity_rule.py
Normal file
33
erpnext/payroll/doctype/gratuity_rule/gratuity_rule.py
Normal file
@@ -0,0 +1,33 @@
|
|||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
# Copyright (c) 2020, Frappe Technologies Pvt. Ltd. and contributors
|
||||||
|
# For license information, please see license.txt
|
||||||
|
|
||||||
|
from __future__ import unicode_literals
|
||||||
|
import frappe
|
||||||
|
from frappe.model.document import Document
|
||||||
|
from frappe import _
|
||||||
|
|
||||||
|
class GratuityRule(Document):
|
||||||
|
|
||||||
|
def validate(self):
|
||||||
|
for current_slab in self.gratuity_rule_slabs:
|
||||||
|
if (current_slab.from_year > current_slab.to_year) and current_slab.to_year != 0:
|
||||||
|
frappe(_("Row {0}: From (Year) can not be greater than To (Year)").format(current_slab.idx))
|
||||||
|
|
||||||
|
if current_slab.to_year == 0 and current_slab.from_year == 0 and len(self.gratuity_rule_slabs) > 1:
|
||||||
|
frappe.throw(_("You can not define multiple slabs if you have a slab with no lower and upper limits."))
|
||||||
|
|
||||||
|
def get_gratuity_rule(name, slabs, **args):
|
||||||
|
args = frappe._dict(args)
|
||||||
|
|
||||||
|
rule = frappe.new_doc("Gratuity Rule")
|
||||||
|
rule.name = name
|
||||||
|
rule.calculate_gratuity_amount_based_on = args.calculate_gratuity_amount_based_on or "Current Slab"
|
||||||
|
rule.work_experience_calculation_method = args.work_experience_calculation_method or "Take Exact Completed Years"
|
||||||
|
rule.minimum_year_for_gratuity = 1
|
||||||
|
|
||||||
|
|
||||||
|
for slab in slabs:
|
||||||
|
slab = frappe._dict(slab)
|
||||||
|
rule.append("gratuity_rule_slabs", slab)
|
||||||
|
return rule
|
||||||
@@ -0,0 +1,13 @@
|
|||||||
|
from __future__ import unicode_literals
|
||||||
|
from frappe import _
|
||||||
|
|
||||||
|
def get_data():
|
||||||
|
return {
|
||||||
|
'fieldname': 'gratuity_rule',
|
||||||
|
'transactions': [
|
||||||
|
{
|
||||||
|
'label': _('Gratuity'),
|
||||||
|
'items': ['Gratuity']
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
10
erpnext/payroll/doctype/gratuity_rule/test_gratuity_rule.py
Normal file
10
erpnext/payroll/doctype/gratuity_rule/test_gratuity_rule.py
Normal 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 TestGratuityRule(unittest.TestCase):
|
||||||
|
pass
|
||||||
@@ -0,0 +1,50 @@
|
|||||||
|
{
|
||||||
|
"actions": [],
|
||||||
|
"creation": "2020-08-05 19:12:49.423500",
|
||||||
|
"doctype": "DocType",
|
||||||
|
"editable_grid": 1,
|
||||||
|
"engine": "InnoDB",
|
||||||
|
"field_order": [
|
||||||
|
"from_year",
|
||||||
|
"to_year",
|
||||||
|
"fraction_of_applicable_earnings"
|
||||||
|
],
|
||||||
|
"fields": [
|
||||||
|
{
|
||||||
|
"fieldname": "fraction_of_applicable_earnings",
|
||||||
|
"fieldtype": "Float",
|
||||||
|
"in_list_view": 1,
|
||||||
|
"label": "Fraction of Applicable Earnings ",
|
||||||
|
"reqd": 1
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"default": "0",
|
||||||
|
"fieldname": "from_year",
|
||||||
|
"fieldtype": "Int",
|
||||||
|
"in_list_view": 1,
|
||||||
|
"label": "From(Year)",
|
||||||
|
"read_only": 1,
|
||||||
|
"reqd": 1
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"default": "0",
|
||||||
|
"fieldname": "to_year",
|
||||||
|
"fieldtype": "Int",
|
||||||
|
"in_list_view": 1,
|
||||||
|
"label": "To(Year)",
|
||||||
|
"reqd": 1
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"istable": 1,
|
||||||
|
"links": [],
|
||||||
|
"modified": "2020-08-17 14:09:56.781712",
|
||||||
|
"modified_by": "Administrator",
|
||||||
|
"module": "Payroll",
|
||||||
|
"name": "Gratuity Rule Slab",
|
||||||
|
"owner": "Administrator",
|
||||||
|
"permissions": [],
|
||||||
|
"quick_entry": 1,
|
||||||
|
"sort_field": "modified",
|
||||||
|
"sort_order": "DESC",
|
||||||
|
"track_changes": 1
|
||||||
|
}
|
||||||
@@ -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 GratuityRuleSlab(Document):
|
||||||
|
pass
|
||||||
@@ -80,9 +80,26 @@ class SalarySlip(TransactionBase):
|
|||||||
if (frappe.db.get_single_value("Payroll Settings", "email_salary_slip_to_employee")) and not frappe.flags.via_payroll_entry:
|
if (frappe.db.get_single_value("Payroll Settings", "email_salary_slip_to_employee")) and not frappe.flags.via_payroll_entry:
|
||||||
self.email_salary_slip()
|
self.email_salary_slip()
|
||||||
|
|
||||||
|
self.update_payment_status_for_gratuity()
|
||||||
|
|
||||||
|
def update_payment_status_for_gratuity(self):
|
||||||
|
add_salary = frappe.db.get_all("Additional Salary",
|
||||||
|
filters = {
|
||||||
|
"payroll_date": ("BETWEEN", [self.start_date, self.end_date]),
|
||||||
|
"employee": self.employee,
|
||||||
|
"ref_doctype": "Gratuity",
|
||||||
|
"docstatus": 1,
|
||||||
|
}, fields = ["ref_docname", "name"], limit=1)
|
||||||
|
|
||||||
|
if len(add_salary):
|
||||||
|
status = "Paid" if self.docstatus == 1 else "Unpaid"
|
||||||
|
if add_salary[0].name in [data.additional_salary for data in self.earnings]:
|
||||||
|
frappe.db.set_value("Gratuity", add_salary.ref_docname, "status", status)
|
||||||
|
|
||||||
def on_cancel(self):
|
def on_cancel(self):
|
||||||
self.set_status()
|
self.set_status()
|
||||||
self.update_status()
|
self.update_status()
|
||||||
|
self.update_payment_status_for_gratuity()
|
||||||
self.cancel_loan_repayment_entry()
|
self.cancel_loan_repayment_entry()
|
||||||
|
|
||||||
def on_trash(self):
|
def on_trash(self):
|
||||||
@@ -506,7 +523,8 @@ class SalarySlip(TransactionBase):
|
|||||||
return amount
|
return amount
|
||||||
|
|
||||||
except NameError as err:
|
except NameError as err:
|
||||||
frappe.throw(_("Name error: {0}").format(err))
|
frappe.throw(_("{0} <br> This error can be due to missing or deleted field.").format(err),
|
||||||
|
title=_("Name error"))
|
||||||
except SyntaxError as err:
|
except SyntaxError as err:
|
||||||
frappe.throw(_("Syntax error in formula or condition: {0}").format(err))
|
frappe.throw(_("Syntax error in formula or condition: {0}").format(err))
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
@@ -573,6 +591,7 @@ class SalarySlip(TransactionBase):
|
|||||||
for d in self.get(key):
|
for d in self.get(key):
|
||||||
if d.salary_component == struct_row.salary_component:
|
if d.salary_component == struct_row.salary_component:
|
||||||
component_row = d
|
component_row = d
|
||||||
|
|
||||||
if not component_row or (struct_row.get("is_additional_component") and not overwrite):
|
if not component_row or (struct_row.get("is_additional_component") and not overwrite):
|
||||||
if amount:
|
if amount:
|
||||||
self.append(key, {
|
self.append(key, {
|
||||||
@@ -930,7 +949,8 @@ class SalarySlip(TransactionBase):
|
|||||||
if condition:
|
if condition:
|
||||||
return frappe.safe_eval(condition, self.whitelisted_globals, data)
|
return frappe.safe_eval(condition, self.whitelisted_globals, data)
|
||||||
except NameError as err:
|
except NameError as err:
|
||||||
frappe.throw(_("Name error: {0}").format(err))
|
frappe.throw(_("{0} <br> This error can be due to missing or deleted field.").format(err),
|
||||||
|
title=_("Name error"))
|
||||||
except SyntaxError as err:
|
except SyntaxError as err:
|
||||||
frappe.throw(_("Syntax error in condition: {0}").format(err))
|
frappe.throw(_("Syntax error in condition: {0}").format(err))
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
|
|||||||
@@ -21,6 +21,7 @@ from erpnext.payroll.doctype.employee_tax_exemption_declaration.test_employee_ta
|
|||||||
class TestSalarySlip(unittest.TestCase):
|
class TestSalarySlip(unittest.TestCase):
|
||||||
def setUp(self):
|
def setUp(self):
|
||||||
setup_test()
|
setup_test()
|
||||||
|
|
||||||
def tearDown(self):
|
def tearDown(self):
|
||||||
frappe.db.set_value("Payroll Settings", None, "include_holidays_in_total_working_days", 0)
|
frappe.db.set_value("Payroll Settings", None, "include_holidays_in_total_working_days", 0)
|
||||||
frappe.set_user("Administrator")
|
frappe.set_user("Administrator")
|
||||||
|
|||||||
@@ -142,6 +142,8 @@ frappe.ui.form.on('Salary Structure', {
|
|||||||
],
|
],
|
||||||
primary_action: function() {
|
primary_action: function() {
|
||||||
var data = d.get_values();
|
var data = d.get_values();
|
||||||
|
delete data.company
|
||||||
|
delete data.currency
|
||||||
frappe.call({
|
frappe.call({
|
||||||
doc: frm.doc,
|
doc: frm.doc,
|
||||||
method: "assign_salary_structure",
|
method: "assign_salary_structure",
|
||||||
|
|||||||
@@ -158,16 +158,18 @@ erpnext.taxes_and_totals = erpnext.payments.extend({
|
|||||||
let me = this;
|
let me = this;
|
||||||
frappe.flags.round_off_applicable_accounts = [];
|
frappe.flags.round_off_applicable_accounts = [];
|
||||||
|
|
||||||
return frappe.call({
|
if (me.frm.doc.company) {
|
||||||
"method": "erpnext.controllers.taxes_and_totals.get_round_off_applicable_accounts",
|
return frappe.call({
|
||||||
"args": {
|
"method": "erpnext.controllers.taxes_and_totals.get_round_off_applicable_accounts",
|
||||||
"company": me.frm.doc.company,
|
"args": {
|
||||||
"account_list": frappe.flags.round_off_applicable_accounts
|
"company": me.frm.doc.company,
|
||||||
},
|
"account_list": frappe.flags.round_off_applicable_accounts
|
||||||
callback: function(r) {
|
},
|
||||||
frappe.flags.round_off_applicable_accounts.push(...r.message);
|
callback: function(r) {
|
||||||
}
|
frappe.flags.round_off_applicable_accounts.push(...r.message);
|
||||||
});
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
determine_exclusive_rate: function() {
|
determine_exclusive_rate: function() {
|
||||||
|
|||||||
@@ -1885,7 +1885,6 @@ erpnext.TransactionController = erpnext.taxes_and_totals.extend({
|
|||||||
frappe.throw(__("Please enter Item Code to get batch no"));
|
frappe.throw(__("Please enter Item Code to get batch no"));
|
||||||
} else if (doc.doctype == "Purchase Receipt" ||
|
} else if (doc.doctype == "Purchase Receipt" ||
|
||||||
(doc.doctype == "Purchase Invoice" && doc.update_stock)) {
|
(doc.doctype == "Purchase Invoice" && doc.update_stock)) {
|
||||||
|
|
||||||
return {
|
return {
|
||||||
filters: {'item': item.item_code}
|
filters: {'item': item.item_code}
|
||||||
}
|
}
|
||||||
@@ -1911,9 +1910,8 @@ erpnext.TransactionController = erpnext.taxes_and_totals.extend({
|
|||||||
set_query_for_item_tax_template: function(doc, cdt, cdn) {
|
set_query_for_item_tax_template: function(doc, cdt, cdn) {
|
||||||
var item = frappe.get_doc(cdt, cdn);
|
var item = frappe.get_doc(cdt, cdn);
|
||||||
if(!item.item_code) {
|
if(!item.item_code) {
|
||||||
frappe.throw(__("Please enter Item Code to get item taxes"));
|
return doc.company ? {filters: {company: doc.company}} : {};
|
||||||
} else {
|
} else {
|
||||||
|
|
||||||
let filters = {
|
let filters = {
|
||||||
'item_code': item.item_code,
|
'item_code': item.item_code,
|
||||||
'valid_from': ["<=", doc.transaction_date || doc.bill_date || doc.posting_date],
|
'valid_from': ["<=", doc.transaction_date || doc.bill_date || doc.posting_date],
|
||||||
|
|||||||
@@ -1,11 +1,18 @@
|
|||||||
frappe.ui.form.ControlData = frappe.ui.form.ControlData.extend( {
|
frappe.ui.form.ControlData = frappe.ui.form.ControlData.extend( {
|
||||||
make_input() {
|
make_input() {
|
||||||
if (!this.df.read_only) {
|
this._super();
|
||||||
this._super();
|
|
||||||
}
|
|
||||||
if (this.df.options == 'Phone') {
|
if (this.df.options == 'Phone') {
|
||||||
this.setup_phone();
|
this.setup_phone();
|
||||||
}
|
}
|
||||||
|
if (this.frm && this.frm.fields_dict) {
|
||||||
|
Object.values(this.frm.fields_dict).forEach(function(field) {
|
||||||
|
if (field.df.read_only === 1 && field.df.options === 'Phone'
|
||||||
|
&& field.disp_area.style[0] != 'display' && !field.has_icon) {
|
||||||
|
field.setup_phone();
|
||||||
|
field.has_icon = true;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
},
|
},
|
||||||
setup_phone() {
|
setup_phone() {
|
||||||
if (frappe.phone_call.handler) {
|
if (frappe.phone_call.handler) {
|
||||||
|
|||||||
@@ -595,21 +595,7 @@ erpnext.utils.update_child_items = function(opts) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
erpnext.utils.map_current_doc = function(opts) {
|
erpnext.utils.map_current_doc = function(opts) {
|
||||||
let query_args = {};
|
function _map() {
|
||||||
if (opts.get_query_filters) {
|
|
||||||
query_args.filters = opts.get_query_filters;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (opts.get_query_method) {
|
|
||||||
query_args.query = opts.get_query_method;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (query_args.filters || query_args.query) {
|
|
||||||
opts.get_query = () => {
|
|
||||||
return query_args;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
var _map = function() {
|
|
||||||
if($.isArray(cur_frm.doc.items) && cur_frm.doc.items.length > 0) {
|
if($.isArray(cur_frm.doc.items) && cur_frm.doc.items.length > 0) {
|
||||||
// remove first item row if empty
|
// remove first item row if empty
|
||||||
if(!cur_frm.doc.items[0].item_code) {
|
if(!cur_frm.doc.items[0].item_code) {
|
||||||
@@ -683,8 +669,22 @@ erpnext.utils.map_current_doc = function(opts) {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
if(opts.source_doctype) {
|
|
||||||
var d = new frappe.ui.form.MultiSelectDialog({
|
let query_args = {};
|
||||||
|
if (opts.get_query_filters) {
|
||||||
|
query_args.filters = opts.get_query_filters;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (opts.get_query_method) {
|
||||||
|
query_args.query = opts.get_query_method;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (query_args.filters || query_args.query) {
|
||||||
|
opts.get_query = () => query_args;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (opts.source_doctype) {
|
||||||
|
const d = new frappe.ui.form.MultiSelectDialog({
|
||||||
doctype: opts.source_doctype,
|
doctype: opts.source_doctype,
|
||||||
target: opts.target,
|
target: opts.target,
|
||||||
date_field: opts.date_field || undefined,
|
date_field: opts.date_field || undefined,
|
||||||
@@ -703,7 +703,11 @@ erpnext.utils.map_current_doc = function(opts) {
|
|||||||
_map();
|
_map();
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
} else if(opts.source_name) {
|
|
||||||
|
return d;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (opts.source_name) {
|
||||||
opts.source_name = [opts.source_name];
|
opts.source_name = [opts.source_name];
|
||||||
_map();
|
_map();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -33,8 +33,7 @@
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
"fieldname": "sb_00",
|
"fieldname": "sb_00",
|
||||||
"fieldtype": "Section Break",
|
"fieldtype": "Section Break"
|
||||||
"label": "Agenda"
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"fieldname": "agenda",
|
"fieldname": "agenda",
|
||||||
@@ -44,13 +43,12 @@
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
"fieldname": "sb_01",
|
"fieldname": "sb_01",
|
||||||
"fieldtype": "Section Break",
|
"fieldtype": "Section Break"
|
||||||
"label": "Minutes"
|
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"index_web_pages_for_search": 1,
|
"index_web_pages_for_search": 1,
|
||||||
"links": [],
|
"links": [],
|
||||||
"modified": "2020-10-27 16:36:45.657883",
|
"modified": "2021-02-27 16:36:45.657883",
|
||||||
"modified_by": "Administrator",
|
"modified_by": "Administrator",
|
||||||
"module": "Quality Management",
|
"module": "Quality Management",
|
||||||
"name": "Quality Meeting",
|
"name": "Quality Meeting",
|
||||||
|
|||||||
@@ -0,0 +1,67 @@
|
|||||||
|
// Copyright (c) 2021, Frappe Technologies Pvt. Ltd. and contributors
|
||||||
|
// For license information, please see license.txt
|
||||||
|
|
||||||
|
frappe.ui.form.on('Tax Exemption 80G Certificate', {
|
||||||
|
refresh: function(frm) {
|
||||||
|
if (frm.doc.donor) {
|
||||||
|
frm.set_query('donation', function() {
|
||||||
|
return {
|
||||||
|
filters: {
|
||||||
|
docstatus: 1,
|
||||||
|
donor: frm.doc.donor
|
||||||
|
}
|
||||||
|
};
|
||||||
|
});
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
recipient: function(frm) {
|
||||||
|
if (frm.doc.recipient === 'Donor') {
|
||||||
|
frm.set_value({
|
||||||
|
'member': '',
|
||||||
|
'member_name': '',
|
||||||
|
'member_email': '',
|
||||||
|
'member_pan_number': '',
|
||||||
|
'fiscal_year': '',
|
||||||
|
'total': 0,
|
||||||
|
'payments': []
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
frm.set_value({
|
||||||
|
'donor': '',
|
||||||
|
'donor_name': '',
|
||||||
|
'donor_email': '',
|
||||||
|
'donor_pan_number': '',
|
||||||
|
'donation': '',
|
||||||
|
'date_of_donation': '',
|
||||||
|
'amount': 0,
|
||||||
|
'mode_of_payment': '',
|
||||||
|
'razorpay_payment_id': ''
|
||||||
|
});
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
get_payments: function(frm) {
|
||||||
|
frm.call({
|
||||||
|
doc: frm.doc,
|
||||||
|
method: 'get_payments',
|
||||||
|
freeze: true
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
company: function(frm) {
|
||||||
|
if ((frm.doc.member || frm.doc.donor) && frm.doc.company) {
|
||||||
|
frm.call({
|
||||||
|
doc: frm.doc,
|
||||||
|
method: 'set_company_address',
|
||||||
|
freeze: true
|
||||||
|
});
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
donation: function(frm) {
|
||||||
|
if (frm.doc.recipient === 'Donor' && !frm.doc.donor) {
|
||||||
|
frappe.msgprint(__('Please select donor first'));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
@@ -0,0 +1,297 @@
|
|||||||
|
{
|
||||||
|
"actions": [],
|
||||||
|
"autoname": "naming_series:",
|
||||||
|
"creation": "2021-02-15 12:37:21.577042",
|
||||||
|
"doctype": "DocType",
|
||||||
|
"editable_grid": 1,
|
||||||
|
"engine": "InnoDB",
|
||||||
|
"field_order": [
|
||||||
|
"naming_series",
|
||||||
|
"recipient",
|
||||||
|
"member",
|
||||||
|
"member_name",
|
||||||
|
"member_email",
|
||||||
|
"member_pan_number",
|
||||||
|
"donor",
|
||||||
|
"donor_name",
|
||||||
|
"donor_email",
|
||||||
|
"donor_pan_number",
|
||||||
|
"column_break_4",
|
||||||
|
"date",
|
||||||
|
"fiscal_year",
|
||||||
|
"section_break_11",
|
||||||
|
"company",
|
||||||
|
"company_address",
|
||||||
|
"company_address_display",
|
||||||
|
"column_break_14",
|
||||||
|
"company_pan_number",
|
||||||
|
"company_80g_number",
|
||||||
|
"company_80g_wef",
|
||||||
|
"title",
|
||||||
|
"section_break_6",
|
||||||
|
"get_payments",
|
||||||
|
"payments",
|
||||||
|
"total",
|
||||||
|
"donation_details_section",
|
||||||
|
"donation",
|
||||||
|
"date_of_donation",
|
||||||
|
"amount",
|
||||||
|
"column_break_27",
|
||||||
|
"mode_of_payment",
|
||||||
|
"razorpay_payment_id"
|
||||||
|
],
|
||||||
|
"fields": [
|
||||||
|
{
|
||||||
|
"fieldname": "recipient",
|
||||||
|
"fieldtype": "Select",
|
||||||
|
"in_list_view": 1,
|
||||||
|
"label": "Certificate Recipient",
|
||||||
|
"options": "Member\nDonor",
|
||||||
|
"reqd": 1
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"depends_on": "eval:doc.recipient === \"Member\";",
|
||||||
|
"fieldname": "member",
|
||||||
|
"fieldtype": "Link",
|
||||||
|
"in_list_view": 1,
|
||||||
|
"label": "Member",
|
||||||
|
"mandatory_depends_on": "eval:doc.recipient === \"Member\";",
|
||||||
|
"options": "Member"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"depends_on": "eval:doc.recipient === \"Member\";",
|
||||||
|
"fetch_from": "member.member_name",
|
||||||
|
"fieldname": "member_name",
|
||||||
|
"fieldtype": "Data",
|
||||||
|
"label": "Member Name",
|
||||||
|
"read_only": 1
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"depends_on": "eval:doc.recipient === \"Donor\";",
|
||||||
|
"fieldname": "donor",
|
||||||
|
"fieldtype": "Link",
|
||||||
|
"in_list_view": 1,
|
||||||
|
"label": "Donor",
|
||||||
|
"mandatory_depends_on": "eval:doc.recipient === \"Donor\";",
|
||||||
|
"options": "Donor"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "column_break_4",
|
||||||
|
"fieldtype": "Column Break"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "date",
|
||||||
|
"fieldtype": "Date",
|
||||||
|
"label": "Date",
|
||||||
|
"reqd": 1
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"depends_on": "eval:doc.recipient === \"Member\";",
|
||||||
|
"fieldname": "section_break_6",
|
||||||
|
"fieldtype": "Section Break"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "payments",
|
||||||
|
"fieldtype": "Table",
|
||||||
|
"label": "Payments",
|
||||||
|
"options": "Tax Exemption 80G Certificate Detail"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "total",
|
||||||
|
"fieldtype": "Currency",
|
||||||
|
"in_list_view": 1,
|
||||||
|
"label": "Total",
|
||||||
|
"read_only": 1
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"depends_on": "eval:doc.recipient === \"Member\";",
|
||||||
|
"fieldname": "fiscal_year",
|
||||||
|
"fieldtype": "Link",
|
||||||
|
"in_list_view": 1,
|
||||||
|
"label": "Fiscal Year",
|
||||||
|
"options": "Fiscal Year"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "company",
|
||||||
|
"fieldtype": "Link",
|
||||||
|
"label": "Company",
|
||||||
|
"options": "Company",
|
||||||
|
"reqd": 1
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "get_payments",
|
||||||
|
"fieldtype": "Button",
|
||||||
|
"label": "Get Memberships"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "naming_series",
|
||||||
|
"fieldtype": "Select",
|
||||||
|
"label": "Naming Series",
|
||||||
|
"options": "NPO-80G-.YYYY.-"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "section_break_11",
|
||||||
|
"fieldtype": "Section Break",
|
||||||
|
"label": "Company Details"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "company_address",
|
||||||
|
"fieldtype": "Link",
|
||||||
|
"label": "Company Address",
|
||||||
|
"options": "Address"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "column_break_14",
|
||||||
|
"fieldtype": "Column Break"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fetch_from": "company.pan_details",
|
||||||
|
"fieldname": "company_pan_number",
|
||||||
|
"fieldtype": "Data",
|
||||||
|
"label": "PAN Number",
|
||||||
|
"read_only": 1
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "company_address_display",
|
||||||
|
"fieldtype": "Small Text",
|
||||||
|
"hidden": 1,
|
||||||
|
"label": "Company Address Display",
|
||||||
|
"print_hide": 1,
|
||||||
|
"read_only": 1
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fetch_from": "company.company_80g_number",
|
||||||
|
"fieldname": "company_80g_number",
|
||||||
|
"fieldtype": "Data",
|
||||||
|
"label": "80G Number",
|
||||||
|
"read_only": 1
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fetch_from": "company.with_effect_from",
|
||||||
|
"fieldname": "company_80g_wef",
|
||||||
|
"fieldtype": "Date",
|
||||||
|
"label": "80G With Effect From",
|
||||||
|
"read_only": 1
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"depends_on": "eval:doc.recipient === \"Donor\";",
|
||||||
|
"fieldname": "donation_details_section",
|
||||||
|
"fieldtype": "Section Break",
|
||||||
|
"label": "Donation Details"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "donation",
|
||||||
|
"fieldtype": "Link",
|
||||||
|
"label": "Donation",
|
||||||
|
"mandatory_depends_on": "eval:doc.recipient === \"Donor\";",
|
||||||
|
"options": "Donation"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fetch_from": "donation.amount",
|
||||||
|
"fieldname": "amount",
|
||||||
|
"fieldtype": "Currency",
|
||||||
|
"label": "Amount",
|
||||||
|
"read_only": 1
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fetch_from": "donation.mode_of_payment",
|
||||||
|
"fieldname": "mode_of_payment",
|
||||||
|
"fieldtype": "Link",
|
||||||
|
"label": "Mode of Payment",
|
||||||
|
"options": "Mode of Payment",
|
||||||
|
"read_only": 1
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fetch_from": "donation.razorpay_payment_id",
|
||||||
|
"fieldname": "razorpay_payment_id",
|
||||||
|
"fieldtype": "Data",
|
||||||
|
"label": "RazorPay Payment ID",
|
||||||
|
"read_only": 1
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fetch_from": "donation.date",
|
||||||
|
"fieldname": "date_of_donation",
|
||||||
|
"fieldtype": "Date",
|
||||||
|
"label": "Date of Donation",
|
||||||
|
"read_only": 1
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "column_break_27",
|
||||||
|
"fieldtype": "Column Break"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"depends_on": "eval:doc.recipient === \"Donor\";",
|
||||||
|
"fetch_from": "donor.donor_name",
|
||||||
|
"fieldname": "donor_name",
|
||||||
|
"fieldtype": "Data",
|
||||||
|
"label": "Donor Name",
|
||||||
|
"read_only": 1
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"depends_on": "eval:doc.recipient === \"Donor\";",
|
||||||
|
"fetch_from": "donor.email",
|
||||||
|
"fieldname": "donor_email",
|
||||||
|
"fieldtype": "Data",
|
||||||
|
"label": "Email",
|
||||||
|
"read_only": 1
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"depends_on": "eval:doc.recipient === \"Member\";",
|
||||||
|
"fetch_from": "member.email_id",
|
||||||
|
"fieldname": "member_email",
|
||||||
|
"fieldtype": "Data",
|
||||||
|
"in_list_view": 1,
|
||||||
|
"label": "Email",
|
||||||
|
"read_only": 1
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"depends_on": "eval:doc.recipient === \"Member\";",
|
||||||
|
"fetch_from": "member.pan_number",
|
||||||
|
"fieldname": "member_pan_number",
|
||||||
|
"fieldtype": "Data",
|
||||||
|
"label": "PAN Details",
|
||||||
|
"read_only": 1
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"depends_on": "eval:doc.recipient === \"Donor\";",
|
||||||
|
"fetch_from": "donor.pan_number",
|
||||||
|
"fieldname": "donor_pan_number",
|
||||||
|
"fieldtype": "Data",
|
||||||
|
"label": "PAN Details",
|
||||||
|
"read_only": 1
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "title",
|
||||||
|
"fieldtype": "Data",
|
||||||
|
"hidden": 1,
|
||||||
|
"label": "Title",
|
||||||
|
"print_hide": 1
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"index_web_pages_for_search": 1,
|
||||||
|
"links": [],
|
||||||
|
"modified": "2021-02-22 00:03:34.215633",
|
||||||
|
"modified_by": "Administrator",
|
||||||
|
"module": "Regional",
|
||||||
|
"name": "Tax Exemption 80G Certificate",
|
||||||
|
"owner": "Administrator",
|
||||||
|
"permissions": [
|
||||||
|
{
|
||||||
|
"create": 1,
|
||||||
|
"delete": 1,
|
||||||
|
"email": 1,
|
||||||
|
"export": 1,
|
||||||
|
"print": 1,
|
||||||
|
"read": 1,
|
||||||
|
"report": 1,
|
||||||
|
"role": "System Manager",
|
||||||
|
"share": 1,
|
||||||
|
"write": 1
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"search_fields": "member, member_name",
|
||||||
|
"sort_field": "modified",
|
||||||
|
"sort_order": "DESC",
|
||||||
|
"title_field": "title",
|
||||||
|
"track_changes": 1
|
||||||
|
}
|
||||||
@@ -0,0 +1,89 @@
|
|||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
# Copyright (c) 2021, Frappe Technologies Pvt. Ltd. and contributors
|
||||||
|
# For license information, please see license.txt
|
||||||
|
|
||||||
|
from __future__ import unicode_literals
|
||||||
|
import frappe
|
||||||
|
from frappe import _
|
||||||
|
from frappe.model.document import Document
|
||||||
|
from frappe.utils import getdate, flt, get_link_to_form
|
||||||
|
from erpnext.accounts.utils import get_fiscal_year
|
||||||
|
from frappe.contacts.doctype.address.address import get_company_address
|
||||||
|
|
||||||
|
class TaxExemption80GCertificate(Document):
|
||||||
|
def validate(self):
|
||||||
|
self.validate_date()
|
||||||
|
self.validate_duplicates()
|
||||||
|
self.validate_company_details()
|
||||||
|
self.set_company_address()
|
||||||
|
self.set_title()
|
||||||
|
|
||||||
|
def validate_date(self):
|
||||||
|
if self.recipient == 'Member':
|
||||||
|
if getdate(self.date):
|
||||||
|
fiscal_year = get_fiscal_year(fiscal_year=self.fiscal_year, as_dict=True)
|
||||||
|
|
||||||
|
if not (fiscal_year.year_start_date <= getdate(self.date) \
|
||||||
|
<= fiscal_year.year_end_date):
|
||||||
|
frappe.throw(_('The Certificate Date is not in the Fiscal Year {0}').format(frappe.bold(self.fiscal_year)))
|
||||||
|
|
||||||
|
def validate_duplicates(self):
|
||||||
|
if self.recipient == 'Donor':
|
||||||
|
certificate = frappe.db.exists(self.doctype, {'donation': self.donation})
|
||||||
|
if certificate:
|
||||||
|
frappe.throw(_('An 80G Certificate {0} already exists for the donation {1}').format(
|
||||||
|
get_link_to_form(self.doctype, certificate), frappe.bold(self.donation)
|
||||||
|
), title=_('Duplicate Certificate'))
|
||||||
|
|
||||||
|
def validate_company_details(self):
|
||||||
|
fields = ['company_80g_number', 'with_effect_from', 'pan_details']
|
||||||
|
company_details = frappe.db.get_value('Company', self.company, fields, as_dict=True)
|
||||||
|
if not company_details.company_80g_number:
|
||||||
|
frappe.throw(_('Please set the {0} for company {1}').format(frappe.bold('80G Number'),
|
||||||
|
get_link_to_form('Company', self.company)))
|
||||||
|
|
||||||
|
if not company_details.pan_details:
|
||||||
|
frappe.throw(_('Please set the {0} for company {1}').format(frappe.bold('PAN Number'),
|
||||||
|
get_link_to_form('Company', self.company)))
|
||||||
|
|
||||||
|
def set_company_address(self):
|
||||||
|
address = get_company_address(self.company)
|
||||||
|
self.company_address = address.company_address
|
||||||
|
self.company_address_display = address.company_address_display
|
||||||
|
|
||||||
|
def set_title(self):
|
||||||
|
if self.recipient == "Member":
|
||||||
|
self.title = self.member_name
|
||||||
|
else:
|
||||||
|
self.title = self.donor_name
|
||||||
|
|
||||||
|
def get_payments(self):
|
||||||
|
if not self.member:
|
||||||
|
frappe.throw(_('Please select a Member first.'))
|
||||||
|
|
||||||
|
fiscal_year = get_fiscal_year(fiscal_year=self.fiscal_year, as_dict=True)
|
||||||
|
|
||||||
|
memberships = frappe.db.get_all('Membership', {
|
||||||
|
'member': self.member,
|
||||||
|
'from_date': ['between', (fiscal_year.year_start_date, fiscal_year.year_end_date)],
|
||||||
|
'to_date': ['between', (fiscal_year.year_start_date, fiscal_year.year_end_date)],
|
||||||
|
'membership_status': ('!=', 'Cancelled')
|
||||||
|
}, ['from_date', 'amount', 'name', 'invoice', 'payment_id'])
|
||||||
|
|
||||||
|
if not memberships:
|
||||||
|
frappe.msgprint(_('No Membership Payments found against the Member {0}').format(self.member))
|
||||||
|
|
||||||
|
total = 0
|
||||||
|
self.payments = []
|
||||||
|
|
||||||
|
for doc in memberships:
|
||||||
|
self.append('payments', {
|
||||||
|
'date': doc.from_date,
|
||||||
|
'amount': doc.amount,
|
||||||
|
'invoice_id': doc.invoice,
|
||||||
|
'razorpay_payment_id': doc.payment_id,
|
||||||
|
'membership': doc.name
|
||||||
|
})
|
||||||
|
total += flt(doc.amount)
|
||||||
|
|
||||||
|
self.total = total
|
||||||
@@ -0,0 +1,101 @@
|
|||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
# Copyright (c) 2021, Frappe Technologies Pvt. Ltd. and Contributors
|
||||||
|
# See license.txt
|
||||||
|
from __future__ import unicode_literals
|
||||||
|
|
||||||
|
import frappe
|
||||||
|
import unittest
|
||||||
|
from frappe.utils import getdate
|
||||||
|
from erpnext.accounts.utils import get_fiscal_year
|
||||||
|
from erpnext.non_profit.doctype.donation.test_donation import create_donor, create_mode_of_payment, create_donor_type
|
||||||
|
from erpnext.non_profit.doctype.donation.donation import create_donation
|
||||||
|
from erpnext.non_profit.doctype.membership.test_membership import setup_membership, make_membership
|
||||||
|
from erpnext.non_profit.doctype.member.member import create_member
|
||||||
|
|
||||||
|
class TestTaxExemption80GCertificate(unittest.TestCase):
|
||||||
|
def setUp(self):
|
||||||
|
frappe.db.sql('delete from `tabTax Exemption 80G Certificate`')
|
||||||
|
frappe.db.sql('delete from `tabMembership`')
|
||||||
|
create_donor_type()
|
||||||
|
settings = frappe.get_doc('Non Profit Settings')
|
||||||
|
settings.company = '_Test Company'
|
||||||
|
settings.donation_company = '_Test Company'
|
||||||
|
settings.default_donor_type = '_Test Donor'
|
||||||
|
settings.creation_user = 'Administrator'
|
||||||
|
settings.save()
|
||||||
|
|
||||||
|
company = frappe.get_doc('Company', '_Test Company')
|
||||||
|
company.pan_details = 'BBBTI3374C'
|
||||||
|
company.company_80g_number = 'NQ.CIT(E)I2018-19/DEL-IE28615-27062018/10087'
|
||||||
|
company.with_effect_from = getdate()
|
||||||
|
company.save()
|
||||||
|
|
||||||
|
def test_duplicate_donation_certificate(self):
|
||||||
|
donor = create_donor()
|
||||||
|
create_mode_of_payment()
|
||||||
|
payment = frappe._dict({
|
||||||
|
'amount': 100,
|
||||||
|
'method': 'Debit Card',
|
||||||
|
'id': 'pay_MeXAmsgeKOhq7O'
|
||||||
|
})
|
||||||
|
donation = create_donation(donor, payment)
|
||||||
|
|
||||||
|
args = frappe._dict({
|
||||||
|
'recipient': 'Donor',
|
||||||
|
'donor': donor.name,
|
||||||
|
'donation': donation.name
|
||||||
|
})
|
||||||
|
certificate = create_80g_certificate(args)
|
||||||
|
certificate.insert()
|
||||||
|
|
||||||
|
# check company details
|
||||||
|
self.assertEquals(certificate.company_pan_number, 'BBBTI3374C')
|
||||||
|
self.assertEquals(certificate.company_80g_number, 'NQ.CIT(E)I2018-19/DEL-IE28615-27062018/10087')
|
||||||
|
|
||||||
|
# check donation details
|
||||||
|
self.assertEquals(certificate.amount, donation.amount)
|
||||||
|
|
||||||
|
duplicate_certificate = create_80g_certificate(args)
|
||||||
|
# duplicate validation
|
||||||
|
self.assertRaises(frappe.ValidationError, duplicate_certificate.insert)
|
||||||
|
|
||||||
|
def test_membership_80g_certificate(self):
|
||||||
|
plan = setup_membership()
|
||||||
|
|
||||||
|
# make test member
|
||||||
|
member_doc = create_member(frappe._dict({
|
||||||
|
'fullname': "_Test_Member",
|
||||||
|
'email': "_test_member_erpnext@example.com",
|
||||||
|
'plan_id': plan.name
|
||||||
|
}))
|
||||||
|
member_doc.make_customer_and_link()
|
||||||
|
member = member_doc.name
|
||||||
|
|
||||||
|
membership = make_membership(member, { "from_date": getdate() })
|
||||||
|
invoice = membership.generate_invoice(save=True)
|
||||||
|
|
||||||
|
args = frappe._dict({
|
||||||
|
'recipient': 'Member',
|
||||||
|
'member': member,
|
||||||
|
'fiscal_year': get_fiscal_year(getdate(), as_dict=True).get('name')
|
||||||
|
})
|
||||||
|
certificate = create_80g_certificate(args)
|
||||||
|
certificate.get_payments()
|
||||||
|
certificate.insert()
|
||||||
|
|
||||||
|
self.assertEquals(len(certificate.payments), 1)
|
||||||
|
self.assertEquals(certificate.payments[0].amount, membership.amount)
|
||||||
|
self.assertEquals(certificate.payments[0].invoice_id, invoice.name)
|
||||||
|
|
||||||
|
|
||||||
|
def create_80g_certificate(args):
|
||||||
|
certificate = frappe.get_doc({
|
||||||
|
'doctype': 'Tax Exemption 80G Certificate',
|
||||||
|
'recipient': args.recipient,
|
||||||
|
'date': getdate(),
|
||||||
|
'company': '_Test Company'
|
||||||
|
})
|
||||||
|
|
||||||
|
certificate.update(args)
|
||||||
|
|
||||||
|
return certificate
|
||||||
@@ -0,0 +1,66 @@
|
|||||||
|
{
|
||||||
|
"actions": [],
|
||||||
|
"creation": "2021-02-15 12:43:52.754124",
|
||||||
|
"doctype": "DocType",
|
||||||
|
"editable_grid": 1,
|
||||||
|
"engine": "InnoDB",
|
||||||
|
"field_order": [
|
||||||
|
"date",
|
||||||
|
"amount",
|
||||||
|
"invoice_id",
|
||||||
|
"column_break_4",
|
||||||
|
"razorpay_payment_id",
|
||||||
|
"membership"
|
||||||
|
],
|
||||||
|
"fields": [
|
||||||
|
{
|
||||||
|
"fieldname": "date",
|
||||||
|
"fieldtype": "Date",
|
||||||
|
"in_list_view": 1,
|
||||||
|
"label": "Date",
|
||||||
|
"reqd": 1
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "amount",
|
||||||
|
"fieldtype": "Currency",
|
||||||
|
"in_list_view": 1,
|
||||||
|
"label": "Amount",
|
||||||
|
"reqd": 1
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "invoice_id",
|
||||||
|
"fieldtype": "Link",
|
||||||
|
"in_list_view": 1,
|
||||||
|
"label": "Invoice ID",
|
||||||
|
"options": "Sales Invoice",
|
||||||
|
"reqd": 1
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "razorpay_payment_id",
|
||||||
|
"fieldtype": "Data",
|
||||||
|
"label": "Razorpay Payment ID"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "membership",
|
||||||
|
"fieldtype": "Link",
|
||||||
|
"label": "Membership",
|
||||||
|
"options": "Membership"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "column_break_4",
|
||||||
|
"fieldtype": "Column Break"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"index_web_pages_for_search": 1,
|
||||||
|
"istable": 1,
|
||||||
|
"links": [],
|
||||||
|
"modified": "2021-02-15 16:35:10.777587",
|
||||||
|
"modified_by": "Administrator",
|
||||||
|
"module": "Regional",
|
||||||
|
"name": "Tax Exemption 80G Certificate Detail",
|
||||||
|
"owner": "Administrator",
|
||||||
|
"permissions": [],
|
||||||
|
"sort_field": "modified",
|
||||||
|
"sort_order": "DESC",
|
||||||
|
"track_changes": 1
|
||||||
|
}
|
||||||
@@ -0,0 +1,10 @@
|
|||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
# Copyright (c) 2021, 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 TaxExemption80GCertificateDetail(Document):
|
||||||
|
pass
|
||||||
@@ -21,6 +21,7 @@ def setup_company_independent_fixtures():
|
|||||||
add_permissions()
|
add_permissions()
|
||||||
add_custom_roles_for_reports()
|
add_custom_roles_for_reports()
|
||||||
frappe.enqueue('erpnext.regional.india.setup.add_hsn_sac_codes', now=frappe.flags.in_test)
|
frappe.enqueue('erpnext.regional.india.setup.add_hsn_sac_codes', now=frappe.flags.in_test)
|
||||||
|
create_gratuity_rule()
|
||||||
add_print_formats()
|
add_print_formats()
|
||||||
|
|
||||||
def add_hsn_sac_codes():
|
def add_hsn_sac_codes():
|
||||||
@@ -105,8 +106,9 @@ def add_print_formats():
|
|||||||
frappe.reload_doc("accounts", "print_format", "gst_pos_invoice")
|
frappe.reload_doc("accounts", "print_format", "gst_pos_invoice")
|
||||||
frappe.reload_doc("accounts", "print_format", "GST E-Invoice")
|
frappe.reload_doc("accounts", "print_format", "GST E-Invoice")
|
||||||
|
|
||||||
frappe.db.sql(""" update `tabPrint Format` set disabled = 0 where
|
frappe.db.set_value("Print Format", "GST POS Invoice", "disabled", 0)
|
||||||
name in('GST POS Invoice', 'GST Tax Invoice', 'GST E-Invoice') """)
|
frappe.db.set_value("Print Format", "GST Tax Invoice", "disabled", 0)
|
||||||
|
frappe.db.set_value("Print Format", "GST E-Invoice", "disabled", 0)
|
||||||
|
|
||||||
def make_custom_fields(update=True):
|
def make_custom_fields(update=True):
|
||||||
hsn_sac_field = dict(fieldname='gst_hsn_code', label='HSN/SAC',
|
hsn_sac_field = dict(fieldname='gst_hsn_code', label='HSN/SAC',
|
||||||
@@ -498,6 +500,14 @@ def make_custom_fields(update=True):
|
|||||||
fieldtype='Link', options='Salary Component', insert_after='basic_component'),
|
fieldtype='Link', options='Salary Component', insert_after='basic_component'),
|
||||||
dict(fieldname='arrear_component', label='Arrear Component',
|
dict(fieldname='arrear_component', label='Arrear Component',
|
||||||
fieldtype='Link', options='Salary Component', insert_after='hra_component'),
|
fieldtype='Link', options='Salary Component', insert_after='hra_component'),
|
||||||
|
dict(fieldname='non_profit_section', label='Non Profit Settings',
|
||||||
|
fieldtype='Section Break', insert_after='asset_received_but_not_billed', collapsible=1),
|
||||||
|
dict(fieldname='company_80g_number', label='80G Number',
|
||||||
|
fieldtype='Data', insert_after='non_profit_section'),
|
||||||
|
dict(fieldname='with_effect_from', label='80G With Effect From',
|
||||||
|
fieldtype='Date', insert_after='company_80g_number'),
|
||||||
|
dict(fieldname='pan_details', label='PAN Number',
|
||||||
|
fieldtype='Data', insert_after='with_effect_from')
|
||||||
],
|
],
|
||||||
'Employee Tax Exemption Declaration':[
|
'Employee Tax Exemption Declaration':[
|
||||||
dict(fieldname='hra_section', label='HRA Exemption',
|
dict(fieldname='hra_section', label='HRA Exemption',
|
||||||
@@ -580,7 +590,15 @@ def make_custom_fields(update=True):
|
|||||||
'options': '\nWith Payment of Tax\nWithout Payment of Tax'
|
'options': '\nWith Payment of Tax\nWithout Payment of Tax'
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"Member": [
|
'Member': [
|
||||||
|
{
|
||||||
|
'fieldname': 'pan_number',
|
||||||
|
'label': 'PAN Details',
|
||||||
|
'fieldtype': 'Data',
|
||||||
|
'insert_after': 'email_id'
|
||||||
|
}
|
||||||
|
],
|
||||||
|
'Donor': [
|
||||||
{
|
{
|
||||||
'fieldname': 'pan_number',
|
'fieldname': 'pan_number',
|
||||||
'label': 'PAN Details',
|
'label': 'PAN Details',
|
||||||
@@ -823,3 +841,23 @@ def get_tds_details(accounts, fiscal_year):
|
|||||||
rates=[{"fiscal_year": fiscal_year, "tax_withholding_rate": 20,
|
rates=[{"fiscal_year": fiscal_year, "tax_withholding_rate": 20,
|
||||||
"single_threshold": 2500, "cumulative_threshold": 0}])
|
"single_threshold": 2500, "cumulative_threshold": 0}])
|
||||||
]
|
]
|
||||||
|
|
||||||
|
def create_gratuity_rule():
|
||||||
|
|
||||||
|
# Standard Indain Gratuity Rule
|
||||||
|
if not frappe.db.exists("Gratuity Rule", "Indian Standard Gratuity Rule"):
|
||||||
|
rule = frappe.new_doc("Gratuity Rule")
|
||||||
|
rule.name = "Indian Standard Gratuity Rule"
|
||||||
|
rule.calculate_gratuity_amount_based_on = "Current Slab"
|
||||||
|
rule.work_experience_calculation_method = "Round Off Work Experience"
|
||||||
|
rule.minimum_year_for_gratuity = 5
|
||||||
|
|
||||||
|
fraction = 15/26
|
||||||
|
rule.append("gratuity_rule_slabs", {
|
||||||
|
"from_year": 0,
|
||||||
|
"to_year":0,
|
||||||
|
"fraction_of_applicable_earnings": fraction
|
||||||
|
})
|
||||||
|
|
||||||
|
rule.flags.ignore_mandatory = True
|
||||||
|
rule.save()
|
||||||
@@ -189,9 +189,7 @@ def make_custom_fields(update=True):
|
|||||||
|
|
||||||
def setup_report():
|
def setup_report():
|
||||||
report_name = 'Electronic Invoice Register'
|
report_name = 'Electronic Invoice Register'
|
||||||
|
frappe.db.set_value("Report", report_name, "disabled", 0)
|
||||||
frappe.db.sql(""" update `tabReport` set disabled = 0 where
|
|
||||||
name = %s """, report_name)
|
|
||||||
|
|
||||||
if not frappe.db.get_value('Custom Role', dict(report=report_name)):
|
if not frappe.db.get_value('Custom Role', dict(report=report_name)):
|
||||||
frappe.get_doc(dict(
|
frappe.get_doc(dict(
|
||||||
|
|||||||
@@ -0,0 +1,26 @@
|
|||||||
|
{
|
||||||
|
"absolute_value": 0,
|
||||||
|
"align_labels_right": 0,
|
||||||
|
"creation": "2021-02-22 00:17:33.878581",
|
||||||
|
"css": ".details {\n font-size: 15px;\n font-family: Tahoma, sans-serif;;\n line-height: 150%;\n}\n\n.certificate-footer {\n font-size: 15px;\n font-family: Tahoma, sans-serif;\n line-height: 140%;\n margin-top: 120px;\n}\n\n.company-address {\n color: #666666;\n font-size: 15px;\n font-family: Tahoma, sans-serif;;\n}",
|
||||||
|
"custom_format": 1,
|
||||||
|
"default_print_language": "en",
|
||||||
|
"disabled": 0,
|
||||||
|
"doc_type": "Tax Exemption 80G Certificate",
|
||||||
|
"docstatus": 0,
|
||||||
|
"doctype": "Print Format",
|
||||||
|
"font": "Default",
|
||||||
|
"html": "{% if letter_head and not no_letterhead -%}\n <div class=\"letter-head\">{{ letter_head }}</div>\n{%- endif %}\n\n<div>\n <h3 class=\"text-center\">{{ doc.company }} 80G Donor Certificate</h3>\n</div>\n<br><br>\n\n<div class=\"details\">\n <p> <b>{{ _(\"Certificate No. : \") }}</b> {{ doc.name }} </p>\n <p>\n \t<b>{{ _(\"Date\") }} :</b> {{ doc.get_formatted(\"date\") }}<br>\n </p>\n <br><br>\n \n <div>\n\n This is to confirm that the {{ doc.company }} received an amount of <b>{{doc.get_formatted(\"amount\")}}</b>\n from <b>{{ doc.donor_name }}</b>\n {% if doc.pan_number -%}\n bearing PAN Number {{ doc.member_pan_number }}\n {%- endif %}\n\n via the Mode of Payment {{doc.mode_of_payment}}\n\n {% if doc.razorpay_payment_id -%}\n bearing RazorPay Payment ID {{ doc.razorpay_payment_id }}\n {%- endif %}\n\n on {{ doc.get_formatted(\"date_of_donation\") }}\n <br><br>\n \n <p>\n We thank you for your contribution towards the corpus of the {{ doc.company }} and helping support our work.\n </p>\n\n </div>\n</div>\n\n<br><br>\n<p class=\"company-address text-left\"> {{doc.company_address_display }}</p>\n\n<div class=\"certificate-footer text-center\">\n <p><i>Computer generated receipt - Does not require signature</i></p><br>\n \n {% if doc.company_pan_number %}\n <p>\n <b>{{ doc.company }}'s PAN Account No :</b> {{ doc.company_pan_number }}\n <p><br>\n {% endif %}\n \n <p>\n <b>80G Number : </b> {{ doc.company_80g_number }}\n {% if doc.company_80g_wef %}\n ( w.e.f. {{ doc.get_formatted('company_80g_wef') }} )\n {% endif %}\n </p><br>\n</div>",
|
||||||
|
"idx": 0,
|
||||||
|
"line_breaks": 0,
|
||||||
|
"modified": "2021-02-22 00:20:08.516600",
|
||||||
|
"modified_by": "Administrator",
|
||||||
|
"module": "Regional",
|
||||||
|
"name": "80G Certificate for Donation",
|
||||||
|
"owner": "Administrator",
|
||||||
|
"print_format_builder": 0,
|
||||||
|
"print_format_type": "Jinja",
|
||||||
|
"raw_printing": 0,
|
||||||
|
"show_section_headings": 0,
|
||||||
|
"standard": "Yes"
|
||||||
|
}
|
||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user