Merge branch 'hotfix' of https://github.com/frappe/erpnext into datev_report

This commit is contained in:
Raffael Meyer
2019-05-09 04:04:33 +02:00
108 changed files with 5714 additions and 3688 deletions

View File

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

View File

@@ -105,20 +105,27 @@ class Account(NestedSet):
descendants = get_descendants_of('Company', self.company)
if not descendants: return
acc_name_map = {}
acc_name = frappe.db.get_value('Account', self.parent_account, "account_name")
parent_acc_name_map = {}
parent_acc_name = frappe.db.get_value('Account', self.parent_account, "account_name")
for d in frappe.db.get_values('Account',
{"company": ["in", descendants], "account_name": acc_name},
{"company": ["in", descendants], "account_name": parent_acc_name},
["company", "name"], as_dict=True):
acc_name_map[d["company"]] = d["name"]
parent_acc_name_map[d["company"]] = d["name"]
if not acc_name_map: return
if not parent_acc_name_map: return
for company in descendants:
if not parent_acc_name_map.get(company):
frappe.throw(_("While creating account for child Company {0}, parent account {1} not found. Please create the parent account in corresponding COA")
.format(company, parent_acc_name))
doc = frappe.copy_doc(self)
doc.flags.ignore_root_company_validation = True
doc.update({"company": company, "account_currency": None,
"parent": acc_name_map[company], "parent_account": acc_name_map[company]})
doc.update({
"company": company,
"account_currency": None,
"parent_account": parent_acc_name_map[company]
})
doc.save()
frappe.msgprint(_("Account {0} is added in the child company {1}")
.format(doc.name, company))

View File

@@ -12,6 +12,11 @@ frappe.ui.form.on('Bank Account', {
}
};
});
frm.set_query("party_type", function() {
return {
query: "erpnext.setup.doctype.party_type.party_type.get_party_type",
};
});
},
refresh: function(frm) {
frappe.dynamic_link = { doc: frm.doc, fieldname: 'name', doctype: 'Bank Account' }

View File

@@ -2,7 +2,7 @@
"allow_copy": 0,
"allow_events_in_timeline": 0,
"allow_guest_to_view": 0,
"allow_import": 0,
"allow_import": 1,
"allow_rename": 1,
"autoname": "",
"beta": 0,
@@ -975,7 +975,7 @@
"email": 1,
"export": 1,
"if_owner": 0,
"import": 0,
"import": 1,
"permlevel": 0,
"print": 1,
"read": 1,

View File

@@ -21,11 +21,39 @@ class BankAccount(Document):
def validate(self):
self.validate_company()
self.validate_iban()
def validate_company(self):
if self.is_company_account and not self.company:
frappe.throw(_("Company is manadatory for company account"))
def validate_iban(self):
'''
Algorithm: https://en.wikipedia.org/wiki/International_Bank_Account_Number#Validating_the_IBAN
'''
# IBAN field is optional
if not self.iban:
return
def encode_char(c):
# Position in the alphabet (A=1, B=2, ...) plus nine
return str(9 + ord(c) - 64)
# remove whitespaces, upper case to get the right number from ord()
iban = ''.join(self.iban.split(' ')).upper()
# Move country code and checksum from the start to the end
flipped = iban[4:] + iban[:4]
# Encode characters as numbers
encoded = [encode_char(c) if ord(c) >= 65 and ord(c) <= 90 else c for c in flipped]
to_check = int(''.join(encoded))
if to_check % 97 != 1:
frappe.throw(_('IBAN is not valid'))
@frappe.whitelist()
def make_bank_account(doctype, docname):
doc = frappe.new_doc("Bank Account")

View File

@@ -4,9 +4,46 @@
from __future__ import unicode_literals
import frappe
from frappe import _
from frappe import ValidationError
import unittest
# test_records = frappe.get_test_records('Bank Account')
class TestBankAccount(unittest.TestCase):
pass
def test_validate_iban(self):
valid_ibans = [
'GB82 WEST 1234 5698 7654 32',
'DE91 1000 0000 0123 4567 89',
'FR76 3000 6000 0112 3456 7890 189'
]
invalid_ibans = [
# wrong checksum (3rd place)
'GB72 WEST 1234 5698 7654 32',
'DE81 1000 0000 0123 4567 89',
'FR66 3000 6000 0112 3456 7890 189'
]
bank_account = frappe.get_doc({'doctype':'Bank Account'})
try:
bank_account.validate_iban()
except AttributeError:
msg = _('BankAccount.validate_iban() failed for empty IBAN')
self.fail(msg=msg)
for iban in valid_ibans:
bank_account.iban = iban
try:
bank_account.validate_iban()
except ValidationError:
msg = _('BankAccount.validate_iban() failed for valid IBAN {}'.format(iban))
self.fail(msg=msg)
for not_iban in invalid_ibans:
bank_account.iban = not_iban
msg = _('BankAccount.validate_iban() accepted invalid IBAN {}'.format(not_iban))
with self.assertRaises(ValidationError, msg=msg):
bank_account.validate_iban()

View File

@@ -2,7 +2,7 @@
// For license information, please see license.txt
frappe.ui.form.on('Bank Transaction', {
onload: function(frm) {
onload(frm) {
frm.set_query('payment_document', 'payment_entries', function() {
return {
"filters": {
@@ -12,3 +12,21 @@ frappe.ui.form.on('Bank Transaction', {
});
}
});
frappe.ui.form.on('Bank Transaction Payments', {
payment_entries_remove: function(frm, cdt, cdn) {
update_clearance_date(frm, cdt, cdn);
}
});
const update_clearance_date = (frm, cdt, cdn) => {
if (frm.doc.docstatus === 1) {
frappe.xcall('erpnext.accounts.doctype.bank_transaction.bank_transaction.unclear_reference_payment',
{doctype: cdt, docname: cdn})
.then(e => {
if (e == "success") {
frappe.show_alert({message:__("Document {0} successfully uncleared", [e]), indicator:'green'});
}
});
}
};

File diff suppressed because it is too large Load Diff

View File

@@ -1,19 +1,32 @@
# -*- coding: utf-8 -*-
# Copyright (c) 2018, Frappe Technologies Pvt. Ltd. and contributors
# Copyright (c) 2019, 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 erpnext.controllers.status_updater import StatusUpdater
from frappe.utils import flt
from six.moves import reduce
from frappe import _
class BankTransaction(Document):
class BankTransaction(StatusUpdater):
def after_insert(self):
self.unallocated_amount = abs(flt(self.credit) - flt(self.debit))
def on_submit(self):
self.clear_linked_payment_entries()
self.set_status()
def on_update_after_submit(self):
allocated_amount = reduce(lambda x, y: flt(x) + flt(y), [x.allocated_amount for x in self.payment_entries])
self.update_allocations()
self.clear_linked_payment_entries()
self.set_status(update=True)
def update_allocations(self):
if self.payment_entries:
allocated_amount = reduce(lambda x, y: flt(x) + flt(y), [x.allocated_amount for x in self.payment_entries])
else:
allocated_amount = 0
if allocated_amount:
frappe.db.set_value(self.doctype, self.name, "allocated_amount", flt(allocated_amount))
@@ -23,4 +36,67 @@ class BankTransaction(Document):
frappe.db.set_value(self.doctype, self.name, "allocated_amount", 0)
frappe.db.set_value(self.doctype, self.name, "unallocated_amount", abs(flt(self.credit) - flt(self.debit)))
self.reload()
self.reload()
def clear_linked_payment_entries(self):
for payment_entry in self.payment_entries:
allocated_amount = get_total_allocated_amount(payment_entry)
paid_amount = get_paid_amount(payment_entry)
if paid_amount and allocated_amount:
if flt(allocated_amount[0]["allocated_amount"]) > flt(paid_amount):
frappe.throw(_("The total allocated amount ({0}) is greated than the paid amount ({1}).".format(flt(allocated_amount[0]["allocated_amount"]), flt(paid_amount))))
elif flt(allocated_amount[0]["allocated_amount"]) == flt(paid_amount):
if payment_entry.payment_document in ["Payment Entry", "Journal Entry", "Purchase Invoice", "Expense Claim"]:
self.clear_simple_entry(payment_entry)
elif payment_entry.payment_document == "Sales Invoice":
self.clear_sales_invoice(payment_entry)
def clear_simple_entry(self, payment_entry):
frappe.db.set_value(payment_entry.payment_document, payment_entry.payment_entry, "clearance_date", self.date)
def clear_sales_invoice(self, payment_entry):
frappe.db.set_value("Sales Invoice Payment", dict(parenttype=payment_entry.payment_document,
parent=payment_entry.payment_entry), "clearance_date", self.date)
def get_total_allocated_amount(payment_entry):
return frappe.db.sql("""
SELECT
SUM(btp.allocated_amount) as allocated_amount,
bt.name
FROM
`tabBank Transaction Payments` as btp
LEFT JOIN
`tabBank Transaction` bt ON bt.name=btp.parent
WHERE
btp.payment_document = %s
AND
btp.payment_entry = %s
AND
bt.docstatus = 1""", (payment_entry.payment_document, payment_entry.payment_entry), as_dict=True)
def get_paid_amount(payment_entry):
if payment_entry.payment_document in ["Payment Entry", "Sales Invoice", "Purchase Invoice"]:
return frappe.db.get_value(payment_entry.payment_document, payment_entry.payment_entry, "paid_amount")
elif payment_entry.payment_document == "Journal Entry":
return frappe.db.get_value(payment_entry.payment_document, payment_entry.payment_entry, "total_credit")
elif payment_entry.payment_document == "Expense Claim":
return frappe.db.get_value(payment_entry.payment_document, payment_entry.payment_entry, "total_amount_reimbursed")
else:
frappe.throw("Please reconcile {0}: {1} manually".format(payment_entry.payment_document, payment_entry.payment_entry))
@frappe.whitelist()
def unclear_reference_payment(doctype, docname):
if frappe.db.exists(doctype, docname):
doc = frappe.get_doc(doctype, docname)
if doctype == "Sales Invoice":
frappe.db.set_value("Sales Invoice Payment", dict(parenttype=doc.payment_document,
parent=doc.payment_entry), "clearance_date", None)
else:
frappe.db.set_value(doc.payment_document, doc.payment_entry, "clearance_date", None)
return doc.payment_entry

View File

@@ -6,7 +6,7 @@ frappe.listview_settings['Bank Transaction'] = {
get_indicator: function(doc) {
if(flt(doc.unallocated_amount)>0) {
return [__("Unreconciled"), "orange", "unallocated_amount,>,0"];
} else if(flt(doc.unallocated_amount)===0) {
} else if(flt(doc.unallocated_amount)<=0) {
return [__("Reconciled"), "green", "unallocated_amount,=,0"];
}
}

View File

@@ -36,7 +36,8 @@ def upload_bank_statement():
def create_bank_entries(columns, data, bank_account):
header_map = get_header_mapping(columns, bank_account)
count = 0
success = 0
errors = 0
for d in json.loads(data):
if all(item is None for item in d) is True:
continue
@@ -44,7 +45,6 @@ def create_bank_entries(columns, data, bank_account):
for key, value in iteritems(header_map):
fields.update({key: d[int(value)-1]})
try:
bank_transaction = frappe.get_doc({
"doctype": "Bank Transaction"
@@ -54,12 +54,12 @@ def create_bank_entries(columns, data, bank_account):
bank_transaction.bank_account = bank_account
bank_transaction.insert()
bank_transaction.submit()
count = count + 1
except Exception as e:
frappe.throw(e)
success += 1
except Exception:
frappe.log_error(frappe.get_traceback())
errors += 1
return count
return {"success": success, "errors": errors}
def get_header_mapping(columns, bank_account):
mapping = get_bank_mapping(bank_account)

View File

@@ -160,7 +160,7 @@ class PaymentEntry(AccountsController):
d.reference_name, self.party_account_currency)
for field, value in iteritems(ref_details):
if not d.get(field) or force:
if field == 'exchange_rate' or not d.get(field) or force:
d.set(field, value)
def validate_payment_type(self):

View File

@@ -285,6 +285,7 @@ erpnext.accounts.PurchaseInvoice = erpnext.buying.BuyingController.extend({
is_paid: function() {
hide_fields(this.frm.doc);
if(cint(this.frm.doc.is_paid)) {
this.frm.set_value("allocate_advances_automatically", 0);
if(!this.frm.doc.company) {
this.frm.set_value("is_paid", 0)
frappe.msgprint(__("Please specify Company to proceed"));

View File

@@ -0,0 +1,38 @@
frappe.ui.form.on("Sales Invoice", {
setup: function(frm) {
frm.set_query('transporter', function() {
return {
filters: {
'is_transporter': 1
}
};
});
frm.set_query('driver', function(doc) {
return {
filters: {
'transporter': doc.transporter
}
};
});
},
refresh: function(frm) {
if(frm.doc.docstatus == 1 && !frm.is_dirty()
&& !frm.doc.is_return && !frm.doc.ewaybill) {
frm.add_custom_button('Generate e-Way Bill JSON', () => {
var w = window.open(
frappe.urllib.get_full_url(
"/api/method/erpnext.regional.india.utils.generate_ewb_json?"
+ "dt=" + encodeURIComponent(frm.doc.doctype)
+ "&dn=" + encodeURIComponent(frm.doc.name)
)
);
if (!w) {
frappe.msgprint(__("Please enable pop-ups")); return;
}
}, __("Make"));
}
}
});

View File

@@ -0,0 +1,33 @@
var globalOnload = frappe.listview_settings['Sales Invoice'].onload;
frappe.listview_settings['Sales Invoice'].onload = function (doclist) {
// Provision in case onload event is added to sales_invoice.js in future
if (globalOnload) {
globalOnload(doclist);
}
const action = () => {
const selected_docs = doclist.get_checked_items();
const docnames = doclist.get_checked_items(true);
for (let doc of selected_docs) {
if (doc.docstatus !== 1) {
frappe.throw(__("e-Way Bill JSON can only be generated from a submitted document"));
}
}
var w = window.open(
frappe.urllib.get_full_url(
"/api/method/erpnext.regional.india.utils.generate_ewb_json?"
+ "dt=" + encodeURIComponent(doclist.doctype)
+ "&dn=" + encodeURIComponent(docnames)
)
);
if (!w) {
frappe.msgprint(__("Please enable pop-ups")); return;
}
};
doclist.page.add_actions_menu_item(__('Generate e-Way Bill JSON'), action, false);
};

View File

@@ -356,7 +356,7 @@ erpnext.accounts.SalesInvoiceController = erpnext.selling.SellingController.exte
set_pos_data: function() {
if(this.frm.doc.is_pos) {
this.frm.set_value("allocate_advances_automatically", this.frm.doc.is_pos ? 0 : 1);
this.frm.set_value("allocate_advances_automatically", 0);
if(!this.frm.doc.company) {
this.frm.set_value("is_pos", 0);
frappe.msgprint(__("Please specify Company to proceed"));

View File

@@ -18,6 +18,8 @@ from erpnext.accounts.doctype.account.test_account import get_inventory_account,
from erpnext.controllers.taxes_and_totals import get_itemised_tax_breakup_data
from erpnext.stock.doctype.item.test_item import create_item
from six import iteritems
from erpnext.regional.india.utils import get_ewb_data
class TestSalesInvoice(unittest.TestCase):
def make(self):
w = frappe.copy_doc(test_records[0])
@@ -1611,6 +1613,110 @@ class TestSalesInvoice(unittest.TestCase):
self.assertEqual(expected_gle[i][2], gle.credit)
self.assertEqual(getdate(expected_gle[i][3]), gle.posting_date)
def test_eway_bill_json(self):
if not frappe.db.exists('Address', '_Test Address for Eway bill-Billing'):
address = frappe.get_doc({
"address_line1": "_Test Address Line 1",
"address_title": "_Test Address for Eway bill",
"address_type": "Billing",
"city": "_Test City",
"state": "Test State",
"country": "India",
"doctype": "Address",
"is_primary_address": 1,
"phone": "+91 0000000000",
"gstin": "27AAECE4835E1ZR",
"gst_state": "Maharashtra",
"gst_state_number": "27",
"pincode": "401108"
}).insert()
address.append("links", {
"link_doctype": "Company",
"link_name": "_Test Company"
})
address.save()
if not frappe.db.exists('Address', '_Test Customer-Address for Eway bill-Shipping'):
address = frappe.get_doc({
"address_line1": "_Test Address Line 1",
"address_title": "_Test Customer-Address for Eway bill",
"address_type": "Shipping",
"city": "_Test City",
"state": "Test State",
"country": "India",
"doctype": "Address",
"is_primary_address": 1,
"phone": "+91 0000000000",
"gst_state": "Maharashtra",
"gst_state_number": "27",
"pincode": "410038"
}).insert()
address.append("links", {
"link_doctype": "Customer",
"link_name": "_Test Customer"
})
address.save()
gst_settings = frappe.get_doc("GST Settings")
gst_account = frappe.get_all(
"GST Account",
fields=["cgst_account", "sgst_account", "igst_account"],
filters = {"company": "_Test Company"})
if not gst_account:
gst_settings.append("gst_accounts", {
"company": "_Test Company",
"cgst_account": "CGST - _TC",
"sgst_account": "SGST - _TC",
"igst_account": "IGST - _TC",
})
gst_settings.save()
si = create_sales_invoice(do_not_save =1, rate = '60000')
si.distance = 2000
si.company_address = "_Test Address for Eway bill-Billing"
si.customer_address = "_Test Customer-Address for Eway bill-Shipping"
si.vehicle_no = "KA12KA1234"
si.append("taxes", {
"charge_type": "On Net Total",
"account_head": "CGST - _TC",
"cost_center": "Main - _TC",
"description": "CGST @ 9.0",
"rate": 9
})
si.append("taxes", {
"charge_type": "On Net Total",
"account_head": "SGST - _TC",
"cost_center": "Main - _TC",
"description": "SGST @ 9.0",
"rate": 9
})
si.submit()
data = get_ewb_data("Sales Invoice", si.name)
self.assertEqual(data['version'], '1.0.1118')
self.assertEqual(data['billLists'][0]['fromGstin'], '27AAECE4835E1ZR')
self.assertEqual(data['billLists'][0]['fromTrdName'], '_Test Company')
self.assertEqual(data['billLists'][0]['toTrdName'], '_Test Customer')
self.assertEqual(data['billLists'][0]['vehicleType'], 'R')
self.assertEqual(data['billLists'][0]['totalValue'], 60000)
self.assertEqual(data['billLists'][0]['cgstValue'], 5400)
self.assertEqual(data['billLists'][0]['sgstValue'], 5400)
self.assertEqual(data['billLists'][0]['vehicleNo'], 'KA12KA1234')
self.assertEqual(data['billLists'][0]['itemList'][0]['taxableAmount'], 60000)
def create_sales_invoice(**args):
si = frappe.new_doc("Sales Invoice")
args = frappe._dict(args)

View File

@@ -191,7 +191,7 @@ erpnext.accounts.bankTransactionUpload = class bankTransactionUpload {
frappe.xcall('erpnext.accounts.doctype.bank_transaction.bank_transaction_upload.create_bank_entries',
{columns: this.datatable.datamanager.columns, data: this.datatable.datamanager.data, bank_account: me.parent.bank_account}
).then((result) => {
let result_title = __("{0} bank transaction(s) created", [result])
let result_title = result.errors == 0 ? __("{0} bank transaction(s) created", [result.success]) : __("{0} bank transaction(s) created and {1} errors", [result.success, result.errors])
let result_msg = `
<div class="flex justify-center align-center text-muted" style="height: 50vh; display: flex;">
<h5 class="text-muted">${result_title}</h5>
@@ -199,7 +199,11 @@ erpnext.accounts.bankTransactionUpload = class bankTransactionUpload {
me.parent.page.clear_primary_action();
me.parent.$main_section.empty();
me.parent.$main_section.append(result_msg);
frappe.show_alert({message:__("All bank transactions have been created"), indicator:'green'});
if (result.errors == 0) {
frappe.show_alert({message:__("All bank transactions have been created"), indicator:'green'});
} else {
frappe.show_alert({message:__("Please check the error log for details about the import errors"), indicator:'red'});
}
})
}
}
@@ -530,11 +534,13 @@ erpnext.accounts.ReconciliationRow = class ReconciliationRow {
.then(doc => {
let displayed_docs = []
if (dt === "Payment Entry") {
doc.currency = doc.payment_type == "Receive" ? doc.paid_to_account_currency : doc.paid_from_account_currency;
displayed_docs.push(doc);
payment.currency = doc.payment_type == "Receive" ? doc.paid_to_account_currency : doc.paid_from_account_currency;
payment.doctype = dt
displayed_docs.push(payment);
} else if (dt === "Journal Entry") {
doc.accounts.forEach(payment => {
if (payment.account === me.gl_account) {
payment.doctype = dt;
payment.posting_date = doc.posting_date;
payment.party = doc.pay_to_recd_from;
payment.reference_no = doc.cheque_no;
@@ -548,6 +554,7 @@ erpnext.accounts.ReconciliationRow = class ReconciliationRow {
} else if (dt === "Sales Invoice") {
doc.payments.forEach(payment => {
if (payment.clearance_date === null || payment.clearance_date === "") {
payment.doctype = dt;
payment.posting_date = doc.posting_date;
payment.party = doc.customer;
payment.reference_no = doc.remarks;

View File

@@ -28,7 +28,6 @@ def reconcile(bank_transaction, payment_doctype, payment_name):
frappe.throw(_("The selected payment entry should be linked with a creditor bank transaction"))
add_payment_to_transaction(transaction, payment_entry, gl_entry)
clear_payment_entry(transaction, payment_entry, gl_entry)
return 'reconciled'
@@ -42,40 +41,6 @@ def add_payment_to_transaction(transaction, payment_entry, gl_entry):
})
transaction.save()
def clear_payment_entry(transaction, payment_entry, gl_entry):
linked_bank_transactions = frappe.db.sql("""
SELECT
bt.credit, bt.debit
FROM
`tabBank Transaction Payments` as btp
LEFT JOIN
`tabBank Transaction` as bt on btp.parent=bt.name
WHERE
btp.payment_document = %s
AND
btp.payment_entry = %s
AND
bt.docstatus = 1
""", (payment_entry.doctype, payment_entry.name), as_dict=True)
amount_cleared = (flt(linked_bank_transactions[0].credit) - flt(linked_bank_transactions[0].debit))
amount_to_be_cleared = (flt(gl_entry.debit) - flt(gl_entry.credit))
if payment_entry.doctype in ("Payment Entry", "Journal Entry", "Purchase Invoice", "Expense Claim"):
clear_simple_entry(amount_cleared, amount_to_be_cleared, payment_entry, transaction)
elif payment_entry.doctype == "Sales Invoice":
clear_sales_invoice(amount_cleared, amount_to_be_cleared, payment_entry, transaction)
def clear_simple_entry(amount_cleared, amount_to_be_cleared, payment_entry, transaction):
if amount_cleared >= amount_to_be_cleared:
frappe.db.set_value(payment_entry.doctype, payment_entry.name, "clearance_date", transaction.date)
def clear_sales_invoice(amount_cleared, amount_to_be_cleared, payment_entry, transaction):
if amount_cleared >= amount_to_be_cleared:
frappe.db.set_value("Sales Invoice Payment", dict(parenttype=payment_entry.doctype,
parent=payment_entry.name), "clearance_date", transaction.date)
@frappe.whitelist()
def get_linked_payments(bank_transaction):
transaction = frappe.get_doc("Bank Transaction", bank_transaction)
@@ -250,7 +215,7 @@ def get_matching_descriptions_data(company, transaction):
if key == "Payment Entry":
data.extend(frappe.get_all("Payment Entry", filters=[["name", "in", value]], fields=["'Payment Entry' as doctype", "posting_date", "party", "reference_no", "reference_date", "paid_amount", "paid_to_account_currency as currency"]))
if key == "Journal Entry":
journal_entries = frappe.get_all("Journal Entry", filters=[["name", "in", value]], fields=["name", "'Journal Entry' as doctype", "posting_date", "paid_to_recd_from as party", "cheque_no as reference_no", "cheque_date as reference_date", "total_credit as paid_amount"])
journal_entries = frappe.get_all("Journal Entry", filters=[["name", "in", value]], fields=["name", "'Journal Entry' as doctype", "posting_date", "pay_to_recd_from as party", "cheque_no as reference_no", "cheque_date as reference_date", "total_credit as paid_amount"])
for journal_entry in journal_entries:
journal_entry_accounts = frappe.get_all("Journal Entry Account", filters={"parenttype": journal_entry["doctype"], "parent": journal_entry["name"]}, fields=["account_currency"])
journal_entry["currency"] = journal_entry_accounts[0]["account_currency"] if journal_entry_accounts else company_currency

View File

@@ -44,7 +44,7 @@ def _get_party_details(party=None, account=None, party_type="Customer", company=
frappe.throw(_("Not permitted for {0}").format(party), frappe.PermissionError)
party = frappe.get_doc(party_type, party)
currency = party.default_currency if party.default_currency else get_company_currency(company)
currency = party.default_currency if party.get("default_currency") else get_company_currency(company)
out["taxes_and_charges"] = set_taxes(party.name, party_type, posting_date, company, out.customer_group, out.supplier_group)
out["payment_terms_template"] = get_pyt_term_template(party.name, party_type, company)
@@ -140,7 +140,7 @@ def set_other_values(out, party, party_type):
def get_default_price_list(party):
"""Return default price list for party (Document object)"""
if party.default_price_list:
if party.get("default_price_list"):
return party.default_price_list
if party.doctype == "Customer":

View File

@@ -359,7 +359,8 @@ def set_gl_entries_by_account(
"from_date": from_date,
"to_date": to_date,
"cost_center": filters.cost_center,
"project": filters.project
"project": filters.project,
"finance_book": filters.get("finance_book")
},
as_dict=True)

View File

@@ -65,7 +65,7 @@ def get_columns(group_wise_columns, filters):
"warehouse": _("Warehouse") + ":Link/Warehouse",
"qty": _("Qty") + ":Float",
"base_rate": _("Avg. Selling Rate") + ":Currency/currency",
"buying_rate": _("Avg. Buying Rate") + ":Currency/currency",
"buying_rate": _("Valuation Rate") + ":Currency/currency",
"base_amount": _("Selling Amount") + ":Currency/currency",
"buying_amount": _("Buying Amount") + ":Currency/currency",
"gross_profit": _("Gross Profit") + ":Currency/currency",

View File

@@ -0,0 +1,34 @@
// Copyright (c) 2016, Frappe Technologies Pvt. Ltd. and contributors
// For license information, please see license.txt
/* eslint-disable */
frappe.query_reports["Inactive Sales Items"] = {
"filters": [
{
fieldname: "item",
label: __("Item"),
fieldtype: "Link",
options: "Item"
},
{
fieldname: "item_group",
label: __("Item Group"),
fieldtype: "Link",
options: "Item Group"
},
{
fieldname: "based_on",
label: __("Based On"),
fieldtype: "Select",
options: "Sales Order\nSales Invoice",
default: "Sales Order"
},
{
fieldname: "days",
label: __("Days Since Last order"),
fieldtype: "Select",
options: [30, 60, 90],
default: 30
},
]
}

View File

@@ -0,0 +1,30 @@
{
"add_total_row": 0,
"creation": "2019-05-01 12:59:52.018850",
"disable_prepared_report": 0,
"disabled": 0,
"docstatus": 0,
"doctype": "Report",
"idx": 0,
"is_standard": "Yes",
"modified": "2019-05-01 13:00:26.545278",
"modified_by": "Administrator",
"module": "Accounts",
"name": "Inactive Sales Items",
"owner": "Administrator",
"prepared_report": 0,
"ref_doctype": "Sales Invoice",
"report_name": "Inactive Sales Items",
"report_type": "Script Report",
"roles": [
{
"role": "Accounts User"
},
{
"role": "Accounts Manager"
},
{
"role": "Auditor"
}
]
}

View File

@@ -0,0 +1,144 @@
# Copyright (c) 2013, Frappe Technologies Pvt. Ltd. and contributors
# For license information, please see license.txt
from __future__ import unicode_literals
import frappe
from frappe.utils import getdate, add_days, today, cint
from frappe import _
def execute(filters=None):
columns = get_columns()
data = get_data(filters)
return columns, data
def get_columns():
columns = [
{
"fieldname": "territory",
"fieldtype": "Link",
"label": _("Territory"),
"options": "Territory",
"width": 100
},
{
"fieldname": "item_group",
"fieldtype": "Link",
"label": _("Item Group"),
"options": "Item Group",
"width": 150
},
{
"fieldname": "item_name",
"fieldtype": "Link",
"options": "Item",
"label": "Item",
"width": 150
},
{
"fieldname": "item_name",
"fieldtype": "Data",
"label": _("Item Name"),
"width": 150
},
{
"fieldname": "customer",
"fieldtype": "Link",
"label": _("Customer"),
"options": "Customer",
"width": 100
},
{
"fieldname": "last_order_date",
"fieldtype": "Date",
"label": _("Last Order Date"),
"width": 100
},
{
"fieldname": "qty",
"fieldtype": "Float",
"label": _("Quantity"),
"width": 100
},
{
"fieldname": "days_since_last_order",
"fieldtype": "Int",
"label": _("Days Since Last Order"),
"width": 100
},
]
return columns
def get_data(filters):
data = []
items = get_items(filters)
sales_invoice_data = get_sales_details(filters)
for item in items:
row = {
"item_group": item.item_group,
"item": item.name,
"item_name": item.item_name
}
if sales_invoice_data.get(item.name):
item_obj = sales_invoice_data[item.name]
if item_obj.days_since_last_order > cint(filters['days']):
row.update({
"territory": item_obj.territory,
"customer": item_obj.customer,
"last_order_date": item_obj.last_order_date,
"qty": item_obj.qty,
"days_since_last_order": item_obj.days_since_last_order
})
data.append(row)
return data
def get_sales_details(filters):
data = []
item_details_map = {}
date_field = "s.transaction_date" if filters["based_on"] == "Sales Order" else "s.posting_date"
sales_data = frappe.db.sql("""
select s.territory, s.customer, si.item_group, si.item_name, si.qty, {date_field} as last_order_date,
DATEDIFF(CURDATE(), {date_field}) as days_since_last_order
from `tab{doctype}` s, `tab{doctype} Item` si
where s.name = si.parent and s.docstatus = 1
group by si.name order by days_since_last_order """ #nosec
.format(date_field = date_field, doctype = filters['based_on']), as_dict=1)
for d in sales_data:
item_details_map.setdefault(d.item_name, d)
return item_details_map
def get_items(filters):
filters_dict = {
"disabled": 0,
"is_stock_item": 1
}
if filters.get("item_group"):
filters_dict.update({
"item_group": filters["item_group"]
})
if filters.get("item"):
filters_dict.update({
"name": filters["item"]
})
items = frappe.get_all("Item", fields=["name", "item_group", "item_name"], filters=filters_dict, order_by="name")
return items

View File

@@ -137,7 +137,7 @@ def get_appropriate_company(filters):
return company
@frappe.whitelist()
def get_invoiced_item_gross_margin(sales_invoice=None, item_code=None, company=None):
def get_invoiced_item_gross_margin(sales_invoice=None, item_code=None, company=None, with_item_data=False):
from erpnext.accounts.report.gross_profit.gross_profit import GrossProfitGenerator
sales_invoice = sales_invoice or frappe.form_dict.get('sales_invoice')
@@ -152,5 +152,8 @@ def get_invoiced_item_gross_margin(sales_invoice=None, item_code=None, company=N
}
gross_profit_data = GrossProfitGenerator(filters)
result = gross_profit_data.grouped_data
if not with_item_data:
result = sum([d.gross_profit for d in result])
return gross_profit_data.grouped_data
return result

View File

@@ -296,6 +296,12 @@ frappe.ui.form.on('Asset', {
frm.toggle_reqd("finance_books", frm.doc.calculate_depreciation);
},
gross_purchase_amount: function(frm) {
frm.doc.finance_books.forEach(d => {
frm.events.set_depreciation_rate(frm, d);
})
},
set_depreciation_rate: function(frm, row) {
if (row.total_number_of_depreciations && row.frequency_of_depreciation) {
frappe.call({

View File

@@ -101,7 +101,7 @@ class Asset(AccountsController):
def set_depreciation_rate(self):
for d in self.get("finance_books"):
d.rate_of_depreciation = self.get_depreciation_rate(d)
d.rate_of_depreciation = self.get_depreciation_rate(d, on_validate=True)
def make_depreciation_schedule(self):
depreciation_method = [d.depreciation_method for d in self.finance_books]
@@ -125,7 +125,7 @@ class Asset(AccountsController):
no_of_depreciations * cint(d.frequency_of_depreciation))
total_days = date_diff(end_date, self.available_for_use_date)
rate_per_day = value_after_depreciation / total_days
rate_per_day = (value_after_depreciation - d.get("expected_value_after_useful_life")) / total_days
number_of_pending_depreciations = cint(d.total_number_of_depreciations) - \
cint(self.number_of_depreciations_booked)
@@ -291,8 +291,8 @@ class Asset(AccountsController):
def validate_expected_value_after_useful_life(self):
for row in self.get('finance_books'):
accumulated_depreciation_after_full_schedule = \
max([d.accumulated_depreciation_amount for d in self.get("schedules") if d.finance_book_id == row.idx])
accumulated_depreciation_after_full_schedule = max([d.accumulated_depreciation_amount
for d in self.get("schedules") if cint(d.finance_book_id) == row.idx])
asset_value_after_full_schedule = flt(flt(self.gross_purchase_amount) -
flt(accumulated_depreciation_after_full_schedule),
@@ -403,7 +403,7 @@ class Asset(AccountsController):
make_gl_entries(gl_entries)
self.db_set('booked_fixed_asset', 1)
def get_depreciation_rate(self, args):
def get_depreciation_rate(self, args, on_validate=False):
if isinstance(args, string_types):
args = json.loads(args)
@@ -420,7 +420,10 @@ class Asset(AccountsController):
if args.get("depreciation_method") == 'Double Declining Balance':
return 200.0 / args.get("total_number_of_depreciations")
if args.get("depreciation_method") == "Written Down Value" and not args.get("rate_of_depreciation"):
if args.get("depreciation_method") == "Written Down Value":
if args.get("rate_of_depreciation") and on_validate:
return args.get("rate_of_depreciation")
no_of_years = flt(args.get("total_number_of_depreciations") * flt(args.get("frequency_of_depreciation"))) / 12
value = flt(args.get("expected_value_after_useful_life")) / flt(self.gross_purchase_amount)

View File

@@ -102,9 +102,9 @@ class TestAsset(unittest.TestCase):
asset.save()
self.assertEqual(asset.status, "Draft")
expected_schedules = [
["2020-06-06", 163.93, 163.93],
["2021-04-06", 49836.07, 50000.0],
["2022-02-06", 40000.0, 90000.00]
["2020-06-06", 147.54, 147.54],
["2021-04-06", 44852.46, 45000.0],
["2022-02-06", 45000.0, 90000.00]
]
schedules = [[cstr(d.schedule_date), d.depreciation_amount, d.accumulated_depreciation_amount]
@@ -130,8 +130,8 @@ class TestAsset(unittest.TestCase):
self.assertEqual(asset.status, "Draft")
asset.save()
expected_schedules = [
["2020-06-06", 197.37, 40197.37],
["2021-04-06", 49802.63, 90000.00]
["2020-06-06", 164.47, 40164.47],
["2021-04-06", 49835.53, 90000.00]
]
schedules = [[cstr(d.schedule_date), flt(d.depreciation_amount, 2), d.accumulated_depreciation_amount]
for d in asset.get("schedules")]
@@ -266,8 +266,8 @@ class TestAsset(unittest.TestCase):
self.assertEqual(asset.get("schedules")[0].journal_entry[:4], "DEPR")
expected_gle = (
("_Test Accumulated Depreciations - _TC", 0.0, 35699.15),
("_Test Depreciations - _TC", 35699.15, 0.0)
("_Test Accumulated Depreciations - _TC", 0.0, 32129.24),
("_Test Depreciations - _TC", 32129.24, 0.0)
)
gle = frappe.db.sql("""select account, debit, credit from `tabGL Entry`

View File

@@ -88,7 +88,8 @@ class AccountsController(TransactionBase):
self.validate_paid_amount()
if self.doctype in ['Purchase Invoice', 'Sales Invoice']:
if cint(self.allocate_advances_automatically) and not cint(self.is_pos):
pos_check_field = "is_pos" if self.doctype=="Sales Invoice" else "is_paid"
if cint(self.allocate_advances_automatically) and not cint(self.get(pos_check_field)):
self.set_advances()
if self.is_return:

View File

@@ -205,11 +205,14 @@ def get_already_returned_items(doc):
def make_return_doc(doctype, source_name, target_doc=None):
from frappe.model.mapper import get_mapped_doc
company = frappe.db.get_value("Delivery Note", source_name, "company")
default_warehouse_for_sales_return = frappe.db.get_value("Company", company, "default_warehouse_for_sales_return")
def set_missing_values(source, target):
doc = frappe.get_doc(target)
doc.is_return = 1
doc.return_against = source.name
doc.ignore_pricing_rule = 1
doc.set_warehouse = ""
if doctype == "Sales Invoice":
doc.is_pos = source.is_pos
@@ -277,12 +280,16 @@ def make_return_doc(doctype, source_name, target_doc=None):
target_doc.so_detail = source_doc.so_detail
target_doc.si_detail = source_doc.si_detail
target_doc.expense_account = source_doc.expense_account
if default_warehouse_for_sales_return:
target_doc.warehouse = default_warehouse_for_sales_return
elif doctype == "Sales Invoice":
target_doc.sales_order = source_doc.sales_order
target_doc.delivery_note = source_doc.delivery_note
target_doc.so_detail = source_doc.so_detail
target_doc.dn_detail = source_doc.dn_detail
target_doc.expense_account = source_doc.expense_account
if default_warehouse_for_sales_return:
target_doc.warehouse = default_warehouse_for_sales_return
def update_terms(source_doc, target_doc, source_parent):
target_doc.payment_amount = -source_doc.payment_amount

View File

@@ -95,6 +95,10 @@ status_map = {
["Ordered", "eval:self.status != 'Stopped' and self.per_ordered == 100 and self.docstatus == 1 and self.material_request_type == 'Purchase'"],
["Transferred", "eval:self.status != 'Stopped' and self.per_ordered == 100 and self.docstatus == 1 and self.material_request_type == 'Material Transfer'"],
["Issued", "eval:self.status != 'Stopped' and self.per_ordered == 100 and self.docstatus == 1 and self.material_request_type == 'Material Issue'"]
],
"Bank Transaction": [
["Unreconciled", "eval:self.docstatus == 1 and self.unallocated_amount>0"],
["Reconciled", "eval:self.docstatus == 1 and self.unallocated_amount<=0"]
]
}

View File

@@ -351,10 +351,12 @@ class StockController(AccountsController):
frappe.throw(_("Row {0}: Quality Inspection rejected for item {1}")
.format(d.idx, d.item_code), QualityInspectionRejectedError)
elif qa_required :
frappe.msgprint(_("Quality Inspection required for Item {0}").format(d.item_code))
if self.docstatus==1:
raise QualityInspectionRequiredError
action = frappe.get_doc('Stock Settings').action_if_quality_inspection_is_not_submitted
if self.docstatus==1 and action == 'Stop':
frappe.throw(_("Quality Inspection required for Item {0} to submit").format(frappe.bold(d.item_code)),
exc=QualityInspectionRequiredError)
else:
frappe.msgprint(_("Create Quality Inspection for Item {0}").format(frappe.bold(d.item_code)))
def update_blanket_order(self):
blanket_orders = list(set([d.blanket_order for d in self.items if d.blanket_order]))

View File

@@ -608,7 +608,7 @@ def get_itemised_tax_breakup_data(doc):
return itemised_tax, itemised_taxable_amount
def get_itemised_tax(taxes):
def get_itemised_tax(taxes, with_tax_account=False):
itemised_tax = {}
for tax in taxes:
if getattr(tax, "category", None) and tax.category=="Valuation":
@@ -633,6 +633,9 @@ def get_itemised_tax(taxes):
tax_amount = tax_amount
))
if with_tax_account:
itemised_tax[item_code][tax.description].tax_account = tax.account_head
return itemised_tax
def get_itemised_taxable_amount(items):

View File

@@ -90,11 +90,11 @@ class Lead(SellingController):
return frappe.db.get_value("Customer", {"lead_name": self.name})
def has_opportunity(self):
return frappe.db.get_value("Opportunity", {"lead": self.name, "status": ["!=", "Lost"]})
return frappe.db.get_value("Opportunity", {"party_name": self.name, "status": ["!=", "Lost"]})
def has_quotation(self):
return frappe.db.get_value("Quotation", {
"lead": self.name,
"party_name": self.name,
"docstatus": 1,
"status": ["!=", "Lost"]

View File

@@ -4,6 +4,10 @@ from frappe import _
def get_data():
return {
'fieldname': 'lead',
'non_standard_fieldnames': {
'Quotation': 'party_name',
'Opportunity': 'party_name'
},
'transactions': [
{
'items': ['Opportunity', 'Quotation']

View File

@@ -9,15 +9,22 @@ frappe.ui.form.on("Opportunity", {
frm.custom_make_buttons = {
'Quotation': 'Quotation',
'Supplier Quotation': 'Supplier Quotation'
}
},
customer: function(frm) {
frm.trigger('set_contact_link');
erpnext.utils.get_party_details(frm);
},
frm.set_query("opportunity_from", function() {
return{
"filters": {
"name": ["in", ["Customer", "Lead"]],
}
}
});
},
lead: function(frm) {
frm.trigger('set_contact_link');
party_name: function(frm) {
if (frm.doc.opportunity_from == "Customer") {
frm.trigger('set_contact_link');
erpnext.utils.get_party_details(frm);
}
},
with_items: function(frm) {
@@ -30,15 +37,14 @@ frappe.ui.form.on("Opportunity", {
contact_person: erpnext.utils.get_contact_details,
enquiry_from: function(frm) {
frm.toggle_reqd("lead", frm.doc.enquiry_from==="Lead");
frm.toggle_reqd("customer", frm.doc.enquiry_from==="Customer");
opportunity_from: function(frm) {
frm.toggle_reqd("party_name", frm.doc.opportunity_from);
frm.trigger("set_dynamic_field_label");
},
refresh: function(frm) {
var doc = frm.doc;
frm.events.enquiry_from(frm);
frm.trigger('set_contact_link');
frm.events.opportunity_from(frm);
frm.trigger('toggle_mandatory');
erpnext.toggle_naming_series();
@@ -75,13 +81,20 @@ frappe.ui.form.on("Opportunity", {
},
set_contact_link: function(frm) {
if(frm.doc.customer) {
if(frm.doc.opportunity_from == "Customer" && frm.doc.party_name) {
frappe.dynamic_link = {doc: frm.doc, fieldname: 'customer', doctype: 'Customer'}
} else if(frm.doc.lead) {
} else if(frm.doc.opportunity_from == "Lead" && frm.doc.party_name) {
frappe.dynamic_link = {doc: frm.doc, fieldname: 'lead', doctype: 'Lead'}
}
},
set_dynamic_field_label: function(frm){
if (frm.doc.opportunity_from) {
frm.set_df_property("party_name", "label", frm.doc.opportunity_from);
}
},
make_supplier_quotation: function(frm) {
frappe.model.open_mapped_doc({
method: "erpnext.crm.doctype.opportunity.opportunity.make_supplier_quotation",
@@ -97,10 +110,6 @@ frappe.ui.form.on("Opportunity", {
// TODO commonify this code
erpnext.crm.Opportunity = frappe.ui.form.Controller.extend({
onload: function() {
if(!this.frm.doc.enquiry_from && this.frm.doc.customer)
this.frm.doc.enquiry_from = "Customer";
if(!this.frm.doc.enquiry_from && this.frm.doc.lead)
this.frm.doc.enquiry_from = "Lead";
if(!this.frm.doc.status)
set_multiple(this.frm.doc.doctype, this.frm.doc.name, { status:'Open' });
@@ -148,7 +157,7 @@ erpnext.crm.Opportunity = frappe.ui.form.Controller.extend({
$.extend(cur_frm.cscript, new erpnext.crm.Opportunity({frm: cur_frm}));
cur_frm.cscript.onload_post_render = function(doc, cdt, cdn) {
if(doc.enquiry_from == 'Lead' && doc.lead)
if(doc.opportunity_from == 'Lead' && doc.party_name)
cur_frm.cscript.lead(doc, cdt, cdn);
}
@@ -171,10 +180,10 @@ cur_frm.cscript.item_code = function(doc, cdt, cdn) {
}
cur_frm.cscript.lead = function(doc, cdt, cdn) {
cur_frm.toggle_display("contact_info", doc.customer || doc.lead);
cur_frm.toggle_display("contact_info", doc.party_name);
erpnext.utils.map_current_doc({
method: "erpnext.crm.doctype.lead.lead.make_opportunity",
source_name: cur_frm.doc.lead,
source_name: cur_frm.doc.party_name,
frm: cur_frm
});
}

View File

@@ -21,6 +21,7 @@
"bold": 0,
"collapsible": 0,
"columns": 0,
"fetch_if_empty": 0,
"fieldname": "from_section",
"fieldtype": "Section Break",
"hidden": 0,
@@ -54,6 +55,7 @@
"collapsible": 0,
"columns": 0,
"default": "",
"fetch_if_empty": 0,
"fieldname": "naming_series",
"fieldtype": "Select",
"hidden": 0,
@@ -88,8 +90,9 @@
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "enquiry_from",
"fieldtype": "Select",
"fetch_if_empty": 0,
"fieldname": "opportunity_from",
"fieldtype": "Link",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
@@ -102,7 +105,7 @@
"no_copy": 0,
"oldfieldname": "enquiry_from",
"oldfieldtype": "Select",
"options": "\nLead\nCustomer",
"options": "DocType",
"permlevel": 0,
"print_hide": 1,
"print_hide_if_no_value": 0,
@@ -122,9 +125,10 @@
"bold": 1,
"collapsible": 0,
"columns": 0,
"depends_on": "eval:doc.enquiry_from===\"Customer\"",
"fieldname": "customer",
"fieldtype": "Link",
"depends_on": "",
"fetch_if_empty": 0,
"fieldname": "party_name",
"fieldtype": "Dynamic Link",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
@@ -132,54 +136,19 @@
"in_global_search": 0,
"in_list_view": 0,
"in_standard_filter": 1,
"label": "Customer",
"label": "Customer/Lead",
"length": 0,
"no_copy": 0,
"oldfieldname": "customer",
"oldfieldtype": "Link",
"options": "Customer",
"options": "opportunity_from",
"permlevel": 0,
"print_hide": 1,
"print_hide_if_no_value": 0,
"read_only": 0,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"translatable": 0,
"unique": 0
},
{
"allow_bulk_edit": 0,
"allow_in_quick_entry": 0,
"allow_on_submit": 0,
"bold": 1,
"collapsible": 0,
"columns": 0,
"depends_on": "eval:doc.enquiry_from===\"Lead\"",
"fieldname": "lead",
"fieldtype": "Link",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 0,
"in_standard_filter": 1,
"label": "Lead",
"length": 0,
"no_copy": 0,
"oldfieldname": "lead",
"oldfieldtype": "Link",
"options": "Lead",
"permlevel": 0,
"print_hide": 1,
"print_hide_if_no_value": 0,
"read_only": 0,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 0,
"reqd": 1,
"search_index": 0,
"set_only_once": 0,
"translatable": 0,
@@ -193,6 +162,8 @@
"collapsible": 0,
"columns": 0,
"depends_on": "",
"fetch_from": "",
"fetch_if_empty": 0,
"fieldname": "customer_name",
"fieldtype": "Data",
"hidden": 0,
@@ -224,6 +195,7 @@
"bold": 0,
"collapsible": 0,
"columns": 0,
"fetch_if_empty": 0,
"fieldname": "column_break0",
"fieldtype": "Column Break",
"hidden": 0,
@@ -256,6 +228,7 @@
"bold": 0,
"collapsible": 0,
"columns": 0,
"fetch_if_empty": 0,
"fieldname": "title",
"fieldtype": "Data",
"hidden": 1,
@@ -289,6 +262,7 @@
"collapsible": 0,
"columns": 0,
"default": "Sales",
"fetch_if_empty": 0,
"fieldname": "opportunity_type",
"fieldtype": "Link",
"hidden": 0,
@@ -324,6 +298,7 @@
"collapsible": 0,
"columns": 0,
"default": "Open",
"fetch_if_empty": 0,
"fieldname": "status",
"fieldtype": "Select",
"hidden": 0,
@@ -359,6 +334,7 @@
"collapsible": 0,
"columns": 0,
"depends_on": "eval:doc.status===\"Lost\"",
"fetch_if_empty": 0,
"fieldname": "order_lost_reason",
"fieldtype": "Small Text",
"hidden": 0,
@@ -390,6 +366,7 @@
"bold": 1,
"collapsible": 0,
"columns": 0,
"fetch_if_empty": 0,
"fieldname": "mins_to_first_response",
"fieldtype": "Float",
"hidden": 0,
@@ -423,6 +400,7 @@
"collapsible": 1,
"collapsible_depends_on": "contact_by",
"columns": 0,
"fetch_if_empty": 0,
"fieldname": "next_contact",
"fieldtype": "Section Break",
"hidden": 0,
@@ -456,6 +434,7 @@
"collapsible": 0,
"columns": 0,
"description": "",
"fetch_if_empty": 0,
"fieldname": "contact_by",
"fieldtype": "Link",
"hidden": 0,
@@ -492,6 +471,7 @@
"collapsible": 0,
"columns": 0,
"description": "",
"fetch_if_empty": 0,
"fieldname": "contact_date",
"fieldtype": "Datetime",
"hidden": 0,
@@ -525,6 +505,7 @@
"bold": 0,
"collapsible": 0,
"columns": 0,
"fetch_if_empty": 0,
"fieldname": "column_break2",
"fieldtype": "Column Break",
"hidden": 0,
@@ -557,6 +538,7 @@
"bold": 0,
"collapsible": 0,
"columns": 0,
"fetch_if_empty": 0,
"fieldname": "to_discuss",
"fieldtype": "Small Text",
"hidden": 0,
@@ -590,6 +572,7 @@
"bold": 0,
"collapsible": 0,
"columns": 0,
"fetch_if_empty": 0,
"fieldname": "section_break_14",
"fieldtype": "Section Break",
"hidden": 0,
@@ -622,6 +605,7 @@
"bold": 0,
"collapsible": 0,
"columns": 0,
"fetch_if_empty": 0,
"fieldname": "currency",
"fieldtype": "Link",
"hidden": 0,
@@ -655,6 +639,7 @@
"bold": 0,
"collapsible": 0,
"columns": 0,
"fetch_if_empty": 0,
"fieldname": "opportunity_amount",
"fieldtype": "Currency",
"hidden": 0,
@@ -687,6 +672,7 @@
"bold": 0,
"collapsible": 0,
"columns": 0,
"fetch_if_empty": 0,
"fieldname": "with_items",
"fieldtype": "Check",
"hidden": 0,
@@ -719,6 +705,7 @@
"bold": 0,
"collapsible": 0,
"columns": 0,
"fetch_if_empty": 0,
"fieldname": "column_break_17",
"fieldtype": "Column Break",
"hidden": 0,
@@ -751,6 +738,7 @@
"collapsible": 0,
"columns": 0,
"default": "Prospecting",
"fetch_if_empty": 0,
"fieldname": "sales_stage",
"fieldtype": "Link",
"hidden": 0,
@@ -785,6 +773,7 @@
"collapsible": 0,
"columns": 0,
"default": "100",
"fetch_if_empty": 0,
"fieldname": "probability",
"fieldtype": "Percent",
"hidden": 0,
@@ -818,6 +807,7 @@
"collapsible": 0,
"columns": 0,
"depends_on": "with_items",
"fetch_if_empty": 0,
"fieldname": "items_section",
"fieldtype": "Section Break",
"hidden": 0,
@@ -852,6 +842,7 @@
"collapsible": 0,
"columns": 0,
"description": "",
"fetch_if_empty": 0,
"fieldname": "items",
"fieldtype": "Table",
"hidden": 0,
@@ -888,6 +879,7 @@
"collapsible_depends_on": "next_contact_by",
"columns": 0,
"depends_on": "eval:doc.lead || doc.customer",
"fetch_if_empty": 0,
"fieldname": "contact_info",
"fieldtype": "Section Break",
"hidden": 0,
@@ -921,6 +913,7 @@
"collapsible": 0,
"columns": 0,
"depends_on": "eval:doc.customer || doc.lead",
"fetch_if_empty": 0,
"fieldname": "customer_address",
"fieldtype": "Link",
"hidden": 0,
@@ -953,6 +946,7 @@
"bold": 0,
"collapsible": 0,
"columns": 0,
"fetch_if_empty": 0,
"fieldname": "address_display",
"fieldtype": "Small Text",
"hidden": 1,
@@ -988,6 +982,7 @@
"columns": 0,
"depends_on": "customer",
"description": "",
"fetch_if_empty": 0,
"fieldname": "territory",
"fieldtype": "Link",
"hidden": 0,
@@ -1022,6 +1017,7 @@
"columns": 0,
"depends_on": "customer",
"description": "",
"fetch_if_empty": 0,
"fieldname": "customer_group",
"fieldtype": "Link",
"hidden": 0,
@@ -1056,6 +1052,7 @@
"bold": 0,
"collapsible": 0,
"columns": 0,
"fetch_if_empty": 0,
"fieldname": "column_break3",
"fieldtype": "Column Break",
"hidden": 0,
@@ -1087,6 +1084,7 @@
"collapsible": 0,
"columns": 0,
"depends_on": "eval:doc.lead || doc.customer",
"fetch_if_empty": 0,
"fieldname": "contact_person",
"fieldtype": "Link",
"hidden": 0,
@@ -1120,6 +1118,7 @@
"collapsible": 0,
"columns": 0,
"depends_on": "customer",
"fetch_if_empty": 0,
"fieldname": "contact_display",
"fieldtype": "Small Text",
"hidden": 0,
@@ -1152,6 +1151,7 @@
"collapsible": 0,
"columns": 0,
"depends_on": "eval:doc.lead || doc.customer",
"fetch_if_empty": 0,
"fieldname": "contact_email",
"fieldtype": "Data",
"hidden": 0,
@@ -1184,6 +1184,7 @@
"collapsible": 0,
"columns": 0,
"depends_on": "eval:doc.lead || doc.customer",
"fetch_if_empty": 0,
"fieldname": "contact_mobile",
"fieldtype": "Small Text",
"hidden": 0,
@@ -1216,6 +1217,7 @@
"collapsible": 1,
"collapsible_depends_on": "",
"columns": 0,
"fetch_if_empty": 0,
"fieldname": "more_info",
"fieldtype": "Section Break",
"hidden": 0,
@@ -1249,6 +1251,7 @@
"bold": 0,
"collapsible": 0,
"columns": 0,
"fetch_if_empty": 0,
"fieldname": "source",
"fieldtype": "Link",
"hidden": 0,
@@ -1285,6 +1288,7 @@
"columns": 0,
"depends_on": "eval: doc.source==\"Campaign\"",
"description": "Enter name of campaign if source of enquiry is campaign",
"fetch_if_empty": 0,
"fieldname": "campaign",
"fieldtype": "Link",
"hidden": 0,
@@ -1319,6 +1323,7 @@
"bold": 0,
"collapsible": 0,
"columns": 0,
"fetch_if_empty": 0,
"fieldname": "column_break1",
"fieldtype": "Column Break",
"hidden": 0,
@@ -1351,6 +1356,7 @@
"bold": 0,
"collapsible": 0,
"columns": 0,
"fetch_if_empty": 0,
"fieldname": "company",
"fieldtype": "Link",
"hidden": 0,
@@ -1386,6 +1392,7 @@
"collapsible": 0,
"columns": 0,
"default": "Today",
"fetch_if_empty": 0,
"fieldname": "transaction_date",
"fieldtype": "Date",
"hidden": 0,
@@ -1420,6 +1427,7 @@
"bold": 0,
"collapsible": 0,
"columns": 0,
"fetch_if_empty": 0,
"fieldname": "amended_from",
"fieldtype": "Link",
"hidden": 0,
@@ -1460,7 +1468,7 @@
"issingle": 0,
"istable": 0,
"max_attachments": 0,
"modified": "2018-10-01 09:28:43.990999",
"modified": "2019-04-25 18:55:43.874656",
"modified_by": "Administrator",
"module": "CRM",
"name": "Opportunity",
@@ -1508,11 +1516,11 @@
"quick_entry": 0,
"read_only": 0,
"read_only_onload": 0,
"search_fields": "status,transaction_date,customer,lead,opportunity_type,territory,company",
"search_fields": "status,transaction_date,party_name,opportunity_type,territory,company",
"show_name_in_global_search": 1,
"sort_field": "modified",
"sort_order": "DESC",
"timeline_field": "customer",
"timeline_field": "party_name",
"title_field": "title",
"track_changes": 0,
"track_seen": 1,

View File

@@ -16,8 +16,8 @@ sender_field = "contact_email"
class Opportunity(TransactionBase):
def after_insert(self):
if self.lead:
frappe.get_doc("Lead", self.lead).set_status(update=True)
if self.opportunity_from == "Lead":
frappe.get_doc("Lead", self.party_name).set_status(update=True)
def validate(self):
self._prev = frappe._dict({
@@ -29,12 +29,8 @@ class Opportunity(TransactionBase):
self.make_new_lead_if_required()
if not self.enquiry_from:
frappe.throw(_("Opportunity From field is mandatory"))
self.validate_item_details()
self.validate_uom_is_integer("uom", "qty")
self.validate_lead_cust()
self.validate_cust_name()
if not self.title:
@@ -45,7 +41,7 @@ class Opportunity(TransactionBase):
def make_new_lead_if_required(self):
"""Set lead against new opportunity"""
if not (self.lead or self.customer) and self.contact_email:
if (not self.get("party_name")) and self.contact_email:
# check if customer is already created agains the self.contact_email
customer = frappe.db.sql("""select
distinct `tabDynamic Link`.link_name as customer
@@ -61,8 +57,8 @@ class Opportunity(TransactionBase):
`tabDynamic Link`.link_doctype='Customer'
""".format(self.contact_email), as_dict=True)
if customer and customer[0].customer:
self.customer = customer[0].customer
self.enquiry_from = "Customer"
self.party_name = customer[0].customer
self.opportunity_from = "Customer"
return
lead_name = frappe.db.get_value("Lead", {"email_id": self.contact_email})
@@ -89,8 +85,8 @@ class Opportunity(TransactionBase):
lead.insert(ignore_permissions=True)
lead_name = lead.name
self.enquiry_from = "Lead"
self.lead = lead_name
self.opportunity_from = "Lead"
self.party_name = lead_name
def declare_enquiry_lost(self,arg):
if not self.has_active_quotation():
@@ -137,10 +133,10 @@ class Opportunity(TransactionBase):
return True
def validate_cust_name(self):
if self.customer:
self.customer_name = frappe.db.get_value("Customer", self.customer, "customer_name")
elif self.lead:
lead_name, company_name = frappe.db.get_value("Lead", self.lead, ["lead_name", "company_name"])
if self.party_name and self.opportunity_from == 'Customer':
self.customer_name = frappe.db.get_value("Customer", self.party_name, "customer_name")
elif self.party_name and self.opportunity_from == 'Lead':
lead_name, company_name = frappe.db.get_value("Lead", self.party_name, ["lead_name", "company_name"])
self.customer_name = company_name or lead_name
def on_update(self):
@@ -153,16 +149,16 @@ class Opportunity(TransactionBase):
opts.description = ""
opts.contact_date = self.contact_date
if self.customer:
if self.party_name and self.opportunity_from == 'Customer':
if self.contact_person:
opts.description = 'Contact '+cstr(self.contact_person)
else:
opts.description = 'Contact customer '+cstr(self.customer)
elif self.lead:
opts.description = 'Contact customer '+cstr(self.party_name)
elif self.party_name and self.opportunity_from == 'Lead':
if self.contact_display:
opts.description = 'Contact '+cstr(self.contact_display)
else:
opts.description = 'Contact lead '+cstr(self.lead)
opts.description = 'Contact lead '+cstr(self.party_name)
opts.subject = opts.description
opts.description += '. By : ' + cstr(self.contact_by)
@@ -187,17 +183,6 @@ class Opportunity(TransactionBase):
for key in item_fields:
if not d.get(key): d.set(key, item.get(key))
def validate_lead_cust(self):
if self.enquiry_from == 'Lead':
if not self.lead:
frappe.throw(_("Lead must be set if Opportunity is made from Lead"))
else:
self.customer = None
elif self.enquiry_from == 'Customer':
if not self.customer:
msgprint(_("Customer is mandatory if 'Opportunity From' is selected as Customer"), raise_exception=1)
else:
self.lead = None
@frappe.whitelist()
def get_item_details(item_code):
@@ -219,8 +204,11 @@ def make_quotation(source_name, target_doc=None):
quotation = frappe.get_doc(target)
company_currency = frappe.get_cached_value('Company', quotation.company, "default_currency")
party_account_currency = get_party_account_currency("Customer", quotation.customer,
quotation.company) if quotation.customer else company_currency
if quotation.quotation_to == 'Customer' and quotation.party_name:
party_account_currency = get_party_account_currency("Customer", quotation.party_name, quotation.company)
else:
party_account_currency = company_currency
quotation.currency = party_account_currency or company_currency
@@ -246,7 +234,7 @@ def make_quotation(source_name, target_doc=None):
"Opportunity": {
"doctype": "Quotation",
"field_map": {
"enquiry_from": "quotation_to",
"opportunity_from": "quotation_to",
"opportunity_type": "order_type",
"name": "enq_no",
}

View File

@@ -1,5 +1,5 @@
frappe.listview_settings['Opportunity'] = {
add_fields: ["customer_name", "opportunity_type", "enquiry_from", "status"],
add_fields: ["customer_name", "opportunity_type", "opportunity_from", "status"],
get_indicator: function(doc) {
var indicator = [__(doc.status), frappe.utils.guess_colour(doc.status), "status,=," + doc.status];
if(doc.status=="Quotation") {

View File

@@ -6,7 +6,7 @@ QUnit.test("test: opportunity", function (assert) {
() => frappe.timeout(1),
() => frappe.click_button('New'),
() => frappe.timeout(1),
() => cur_frm.set_value('enquiry_from', 'Customer'),
() => cur_frm.set_value('opportunity_from', 'Customer'),
() => cur_frm.set_value('customer', 'Test Customer 1'),
// check items

View File

@@ -37,13 +37,13 @@ class TestOpportunity(unittest.TestCase):
# new lead should be created against the new.opportunity@example.com
opp_doc = frappe.get_doc(args).insert(ignore_permissions=True)
self.assertTrue(opp_doc.lead)
self.assertEqual(opp_doc.enquiry_from, "Lead")
self.assertEqual(frappe.db.get_value("Lead", opp_doc.lead, "email_id"),
self.assertTrue(opp_doc.party_name)
self.assertEqual(opp_doc.opportunity_from, "Lead")
self.assertEqual(frappe.db.get_value("Lead", opp_doc.party_name, "email_id"),
'new.opportunity@example.com')
# create new customer and create new contact against 'new.opportunity@example.com'
customer = make_customer(opp_doc.lead).insert(ignore_permissions=True)
customer = make_customer(opp_doc.party_name).insert(ignore_permissions=True)
frappe.get_doc({
"doctype": "Contact",
"email_id": "new.opportunity@example.com",
@@ -55,9 +55,9 @@ class TestOpportunity(unittest.TestCase):
}).insert(ignore_permissions=True)
opp_doc = frappe.get_doc(args).insert(ignore_permissions=True)
self.assertTrue(opp_doc.customer)
self.assertEqual(opp_doc.enquiry_from, "Customer")
self.assertEqual(opp_doc.customer, customer.name)
self.assertTrue(opp_doc.party_name)
self.assertEqual(opp_doc.opportunity_from, "Customer")
self.assertEqual(opp_doc.party_name, customer.name)
def make_opportunity(**args):
args = frappe._dict(args)
@@ -65,17 +65,17 @@ def make_opportunity(**args):
opp_doc = frappe.get_doc({
"doctype": "Opportunity",
"company": args.company or "_Test Company",
"enquiry_from": args.enquiry_from or "Customer",
"opportunity_from": args.opportunity_from or "Customer",
"opportunity_type": "Sales",
"with_items": args.with_items or 0,
"transaction_date": today()
})
if opp_doc.enquiry_from == 'Customer':
opp_doc.customer = args.customer or "_Test Customer"
if opp_doc.opportunity_from == 'Customer':
opp_doc.party_name= args.customer or "_Test Customer"
if opp_doc.enquiry_from == 'Lead':
opp_doc.customer = args.lead or "_T-Lead-00001"
if opp_doc.opportunity_from == 'Lead':
opp_doc.party_name = args.lead or "_T-Lead-00001"
if args.with_items:
opp_doc.append('items', {

View File

@@ -2,9 +2,9 @@
{
"doctype": "Opportunity",
"name": "_Test Opportunity 1",
"enquiry_from": "Lead",
"opportunity_from": "Lead",
"enquiry_type": "Sales",
"lead": "_T-Lead-00001",
"party_name": "_T-Lead-00001",
"transaction_date": "2013-12-12",
"items": [{
"item_name": "Test Item",

View File

@@ -66,7 +66,7 @@ def get_columns():
def get_communication_details(filters):
communication_count = None
communication_list = []
opportunities = frappe.db.get_values('Opportunity', {'enquiry_from': 'Lead'},\
opportunities = frappe.db.get_values('Opportunity', {'opportunity_from': 'Lead'},\
['name', 'customer_name', 'lead', 'contact_email'], as_dict=1)
for d in opportunities:

View File

@@ -56,7 +56,7 @@ def work(domain="Manufacturing"):
def make_opportunity(domain):
b = frappe.get_doc({
"doctype": "Opportunity",
"enquiry_from": "Customer",
"opportunity_from": "Customer",
"customer": get_random("Customer"),
"opportunity_type": "Sales",
"with_items": 1,

View File

@@ -186,7 +186,7 @@ def link_item(item_data,item_status):
item.item_name = str(item_data.get("name"))
item.item_code = "woocommerce - " + str(item_data.get("product_id"))
item.woocommerce_id = str(item_data.get("product_id"))
item.item_group = "WooCommerce Products"
item.item_group = _("WooCommerce Products")
item.stock_uom = woocommerce_settings.uom or _("Nos")
item.save()
frappe.db.commit()

View File

@@ -22,6 +22,9 @@ erpnext.integrations.plaidLink = class plaidLink {
frappe.xcall('erpnext.erpnext_integrations.doctype.plaid_settings.plaid_settings.plaid_configuration')
.then(result => {
if (result !== "disabled") {
if (result.plaid_env == undefined || result.plaid_public_key == undefined) {
frappe.throw(__("Please add valid Plaid api keys in site_config.json first"));
}
me.plaid_env = result.plaid_env;
me.plaid_public_key = result.plaid_public_key;
me.client_name = result.client_name;

View File

@@ -4,8 +4,8 @@
from __future__ import unicode_literals
import frappe
from frappe import _
from frappe.utils.nestedset import get_root_of
from frappe.model.document import Document
from six.moves.urllib.parse import urlparse
@@ -62,10 +62,10 @@ class WoocommerceSettings(Document):
custom.read_only = 1
custom.save()
if not frappe.get_value("Item Group",{"name": "WooCommerce Products"}):
if not frappe.get_value("Item Group",{"name": _("WooCommerce Products")}):
item_group = frappe.new_doc("Item Group")
item_group.item_group_name = "WooCommerce Products"
item_group.parent_item_group = _("All Item Groups")
item_group.item_group_name = _("WooCommerce Products")
item_group.parent_item_group = get_root_of("Item Group")
item_group.save()
@@ -83,7 +83,7 @@ class WoocommerceSettings(Document):
for name in email_names:
frappe.delete_doc("Custom Field",name)
frappe.delete_doc("Item Group","WooCommerce Products")
frappe.delete_doc("Item Group", _("WooCommerce Products"))
frappe.db.commit()

View File

@@ -256,7 +256,7 @@ scheduler_events = {
"daily_long": [
"erpnext.manufacturing.doctype.bom_update_tool.bom_update_tool.update_latest_price_in_all_boms"
],
"monthly": [
"monthly_long": [
"erpnext.accounts.deferred_revenue.convert_deferred_revenue_to_income",
"erpnext.accounts.deferred_revenue.convert_deferred_expense_to_expense",
"erpnext.hr.utils.allocate_earned_leaves"

View File

@@ -78,7 +78,7 @@ class Employee(NestedSet):
def update_user_permissions(self):
if not self.create_user_permission: return
if not has_permission('User Permission', ptype='write'): return
if not has_permission('User Permission', ptype='write', raise_exception=False): return
employee_user_permission_exists = frappe.db.exists('User Permission', {
'allow': 'Employee',
@@ -237,7 +237,7 @@ def validate_employee_role(doc, method):
def update_user_permissions(doc, method):
# called via User hook
if "Employee" in [d.role for d in doc.get("roles")]:
if not has_permission('User Permission', ptype='write'): return
if not has_permission('User Permission', ptype='write', raise_exception=False): return
employee = frappe.get_doc("Employee", {"user_id": doc.name})
employee.update_user_permissions()

View File

@@ -177,9 +177,12 @@ def get_benefit_component_amount(employee, start_date, end_date, struct_row, sal
# Considering there is only one application for a year
benefit_application_name = frappe.db.sql("""
select name from `tabEmployee Benefit Application`
where payroll_period=%(payroll_period)s and employee=%(employee)s
and docstatus = 1
select name
from `tabEmployee Benefit Application`
where
payroll_period=%(payroll_period)s
and employee=%(employee)s
and docstatus = 1
""", {
'employee': employee,
'payroll_period': payroll_period
@@ -209,7 +212,8 @@ def get_benefit_pro_rata_ratio_amount(sal_struct, component_max):
total_pro_rata_max = 0
benefit_amount = 0
for sal_struct_row in sal_struct.get("earnings"):
pay_against_benefit_claim, max_benefit_amount = frappe.db.get_value("Salary Component", sal_struct_row.salary_component, ["pay_against_benefit_claim", "max_benefit_amount"])
pay_against_benefit_claim, max_benefit_amount = frappe.db.get_value("Salary Component",
sal_struct_row.salary_component, ["pay_against_benefit_claim", "max_benefit_amount"])
if sal_struct_row.is_flexible_benefit == 1 and pay_against_benefit_claim != 1:
total_pro_rata_max += max_benefit_amount
if total_pro_rata_max > 0:

View File

@@ -1,5 +1,6 @@
{
"allow_copy": 0,
"allow_events_in_timeline": 0,
"allow_guest_to_view": 0,
"allow_import": 1,
"allow_rename": 1,
@@ -20,6 +21,7 @@
"bold": 0,
"collapsible": 0,
"columns": 0,
"fetch_if_empty": 0,
"fieldname": "max_amount",
"fieldtype": "Currency",
"hidden": 0,
@@ -29,7 +31,7 @@
"in_global_search": 0,
"in_list_view": 1,
"in_standard_filter": 0,
"label": "Max Amount",
"label": "Max Exemption Amount",
"length": 0,
"no_copy": 0,
"permlevel": 0,
@@ -39,7 +41,7 @@
"read_only": 0,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 1,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"translatable": 0,
@@ -52,6 +54,8 @@
"bold": 0,
"collapsible": 0,
"columns": 0,
"default": "1",
"fetch_if_empty": 0,
"fieldname": "is_active",
"fieldtype": "Check",
"hidden": 0,
@@ -88,7 +92,7 @@
"issingle": 0,
"istable": 0,
"max_attachments": 0,
"modified": "2018-06-19 16:33:48.419267",
"modified": "2019-04-25 13:20:31.367158",
"modified_by": "Administrator",
"module": "HR",
"name": "Employee Tax Exemption Category",
@@ -159,6 +163,7 @@
"show_name_in_global_search": 0,
"sort_field": "modified",
"sort_order": "DESC",
"track_changes": 1,
"track_seen": 0
"track_changes": 0,
"track_seen": 0,
"track_views": 0
}

View File

@@ -10,6 +10,7 @@ frappe.ui.form.on('Employee Tax Exemption Declaration', {
}
}
});
frm.set_query('payroll_period', function() {
const fields = {'employee': 'Employee', 'company': 'Company'};
@@ -27,6 +28,7 @@ frappe.ui.form.on('Employee Tax Exemption Declaration', {
}
}
});
frm.set_query('exemption_sub_category', 'declarations', function() {
return {
filters: {
@@ -34,5 +36,16 @@ frappe.ui.form.on('Employee Tax Exemption Declaration', {
}
}
});
},
refresh: function(frm) {
if(frm.doc.docstatus==1) {
frm.add_custom_button(__('Submit Proof'), function() {
frappe.model.open_mapped_doc({
method: "erpnext.hr.doctype.employee_tax_exemption_declaration.employee_tax_exemption_declaration.make_proof_submission",
frm: frm
});
}).addClass("btn-primary");
}
}
});

View File

@@ -55,9 +55,43 @@
"bold": 0,
"collapsible": 0,
"columns": 0,
"fetch_from": "employee.company",
"fetch_from": "employee.employee_name",
"fetch_if_empty": 0,
"fieldname": "company",
"fieldname": "employee_name",
"fieldtype": "Data",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 0,
"in_standard_filter": 0,
"label": "Employee Name",
"length": 0,
"no_copy": 0,
"permlevel": 0,
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 1,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"translatable": 0,
"unique": 0
},
{
"allow_bulk_edit": 0,
"allow_in_quick_entry": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fetch_from": "employee.department",
"fetch_if_empty": 0,
"fieldname": "department",
"fieldtype": "Link",
"hidden": 0,
"ignore_user_permissions": 0,
@@ -66,15 +100,15 @@
"in_global_search": 0,
"in_list_view": 0,
"in_standard_filter": 0,
"label": "Company",
"label": "Department",
"length": 0,
"no_copy": 0,
"options": "Company",
"options": "Department",
"permlevel": 0,
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 0,
"read_only": 1,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 0,
@@ -156,42 +190,9 @@
"bold": 0,
"collapsible": 0,
"columns": 0,
"fetch_from": "employee.company",
"fetch_if_empty": 0,
"fieldname": "total_exemption_amount",
"fieldtype": "Currency",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 0,
"in_standard_filter": 0,
"label": "Total Exemption Amount",
"length": 0,
"no_copy": 0,
"permlevel": 0,
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 1,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"translatable": 0,
"unique": 0
},
{
"allow_bulk_edit": 0,
"allow_in_quick_entry": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fetch_from": "employee.department",
"fetch_if_empty": 0,
"fieldname": "department",
"fieldname": "company",
"fieldtype": "Link",
"hidden": 0,
"ignore_user_permissions": 0,
@@ -200,15 +201,15 @@
"in_global_search": 0,
"in_list_view": 0,
"in_standard_filter": 0,
"label": "Department",
"label": "Company",
"length": 0,
"no_copy": 0,
"options": "Department",
"options": "Company",
"permlevel": 0,
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 1,
"read_only": 0,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 0,
@@ -315,6 +316,136 @@
"set_only_once": 0,
"translatable": 0,
"unique": 0
},
{
"allow_bulk_edit": 0,
"allow_in_quick_entry": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fetch_if_empty": 0,
"fieldname": "section_break_10",
"fieldtype": "Section Break",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 0,
"in_standard_filter": 0,
"length": 0,
"no_copy": 0,
"permlevel": 0,
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 0,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"translatable": 0,
"unique": 0
},
{
"allow_bulk_edit": 0,
"allow_in_quick_entry": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fetch_if_empty": 0,
"fieldname": "total_declared_amount",
"fieldtype": "Currency",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 0,
"in_standard_filter": 0,
"label": "Total Declared Amount",
"length": 0,
"no_copy": 0,
"permlevel": 0,
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 1,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"translatable": 0,
"unique": 0
},
{
"allow_bulk_edit": 0,
"allow_in_quick_entry": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fetch_if_empty": 0,
"fieldname": "column_break_12",
"fieldtype": "Column Break",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 0,
"in_standard_filter": 0,
"length": 0,
"no_copy": 0,
"permlevel": 0,
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 0,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"translatable": 0,
"unique": 0
},
{
"allow_bulk_edit": 0,
"allow_in_quick_entry": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fetch_if_empty": 0,
"fieldname": "total_exemption_amount",
"fieldtype": "Currency",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 0,
"in_standard_filter": 0,
"label": "Total Exemption Amount",
"length": 0,
"no_copy": 0,
"permlevel": 0,
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 1,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"translatable": 0,
"unique": 0
}
],
"has_web_view": 0,
@@ -327,7 +458,7 @@
"issingle": 0,
"istable": 0,
"max_attachments": 0,
"modified": "2019-04-23 15:50:48.693555",
"modified": "2019-04-25 16:38:05.847925",
"modified_by": "Administrator",
"module": "HR",
"name": "Employee Tax Exemption Declaration",

View File

@@ -6,28 +6,61 @@ from __future__ import unicode_literals
import frappe
from frappe.model.document import Document
from frappe import _
from erpnext.hr.utils import validate_tax_declaration, calculate_annual_eligible_hra_exemption
from frappe.utils import flt
from frappe.model.mapper import get_mapped_doc
from erpnext.hr.utils import validate_tax_declaration, get_total_exemption_amount, calculate_annual_eligible_hra_exemption
class DuplicateDeclarationError(frappe.ValidationError): pass
class EmployeeTaxExemptionDeclaration(Document):
def validate(self):
validate_tax_declaration(self.declarations)
self.total_exemption_amount = 0
self.validate_duplicate()
self.set_total_declared_amount()
self.set_total_exemption_amount()
self.calculate_hra_exemption()
for item in self.declarations:
self.total_exemption_amount += item.amount
def before_submit(self):
if frappe.db.exists({"doctype": "Employee Tax Exemption Declaration",
"employee": self.employee,
"payroll_period": self.payroll_period,
"docstatus": 1}):
frappe.throw(_("Tax Declaration of {0} for period {1} already submitted.")\
.format(self.employee, self.payroll_period), frappe.DocstatusTransitionError)
def validate_duplicate(self):
duplicate = frappe.db.get_value("Employee Tax Exemption Declaration",
filters = {
"employee": self.employee,
"payroll_period": self.payroll_period,
"name": ["!=", self.name]
}
)
if duplicate:
frappe.throw(_("Duplicate Tax Declaration of {0} for period {1}")
.format(self.employee, self.payroll_period), DuplicateDeclarationError)
def set_total_declared_amount(self):
self.total_declared_amount = 0.0
for d in self.declarations:
self.total_declared_amount += flt(d.amount)
def set_total_exemption_amount(self):
self.total_exemption_amount = get_total_exemption_amount(self.declarations)
def calculate_hra_exemption(self):
hra_exemption = calculate_annual_eligible_hra_exemption(self)
if hra_exemption:
self.total_exemption_amount += hra_exemption["annual_exemption"]
self.salary_structure_hra = hra_exemption["hra_amount"]
self.annual_hra_exemption = hra_exemption["annual_exemption"]
self.monthly_hra_exemption = hra_exemption["monthly_exemption"]
self.salary_structure_hra, self.annual_hra_exemption, self.monthly_hra_exemption = 0, 0, 0
if self.get("monthly_house_rent"):
hra_exemption = calculate_annual_eligible_hra_exemption(self)
if hra_exemption:
self.total_exemption_amount += hra_exemption["annual_exemption"]
self.salary_structure_hra = hra_exemption["hra_amount"]
self.annual_hra_exemption = hra_exemption["annual_exemption"]
self.monthly_hra_exemption = hra_exemption["monthly_exemption"]
@frappe.whitelist()
def make_proof_submission(source_name, target_doc=None):
doclist = get_mapped_doc("Employee Tax Exemption Declaration", source_name, {
"Employee Tax Exemption Declaration": {
"doctype": "Employee Tax Exemption Proof Submission",
"field_no_map": ["monthly_house_rent", "monthly_hra_exemption"]
},
"Employee Tax Exemption Declaration Category": {
"doctype": "Employee Tax Exemption Proof Submission Detail",
"add_if_empty": True
}
}, target_doc)
return doclist

View File

@@ -6,6 +6,7 @@ from __future__ import unicode_literals
import frappe, erpnext
import unittest
from erpnext.hr.doctype.employee.test_employee import make_employee
from erpnext.hr.doctype.employee_tax_exemption_declaration.employee_tax_exemption_declaration import DuplicateDeclarationError
class TestEmployeeTaxExemptionDeclaration(unittest.TestCase):
def setUp(self):
@@ -15,71 +16,71 @@ class TestEmployeeTaxExemptionDeclaration(unittest.TestCase):
create_exemption_category()
frappe.db.sql("""delete from `tabEmployee Tax Exemption Declaration`""")
def test_exemption_amount_greater_than_category_max(self):
declaration = frappe.get_doc({
"doctype": "Employee Tax Exemption Declaration",
"employee": frappe.get_value("Employee", {"user_id":"employee@taxexepmtion.com"}, "name"),
"payroll_period": "_Test Payroll Period",
"declarations": [dict(exemption_sub_category = "_Test Sub Category",
exemption_category = "_Test Category",
amount = 150000)]
})
self.assertRaises(frappe.ValidationError, declaration.save)
declaration = frappe.get_doc({
"doctype": "Employee Tax Exemption Declaration",
"payroll_period": "_Test Payroll Period",
"employee": frappe.get_value("Employee", {"user_id":"employee@taxexepmtion.com"}, "name"),
"declarations": [dict(exemption_sub_category = "_Test Sub Category",
exemption_category = "_Test Category",
amount = 90000)]
})
self.assertTrue(declaration.save)
def test_duplicate_category_in_declaration(self):
declaration = frappe.get_doc({
"doctype": "Employee Tax Exemption Declaration",
"employee": frappe.get_value("Employee", {"user_id":"employee@taxexepmtion.com"}, "name"),
"company": erpnext.get_default_company(),
"payroll_period": "_Test Payroll Period",
"declarations": [dict(exemption_sub_category = "_Test Sub Category",
exemption_category = "_Test Category",
amount = 100000),
dict(exemption_sub_category = "_Test Sub Category",
exemption_category = "_Test Category",
amount = 50000),
]
"declarations": [
dict(exemption_sub_category = "_Test Sub Category",
exemption_category = "_Test Category",
amount = 100000),
dict(exemption_sub_category = "_Test Sub Category",
exemption_category = "_Test Category",
amount = 50000)
]
})
self.assertRaises(frappe.ValidationError, declaration.save)
def test_duplicate_submission_for_payroll_period(self):
def test_duplicate_entry_for_payroll_period(self):
declaration = frappe.get_doc({
"doctype": "Employee Tax Exemption Declaration",
"employee": frappe.get_value("Employee", {"user_id":"employee@taxexepmtion.com"}, "name"),
"company": erpnext.get_default_company(),
"payroll_period": "_Test Payroll Period",
"declarations": [dict(exemption_sub_category = "_Test Sub Category",
exemption_category = "_Test Category",
amount = 100000),
dict(exemption_sub_category = "_Test1 Sub Category",
exemption_category = "_Test Category",
amount = 50000),
]
"declarations": [
dict(exemption_sub_category = "_Test Sub Category",
exemption_category = "_Test Category",
amount = 100000),
dict(exemption_sub_category = "_Test1 Sub Category",
exemption_category = "_Test Category",
amount = 50000),
]
}).insert()
declaration.submit()
self.assertEquals(declaration.docstatus, 1)
duplicate_declaration = frappe.get_doc({
"doctype": "Employee Tax Exemption Declaration",
"employee": frappe.get_value("Employee", {"user_id":"employee@taxexepmtion.com"}, "name"),
"company": erpnext.get_default_company(),
"payroll_period": "_Test Payroll Period",
"declarations": [dict(exemption_sub_category = "_Test Sub Category",
exemption_category = "_Test Category",
amount = 100000)
]
}).insert()
self.assertRaises(frappe.DocstatusTransitionError, duplicate_declaration.submit)
"declarations": [
dict(exemption_sub_category = "_Test Sub Category",
exemption_category = "_Test Category",
amount = 100000)
]
})
self.assertRaises(DuplicateDeclarationError, duplicate_declaration.insert)
duplicate_declaration.employee = frappe.get_value("Employee", {"user_id":"employee1@taxexepmtion.com"}, "name")
self.assertTrue(duplicate_declaration.submit)
self.assertTrue(duplicate_declaration.insert)
def test_exemption_amount(self):
declaration = frappe.get_doc({
"doctype": "Employee Tax Exemption Declaration",
"employee": frappe.get_value("Employee", {"user_id":"employee@taxexepmtion.com"}, "name"),
"company": erpnext.get_default_company(),
"payroll_period": "_Test Payroll Period",
"declarations": [
dict(exemption_sub_category = "_Test Sub Category",
exemption_category = "_Test Category",
amount = 80000),
dict(exemption_sub_category = "_Test1 Sub Category",
exemption_category = "_Test Category",
amount = 60000),
]
}).insert()
self.assertEqual(declaration.total_exemption_amount, 100000)
def create_payroll_period():
if not frappe.db.exists("Payroll Period", "_Test Payroll Period"):

View File

@@ -1,5 +1,6 @@
{
"allow_copy": 0,
"allow_events_in_timeline": 0,
"allow_guest_to_view": 0,
"allow_import": 0,
"allow_rename": 0,
@@ -19,6 +20,7 @@
"bold": 0,
"collapsible": 0,
"columns": 0,
"fetch_if_empty": 0,
"fieldname": "exemption_sub_category",
"fieldtype": "Link",
"hidden": 0,
@@ -53,6 +55,7 @@
"collapsible": 0,
"columns": 0,
"fetch_from": "exemption_sub_category.exemption_category",
"fetch_if_empty": 0,
"fieldname": "exemption_category",
"fieldtype": "Link",
"hidden": 0,
@@ -70,7 +73,7 @@
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 0,
"read_only": 1,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 1,
@@ -86,6 +89,41 @@
"bold": 0,
"collapsible": 0,
"columns": 0,
"fetch_from": "exemption_sub_category.max_amount",
"fetch_if_empty": 0,
"fieldname": "max_amount",
"fieldtype": "Currency",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 1,
"in_standard_filter": 0,
"label": "Maximum Exempted Amount",
"length": 0,
"no_copy": 0,
"permlevel": 0,
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 1,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 1,
"search_index": 0,
"set_only_once": 0,
"translatable": 0,
"unique": 0
},
{
"allow_bulk_edit": 0,
"allow_in_quick_entry": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fetch_if_empty": 0,
"fieldname": "amount",
"fieldtype": "Currency",
"hidden": 0,
@@ -95,7 +133,7 @@
"in_global_search": 0,
"in_list_view": 1,
"in_standard_filter": 0,
"label": "Amount",
"label": "Declared Amount",
"length": 0,
"no_copy": 0,
"permlevel": 0,
@@ -122,7 +160,7 @@
"issingle": 0,
"istable": 1,
"max_attachments": 0,
"modified": "2018-05-29 15:58:05.779031",
"modified": "2019-04-26 11:28:14.023086",
"modified_by": "Administrator",
"module": "HR",
"name": "Employee Tax Exemption Declaration Category",
@@ -136,5 +174,6 @@
"sort_field": "modified",
"sort_order": "DESC",
"track_changes": 1,
"track_seen": 0
"track_seen": 0,
"track_views": 0
}

View File

@@ -10,6 +10,7 @@ frappe.ui.form.on('Employee Tax Exemption Proof Submission', {
}
}
});
frm.set_query('payroll_period', function() {
if(frm.doc.employee && frm.doc.company){
return {
@@ -21,6 +22,7 @@ frappe.ui.form.on('Employee Tax Exemption Proof Submission', {
frappe.msgprint(__("Please select Employee"));
}
});
frm.set_query('exemption_sub_category', 'tax_exemption_proofs', function() {
return {
filters: {
@@ -29,11 +31,28 @@ frappe.ui.form.on('Employee Tax Exemption Proof Submission', {
}
});
},
employee: function(frm){
if(frm.doc.employee){
frm.add_fetch('employee', 'company', 'company');
}else{
frm.set_value('company', '');
refresh: function(frm) {
if(frm.doc.docstatus === 0) {
let filters = {
docstatus: 1,
company: frm.doc.company
};
if(frm.doc.employee) filters["employee"] = frm.doc.employee;
if(frm.doc.payroll_period) filters["payroll_period"] = frm.doc.payroll_period;
frm.add_custom_button(__('Get Details From Declaration'), function() {
erpnext.utils.map_current_doc({
method: "erpnext.hr.doctype.employee_tax_exemption_declaration.employee_tax_exemption_declaration.make_proof_submission",
source_doctype: "Employee Tax Exemption Declaration",
target: frm,
date_field: "creation",
setters: {
employee: frm.doc.employee || undefined
},
get_query_filters: filters
});
});
}
}
});

View File

@@ -1,8 +1,9 @@
{
"allow_copy": 0,
"allow_events_in_timeline": 0,
"allow_guest_to_view": 0,
"allow_import": 0,
"allow_rename": 0,
"allow_import": 1,
"allow_rename": 1,
"autoname": "HR-TAX-PRF-.YYYY.-.#####",
"beta": 0,
"creation": "2018-04-13 17:24:11.456132",
@@ -20,6 +21,7 @@
"bold": 0,
"collapsible": 0,
"columns": 0,
"fetch_if_empty": 0,
"fieldname": "employee",
"fieldtype": "Link",
"hidden": 0,
@@ -53,8 +55,10 @@
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "company",
"fieldtype": "Link",
"fetch_from": "employee.employee_name",
"fetch_if_empty": 0,
"fieldname": "employee_name",
"fieldtype": "Data",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
@@ -62,10 +66,9 @@
"in_global_search": 0,
"in_list_view": 0,
"in_standard_filter": 0,
"label": "Company",
"label": "Employee Name",
"length": 0,
"no_copy": 0,
"options": "Company",
"permlevel": 0,
"precision": "",
"print_hide": 0,
@@ -79,70 +82,6 @@
"translatable": 0,
"unique": 0
},
{
"allow_bulk_edit": 0,
"allow_in_quick_entry": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "column_break_2",
"fieldtype": "Column Break",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 0,
"in_standard_filter": 0,
"length": 0,
"no_copy": 0,
"permlevel": 0,
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 0,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"translatable": 0,
"unique": 0
},
{
"allow_bulk_edit": 0,
"allow_in_quick_entry": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "payroll_period",
"fieldtype": "Link",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 1,
"in_standard_filter": 0,
"label": "Payroll Period",
"length": 0,
"no_copy": 0,
"options": "Payroll Period",
"permlevel": 0,
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 0,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 1,
"search_index": 0,
"set_only_once": 0,
"translatable": 0,
"unique": 0
},
{
"allow_bulk_edit": 0,
"allow_in_quick_entry": 0,
@@ -151,6 +90,7 @@
"collapsible": 0,
"columns": 0,
"fetch_from": "employee.department",
"fetch_if_empty": 0,
"fieldname": "department",
"fieldtype": "Link",
"hidden": 0,
@@ -184,8 +124,9 @@
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "submission_date",
"fieldtype": "Date",
"fetch_if_empty": 0,
"fieldname": "column_break_2",
"fieldtype": "Column Break",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
@@ -193,7 +134,6 @@
"in_global_search": 0,
"in_list_view": 0,
"in_standard_filter": 0,
"label": "Submission Date",
"length": 0,
"no_copy": 0,
"permlevel": 0,
@@ -216,6 +156,273 @@
"bold": 0,
"collapsible": 0,
"columns": 0,
"default": "Today",
"fetch_if_empty": 0,
"fieldname": "submission_date",
"fieldtype": "Date",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 0,
"in_standard_filter": 0,
"label": "Submission Date",
"length": 0,
"no_copy": 0,
"permlevel": 0,
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 0,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 1,
"search_index": 0,
"set_only_once": 0,
"translatable": 0,
"unique": 0
},
{
"allow_bulk_edit": 0,
"allow_in_quick_entry": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fetch_if_empty": 0,
"fieldname": "payroll_period",
"fieldtype": "Link",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 1,
"in_standard_filter": 0,
"label": "Payroll Period",
"length": 0,
"no_copy": 0,
"options": "Payroll Period",
"permlevel": 0,
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 0,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 1,
"search_index": 0,
"set_only_once": 0,
"translatable": 0,
"unique": 0
},
{
"allow_bulk_edit": 0,
"allow_in_quick_entry": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fetch_from": "employee.company",
"fetch_if_empty": 0,
"fieldname": "company",
"fieldtype": "Link",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 0,
"in_standard_filter": 0,
"label": "Company",
"length": 0,
"no_copy": 0,
"options": "Company",
"permlevel": 0,
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 1,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 1,
"search_index": 0,
"set_only_once": 0,
"translatable": 0,
"unique": 0
},
{
"allow_bulk_edit": 0,
"allow_in_quick_entry": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fetch_if_empty": 0,
"fieldname": "section_break_5",
"fieldtype": "Section Break",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 0,
"in_standard_filter": 0,
"length": 0,
"no_copy": 0,
"permlevel": 0,
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 0,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"translatable": 0,
"unique": 0
},
{
"allow_bulk_edit": 0,
"allow_in_quick_entry": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fetch_if_empty": 0,
"fieldname": "tax_exemption_proofs",
"fieldtype": "Table",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 0,
"in_standard_filter": 0,
"label": "Tax Exemption Proofs",
"length": 0,
"no_copy": 0,
"options": "Employee Tax Exemption Proof Submission Detail",
"permlevel": 0,
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 0,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"translatable": 0,
"unique": 0
},
{
"allow_bulk_edit": 0,
"allow_in_quick_entry": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fetch_if_empty": 0,
"fieldname": "section_break_10",
"fieldtype": "Section Break",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 0,
"in_standard_filter": 0,
"length": 0,
"no_copy": 0,
"permlevel": 0,
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 0,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"translatable": 0,
"unique": 0
},
{
"allow_bulk_edit": 0,
"allow_in_quick_entry": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fetch_if_empty": 0,
"fieldname": "total_actual_amount",
"fieldtype": "Currency",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 0,
"in_standard_filter": 0,
"label": "Total Actual Amount",
"length": 0,
"no_copy": 0,
"permlevel": 0,
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 1,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"translatable": 0,
"unique": 0
},
{
"allow_bulk_edit": 0,
"allow_in_quick_entry": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fetch_if_empty": 0,
"fieldname": "column_break_12",
"fieldtype": "Column Break",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 0,
"in_standard_filter": 0,
"length": 0,
"no_copy": 0,
"permlevel": 0,
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 0,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"translatable": 0,
"unique": 0
},
{
"allow_bulk_edit": 0,
"allow_in_quick_entry": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fetch_if_empty": 0,
"fieldname": "exemption_amount",
"fieldtype": "Currency",
"hidden": 0,
@@ -248,70 +455,7 @@
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "section_break_5",
"fieldtype": "Section Break",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 0,
"in_standard_filter": 0,
"length": 0,
"no_copy": 0,
"permlevel": 0,
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 0,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"translatable": 0,
"unique": 0
},
{
"allow_bulk_edit": 0,
"allow_in_quick_entry": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "tax_exemption_proofs",
"fieldtype": "Table",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 0,
"in_standard_filter": 0,
"label": "Tax Exemption Proofs",
"length": 0,
"no_copy": 0,
"options": "Employee Tax Exemption Proof Submission Detail",
"permlevel": 0,
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 0,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 1,
"search_index": 0,
"set_only_once": 0,
"translatable": 0,
"unique": 0
},
{
"allow_bulk_edit": 0,
"allow_in_quick_entry": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fetch_if_empty": 0,
"fieldname": "attachment_section",
"fieldtype": "Section Break",
"hidden": 0,
@@ -344,6 +488,7 @@
"bold": 0,
"collapsible": 0,
"columns": 0,
"fetch_if_empty": 0,
"fieldname": "attachments",
"fieldtype": "Attach",
"hidden": 0,
@@ -376,6 +521,7 @@
"bold": 0,
"collapsible": 0,
"columns": 0,
"fetch_if_empty": 0,
"fieldname": "amended_from",
"fieldtype": "Link",
"hidden": 0,
@@ -412,7 +558,7 @@
"issingle": 0,
"istable": 0,
"max_attachments": 0,
"modified": "2018-08-21 16:15:38.096846",
"modified": "2019-04-25 17:06:36.569549",
"modified_by": "Administrator",
"module": "HR",
"name": "Employee Tax Exemption Proof Submission",

View File

@@ -6,20 +6,30 @@ from __future__ import unicode_literals
import frappe
from frappe.model.document import Document
from frappe import _
from erpnext.hr.utils import validate_tax_declaration, calculate_hra_exemption_for_period
from frappe.utils import flt
from erpnext.hr.utils import validate_tax_declaration, get_total_exemption_amount, calculate_hra_exemption_for_period
class EmployeeTaxExemptionProofSubmission(Document):
def validate(self):
validate_tax_declaration(self.tax_exemption_proofs)
self.exemption_amount = 0
self.set_total_actual_amount()
self.set_total_exemption_amount()
self.calculate_hra_exemption()
for proof in self.tax_exemption_proofs:
self.exemption_amount += proof.amount
def set_total_actual_amount(self):
self.total_actual_amount = flt(self.get("house_rent_payment_amount"))
for d in self.tax_exemption_proofs:
self.total_actual_amount += flt(d.amount)
def set_total_exemption_amount(self):
self.exemption_amount = get_total_exemption_amount(self.tax_exemption_proofs)
def calculate_hra_exemption(self):
hra_exemption = calculate_hra_exemption_for_period(self)
if hra_exemption:
self.exemption_amount += hra_exemption["total_eligible_hra_exemption"]
self.monthly_hra_exemption = hra_exemption["monthly_exemption"]
self.monthly_house_rent = hra_exemption["monthly_house_rent"]
self.total_eligible_hra_exemption = hra_exemption["total_eligible_hra_exemption"]
self.monthly_hra_exemption, self.monthly_house_rent, self.total_eligible_hra_exemption = 0, 0, 0
if self.get("house_rent_payment_amount"):
hra_exemption = calculate_hra_exemption_for_period(self)
if hra_exemption:
self.exemption_amount += hra_exemption["total_eligible_hra_exemption"]
self.monthly_hra_exemption = hra_exemption["monthly_exemption"]
self.monthly_house_rent = hra_exemption["monthly_house_rent"]
self.total_eligible_hra_exemption = hra_exemption["total_eligible_hra_exemption"]

View File

@@ -1,5 +1,6 @@
{
"allow_copy": 0,
"allow_events_in_timeline": 0,
"allow_guest_to_view": 0,
"allow_import": 0,
"allow_rename": 0,
@@ -14,10 +15,12 @@
"fields": [
{
"allow_bulk_edit": 0,
"allow_in_quick_entry": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fetch_if_empty": 0,
"fieldname": "exemption_sub_category",
"fieldtype": "Link",
"hidden": 0,
@@ -41,16 +44,18 @@
"reqd": 1,
"search_index": 0,
"set_only_once": 0,
"translatable": 0,
"translatable": 0,
"unique": 0
},
{
"allow_bulk_edit": 0,
"allow_in_quick_entry": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fetch_from": "exemption_sub_category.exemption_category",
"fetch_from": "exemption_sub_category.exemption_category",
"fetch_if_empty": 0,
"fieldname": "exemption_category",
"fieldtype": "Read Only",
"hidden": 0,
@@ -74,15 +79,51 @@
"reqd": 1,
"search_index": 0,
"set_only_once": 0,
"translatable": 0,
"translatable": 0,
"unique": 0
},
{
"allow_bulk_edit": 0,
"allow_in_quick_entry": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fetch_from": "exemption_sub_category.max_amount",
"fetch_if_empty": 0,
"fieldname": "max_amount",
"fieldtype": "Currency",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 1,
"in_standard_filter": 0,
"label": "Maximum Exemption Amount",
"length": 0,
"no_copy": 0,
"permlevel": 0,
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 1,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 1,
"search_index": 0,
"set_only_once": 0,
"translatable": 0,
"unique": 0
},
{
"allow_bulk_edit": 0,
"allow_in_quick_entry": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fetch_if_empty": 0,
"fieldname": "type_of_proof",
"fieldtype": "Data",
"hidden": 0,
@@ -106,15 +147,17 @@
"reqd": 1,
"search_index": 0,
"set_only_once": 0,
"translatable": 0,
"translatable": 0,
"unique": 0
},
{
"allow_bulk_edit": 0,
"allow_in_quick_entry": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fetch_if_empty": 0,
"fieldname": "amount",
"fieldtype": "Currency",
"hidden": 0,
@@ -124,7 +167,7 @@
"in_global_search": 0,
"in_list_view": 1,
"in_standard_filter": 0,
"label": "Amount",
"label": "Actual Amount",
"length": 0,
"no_copy": 0,
"permlevel": 0,
@@ -134,10 +177,10 @@
"read_only": 0,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 1,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"translatable": 0,
"translatable": 0,
"unique": 0
}
],
@@ -151,7 +194,7 @@
"issingle": 0,
"istable": 1,
"max_attachments": 0,
"modified": "2018-05-16 22:42:59.750733",
"modified": "2019-04-25 15:45:03.154904",
"modified_by": "Administrator",
"module": "HR",
"name": "Employee Tax Exemption Proof Submission Detail",
@@ -165,5 +208,6 @@
"sort_field": "modified",
"sort_order": "DESC",
"track_changes": 1,
"track_seen": 0
"track_seen": 0,
"track_views": 0
}

View File

@@ -1,5 +1,6 @@
{
"allow_copy": 0,
"allow_events_in_timeline": 0,
"allow_guest_to_view": 0,
"allow_import": 1,
"allow_rename": 1,
@@ -15,10 +16,12 @@
"fields": [
{
"allow_bulk_edit": 0,
"allow_in_quick_entry": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fetch_if_empty": 0,
"fieldname": "exemption_category",
"fieldtype": "Link",
"hidden": 0,
@@ -26,8 +29,8 @@
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 0,
"in_standard_filter": 0,
"in_list_view": 1,
"in_standard_filter": 1,
"label": "Tax Exemption Category",
"length": 0,
"no_copy": 0,
@@ -42,14 +45,18 @@
"reqd": 1,
"search_index": 0,
"set_only_once": 0,
"translatable": 0,
"unique": 0
},
{
"allow_bulk_edit": 0,
"allow_in_quick_entry": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fetch_from": "exemption_category.max_amount",
"fetch_if_empty": 1,
"fieldname": "max_amount",
"fieldtype": "Currency",
"hidden": 0,
@@ -59,7 +66,7 @@
"in_global_search": 0,
"in_list_view": 1,
"in_standard_filter": 0,
"label": "Max Amount",
"label": "Max Exemption Amount",
"length": 0,
"no_copy": 0,
"permlevel": 0,
@@ -69,17 +76,21 @@
"read_only": 0,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 1,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"translatable": 0,
"unique": 0
},
{
"allow_bulk_edit": 0,
"allow_in_quick_entry": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"default": "1",
"fetch_if_empty": 0,
"fieldname": "is_active",
"fieldtype": "Check",
"hidden": 0,
@@ -102,6 +113,7 @@
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"translatable": 0,
"unique": 0
}
],
@@ -115,7 +127,7 @@
"issingle": 0,
"istable": 0,
"max_attachments": 0,
"modified": "2018-05-09 13:25:01.595240",
"modified": "2019-04-25 13:24:05.164877",
"modified_by": "Administrator",
"module": "HR",
"name": "Employee Tax Exemption Sub Category",
@@ -124,7 +136,6 @@
"permissions": [
{
"amend": 0,
"apply_user_permissions": 0,
"cancel": 0,
"create": 1,
"delete": 1,
@@ -144,7 +155,6 @@
},
{
"amend": 0,
"apply_user_permissions": 0,
"cancel": 0,
"create": 1,
"delete": 1,
@@ -164,7 +174,6 @@
},
{
"amend": 0,
"apply_user_permissions": 0,
"cancel": 0,
"create": 1,
"delete": 1,
@@ -189,6 +198,7 @@
"show_name_in_global_search": 0,
"sort_field": "modified",
"sort_order": "DESC",
"track_changes": 1,
"track_seen": 0
"track_changes": 0,
"track_seen": 0,
"track_views": 0
}

View File

@@ -4,7 +4,13 @@
from __future__ import unicode_literals
import frappe
from frappe import _
from frappe.utils import flt
from frappe.model.document import Document
class EmployeeTaxExemptionSubCategory(Document):
pass
def validate(self):
category_max_amount = frappe.db.get_value("Employee Tax Exemption Category", self.exemption_category, "max_amount")
if flt(self.max_amount) > flt(category_max_amount):
frappe.throw(_("Max Exemption Amount cannot be greater than maximum exemption amount {0} of Tax Exemption Category {1}")
.format(category_max_amount, self.exemption_category))

View File

@@ -1,5 +1,6 @@
{
"allow_copy": 0,
"allow_events_in_timeline": 0,
"allow_guest_to_view": 0,
"allow_import": 1,
"allow_rename": 0,
@@ -20,6 +21,7 @@
"bold": 0,
"collapsible": 0,
"columns": 0,
"fetch_if_empty": 0,
"fieldname": "company",
"fieldtype": "Link",
"hidden": 0,
@@ -53,6 +55,7 @@
"bold": 0,
"collapsible": 0,
"columns": 0,
"fetch_if_empty": 0,
"fieldname": "column_break_2",
"fieldtype": "Column Break",
"hidden": 0,
@@ -84,6 +87,7 @@
"bold": 0,
"collapsible": 0,
"columns": 0,
"fetch_if_empty": 0,
"fieldname": "start_date",
"fieldtype": "Date",
"hidden": 0,
@@ -116,6 +120,7 @@
"bold": 0,
"collapsible": 0,
"columns": 0,
"fetch_if_empty": 0,
"fieldname": "end_date",
"fieldtype": "Date",
"hidden": 0,
@@ -148,6 +153,7 @@
"bold": 0,
"collapsible": 0,
"columns": 0,
"fetch_if_empty": 0,
"fieldname": "section_break_5",
"fieldtype": "Section Break",
"hidden": 1,
@@ -180,6 +186,7 @@
"bold": 0,
"collapsible": 0,
"columns": 0,
"fetch_if_empty": 0,
"fieldname": "periods",
"fieldtype": "Table",
"hidden": 0,
@@ -213,6 +220,7 @@
"bold": 0,
"collapsible": 0,
"columns": 0,
"fetch_if_empty": 0,
"fieldname": "section_break_7",
"fieldtype": "Section Break",
"hidden": 0,
@@ -245,6 +253,7 @@
"bold": 0,
"collapsible": 0,
"columns": 0,
"fetch_if_empty": 0,
"fieldname": "taxable_salary_slabs",
"fieldtype": "Table",
"hidden": 0,
@@ -270,6 +279,39 @@
"set_only_once": 0,
"translatable": 0,
"unique": 0
},
{
"allow_bulk_edit": 0,
"allow_in_quick_entry": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fetch_if_empty": 0,
"fieldname": "standard_tax_exemption_amount",
"fieldtype": "Currency",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 0,
"in_standard_filter": 0,
"label": "Standard Tax Exemption Amount",
"length": 0,
"no_copy": 0,
"permlevel": 0,
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 0,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"translatable": 0,
"unique": 0
}
],
"has_web_view": 0,
@@ -282,7 +324,7 @@
"issingle": 0,
"istable": 0,
"max_attachments": 0,
"modified": "2018-05-25 12:29:07.207927",
"modified": "2019-04-26 01:45:03.160929",
"modified_by": "Administrator",
"module": "HR",
"name": "Payroll Period",
@@ -354,5 +396,6 @@
"sort_field": "modified",
"sort_order": "DESC",
"track_changes": 1,
"track_seen": 0
"track_seen": 0,
"track_views": 0
}

View File

@@ -5,10 +5,8 @@ frappe.ui.form.on('Salary Component', {
setup: function(frm) {
frm.set_query("default_account", "accounts", function(doc, cdt, cdn) {
var d = locals[cdt][cdn];
var root_types = ["Expense", "Liability"];
return {
filters: {
"root_type": ["in", root_types],
"is_group": 0,
"company": d.company
}

View File

@@ -2,7 +2,7 @@
"allow_copy": 0,
"allow_events_in_timeline": 0,
"allow_guest_to_view": 0,
"allow_import": 0,
"allow_import": 1,
"allow_rename": 0,
"beta": 0,
"creation": "2013-01-10 16:34:15",
@@ -21,6 +21,7 @@
"collapsible": 0,
"columns": 0,
"default": "Today",
"fetch_if_empty": 0,
"fieldname": "posting_date",
"fieldtype": "Date",
"hidden": 0,
@@ -53,6 +54,7 @@
"bold": 0,
"collapsible": 0,
"columns": 0,
"fetch_if_empty": 0,
"fieldname": "employee",
"fieldtype": "Link",
"hidden": 0,
@@ -88,6 +90,7 @@
"collapsible": 0,
"columns": 0,
"fetch_from": "employee.employee_name",
"fetch_if_empty": 0,
"fieldname": "employee_name",
"fieldtype": "Read Only",
"hidden": 0,
@@ -123,6 +126,7 @@
"collapsible": 0,
"columns": 0,
"fetch_from": "employee.department",
"fetch_if_empty": 0,
"fieldname": "department",
"fieldtype": "Link",
"hidden": 0,
@@ -159,6 +163,7 @@
"columns": 0,
"depends_on": "eval:doc.designation",
"fetch_from": "employee.designation",
"fetch_if_empty": 0,
"fieldname": "designation",
"fieldtype": "Read Only",
"hidden": 0,
@@ -194,6 +199,7 @@
"collapsible": 0,
"columns": 0,
"fetch_from": "employee.branch",
"fetch_if_empty": 0,
"fieldname": "branch",
"fieldtype": "Link",
"hidden": 0,
@@ -228,6 +234,7 @@
"bold": 0,
"collapsible": 0,
"columns": 0,
"fetch_if_empty": 0,
"fieldname": "column_break1",
"fieldtype": "Column Break",
"hidden": 0,
@@ -260,6 +267,7 @@
"bold": 0,
"collapsible": 0,
"columns": 0,
"fetch_if_empty": 0,
"fieldname": "status",
"fieldtype": "Select",
"hidden": 0,
@@ -293,6 +301,7 @@
"bold": 0,
"collapsible": 0,
"columns": 0,
"fetch_if_empty": 0,
"fieldname": "journal_entry",
"fieldtype": "Link",
"hidden": 0,
@@ -326,6 +335,7 @@
"bold": 0,
"collapsible": 0,
"columns": 0,
"fetch_if_empty": 0,
"fieldname": "payroll_entry",
"fieldtype": "Link",
"hidden": 0,
@@ -359,6 +369,7 @@
"bold": 0,
"collapsible": 0,
"columns": 0,
"fetch_if_empty": 0,
"fieldname": "company",
"fieldtype": "Link",
"hidden": 0,
@@ -391,6 +402,7 @@
"bold": 0,
"collapsible": 0,
"columns": 0,
"fetch_if_empty": 0,
"fieldname": "letter_head",
"fieldtype": "Link",
"hidden": 0,
@@ -423,6 +435,7 @@
"bold": 0,
"collapsible": 0,
"columns": 0,
"fetch_if_empty": 0,
"fieldname": "section_break_10",
"fieldtype": "Section Break",
"hidden": 0,
@@ -455,6 +468,7 @@
"collapsible": 0,
"columns": 0,
"depends_on": "",
"fetch_if_empty": 0,
"fieldname": "salary_slip_based_on_timesheet",
"fieldtype": "Check",
"hidden": 0,
@@ -489,6 +503,7 @@
"collapsible": 0,
"columns": 0,
"default": "",
"fetch_if_empty": 0,
"fieldname": "start_date",
"fieldtype": "Date",
"hidden": 0,
@@ -523,6 +538,7 @@
"columns": 0,
"default": "",
"depends_on": "",
"fetch_if_empty": 0,
"fieldname": "end_date",
"fieldtype": "Date",
"hidden": 0,
@@ -555,6 +571,7 @@
"bold": 0,
"collapsible": 0,
"columns": 0,
"fetch_if_empty": 0,
"fieldname": "column_break_15",
"fieldtype": "Column Break",
"hidden": 0,
@@ -587,6 +604,7 @@
"collapsible": 0,
"columns": 0,
"depends_on": "",
"fetch_if_empty": 0,
"fieldname": "salary_structure",
"fieldtype": "Link",
"hidden": 0,
@@ -622,6 +640,7 @@
"columns": 0,
"default": "",
"depends_on": "eval:(!doc.salary_slip_based_on_timesheet)",
"fetch_if_empty": 0,
"fieldname": "payroll_frequency",
"fieldtype": "Select",
"hidden": 0,
@@ -656,6 +675,7 @@
"collapsible": 0,
"columns": 0,
"depends_on": "",
"fetch_if_empty": 0,
"fieldname": "total_working_days",
"fieldtype": "Float",
"hidden": 0,
@@ -690,6 +710,7 @@
"collapsible": 0,
"columns": 0,
"depends_on": "",
"fetch_if_empty": 0,
"fieldname": "leave_without_pay",
"fieldtype": "Float",
"hidden": 0,
@@ -724,6 +745,7 @@
"collapsible": 0,
"columns": 0,
"depends_on": "",
"fetch_if_empty": 0,
"fieldname": "payment_days",
"fieldtype": "Float",
"hidden": 0,
@@ -758,6 +780,7 @@
"collapsible": 0,
"columns": 0,
"depends_on": "",
"fetch_if_empty": 0,
"fieldname": "hourly_wages",
"fieldtype": "Section Break",
"hidden": 0,
@@ -791,6 +814,7 @@
"collapsible": 0,
"columns": 0,
"depends_on": "",
"fetch_if_empty": 0,
"fieldname": "timesheets",
"fieldtype": "Table",
"hidden": 0,
@@ -824,6 +848,7 @@
"bold": 0,
"collapsible": 0,
"columns": 0,
"fetch_if_empty": 0,
"fieldname": "column_break_20",
"fieldtype": "Column Break",
"hidden": 0,
@@ -855,6 +880,7 @@
"bold": 0,
"collapsible": 0,
"columns": 0,
"fetch_if_empty": 0,
"fieldname": "total_working_hours",
"fieldtype": "Float",
"hidden": 0,
@@ -887,6 +913,7 @@
"bold": 0,
"collapsible": 0,
"columns": 0,
"fetch_if_empty": 0,
"fieldname": "hour_rate",
"fieldtype": "Currency",
"hidden": 0,
@@ -921,6 +948,7 @@
"collapsible": 0,
"columns": 0,
"depends_on": "",
"fetch_if_empty": 0,
"fieldname": "section_break_26",
"fieldtype": "Section Break",
"hidden": 0,
@@ -953,6 +981,7 @@
"bold": 0,
"collapsible": 0,
"columns": 0,
"fetch_if_empty": 0,
"fieldname": "bank_name",
"fieldtype": "Data",
"hidden": 0,
@@ -986,6 +1015,7 @@
"bold": 0,
"collapsible": 0,
"columns": 0,
"fetch_if_empty": 0,
"fieldname": "bank_account_no",
"fieldtype": "Data",
"hidden": 0,
@@ -1019,6 +1049,7 @@
"bold": 0,
"collapsible": 0,
"columns": 0,
"fetch_if_empty": 0,
"fieldname": "column_break_01",
"fieldtype": "Column Break",
"hidden": 0,
@@ -1050,6 +1081,7 @@
"bold": 0,
"collapsible": 0,
"columns": 0,
"fetch_if_empty": 0,
"fieldname": "amended_from",
"fieldtype": "Link",
"hidden": 0,
@@ -1084,6 +1116,7 @@
"bold": 0,
"collapsible": 0,
"columns": 0,
"fetch_if_empty": 0,
"fieldname": "section_break_32",
"fieldtype": "Section Break",
"hidden": 0,
@@ -1115,6 +1148,7 @@
"bold": 0,
"collapsible": 0,
"columns": 0,
"fetch_if_empty": 0,
"fieldname": "deduct_tax_for_unclaimed_employee_benefits",
"fieldtype": "Check",
"hidden": 0,
@@ -1147,6 +1181,7 @@
"bold": 0,
"collapsible": 0,
"columns": 0,
"fetch_if_empty": 0,
"fieldname": "deduct_tax_for_unsubmitted_tax_exemption_proof",
"fieldtype": "Check",
"hidden": 0,
@@ -1179,6 +1214,7 @@
"bold": 0,
"collapsible": 0,
"columns": 0,
"fetch_if_empty": 0,
"fieldname": "earning_deduction",
"fieldtype": "Section Break",
"hidden": 0,
@@ -1211,6 +1247,7 @@
"bold": 0,
"collapsible": 0,
"columns": 0,
"fetch_if_empty": 0,
"fieldname": "earning",
"fieldtype": "Column Break",
"hidden": 0,
@@ -1245,6 +1282,7 @@
"collapsible": 0,
"columns": 0,
"depends_on": "",
"fetch_if_empty": 0,
"fieldname": "earnings",
"fieldtype": "Table",
"hidden": 0,
@@ -1279,6 +1317,7 @@
"bold": 0,
"collapsible": 0,
"columns": 0,
"fetch_if_empty": 0,
"fieldname": "deduction",
"fieldtype": "Column Break",
"hidden": 0,
@@ -1312,6 +1351,7 @@
"bold": 0,
"collapsible": 0,
"columns": 0,
"fetch_if_empty": 0,
"fieldname": "deductions",
"fieldtype": "Table",
"hidden": 0,
@@ -1346,6 +1386,7 @@
"bold": 0,
"collapsible": 0,
"columns": 0,
"fetch_if_empty": 0,
"fieldname": "totals",
"fieldtype": "Section Break",
"hidden": 0,
@@ -1378,6 +1419,7 @@
"bold": 0,
"collapsible": 0,
"columns": 0,
"fetch_if_empty": 0,
"fieldname": "gross_pay",
"fieldtype": "Currency",
"hidden": 0,
@@ -1412,6 +1454,7 @@
"bold": 0,
"collapsible": 0,
"columns": 0,
"fetch_if_empty": 0,
"fieldname": "column_break_25",
"fieldtype": "Column Break",
"hidden": 0,
@@ -1442,6 +1485,7 @@
"bold": 0,
"collapsible": 0,
"columns": 0,
"fetch_if_empty": 0,
"fieldname": "total_deduction",
"fieldtype": "Currency",
"hidden": 0,
@@ -1477,6 +1521,7 @@
"collapsible": 0,
"columns": 0,
"depends_on": "total_loan_repayment",
"fetch_if_empty": 0,
"fieldname": "loan_repayment",
"fieldtype": "Section Break",
"hidden": 0,
@@ -1509,6 +1554,7 @@
"bold": 0,
"collapsible": 0,
"columns": 0,
"fetch_if_empty": 0,
"fieldname": "loans",
"fieldtype": "Table",
"hidden": 0,
@@ -1542,6 +1588,7 @@
"bold": 0,
"collapsible": 0,
"columns": 0,
"fetch_if_empty": 0,
"fieldname": "section_break_43",
"fieldtype": "Section Break",
"hidden": 0,
@@ -1574,6 +1621,7 @@
"collapsible": 0,
"columns": 0,
"default": "0",
"fetch_if_empty": 0,
"fieldname": "total_principal_amount",
"fieldtype": "Currency",
"hidden": 0,
@@ -1608,6 +1656,7 @@
"collapsible": 0,
"columns": 0,
"default": "0",
"fetch_if_empty": 0,
"fieldname": "total_interest_amount",
"fieldtype": "Currency",
"hidden": 0,
@@ -1641,6 +1690,7 @@
"bold": 0,
"collapsible": 0,
"columns": 0,
"fetch_if_empty": 0,
"fieldname": "column_break_45",
"fieldtype": "Column Break",
"hidden": 0,
@@ -1673,6 +1723,7 @@
"collapsible": 0,
"columns": 0,
"default": "0",
"fetch_if_empty": 0,
"fieldname": "total_loan_repayment",
"fieldtype": "Currency",
"hidden": 0,
@@ -1706,6 +1757,7 @@
"bold": 0,
"collapsible": 0,
"columns": 0,
"fetch_if_empty": 0,
"fieldname": "net_pay_info",
"fieldtype": "Section Break",
"hidden": 0,
@@ -1739,6 +1791,7 @@
"collapsible": 0,
"columns": 0,
"description": "Gross Pay - Total Deduction - Loan Repayment",
"fetch_if_empty": 0,
"fieldname": "net_pay",
"fieldtype": "Currency",
"hidden": 0,
@@ -1773,6 +1826,7 @@
"bold": 0,
"collapsible": 0,
"columns": 0,
"fetch_if_empty": 0,
"fieldname": "column_break_53",
"fieldtype": "Column Break",
"hidden": 0,
@@ -1804,6 +1858,7 @@
"bold": 1,
"collapsible": 0,
"columns": 0,
"fetch_if_empty": 0,
"fieldname": "rounded_total",
"fieldtype": "Currency",
"hidden": 0,
@@ -1836,6 +1891,7 @@
"bold": 0,
"collapsible": 0,
"columns": 0,
"fetch_if_empty": 0,
"fieldname": "section_break_55",
"fieldtype": "Section Break",
"hidden": 0,
@@ -1868,6 +1924,7 @@
"collapsible": 0,
"columns": 0,
"description": "Net Pay (in words) will be visible once you save the Salary Slip.",
"fetch_if_empty": 0,
"fieldname": "total_in_words",
"fieldtype": "Data",
"hidden": 0,
@@ -1906,7 +1963,7 @@
"issingle": 0,
"istable": 0,
"max_attachments": 0,
"modified": "2019-02-18 18:54:36.161027",
"modified": "2019-05-08 20:16:21.549386",
"modified_by": "Administrator",
"module": "HR",
"name": "Salary Slip",

View File

@@ -107,8 +107,8 @@ class SalarySlip(TransactionBase):
for d in self.get("earnings"):
if d.is_flexible_benefit == 1:
current_flexi_amount += d.amount
last_benefits = get_last_payroll_period_benefits(self.employee, self.start_date, self.end_date,\
current_flexi_amount, payroll_period, self._salary_structure_doc)
last_benefits = get_last_payroll_period_benefits(self.employee, self.start_date, self.end_date,
current_flexi_amount, payroll_period, self._salary_structure_doc)
if last_benefits:
for last_benefit in last_benefits:
last_benefit = frappe._dict(last_benefit)
@@ -118,7 +118,7 @@ class SalarySlip(TransactionBase):
def add_employee_flexi_benefits(self, struct_row):
if frappe.db.get_value("Salary Component", struct_row.salary_component, "pay_against_benefit_claim") != 1:
benefit_component_amount = get_benefit_component_amount(self.employee, self.start_date, self.end_date, \
struct_row, self._salary_structure_doc, self.total_working_days, self.payroll_frequency)
struct_row, self._salary_structure_doc, self.total_working_days, self.payroll_frequency)
if benefit_component_amount:
self.update_component_row(struct_row, benefit_component_amount, "earnings")
else:
@@ -418,7 +418,7 @@ class SalarySlip(TransactionBase):
for d in self.get(component_type):
if (self.salary_structure and
cint(d.depends_on_payment_days) and
cint(d.depends_on_payment_days) and cint(self.total_working_days) and
(not
self.salary_slip_based_on_timesheet or
getdate(self.start_date) < joining_date or
@@ -574,8 +574,8 @@ class SalarySlip(TransactionBase):
def calculate_variable_tax(self, tax_component, payroll_period):
annual_taxable_earning, period_factor = 0, 0
pro_rata_tax_paid, additional_tax_paid, benefit_tax_paid = 0, 0, 0
unclaimed_earning, unclaimed_benefit, additional_income = 0, 0, 0
pro_rata_tax_paid, additional_tax_paid, benefit_tax_paid = 0.0, 0.0, 0.0
unclaimed_earning, unclaimed_benefit, additional_income = 0.0, 0.0, 0.0
# get taxable_earning, additional_income in this slip
taxable_earning = self.get_taxable_earnings()
@@ -590,7 +590,7 @@ class SalarySlip(TransactionBase):
unclaimed_earning = self.calculate_unclaimed_taxable_earning(payroll_period, tax_component)
earning_in_period = taxable_earning["taxable_earning"] + unclaimed_earning
period_factor = self.get_period_factor(payroll_period.start_date, payroll_period.end_date,
payroll_period.start_date, self.end_date)
payroll_period.start_date, self.end_date)
annual_taxable_earning = earning_in_period * period_factor
additional_income += self.get_total_additional_income(payroll_period.start_date)
else:
@@ -604,6 +604,7 @@ class SalarySlip(TransactionBase):
{"employee": self.employee, "payroll_period": payroll_period.name, "docstatus": 1},
"total_exemption_amount")
annual_taxable_earning = annual_earning - exemption_amount
if self.deduct_tax_for_unclaimed_employee_benefits or self.deduct_tax_for_unsubmitted_tax_exemption_proof:
tax_detail = self.get_tax_paid_in_period(payroll_period, tax_component)
if tax_detail:
@@ -613,11 +614,17 @@ class SalarySlip(TransactionBase):
# add any additional income in this slip
additional_income += taxable_earning["additional_income"]
args = {"payroll_period": payroll_period.name, "tax_component": tax_component,
"annual_taxable_earning": annual_taxable_earning, "period_factor": period_factor,
"unclaimed_benefit": unclaimed_benefit, "additional_income": additional_income,
"pro_rata_tax_paid": pro_rata_tax_paid, "benefit_tax_paid": benefit_tax_paid,
"additional_tax_paid": additional_tax_paid}
args = {
"payroll_period": payroll_period.name,
"tax_component": tax_component,
"period_factor": period_factor,
"annual_taxable_earning": annual_taxable_earning,
"additional_income": additional_income,
"unclaimed_benefit": unclaimed_benefit,
"pro_rata_tax_paid": pro_rata_tax_paid,
"benefit_tax_paid": benefit_tax_paid,
"additional_tax_paid": additional_tax_paid
}
return self.calculate_tax(args)
def calculate_unclaimed_taxable_benefit(self, payroll_period):
@@ -664,27 +671,49 @@ class SalarySlip(TransactionBase):
return total_taxable_earning
def get_total_additional_income(self, from_date):
total_additional_pay = 0
sum_additional_earning = frappe.db.sql("""select sum(sd.amount) from `tabSalary Detail` sd join
`tabSalary Slip` ss on sd.parent=ss.name where sd.parentfield='earnings'
and sd.is_tax_applicable=1 and is_additional_component=1 and is_flexible_benefit=0
and ss.docstatus=1 and ss.employee='{0}' and ss.start_date between '{1}' and '{2}'
and ss.end_date between '{1}' and '{2}'""".format(self.employee,
from_date, self.start_date))
if sum_additional_earning and sum_additional_earning[0][0]:
total_additional_pay = sum_additional_earning[0][0]
return total_additional_pay
sum_additional_earning = frappe.db.sql("""
select sum(sd.amount)
from
`tabSalary Detail` sd join `tabSalary Slip` ss on sd.parent=ss.name
where
sd.parentfield='earnings'
and sd.is_tax_applicable=1 and is_additional_component=1
and is_flexible_benefit=0 and ss.docstatus=1
and ss.employee=%(employee)s
and ss.start_date between %(from_date)s and %(to_date)s
and ss.end_date between %(from_date)s and %(to_date)s
""", {
"employee": self.employee,
"from_date": from_date,
"to_date": self.start_date
})
return flt(sum_additional_earning[0][0]) if sum_additional_earning else 0
def get_tax_paid_in_period(self, payroll_period, tax_component, only_total=False):
# find total_tax_paid, tax paid for benefit, additional_salary
sum_tax_paid = frappe.db.sql("""select sum(sd.amount), sum(tax_on_flexible_benefit),
sum(tax_on_additional_salary) from `tabSalary Detail` sd join `tabSalary Slip`
ss on sd.parent=ss.name where sd.parentfield='deductions' and sd.salary_component='{3}'
and sd.variable_based_on_taxable_salary=1 and ss.docstatus=1 and ss.employee='{0}'
and ss.start_date between '{1}' and '{2}' and ss.end_date between '{1}' and
'{2}'""".format(self.employee, payroll_period.start_date, self.start_date, tax_component))
sum_tax_paid = frappe.db.sql("""
select
sum(sd.amount), sum(tax_on_flexible_benefit), sum(tax_on_additional_salary)
from
`tabSalary Detail` sd join `tabSalary Slip` ss on sd.parent=ss.name
where
sd.parentfield='deductions' and sd.salary_component=%(salary_component)s
and sd.variable_based_on_taxable_salary=1
and ss.docstatus=1 and ss.employee=%(employee)s
and ss.start_date between %(from_date)s and %(to_date)s
and ss.end_date between %(from_date)s and %(to_date)s
""", {
"salary_component": tax_component,
"employee": self.employee,
"from_date": payroll_period.start_date,
"to_date": self.start_date
})
if sum_tax_paid and sum_tax_paid[0][0]:
return {'total_tax_paid': sum_tax_paid[0][0], 'benefit_tax':sum_tax_paid[0][1], 'additional_tax': sum_tax_paid[0][2]}
return {
'total_tax_paid': sum_tax_paid[0][0],
'benefit_tax':sum_tax_paid[0][1],
'additional_tax': sum_tax_paid[0][2]
}
def get_taxable_earnings(self, include_flexi=0, only_flexi=0):
taxable_earning = 0
@@ -695,22 +724,22 @@ class SalarySlip(TransactionBase):
additional_income += earning.amount
continue
if only_flexi:
if earning.is_tax_applicable and earning.is_flexible_benefit:
if earning.is_flexible_benefit:
taxable_earning += earning.amount
continue
if include_flexi:
if earning.is_tax_applicable or (earning.is_tax_applicable and earning.is_flexible_benefit):
taxable_earning += earning.amount
else:
if earning.is_tax_applicable and not earning.is_flexible_benefit:
taxable_earning += earning.amount
return {"taxable_earning": taxable_earning, "additional_income": additional_income}
if include_flexi or not earning.is_flexible_benefit:
taxable_earning += earning.amount
return {
"taxable_earning": taxable_earning,
"additional_income": additional_income
}
def calculate_tax(self, args):
tax_amount, benefit_tax, additional_tax = 0, 0, 0
annual_taxable_earning = args.get("annual_taxable_earning")
benefit_to_tax = args.get("unclaimed_benefit")
additional_income = args.get("additional_income")
# Get tax calc by period
annual_tax = self.calculate_tax_by_tax_slab(args.get("payroll_period"), annual_taxable_earning)
@@ -741,8 +770,10 @@ class SalarySlip(TransactionBase):
def calculate_tax_by_tax_slab(self, payroll_period, annual_taxable_earning):
payroll_period_obj = frappe.get_doc("Payroll Period", payroll_period)
annual_taxable_earning -= flt(payroll_period_obj.standard_tax_exemption_amount)
data = self.get_data_for_eval()
data.update({"annual_taxable_earning": annual_taxable_earning})
taxable_amount = 0
for slab in payroll_period_obj.taxable_salary_slabs:
if slab.condition and not self.eval_tax_slab_condition(slab.condition, data):

View File

@@ -13,7 +13,8 @@ from frappe.utils import getdate, nowdate, add_days, add_months, flt, get_first_
from erpnext.hr.doctype.salary_structure.salary_structure import make_salary_slip
from erpnext.hr.doctype.payroll_entry.payroll_entry import get_month_details
from erpnext.hr.doctype.employee.test_employee import make_employee
from erpnext.hr.doctype.employee_tax_exemption_declaration.test_employee_tax_exemption_declaration import create_payroll_period, create_exemption_category
from erpnext.hr.doctype.employee_tax_exemption_declaration.test_employee_tax_exemption_declaration \
import create_payroll_period, create_exemption_category
class TestSalarySlip(unittest.TestCase):
def setUp(self):
@@ -36,8 +37,10 @@ class TestSalarySlip(unittest.TestCase):
no_of_days = self.get_no_of_days()
frappe.db.set_value("HR Settings", None, "include_holidays_in_total_working_days", 1)
make_employee("test_employee@salary.com")
frappe.db.set_value("Employee", frappe.get_value("Employee", {"employee_name":"test_employee@salary.com"}, "name"), "relieving_date", None)
frappe.db.set_value("Employee", frappe.get_value("Employee", {"employee_name":"test_employee@salary.com"}, "name"), "status", "Active")
frappe.db.set_value("Employee", frappe.get_value("Employee",
{"employee_name":"test_employee@salary.com"}, "name"), "relieving_date", None)
frappe.db.set_value("Employee", frappe.get_value("Employee",
{"employee_name":"test_employee@salary.com"}, "name"), "status", "Active")
ss = make_employee_salary_slip("test_employee@salary.com", "Monthly")
self.assertEqual(ss.total_working_days, no_of_days[0])
@@ -53,8 +56,10 @@ class TestSalarySlip(unittest.TestCase):
no_of_days = self.get_no_of_days()
frappe.db.set_value("HR Settings", None, "include_holidays_in_total_working_days", 0)
make_employee("test_employee@salary.com")
frappe.db.set_value("Employee", frappe.get_value("Employee", {"employee_name":"test_employee@salary.com"}, "name"), "relieving_date", None)
frappe.db.set_value("Employee", frappe.get_value("Employee", {"employee_name":"test_employee@salary.com"}, "name"), "status", "Active")
frappe.db.set_value("Employee", frappe.get_value("Employee",
{"employee_name":"test_employee@salary.com"}, "name"), "relieving_date", None)
frappe.db.set_value("Employee", frappe.get_value("Employee",
{"employee_name":"test_employee@salary.com"}, "name"), "status", "Active")
ss = make_employee_salary_slip("test_employee@salary.com", "Monthly")
self.assertEqual(ss.total_working_days, no_of_days[0] - no_of_days[1])
@@ -100,16 +105,21 @@ class TestSalarySlip(unittest.TestCase):
self.assertEqual(ss.payment_days, (no_of_days[0] - getdate(date_of_joining).day + 1))
# set relieving date in the same month
frappe.db.set_value("Employee", frappe.get_value("Employee", {"employee_name":"test_employee@salary.com"}, "name"), "date_of_joining", (add_days(nowdate(),-60)))
frappe.db.set_value("Employee", frappe.get_value("Employee", {"employee_name":"test_employee@salary.com"}, "name"), "relieving_date", relieving_date)
frappe.db.set_value("Employee", frappe.get_value("Employee", {"employee_name":"test_employee@salary.com"}, "name"), "status", "Left")
frappe.db.set_value("Employee",frappe.get_value("Employee",
{"employee_name":"test_employee@salary.com"}, "name"), "date_of_joining", (add_days(nowdate(),-60)))
frappe.db.set_value("Employee", frappe.get_value("Employee",
{"employee_name":"test_employee@salary.com"}, "name"), "relieving_date", relieving_date)
frappe.db.set_value("Employee", frappe.get_value("Employee",
{"employee_name":"test_employee@salary.com"}, "name"), "status", "Left")
ss.save()
self.assertEqual(ss.total_working_days, no_of_days[0])
self.assertEqual(ss.payment_days, getdate(relieving_date).day)
frappe.db.set_value("Employee", frappe.get_value("Employee", {"employee_name":"test_employee@salary.com"}, "name"), "relieving_date", None)
frappe.db.set_value("Employee", frappe.get_value("Employee", {"employee_name":"test_employee@salary.com"}, "name"), "status", "Active")
frappe.db.set_value("Employee", frappe.get_value("Employee",
{"employee_name":"test_employee@salary.com"}, "name"), "relieving_date", None)
frappe.db.set_value("Employee", frappe.get_value("Employee",
{"employee_name":"test_employee@salary.com"}, "name"), "status", "Active")
def test_employee_salary_slip_read_permission(self):
make_employee("test_employee@salary.com")
@@ -149,36 +159,41 @@ class TestSalarySlip(unittest.TestCase):
month = "%02d" % getdate(nowdate()).month
m = get_month_details(fiscal_year, month)
for payroll_frequncy in ["Monthly", "Bimonthly", "Fortnightly", "Weekly", "Daily"]:
make_employee(payroll_frequncy + "_test_employee@salary.com")
ss = make_employee_salary_slip(payroll_frequncy + "_test_employee@salary.com", payroll_frequncy)
if payroll_frequncy == "Monthly":
for payroll_frequency in ["Monthly", "Bimonthly", "Fortnightly", "Weekly", "Daily"]:
make_employee(payroll_frequency + "_test_employee@salary.com")
ss = make_employee_salary_slip(payroll_frequency + "_test_employee@salary.com", payroll_frequency)
if payroll_frequency == "Monthly":
self.assertEqual(ss.end_date, m['month_end_date'])
elif payroll_frequncy == "Bimonthly":
elif payroll_frequency == "Bimonthly":
if getdate(ss.start_date).day <= 15:
self.assertEqual(ss.end_date, m['month_mid_end_date'])
else:
self.assertEqual(ss.end_date, m['month_end_date'])
elif payroll_frequncy == "Fortnightly":
elif payroll_frequency == "Fortnightly":
self.assertEqual(ss.end_date, add_days(nowdate(),13))
elif payroll_frequncy == "Weekly":
elif payroll_frequency == "Weekly":
self.assertEqual(ss.end_date, add_days(nowdate(),6))
elif payroll_frequncy == "Daily":
elif payroll_frequency == "Daily":
self.assertEqual(ss.end_date, nowdate())
def test_tax_for_payroll_period(self):
data = {}
# test the impact of tax exemption declaration, tax exemption proof submission and deduct check boxes in annual tax calculation
# test the impact of tax exemption declaration, tax exemption proof submission
# and deduct check boxes in annual tax calculation
# as per assigned salary structure 40500 in monthly salary so 236000*5/100/12
frappe.db.sql("""delete from `tabPayroll Period`""")
frappe.db.sql("""delete from `tabSalary Component`""")
payroll_period = create_payroll_period()
create_tax_slab(payroll_period)
employee = make_employee("test_tax@salary.slip")
delete_docs = ["Salary Slip", "Additional Salary",
"Employee Tax Exemption Declaration",
"Employee Tax Exemption Proof Submission",
"Employee Benefit Claim", "Salary Structure Assignment"]
delete_docs = [
"Salary Slip",
"Additional Salary",
"Employee Tax Exemption Declaration",
"Employee Tax Exemption Proof Submission",
"Employee Benefit Claim",
"Salary Structure Assignment"
]
for doc in delete_docs:
frappe.db.sql("delete from `tab%s` where employee='%s'" % (doc, employee))
@@ -298,12 +313,11 @@ def make_employee_salary_slip(user, payroll_frequency, salary_structure=None):
if not salary_slip:
salary_slip = make_salary_slip(salary_structure_doc.name, employee = employee)
salary_slip.employee_name = frappe.get_value("Employee", {"name":frappe.db.get_value("Employee", {"user_id": user})}, "employee_name")
salary_slip.employee_name = frappe.get_value("Employee",
{"name":frappe.db.get_value("Employee", {"user_id": user})}, "employee_name")
salary_slip.payroll_frequency = payroll_frequency
salary_slip.posting_date = nowdate()
salary_slip.insert()
# salary_slip.submit()
# salary_slip = salary_slip.name
return salary_slip
@@ -338,99 +352,99 @@ def create_account(company):
salary_account = frappe.db.get_value("Account", "Salary - " + frappe.get_cached_value('Company', company, 'abbr'))
if not salary_account:
frappe.get_doc({
"doctype": "Account",
"account_name": "Salary",
"parent_account": "Indirect Expenses - " + frappe.get_cached_value('Company', company, 'abbr'),
"company": company
"doctype": "Account",
"account_name": "Salary",
"parent_account": "Indirect Expenses - " + frappe.get_cached_value('Company', company, 'abbr'),
"company": company
}).insert()
return salary_account
def make_earning_salary_component(setup=False, test_tax=False):
data = [
{
"salary_component": 'Basic Salary',
"abbr":'BS',
"condition": 'base > 10000',
"formula": 'base*.5',
"type": "Earning",
"amount_based_on_formula": 1
},
{
"salary_component": 'HRA',
"abbr":'H',
"amount": 3000,
"type": "Earning"
},
{
"salary_component": 'Special Allowance',
"abbr":'SA',
"condition": 'H < 10000',
"formula": 'BS*.5',
"type": "Earning",
"amount_based_on_formula": 1
},
{
"salary_component": "Leave Encashment",
"abbr": 'LE',
"is_additional_component": 1,
"type": "Earning"
}
]
if test_tax:
data.extend([
{
"salary_component": 'Basic Salary',
"abbr":'BS',
"condition": 'base > 10000',
"formula": 'base*.5',
"salary_component": "Leave Travel Allowance",
"abbr": 'B',
"is_flexible_benefit": 1,
"type": "Earning",
"amount_based_on_formula": 1
"pay_against_benefit_claim": 1,
"max_benefit_amount": 100000
},
{
"salary_component": 'HRA',
"abbr":'H',
"amount": 3000,
"type": "Earning"
},
{
"salary_component": 'Special Allowance',
"abbr":'SA',
"condition": 'H < 10000',
"formula": 'BS*.5',
"salary_component": "Medical Allowance",
"abbr": 'B',
"is_flexible_benefit": 1,
"pay_against_benefit_claim": 0,
"type": "Earning",
"amount_based_on_formula": 1
"max_benefit_amount": 15000
},
{
"salary_component": "Leave Encashment",
"abbr": 'LE',
"salary_component": "Perfomance Bonus",
"abbr": 'B',
"is_additional_component": 1,
"type": "Earning"
}
]
if test_tax:
data.extend([
{
"salary_component": "Leave Travel Allowance",
"abbr": 'B',
"is_flexible_benefit": 1,
"type": "Earning",
"pay_against_benefit_claim": 1,
"max_benefit_amount": 100000
},
{
"salary_component": "Medical Allowance",
"abbr": 'B',
"is_flexible_benefit": 1,
"pay_against_benefit_claim": 0,
"type": "Earning",
"max_benefit_amount": 15000
},
{
"salary_component": "Perfomance Bonus",
"abbr": 'B',
"is_additional_component": 1,
"type": "Earning"
}
])
])
if setup or test_tax:
make_salary_component(data, test_tax)
data.append({
"salary_component": 'Basic Salary',
"abbr":'BS',
"condition": 'base < 10000',
"formula": 'base*.2',
"type": "Earning",
"amount_based_on_formula": 1
})
"salary_component": 'Basic Salary',
"abbr":'BS',
"condition": 'base < 10000',
"formula": 'base*.2',
"type": "Earning",
"amount_based_on_formula": 1
})
return data
def make_deduction_salary_component(setup=False, test_tax=False):
data = [
{
"salary_component": 'Professional Tax',
"abbr":'PT',
"condition": 'base > 10000',
"formula": 'base*.1',
"type": "Deduction",
"amount_based_on_formula": 1
},
{
"salary_component": 'TDS',
"abbr":'T',
"formula": 'base*.1',
"type": "Deduction",
"amount_based_on_formula": 1
}
]
{
"salary_component": 'Professional Tax',
"abbr":'PT',
"condition": 'base > 10000',
"formula": 'base*.1',
"type": "Deduction",
"amount_based_on_formula": 1
},
{
"salary_component": 'TDS',
"abbr":'T',
"formula": 'base*.1',
"type": "Deduction",
"amount_based_on_formula": 1
}
]
if not test_tax:
data.append({
"salary_component": 'TDS',
@@ -453,48 +467,63 @@ def get_tax_paid_in_period(employee):
def create_exemption_declaration(employee, payroll_period):
create_exemption_category()
declaration = frappe.get_doc({"doctype": "Employee Tax Exemption Declaration",
"employee": employee,
"payroll_period": payroll_period,
"company": erpnext.get_default_company()})
declaration.append("declarations", {"exemption_sub_category": "_Test Sub Category",
"exemption_category": "_Test Category",
"amount": 100000})
declaration = frappe.get_doc({
"doctype": "Employee Tax Exemption Declaration",
"employee": employee,
"payroll_period": payroll_period,
"company": erpnext.get_default_company()
})
declaration.append("declarations", {
"exemption_sub_category": "_Test Sub Category",
"exemption_category": "_Test Category",
"amount": 100000
})
declaration.submit()
def create_proof_submission(employee, payroll_period, amount):
submission_date = add_months(payroll_period.start_date, random.randint(0, 11))
proof_submission = frappe.get_doc({"doctype": "Employee Tax Exemption Proof Submission",
"employee": employee,
"payroll_period": payroll_period.name,
"submission_date": submission_date})
proof_submission.append("tax_exemption_proofs", {"exemption_sub_category": "_Test Sub Category",
"exemption_category": "_Test Category", "type_of_proof": "Test", "amount": amount})
proof_submission = frappe.get_doc({
"doctype": "Employee Tax Exemption Proof Submission",
"employee": employee,
"payroll_period": payroll_period.name,
"submission_date": submission_date
})
proof_submission.append("tax_exemption_proofs", {
"exemption_sub_category": "_Test Sub Category",
"exemption_category": "_Test Category",
"type_of_proof": "Test", "amount": amount
})
proof_submission.submit()
return submission_date
def create_benefit_claim(employee, payroll_period, amount, component):
claim_date = add_months(payroll_period.start_date, random.randint(0, 11))
frappe.get_doc({"doctype": "Employee Benefit Claim", "employee": employee,
"claimed_amount": amount, "claim_date": claim_date, "earning_component":
component}).submit()
frappe.get_doc({
"doctype": "Employee Benefit Claim",
"employee": employee,
"claimed_amount": amount,
"claim_date": claim_date,
"earning_component": component
}).submit()
return claim_date
def create_tax_slab(payroll_period):
data = [{
"from_amount": 250000,
"to_amount": 500000,
"percent_deduction": 5
},
{
"from_amount": 500000,
"to_amount": 1000000,
"percent_deduction": 20
},
{
"from_amount": 1000000,
"percent_deduction": 30
}]
data = [
{
"from_amount": 250000,
"to_amount": 500000,
"percent_deduction": 5
},
{
"from_amount": 500000,
"to_amount": 1000000,
"percent_deduction": 20
},
{
"from_amount": 1000000,
"percent_deduction": 30
}
]
payroll_period.taxable_salary_slabs = []
for item in data:
payroll_period.append("taxable_salary_slabs", item)
@@ -526,9 +555,13 @@ def create_salary_slips_for_payroll_period(employee, salary_structure, payroll_p
def create_additional_salary(employee, payroll_period, amount):
salary_date = add_months(payroll_period.start_date, random.randint(0, 11))
frappe.get_doc({"doctype": "Additional Salary", "employee": employee,
"company": erpnext.get_default_company(),
"salary_component": "Perfomance Bonus",
"payroll_date": salary_date,
"amount": amount, "type": "Earning"}).submit()
frappe.get_doc({
"doctype": "Additional Salary",
"employee": employee,
"company": erpnext.get_default_company(),
"salary_component": "Perfomance Bonus",
"payroll_date": salary_date,
"amount": amount,
"type": "Earning"
}).submit()
return salary_date

View File

@@ -57,11 +57,12 @@ class SalaryStructure(Document):
have_a_flexi = True
max_of_component = frappe.db.get_value("Salary Component", earning_component.salary_component, "max_benefit_amount")
flexi_amount += max_of_component
if have_a_flexi and flt(self.max_benefits) == 0:
frappe.throw(_("Max benefits should be greater than zero to dispense benefits"))
if have_a_flexi and flt(self.max_benefits) > flexi_amount:
frappe.throw(_("Total flexible benefit component amount {0} should not be less \
than max benefits {1}").format(flexi_amount, self.max_benefits))
if have_a_flexi and flexi_amount and flt(self.max_benefits) > flexi_amount:
frappe.throw(_("Total flexible benefit component amount {0} should not be less than max benefits {1}")
.format(flexi_amount, self.max_benefits))
if not have_a_flexi and flt(self.max_benefits) > 0:
frappe.throw(_("Salary Structure should have flexible benefit component(s) to dispense benefit amount"))

View File

@@ -220,16 +220,30 @@ def get_employee_leave_policy(employee):
def validate_tax_declaration(declarations):
subcategories = []
for declaration in declarations:
if declaration.exemption_sub_category in subcategories:
frappe.throw(_("More than one selection for {0} not \
allowed").format(declaration.exemption_sub_category), frappe.ValidationError)
subcategories.append(declaration.exemption_sub_category)
max_amount = frappe.db.get_value("Employee Tax Exemption Sub Category", \
declaration.exemption_sub_category, "max_amount")
if declaration.amount > max_amount:
frappe.throw(_("Max exemption amount for {0} is {1}").format(\
declaration.exemption_sub_category, max_amount), frappe.ValidationError)
for d in declarations:
if d.exemption_sub_category in subcategories:
frappe.throw(_("More than one selection for {0} not allowed").format(d.exemption_sub_category))
subcategories.append(d.exemption_sub_category)
def get_total_exemption_amount(declarations):
exemptions = frappe._dict()
for d in declarations:
exemptions.setdefault(d.exemption_category, frappe._dict())
category_max_amount = exemptions.get(d.exemption_category).max_amount
if not category_max_amount:
category_max_amount = frappe.db.get_value("Employee Tax Exemption Category", d.exemption_category, "max_amount")
exemptions.get(d.exemption_category).max_amount = category_max_amount
sub_category_exemption_amount = d.max_amount \
if (d.max_amount and flt(d.amount) > flt(d.max_amount)) else d.amount
exemptions.get(d.exemption_category).setdefault("total_exemption_amount", 0.0)
exemptions.get(d.exemption_category).total_exemption_amount += flt(sub_category_exemption_amount)
if category_max_amount and exemptions.get(d.exemption_category).total_exemption_amount > category_max_amount:
exemptions.get(d.exemption_category).total_exemption_amount = category_max_amount
total_exemption_amount = sum([flt(d.total_exemption_amount) for d in exemptions.values()])
return total_exemption_amount
def get_leave_period(from_date, to_date, company):
leave_period = frappe.db.sql("""

View File

@@ -28,7 +28,7 @@ def make_opportunity(buyer_name, email_id):
lead.save(ignore_permissions=True)
o = frappe.new_doc("Opportunity")
o.enquiry_from = "Lead"
o.opportunity_from = "Lead"
o.lead = frappe.get_all("Lead", filters={"email_id": email_id}, fields = ["name"])[0]["name"]
o.save(ignore_permissions=True)

View File

@@ -5,11 +5,6 @@ frappe.provide("erpnext.bom");
frappe.ui.form.on("BOM", {
setup: function(frm) {
frm.add_fetch("item", "description", "description");
frm.add_fetch("item", "image", "image");
frm.add_fetch("item", "item_name", "item_name");
frm.add_fetch("item", "stock_uom", "uom");
frm.set_query("bom_no", "items", function() {
return {
filters: {
@@ -413,8 +408,4 @@ frappe.ui.form.on("BOM", "with_operations", function(frm) {
frm.set_value("operations", []);
}
toggle_operations(frm);
});
cur_frm.cscript.image = function() {
refresh_field("image_view");
};
});

View File

@@ -20,6 +20,7 @@
"collapsible": 0,
"columns": 0,
"description": "Item to be manufactured or repacked",
"fetch_if_empty": 0,
"fieldname": "item",
"fieldtype": "Link",
"hidden": 0,
@@ -55,6 +56,8 @@
"collapsible": 0,
"columns": 0,
"depends_on": "",
"fetch_from": "item.item_name",
"fetch_if_empty": 0,
"fieldname": "item_name",
"fieldtype": "Data",
"hidden": 0,
@@ -87,6 +90,43 @@
"bold": 0,
"collapsible": 0,
"columns": 0,
"fetch_from": "item.image",
"fetch_if_empty": 0,
"fieldname": "image",
"fieldtype": "Attach Image",
"hidden": 1,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 0,
"in_standard_filter": 0,
"label": "Image",
"length": 0,
"no_copy": 0,
"options": "image",
"permlevel": 0,
"precision": "",
"print_hide": 1,
"print_hide_if_no_value": 0,
"read_only": 1,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"translatable": 0,
"unique": 0
},
{
"allow_bulk_edit": 0,
"allow_in_quick_entry": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fetch_from": "item.stock_uom",
"fetch_if_empty": 0,
"fieldname": "uom",
"fieldtype": "Link",
"hidden": 0,
@@ -121,6 +161,7 @@
"columns": 0,
"default": "1",
"description": "Quantity of item obtained after manufacturing / repacking from given quantities of raw materials",
"fetch_if_empty": 0,
"fieldname": "quantity",
"fieldtype": "Float",
"hidden": 0,
@@ -154,6 +195,7 @@
"bold": 0,
"collapsible": 0,
"columns": 0,
"fetch_if_empty": 0,
"fieldname": "cb0",
"fieldtype": "Column Break",
"hidden": 0,
@@ -185,6 +227,7 @@
"collapsible": 0,
"columns": 0,
"default": "1",
"fetch_if_empty": 0,
"fieldname": "is_active",
"fieldtype": "Check",
"hidden": 0,
@@ -219,6 +262,7 @@
"collapsible": 0,
"columns": 0,
"default": "1",
"fetch_if_empty": 0,
"fieldname": "is_default",
"fieldtype": "Check",
"hidden": 0,
@@ -253,6 +297,7 @@
"collapsible": 0,
"columns": 0,
"description": "Manage cost of operations",
"fetch_if_empty": 0,
"fieldname": "with_operations",
"fieldtype": "Check",
"hidden": 0,
@@ -284,6 +329,7 @@
"bold": 0,
"collapsible": 0,
"columns": 0,
"fetch_if_empty": 0,
"fieldname": "inspection_required",
"fieldtype": "Check",
"hidden": 0,
@@ -316,6 +362,7 @@
"bold": 0,
"collapsible": 0,
"columns": 0,
"fetch_if_empty": 0,
"fieldname": "allow_alternative_item",
"fieldtype": "Check",
"hidden": 0,
@@ -348,6 +395,7 @@
"bold": 0,
"collapsible": 0,
"columns": 0,
"fetch_if_empty": 0,
"fieldname": "allow_same_item_multiple_times",
"fieldtype": "Check",
"hidden": 0,
@@ -381,6 +429,7 @@
"collapsible": 0,
"columns": 0,
"default": "1",
"fetch_if_empty": 0,
"fieldname": "set_rate_of_sub_assembly_item_based_on_bom",
"fieldtype": "Check",
"hidden": 0,
@@ -414,6 +463,7 @@
"collapsible": 0,
"columns": 0,
"depends_on": "inspection_required",
"fetch_if_empty": 0,
"fieldname": "quality_inspection_template",
"fieldtype": "Link",
"hidden": 0,
@@ -447,6 +497,7 @@
"bold": 0,
"collapsible": 0,
"columns": 0,
"fetch_if_empty": 0,
"fieldname": "currency_detail",
"fieldtype": "Section Break",
"hidden": 0,
@@ -479,6 +530,7 @@
"bold": 0,
"collapsible": 0,
"columns": 0,
"fetch_if_empty": 0,
"fieldname": "company",
"fieldtype": "Link",
"hidden": 0,
@@ -513,6 +565,7 @@
"collapsible": 0,
"columns": 0,
"default": "",
"fetch_if_empty": 0,
"fieldname": "transfer_material_against",
"fieldtype": "Select",
"hidden": 0,
@@ -546,6 +599,7 @@
"bold": 0,
"collapsible": 0,
"columns": 0,
"fetch_if_empty": 0,
"fieldname": "conversion_rate",
"fieldtype": "Float",
"hidden": 0,
@@ -578,6 +632,7 @@
"bold": 0,
"collapsible": 0,
"columns": 0,
"fetch_if_empty": 0,
"fieldname": "column_break_12",
"fieldtype": "Column Break",
"hidden": 0,
@@ -609,6 +664,7 @@
"bold": 0,
"collapsible": 0,
"columns": 0,
"fetch_if_empty": 0,
"fieldname": "currency",
"fieldtype": "Link",
"hidden": 0,
@@ -643,6 +699,7 @@
"collapsible": 0,
"columns": 0,
"default": "Valuation Rate",
"fetch_if_empty": 0,
"fieldname": "rm_cost_as_per",
"fieldtype": "Select",
"hidden": 0,
@@ -676,6 +733,7 @@
"collapsible": 0,
"columns": 0,
"depends_on": "eval:doc.rm_cost_as_per===\"Price List\"",
"fetch_if_empty": 0,
"fieldname": "buying_price_list",
"fieldtype": "Link",
"hidden": 0,
@@ -710,6 +768,7 @@
"columns": 0,
"depends_on": "",
"description": "",
"fetch_if_empty": 0,
"fieldname": "operations_section",
"fieldtype": "Section Break",
"hidden": 0,
@@ -742,6 +801,7 @@
"bold": 0,
"collapsible": 0,
"columns": 0,
"fetch_if_empty": 0,
"fieldname": "routing",
"fieldtype": "Link",
"hidden": 0,
@@ -775,6 +835,7 @@
"bold": 0,
"collapsible": 0,
"columns": 0,
"fetch_if_empty": 0,
"fieldname": "operations",
"fieldtype": "Table",
"hidden": 0,
@@ -809,6 +870,7 @@
"bold": 0,
"collapsible": 0,
"columns": 0,
"fetch_if_empty": 0,
"fieldname": "materials_section",
"fieldtype": "Section Break",
"hidden": 0,
@@ -841,6 +903,7 @@
"bold": 0,
"collapsible": 0,
"columns": 0,
"fetch_if_empty": 0,
"fieldname": "items",
"fieldtype": "Table",
"hidden": 0,
@@ -875,6 +938,7 @@
"bold": 0,
"collapsible": 1,
"columns": 0,
"fetch_if_empty": 0,
"fieldname": "scrap_section",
"fieldtype": "Section Break",
"hidden": 0,
@@ -907,6 +971,7 @@
"bold": 0,
"collapsible": 0,
"columns": 0,
"fetch_if_empty": 0,
"fieldname": "scrap_items",
"fieldtype": "Table",
"hidden": 0,
@@ -940,6 +1005,7 @@
"bold": 0,
"collapsible": 0,
"columns": 0,
"fetch_if_empty": 0,
"fieldname": "costing",
"fieldtype": "Section Break",
"hidden": 0,
@@ -972,6 +1038,7 @@
"bold": 0,
"collapsible": 0,
"columns": 0,
"fetch_if_empty": 0,
"fieldname": "operating_cost",
"fieldtype": "Currency",
"hidden": 0,
@@ -1004,6 +1071,7 @@
"bold": 0,
"collapsible": 0,
"columns": 0,
"fetch_if_empty": 0,
"fieldname": "raw_material_cost",
"fieldtype": "Currency",
"hidden": 0,
@@ -1036,6 +1104,7 @@
"bold": 0,
"collapsible": 0,
"columns": 0,
"fetch_if_empty": 0,
"fieldname": "scrap_material_cost",
"fieldtype": "Currency",
"hidden": 0,
@@ -1069,6 +1138,7 @@
"bold": 0,
"collapsible": 0,
"columns": 0,
"fetch_if_empty": 0,
"fieldname": "cb1",
"fieldtype": "Column Break",
"hidden": 0,
@@ -1099,6 +1169,7 @@
"bold": 0,
"collapsible": 0,
"columns": 0,
"fetch_if_empty": 0,
"fieldname": "base_operating_cost",
"fieldtype": "Currency",
"hidden": 0,
@@ -1132,6 +1203,7 @@
"bold": 0,
"collapsible": 0,
"columns": 0,
"fetch_if_empty": 0,
"fieldname": "base_raw_material_cost",
"fieldtype": "Currency",
"hidden": 0,
@@ -1165,6 +1237,7 @@
"bold": 0,
"collapsible": 0,
"columns": 0,
"fetch_if_empty": 0,
"fieldname": "base_scrap_material_cost",
"fieldtype": "Data",
"hidden": 0,
@@ -1198,6 +1271,7 @@
"bold": 0,
"collapsible": 0,
"columns": 0,
"fetch_if_empty": 0,
"fieldname": "total_cost_of_bom",
"fieldtype": "Section Break",
"hidden": 0,
@@ -1229,6 +1303,7 @@
"bold": 0,
"collapsible": 0,
"columns": 0,
"fetch_if_empty": 0,
"fieldname": "total_cost",
"fieldtype": "Currency",
"hidden": 0,
@@ -1261,6 +1336,7 @@
"bold": 0,
"collapsible": 0,
"columns": 0,
"fetch_if_empty": 0,
"fieldname": "column_break_26",
"fieldtype": "Column Break",
"hidden": 0,
@@ -1292,6 +1368,7 @@
"bold": 0,
"collapsible": 0,
"columns": 0,
"fetch_if_empty": 0,
"fieldname": "base_total_cost",
"fieldtype": "Currency",
"hidden": 0,
@@ -1325,6 +1402,7 @@
"bold": 0,
"collapsible": 0,
"columns": 0,
"fetch_if_empty": 0,
"fieldname": "more_info_section",
"fieldtype": "Section Break",
"hidden": 0,
@@ -1356,6 +1434,7 @@
"bold": 0,
"collapsible": 0,
"columns": 0,
"fetch_if_empty": 0,
"fieldname": "project",
"fieldtype": "Link",
"hidden": 0,
@@ -1390,6 +1469,7 @@
"bold": 0,
"collapsible": 0,
"columns": 0,
"fetch_if_empty": 0,
"fieldname": "amended_from",
"fieldtype": "Link",
"hidden": 0,
@@ -1422,6 +1502,7 @@
"bold": 0,
"collapsible": 0,
"columns": 0,
"fetch_if_empty": 0,
"fieldname": "col_break23",
"fieldtype": "Column Break",
"hidden": 0,
@@ -1452,6 +1533,7 @@
"bold": 0,
"collapsible": 0,
"columns": 0,
"fetch_if_empty": 0,
"fieldname": "section_break_25",
"fieldtype": "Section Break",
"hidden": 0,
@@ -1483,6 +1565,8 @@
"bold": 0,
"collapsible": 0,
"columns": 0,
"fetch_from": "item.description",
"fetch_if_empty": 0,
"fieldname": "description",
"fieldtype": "Small Text",
"hidden": 0,
@@ -1514,6 +1598,7 @@
"bold": 0,
"collapsible": 0,
"columns": 0,
"fetch_if_empty": 0,
"fieldname": "column_break_27",
"fieldtype": "Column Break",
"hidden": 0,
@@ -1538,71 +1623,6 @@
"translatable": 0,
"unique": 0
},
{
"allow_bulk_edit": 0,
"allow_in_quick_entry": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "image",
"fieldtype": "Attach",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 0,
"in_standard_filter": 0,
"label": "Image",
"length": 0,
"no_copy": 0,
"permlevel": 0,
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 0,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"translatable": 0,
"unique": 0
},
{
"allow_bulk_edit": 0,
"allow_in_quick_entry": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "image_view",
"fieldtype": "Image",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 0,
"in_standard_filter": 0,
"label": "Image View",
"length": 0,
"no_copy": 0,
"options": "image",
"permlevel": 0,
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 0,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"translatable": 0,
"unique": 0
},
{
"allow_bulk_edit": 0,
"allow_in_quick_entry": 0,
@@ -1611,6 +1631,7 @@
"collapsible": 0,
"columns": 0,
"depends_on": "eval:!doc.__islocal",
"fetch_if_empty": 0,
"fieldname": "section_break0",
"fieldtype": "Section Break",
"hidden": 0,
@@ -1642,6 +1663,7 @@
"bold": 0,
"collapsible": 0,
"columns": 0,
"fetch_if_empty": 0,
"fieldname": "exploded_items",
"fieldtype": "Table",
"hidden": 0,
@@ -1676,6 +1698,7 @@
"bold": 0,
"collapsible": 1,
"columns": 0,
"fetch_if_empty": 0,
"fieldname": "website_section",
"fieldtype": "Section Break",
"hidden": 0,
@@ -1710,6 +1733,7 @@
"collapsible": 0,
"columns": 0,
"depends_on": "",
"fetch_if_empty": 0,
"fieldname": "show_in_website",
"fieldtype": "Check",
"hidden": 0,
@@ -1742,6 +1766,7 @@
"bold": 0,
"collapsible": 0,
"columns": 0,
"fetch_if_empty": 0,
"fieldname": "route",
"fieldtype": "Small Text",
"hidden": 0,
@@ -1776,6 +1801,7 @@
"columns": 0,
"depends_on": "show_in_website",
"description": "Item Image (if not slideshow)",
"fetch_if_empty": 0,
"fieldname": "website_image",
"fieldtype": "Attach Image",
"hidden": 0,
@@ -1808,6 +1834,7 @@
"bold": 0,
"collapsible": 0,
"columns": 0,
"fetch_if_empty": 0,
"fieldname": "thumbnail",
"fieldtype": "Data",
"hidden": 0,
@@ -1842,6 +1869,7 @@
"collapsible_depends_on": "website_items",
"columns": 0,
"depends_on": "show_in_website",
"fetch_if_empty": 0,
"fieldname": "sb_web_spec",
"fieldtype": "Section Break",
"hidden": 0,
@@ -1875,6 +1903,7 @@
"collapsible": 0,
"columns": 0,
"depends_on": "show_in_website",
"fetch_if_empty": 0,
"fieldname": "web_long_description",
"fieldtype": "Text Editor",
"hidden": 0,
@@ -1908,6 +1937,7 @@
"collapsible": 0,
"columns": 0,
"depends_on": "show_in_website",
"fetch_if_empty": 0,
"fieldname": "show_items",
"fieldtype": "Check",
"hidden": 0,
@@ -1941,6 +1971,7 @@
"collapsible": 0,
"columns": 0,
"depends_on": "eval:(doc.show_in_website && doc.with_operations)",
"fetch_if_empty": 0,
"fieldname": "show_operations",
"fieldtype": "Check",
"hidden": 0,
@@ -1972,13 +2003,14 @@
"hide_toolbar": 0,
"icon": "fa fa-sitemap",
"idx": 1,
"image_field": "image",
"image_view": 0,
"in_create": 0,
"is_submittable": 1,
"issingle": 0,
"istable": 0,
"max_attachments": 0,
"modified": "2019-01-30 16:39:34.353721",
"modified": "2019-05-01 16:36:05.197126",
"modified_by": "Administrator",
"module": "Manufacturing",
"name": "BOM",
@@ -2026,7 +2058,7 @@
"quick_entry": 0,
"read_only": 0,
"read_only_onload": 0,
"search_fields": "item",
"search_fields": "item, item_name",
"show_name_in_global_search": 1,
"sort_field": "modified",
"sort_order": "DESC",

File diff suppressed because it is too large Load Diff

View File

@@ -571,7 +571,7 @@ execute:frappe.delete_doc_if_exists("Page", "sales-analytics")
execute:frappe.delete_doc_if_exists("Page", "purchase-analytics")
execute:frappe.delete_doc_if_exists("Page", "stock-analytics")
execute:frappe.delete_doc_if_exists("Page", "production-analytics")
erpnext.patches.v11_0.ewaybill_fields_gst_india #2018-11-13 #2019-01-09 #2019-04-01
erpnext.patches.v11_0.ewaybill_fields_gst_india #2018-11-13 #2019-01-09 #2019-04-01 #2019-04-26 #2019-05-03
erpnext.patches.v11_0.drop_column_max_days_allowed
erpnext.patches.v11_0.change_healthcare_desktop_icons
erpnext.patches.v10_0.update_user_image_in_employee
@@ -590,7 +590,12 @@ erpnext.patches.v10_0.item_barcode_childtable_migrate # 16-02-2019
erpnext.patches.v11_0.make_italian_localization_fields # 26-03-2019
erpnext.patches.v11_1.make_job_card_time_logs
erpnext.patches.v11_1.set_variant_based_on
erpnext.patches.v11_1.move_customer_lead_to_dynamic_column
erpnext.patches.v11_1.woocommerce_set_creation_user
erpnext.patches.v11_1.set_default_action_for_quality_inspection
erpnext.patches.v11_1.delete_bom_browser
erpnext.patches.v11_1.set_salary_details_submittable
erpnext.patches.v11_1.rename_depends_on_lwp
erpnext.patches.v11_1.set_missing_title_for_quotation
execute:frappe.delete_doc("Report", "Inactive Items")
erpnext.patches.v11_1.delete_scheduling_tool

View File

@@ -0,0 +1,9 @@
# Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors
# License: GNU General Public License v3. See license.txt
from __future__ import unicode_literals
import frappe
def execute():
if frappe.db.exists("DocType", "Scheduling Tool"):
frappe.delete_doc("DocType", "Scheduling Tool", ignore_permissions=True)

View File

@@ -0,0 +1,14 @@
# Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors
# License: GNU General Public License v3. See license.txt
from __future__ import unicode_literals
import frappe
def execute():
frappe.reload_doctype("Quotation")
frappe.db.sql(""" UPDATE `tabQuotation` set party_name = lead WHERE quotation_to = 'Lead' """)
frappe.db.sql(""" UPDATE `tabQuotation` set party_name = customer WHERE quotation_to = 'Customer' """)
frappe.reload_doctype("Opportunity")
frappe.db.sql(""" UPDATE `tabOpportunity` set party_name = lead WHERE opportunity_from = 'Lead' """)
frappe.db.sql(""" UPDATE `tabOpportunity` set party_name = customer WHERE opportunity_from = 'Customer' """)

View File

@@ -0,0 +1,10 @@
# Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors
# License: GNU General Public License v3. See license.txt
from __future__ import unicode_literals
import frappe
def execute():
stock_settings = frappe.get_doc('Stock Settings')
stock_settings.action_if_quality_inspection_is_not_submitted = "Stop"
stock_settings.save()

View File

@@ -0,0 +1,27 @@
import frappe
def execute():
# update customer_name from Customer document if quotation_to is set to Customer
frappe.db.sql('''
update tabQuotation, tabCustomer
set
tabQuotation.customer_name = tabCustomer.customer_name,
tabQuotation.title = tabCustomer.customer_name
where
tabQuotation.customer_name is null
and tabQuotation.party_name = tabCustomer.name
and tabQuotation.quotation_to = 'Customer'
''')
# update customer_name from Lead document if quotation_to is set to Lead
frappe.db.sql('''
update tabQuotation, tabLead
set
tabQuotation.customer_name = case when ifnull(tabLead.company_name, '') != '' then tabLead.company_name else tabLead.lead_name end,
tabQuotation.title = case when ifnull(tabLead.company_name, '') != '' then tabLead.company_name else tabLead.lead_name end
where
tabQuotation.customer_name is null
and tabQuotation.party_name = tabLead.name
and tabQuotation.quotation_to = 'Lead'
''')

View File

@@ -74,7 +74,7 @@ class Project(Document):
self.load_tasks()
self.validate_dates()
self.send_welcome_email()
self.update_percent_complete()
self.update_percent_complete(from_validate=True)
def validate_project_name(self):
if self.get("__islocal") and frappe.db.exists("Project", self.project_name):
@@ -198,7 +198,7 @@ class Project(Document):
if self.sales_order:
frappe.db.set_value("Sales Order", self.sales_order, "project", self.name)
def update_percent_complete(self):
def update_percent_complete(self, from_validate=False):
if not self.tasks: return
total = frappe.db.sql("""select count(name) from tabTask where project=%s""", self.name)[0][0]
if not total and self.percent_complete:
@@ -227,7 +227,9 @@ class Project(Document):
self.status = "Completed"
elif not self.status == "Cancelled":
self.status = "Open"
self.db_update()
if not from_validate:
self.db_update()
def update_costing(self):
from_time_sheet = frappe.db.sql("""select
@@ -316,7 +318,8 @@ class Project(Document):
if not self.get('deleted_task_list'): return
for d in self.get('deleted_task_list'):
frappe.delete_doc("Task", d)
# unlink project
frappe.db.set_value('Task', d, 'project', '')
self.deleted_task_list = []

View File

@@ -29,7 +29,7 @@
"in_filter": 0,
"in_global_search": 1,
"in_list_view": 0,
"in_standard_filter": 0,
"in_standard_filter": 1,
"label": "Subject",
"length": 0,
"no_copy": 0,
@@ -1396,7 +1396,7 @@
"istable": 0,
"max_attachments": 5,
"menu_index": 0,
"modified": "2019-04-18 22:33:03.798331",
"modified": "2019-04-24 23:10:00.014378",
"modified_by": "Administrator",
"module": "Projects",
"name": "Task",

View File

@@ -157,6 +157,12 @@ class Task(NestedSet):
if check_if_child_exists(self.name):
throw(_("Child Task exists for this Task. You can not delete this Task."))
if self.project:
tasks = frappe.get_doc('Project', self.project).tasks
for task in tasks:
if (task.get('task_id') == self.name):
frappe.delete_doc('Project Task', task.name)
self.update_nsm_model()
def update_status(self):

View File

@@ -5,7 +5,7 @@
from __future__ import unicode_literals
import frappe
from frappe import _
from frappe.utils import time_diff_in_hours
from frappe.utils import time_diff_in_hours, flt
def get_columns():
return [
@@ -43,7 +43,7 @@ def get_columns():
"width": 50
},
{
"label": _("Amount"),
"label": _("Billing Amount"),
"fieldtype": "Currency",
"fieldname": "amount",
"width": 100
@@ -52,46 +52,53 @@ def get_columns():
def get_data(filters):
data = []
record = get_records(filters)
if(filters.from_date > filters.to_date):
frappe.msgprint(_(" From Date can not be greater than To Date"))
return data
for entries in record:
timesheets = get_timesheets(filters)
filters.from_date = frappe.utils.get_datetime(filters.from_date)
filters.to_date = frappe.utils.add_to_date(frappe.utils.get_datetime(filters.to_date), days=1, seconds=-1)
timesheet_details = get_timesheet_details(filters, timesheets.keys())
for ts, ts_details in timesheet_details.items():
total_hours = 0
total_billable_hours = 0
total_billing_hours = 0
total_amount = 0
entries_exists = False
timesheet_details = get_timesheet_details(filters, entries.name)
for activity in timesheet_details:
entries_exists = True
time_start = activity.from_time
time_end = frappe.utils.add_to_date(activity.from_time, hours=activity.hours)
from_date = frappe.utils.get_datetime(filters.from_date)
to_date = frappe.utils.get_datetime(filters.to_date)
for row in ts_details:
from_time, to_time = filters.from_date, filters.to_date
if time_start <= from_date and time_end >= from_date:
total_hours, total_billable_hours, total_amount = get_billable_and_total_hours(activity,
time_end, from_date, total_hours, total_billable_hours, total_amount)
elif time_start <= to_date and time_end >= to_date:
total_hours, total_billable_hours, total_amount = get_billable_and_total_hours(activity,
to_date, time_start, total_hours, total_billable_hours, total_amount)
elif time_start >= from_date and time_end <= to_date:
total_hours, total_billable_hours, total_amount = get_billable_and_total_hours(activity,
time_end, time_start, total_hours, total_billable_hours, total_amount)
if row.to_time < from_time or row.from_time > to_time:
continue
if row.from_time > from_time:
from_time = row.from_time
if row.to_time < to_time:
to_time = row.to_time
activity_duration, billing_duration = get_billable_and_total_duration(row, from_time, to_time)
total_hours += activity_duration
total_billing_hours += billing_duration
total_amount += billing_duration * flt(row.billing_rate)
if total_hours:
data.append({
"employee": timesheets.get(ts).employee,
"employee_name": timesheets.get(ts).employee_name,
"timesheet": ts,
"total_billable_hours": total_billing_hours,
"total_hours": total_hours,
"amount": total_amount
})
row = {
"employee": entries.employee,
"employee_name": entries.employee_name,
"timesheet": entries.name,
"total_billable_hours": total_billable_hours,
"total_hours": total_hours,
"amount": total_amount
}
if entries_exists:
data.append(row)
entries_exists = False
return data
def get_records(filters):
def get_timesheets(filters):
record_filters = [
["start_date", "<=", filters.to_date],
["end_date", ">=", filters.from_date],
@@ -101,23 +108,39 @@ def get_records(filters):
if "employee" in filters:
record_filters.append(["employee", "=", filters.employee])
return frappe.get_all("Timesheet", filters=record_filters, fields=[" * "] )
timesheets = frappe.get_all("Timesheet", filters=record_filters, fields=["employee", "employee_name", "name"])
timesheet_map = frappe._dict()
for d in timesheets:
timesheet_map.setdefault(d.name, d)
def get_billable_and_total_hours(activity, end, start, total_hours, total_billable_hours, total_amount):
total_hours += abs(time_diff_in_hours(end, start))
if activity.billable:
total_billable_hours += abs(time_diff_in_hours(end, start))
total_amount += total_billable_hours * activity.billing_rate
return total_hours, total_billable_hours, total_amount
return timesheet_map
def get_timesheet_details(filters, parent):
timesheet_details_filter = {"parent": parent}
def get_timesheet_details(filters, timesheet_list):
timesheet_details_filter = {
"parent": ["in", timesheet_list]
}
if "project" in filters:
timesheet_details_filter["project"] = filters.project
return frappe.get_all(
timesheet_details = frappe.get_all(
"Timesheet Detail",
filters = timesheet_details_filter,
fields=["*"]
)
fields=["from_time", "to_time", "hours", "billable", "billing_hours", "billing_rate", "parent"]
)
timesheet_details_map = frappe._dict()
for d in timesheet_details:
timesheet_details_map.setdefault(d.parent, []).append(d)
return timesheet_details_map
def get_billable_and_total_duration(activity, start_time, end_time):
activity_duration = time_diff_in_hours(end_time, start_time)
billing_duration = 0.0
if activity.billable:
billing_duration = activity.billing_hours
if activity_duration != activity.billing_hours:
billing_duration = activity_duration * activity.billing_hours / activity.hours
return flt(activity_duration, 2), flt(billing_duration, 2)

View File

@@ -428,7 +428,7 @@ erpnext.TransactionController = erpnext.taxes_and_totals.extend({
serial_no: item.serial_no,
set_warehouse: me.frm.doc.set_warehouse,
warehouse: item.warehouse,
customer: me.frm.doc.customer,
customer: me.frm.doc.customer || me.frm.doc.party_name,
supplier: me.frm.doc.supplier,
currency: me.frm.doc.currency,
update_stock: update_stock,
@@ -1118,7 +1118,7 @@ erpnext.TransactionController = erpnext.taxes_and_totals.extend({
var me = this;
return {
"items": this._get_item_list(item),
"customer": me.frm.doc.customer,
"customer": me.frm.doc.customer || me.frm.doc.party_name,
"customer_group": me.frm.doc.customer_group,
"territory": me.frm.doc.territory,
"supplier": me.frm.doc.supplier,
@@ -1154,6 +1154,7 @@ erpnext.TransactionController = erpnext.taxes_and_totals.extend({
"brand": d.brand,
"qty": d.qty,
"uom": d.uom,
"stock_uom": d.stock_uom,
"parenttype": d.parenttype,
"parent": d.parent,
"pricing_rule": d.pricing_rule,

View File

@@ -8,10 +8,17 @@ erpnext.utils.get_party_details = function(frm, method, args, callback) {
method = "erpnext.accounts.party.get_party_details";
}
if(!args) {
if(frm.doctype != "Purchase Order" && frm.doc.customer) {
if((frm.doctype != "Purchase Order" && frm.doc.customer)
|| (frm.doc.party_name && in_list(['Quotation', 'Opportunity'], frm.doc.doctype))) {
let party_type = "Customer";
if(frm.doc.quotation_to && frm.doc.quotation_to === "Lead") {
party_type = "Lead";
}
args = {
party: frm.doc.customer,
party_type: "Customer",
party: frm.doc.customer || frm.doc.party_name,
party_type: party_type,
price_list: frm.doc.selling_price_list
};
} else if(frm.doc.supplier) {
@@ -103,7 +110,7 @@ erpnext.utils.get_address_display = function(frm, address_field, display_field,
erpnext.utils.set_taxes = function(frm, address_field, display_field, is_your_company_address) {
if(frappe.meta.get_docfield(frm.doc.doctype, "taxes") && !is_your_company_address) {
if(!erpnext.utils.validate_mandatory(frm, "Lead/Customer/Supplier",
frm.doc.customer || frm.doc.supplier || frm.doc.lead, address_field)) {
frm.doc.customer || frm.doc.supplier || frm.doc.lead || frm.doc.party_name , address_field)) {
return;
}
@@ -125,6 +132,9 @@ erpnext.utils.set_taxes = function(frm, address_field, display_field, is_your_co
} else if (frm.doc.supplier) {
party_type = 'Supplier';
party = frm.doc.supplier;
} else if (frm.doc.quotation_to){
party_type = frm.doc.quotation_to;
party = frm.doc.party_name;
}
frappe.call({

View File

@@ -189,9 +189,10 @@ def make_custom_fields(update=True):
'fieldname': 'gst_transporter_id',
'label': 'GST Transporter ID',
'fieldtype': 'Data',
'insert_after': 'transporter_name',
'insert_after': 'transporter',
'fetch_from': 'transporter.gst_transporter_id',
'print_hide': 1
'print_hide': 1,
'translatable': 0
},
{
'fieldname': 'mode_of_transport',
@@ -199,18 +200,142 @@ def make_custom_fields(update=True):
'fieldtype': 'Select',
'options': '\nRoad\nAir\nRail\nShip',
'default': 'Road',
'insert_after': 'transporter_name',
'print_hide': 1,
'translatable': 0
},
{
'fieldname': 'gst_vehicle_type',
'label': 'GST Vehicle Type',
'fieldtype': 'Select',
'options': 'Regular\nOver Dimensional Cargo (ODC)',
'depends_on': 'eval:(doc.mode_of_transport === "Road")',
'default': 'Regular',
'insert_after': 'lr_date',
'print_hide': 1,
'translatable': 0
}
]
si_ewaybill_fields = [
{
'fieldname': 'transporter_info',
'label': 'Transporter Info',
'fieldtype': 'Section Break',
'insert_after': 'terms',
'collapsible': 1,
'collapsible_depends_on': 'transporter',
'print_hide': 1
},
{
'fieldname': 'transporter',
'label': 'Transporter',
'fieldtype': 'Link',
'insert_after': 'transporter_info',
'options': 'Supplier',
'print_hide': 1
},
{
'fieldname': 'gst_transporter_id',
'label': 'GST Transporter ID',
'fieldtype': 'Data',
'insert_after': 'transporter',
'fetch_from': 'transporter.gst_transporter_id',
'print_hide': 1,
'translatable': 0
},
{
'fieldname': 'driver',
'label': 'Driver',
'fieldtype': 'Link',
'insert_after': 'gst_transporter_id',
'options': 'Driver',
'print_hide': 1
},
{
'fieldname': 'lr_no',
'label': 'Transport Receipt No',
'fieldtype': 'Data',
'insert_after': 'driver',
'print_hide': 1,
'translatable': 0
},
{
'fieldname': 'vehicle_no',
'label': 'Vehicle No',
'fieldtype': 'Data',
'insert_after': 'lr_no',
'print_hide': 1,
'translatable': 0
},
{
'fieldname': 'distance',
'label': 'Distance (in km)',
'fieldtype': 'Float',
'insert_after': 'vehicle_no',
'print_hide': 1
},
{
'fieldname': 'transporter_col_break',
'fieldtype': 'Column Break',
'insert_after': 'distance'
},
{
'fieldname': 'transporter_name',
'label': 'Transporter Name',
'fieldtype': 'Data',
'insert_after': 'transporter_col_break',
'fetch_from': 'transporter.name',
'read_only': 1,
'print_hide': 1,
'translatable': 0
},
{
'fieldname': 'mode_of_transport',
'label': 'Mode of Transport',
'fieldtype': 'Select',
'options': '\nRoad\nAir\nRail\nShip',
'default': 'Road',
'insert_after': 'transporter_name',
'print_hide': 1,
'translatable': 0
},
{
'fieldname': 'driver_name',
'label': 'Driver Name',
'fieldtype': 'Data',
'insert_after': 'mode_of_transport',
'fetch_from': 'driver.full_name',
'print_hide': 1,
'translatable': 0
},
{
'fieldname': 'lr_date',
'label': 'Transport Receipt Date',
'fieldtype': 'Date',
'insert_after': 'driver_name',
'default': 'Today',
'print_hide': 1
},
{
'fieldname': 'gst_vehicle_type',
'label': 'GST Vehicle Type',
'fieldtype': 'Select',
'options': '\nRegular\nOver Dimensional Cargo (ODC)',
'default': 'Regular',
'options': 'Regular\nOver Dimensional Cargo (ODC)',
'depends_on': 'eval:(doc.mode_of_transport === "Road")',
'insert_after': 'mode_of_transport',
'print_hide': 1
'default': 'Regular',
'insert_after': 'lr_date',
'print_hide': 1,
'translatable': 0
},
{
'fieldname': 'ewaybill',
'label': 'e-Way Bill No.',
'fieldtype': 'Data',
'depends_on': 'eval:(doc.docstatus === 1)',
'allow_on_submit': 1,
'insert_after': 'project',
'translatable': 0
}
]
@@ -226,7 +351,8 @@ def make_custom_fields(update=True):
'Purchase Invoice': invoice_gst_fields + purchase_invoice_gst_fields + purchase_invoice_itc_fields,
'Purchase Order': purchase_invoice_gst_fields,
'Purchase Receipt': purchase_invoice_gst_fields,
'Sales Invoice': invoice_gst_fields + sales_invoice_gst_fields + sales_invoice_shipping_fields,
'Sales Invoice': (invoice_gst_fields + sales_invoice_gst_fields
+ sales_invoice_shipping_fields + si_ewaybill_fields),
'Delivery Note': sales_invoice_gst_fields + ewaybill_fields + sales_invoice_shipping_fields,
'Sales Order': sales_invoice_gst_fields,
'Sales Taxes and Charges Template': inter_state_gst_field,
@@ -262,18 +388,18 @@ def make_custom_fields(update=True):
'Employee Tax Exemption Declaration':[
dict(fieldname='hra_section', label='HRA Exemption',
fieldtype='Section Break', insert_after='declarations'),
dict(fieldname='salary_structure_hra', label='HRA as per Salary Structure',
fieldtype='Currency', insert_after='hra_section', read_only=1),
dict(fieldname='monthly_house_rent', label='Monthly House Rent',
fieldtype='Currency', insert_after='salary_structure_hra'),
fieldtype='Currency', insert_after='hra_section'),
dict(fieldname='rented_in_metro_city', label='Rented in Metro City',
fieldtype='Check', insert_after='monthly_house_rent'),
fieldtype='Check', insert_after='monthly_house_rent', depends_on='monthly_house_rent'),
dict(fieldname='salary_structure_hra', label='HRA as per Salary Structure',
fieldtype='Currency', insert_after='rented_in_metro_city', read_only=1, depends_on='monthly_house_rent'),
dict(fieldname='hra_column_break', fieldtype='Column Break',
insert_after='rented_in_metro_city'),
insert_after='salary_structure_hra', depends_on='monthly_house_rent'),
dict(fieldname='annual_hra_exemption', label='Annual HRA Exemption',
fieldtype='Currency', insert_after='hra_column_break', read_only=1),
fieldtype='Currency', insert_after='hra_column_break', read_only=1, depends_on='monthly_house_rent'),
dict(fieldname='monthly_hra_exemption', label='Monthly HRA Exemption',
fieldtype='Currency', insert_after='annual_hra_exemption', read_only=1)
fieldtype='Currency', insert_after='annual_hra_exemption', read_only=1, depends_on='monthly_house_rent')
],
'Employee Tax Exemption Proof Submission': [
dict(fieldname='hra_section', label='HRA Exemption',
@@ -281,19 +407,19 @@ def make_custom_fields(update=True):
dict(fieldname='house_rent_payment_amount', label='House Rent Payment Amount',
fieldtype='Currency', insert_after='hra_section'),
dict(fieldname='rented_in_metro_city', label='Rented in Metro City',
fieldtype='Check', insert_after='house_rent_payment_amount'),
fieldtype='Check', insert_after='house_rent_payment_amount', depends_on='house_rent_payment_amount'),
dict(fieldname='rented_from_date', label='Rented From Date',
fieldtype='Date', insert_after='rented_in_metro_city'),
fieldtype='Date', insert_after='rented_in_metro_city', depends_on='house_rent_payment_amount'),
dict(fieldname='rented_to_date', label='Rented To Date',
fieldtype='Date', insert_after='rented_from_date'),
fieldtype='Date', insert_after='rented_from_date', depends_on='house_rent_payment_amount'),
dict(fieldname='hra_column_break', fieldtype='Column Break',
insert_after='rented_to_date'),
insert_after='rented_to_date', depends_on='house_rent_payment_amount'),
dict(fieldname='monthly_house_rent', label='Monthly House Rent',
fieldtype='Currency', insert_after='hra_column_break', read_only=1),
fieldtype='Currency', insert_after='hra_column_break', read_only=1, depends_on='house_rent_payment_amount'),
dict(fieldname='monthly_hra_exemption', label='Monthly Eligible Amount',
fieldtype='Currency', insert_after='monthly_house_rent', read_only=1),
fieldtype='Currency', insert_after='monthly_house_rent', read_only=1, depends_on='house_rent_payment_amount'),
dict(fieldname='total_eligible_hra_exemption', label='Total Eligible HRA Exemption',
fieldtype='Currency', insert_after='monthly_hra_exemption', read_only=1)
fieldtype='Currency', insert_after='monthly_hra_exemption', read_only=1, depends_on='house_rent_payment_amount')
],
'Supplier': [
{
@@ -306,7 +432,7 @@ def make_custom_fields(update=True):
]
}
create_custom_fields(custom_fields, ignore_validate = frappe.flags.in_patch, update=update)
create_custom_fields(custom_fields, update=update)
def make_fixtures(company=None):
docs = []

View File

@@ -1,7 +1,7 @@
from __future__ import unicode_literals
import frappe, re
import frappe, re, json
from frappe import _
from frappe.utils import cstr, flt, date_diff, getdate
from frappe.utils import cstr, flt, date_diff, nowdate
from erpnext.regional.india import states, state_numbers
from erpnext.controllers.taxes_and_totals import get_itemised_tax, get_itemised_taxable_amount
from erpnext.controllers.accounts_controller import get_taxes_and_charges
@@ -14,6 +14,8 @@ def validate_gstin_for_india(doc, method):
if not hasattr(doc, 'gstin') or not doc.gstin:
return
set_gst_state_and_state_number(doc)
doc.gstin = doc.gstin.upper().strip() if doc.gstin else ""
if not doc.gstin or doc.gstin == 'NA':
return
@@ -27,6 +29,11 @@ def validate_gstin_for_india(doc, method):
validate_gstin_check_digit(doc.gstin)
if doc.gst_state_number != doc.gstin[:2]:
frappe.throw(_("Invalid GSTIN! First 2 digits of GSTIN should match with State number {0}.")
.format(doc.gst_state_number))
def set_gst_state_and_state_number(doc):
if not doc.gst_state:
if not doc.state:
return
@@ -38,11 +45,9 @@ def validate_gstin_for_india(doc, method):
return
doc.gst_state_number = state_numbers[doc.gst_state]
if doc.gst_state_number != doc.gstin[:2]:
frappe.throw(_("Invalid GSTIN! First 2 digits of GSTIN should match with State number {0}.")
.format(doc.gst_state_number))
def validate_gstin_check_digit(gstin):
def validate_gstin_check_digit(gstin, label='GSTIN'):
''' Function to validate the check digit of the GSTIN.'''
factor = 1
total = 0
@@ -55,8 +60,8 @@ def validate_gstin_check_digit(gstin):
total += digit
factor = 2 if factor == 1 else 1
if gstin[-1] != code_point_chars[((mod - (total % mod)) % mod)]:
frappe.throw(_("Invalid GSTIN! The check digit validation has failed. " +
"Please ensure you've typed the GSTIN correctly."))
frappe.throw(_("Invalid {0}! The check digit validation has failed. " +
"Please ensure you've typed the {0} correctly.".format(label)))
def get_itemised_tax_breakup_header(item_doctype, tax_accounts):
if frappe.get_meta(item_doctype).has_field('gst_hsn_code'):
@@ -64,8 +69,8 @@ def get_itemised_tax_breakup_header(item_doctype, tax_accounts):
else:
return [_("Item"), _("Taxable Amount")] + tax_accounts
def get_itemised_tax_breakup_data(doc):
itemised_tax = get_itemised_tax(doc.taxes)
def get_itemised_tax_breakup_data(doc, account_wise=False):
itemised_tax = get_itemised_tax(doc.taxes, with_tax_account=account_wise)
itemised_taxable_amount = get_itemised_taxable_amount(doc.items)
@@ -80,14 +85,17 @@ def get_itemised_tax_breakup_data(doc):
for item, taxes in itemised_tax.items():
hsn_code = item_hsn_map.get(item)
hsn_tax.setdefault(hsn_code, frappe._dict())
for tax_account, tax_detail in taxes.items():
hsn_tax[hsn_code].setdefault(tax_account, {"tax_rate": 0, "tax_amount": 0})
hsn_tax[hsn_code][tax_account]["tax_rate"] = tax_detail.get("tax_rate")
hsn_tax[hsn_code][tax_account]["tax_amount"] += tax_detail.get("tax_amount")
for tax_desc, tax_detail in taxes.items():
key = tax_desc
if account_wise:
key = tax_detail.get('tax_account')
hsn_tax[hsn_code].setdefault(key, {"tax_rate": 0, "tax_amount": 0})
hsn_tax[hsn_code][key]["tax_rate"] = tax_detail.get("tax_rate")
hsn_tax[hsn_code][key]["tax_amount"] += tax_detail.get("tax_amount")
# set taxable amount
hsn_taxable_amount = frappe._dict()
for item, taxable_amount in itemised_taxable_amount.items():
for item in itemised_taxable_amount:
hsn_code = item_hsn_map.get(item)
hsn_taxable_amount.setdefault(hsn_code, 0)
hsn_taxable_amount[hsn_code] += itemised_taxable_amount.get(item)
@@ -144,24 +152,40 @@ def get_regional_address_details(out, doctype, company):
def calculate_annual_eligible_hra_exemption(doc):
basic_component = frappe.get_cached_value('Company', doc.company, "basic_component")
hra_component = frappe.get_cached_value('Company', doc.company, "hra_component")
if not (basic_component and hra_component):
frappe.throw(_("Please mention Basic and HRA component in Company"))
annual_exemption, monthly_exemption, hra_amount = 0, 0, 0
if hra_component and basic_component:
assignment = get_salary_assignment(doc.employee, getdate())
if assignment and frappe.db.exists("Salary Detail", {
"parent": assignment.salary_structure,
"salary_component": hra_component, "parentfield": "earnings"}):
basic_amount, hra_amount = get_component_amt_from_salary_slip(doc.employee,
assignment.salary_structure, basic_component, hra_component)
if hra_amount:
if doc.monthly_house_rent:
annual_exemption = calculate_hra_exemption(assignment.salary_structure,
basic_amount, hra_amount, doc.monthly_house_rent,
doc.rented_in_metro_city)
if annual_exemption > 0:
monthly_exemption = annual_exemption / 12
else:
annual_exemption = 0
return {"hra_amount": hra_amount, "annual_exemption": annual_exemption, "monthly_exemption": monthly_exemption}
assignment = get_salary_assignment(doc.employee, nowdate())
if assignment:
hra_component_exists = frappe.db.exists("Salary Detail", {
"parent": assignment.salary_structure,
"salary_component": hra_component,
"parentfield": "earnings",
"parenttype": "Salary Structure"
})
if hra_component_exists:
basic_amount, hra_amount = get_component_amt_from_salary_slip(doc.employee,
assignment.salary_structure, basic_component, hra_component)
if hra_amount:
if doc.monthly_house_rent:
annual_exemption = calculate_hra_exemption(assignment.salary_structure,
basic_amount, hra_amount, doc.monthly_house_rent,
doc.rented_in_metro_city)
if annual_exemption > 0:
monthly_exemption = annual_exemption / 12
else:
annual_exemption = 0
elif doc.docstatus == 1:
frappe.throw(_("Salary Structure must be submitted before submission of Tax Ememption Declaration"))
return frappe._dict({
"hra_amount": hra_amount,
"annual_exemption": annual_exemption,
"monthly_exemption": monthly_exemption
})
def get_component_amt_from_salary_slip(employee, salary_structure, basic_component, hra_component):
salary_slip = make_salary_slip(salary_structure, employee=employee)
@@ -181,8 +205,10 @@ def calculate_hra_exemption(salary_structure, basic, monthly_hra, monthly_house_
frequency = frappe.get_value("Salary Structure", salary_structure, "payroll_frequency")
# case 1: The actual amount allotted by the employer as the HRA.
exemptions.append(get_annual_component_pay(frequency, monthly_hra))
actual_annual_rent = monthly_house_rent * 12
annual_basic = get_annual_component_pay(frequency, basic)
# case 2: Actual rent paid less 10% of the basic salary.
exemptions.append(flt(actual_annual_rent) - flt(annual_basic * 0.1))
# case 3: 50% of the basic salary, if the employee is staying in a metro city (40% for a non-metro city).
@@ -205,15 +231,25 @@ def get_annual_component_pay(frequency, amount):
def validate_house_rent_dates(doc):
if not doc.rented_to_date or not doc.rented_from_date:
frappe.throw(_("House rented dates required for exemption calculation"))
if date_diff(doc.rented_to_date, doc.rented_from_date) < 14:
frappe.throw(_("House rented dates should be atleast 15 days apart"))
proofs = frappe.db.sql("""select name from `tabEmployee Tax Exemption Proof Submission`
where docstatus=1 and employee='{0}' and payroll_period='{1}' and
(rented_from_date between '{2}' and '{3}' or rented_to_date between
'{2}' and '{3}')""".format(doc.employee, doc.payroll_period,
doc.rented_from_date, doc.rented_to_date))
proofs = frappe.db.sql("""
select name
from `tabEmployee Tax Exemption Proof Submission`
where
docstatus=1 and employee=%(employee)s and payroll_period=%(payroll_period)s
and (rented_from_date between %(from_date)s and %(to_date)s or rented_to_date between %(from_date)s and %(to_date)s)
""", {
"employee": doc.employee,
"payroll_period": doc.payroll_period,
"from_date": doc.rented_from_date,
"to_date": doc.rented_to_date
})
if proofs:
frappe.throw(_("House rent paid days overlap with {0}").format(proofs[0][0]))
frappe.throw(_("House rent paid days overlapping with {0}").format(proofs[0][0]))
def calculate_hra_exemption_for_period(doc):
monthly_rent, eligible_hra = 0, 0
@@ -234,3 +270,276 @@ def calculate_hra_exemption_for_period(doc):
exemptions["monthly_house_rent"] = monthly_rent
exemptions["total_eligible_hra_exemption"] = eligible_hra
return exemptions
def get_ewb_data(dt, dn):
if dt != 'Sales Invoice':
frappe.throw(_('e-Way Bill JSON can only be generated from Sales Invoice'))
dn = dn.split(',')
ewaybills = []
for doc_name in dn:
doc = frappe.get_doc(dt, doc_name)
validate_sales_invoice(doc)
data = frappe._dict({
"transporterId": "",
"TotNonAdvolVal": 0,
})
data.userGstin = data.fromGstin = doc.company_gstin
data.supplyType = 'O'
if doc.invoice_type in ['Regular', 'SEZ']:
data.subSupplyType = 1
elif doc.invoice_type in ['Export', 'Deemed Export']:
data.subSupplyType = 3
else:
frappe.throw(_('Unsupported Invoice Type for e-Way Bill JSON generation'))
data.docType = 'INV'
data.docDate = frappe.utils.formatdate(doc.posting_date, 'dd/mm/yyyy')
company_address = frappe.get_doc('Address', doc.company_address)
billing_address = frappe.get_doc('Address', doc.customer_address)
shipping_address = frappe.get_doc('Address', doc.shipping_address_name)
data = get_address_details(data, doc, company_address, billing_address)
data.itemList = []
data.totalValue = doc.total
data = get_item_list(data, doc)
disable_rounded = frappe.db.get_single_value('Global Defaults', 'disable_rounded_total')
data.totInvValue = doc.grand_total if disable_rounded else doc.rounded_total
data = get_transport_details(data, doc)
fields = {
"/. -": {
'docNo': doc.name,
'fromTrdName': doc.company,
'toTrdName': doc.customer_name,
'transDocNo': doc.lr_no,
},
"@#/,&. -": {
'fromAddr1': company_address.address_line1,
'fromAddr2': company_address.address_line2,
'fromPlace': company_address.city,
'toAddr1': shipping_address.address_line1,
'toAddr2': shipping_address.address_line2,
'toPlace': shipping_address.city,
'transporterName': doc.transporter_name
}
}
for allowed_chars, field_map in fields.items():
for key, value in field_map.items():
if not value:
data[key] = ''
else:
data[key] = re.sub(r'[^\w' + allowed_chars + ']', '', value)
ewaybills.append(data)
data = {
'version': '1.0.1118',
'billLists': ewaybills
}
return data
@frappe.whitelist()
def generate_ewb_json(dt, dn):
data = get_ewb_data(dt, dn)
frappe.local.response.filecontent = json.dumps(data, indent=4, sort_keys=True)
frappe.local.response.type = 'download'
if len(data['billLists']) > 1:
doc_name = 'Bulk'
else:
doc_name = dn
frappe.local.response.filename = '{0}_e-WayBill_Data_{1}.json'.format(doc_name, frappe.utils.random_string(5))
def get_address_details(data, doc, company_address, billing_address):
data.fromPincode = validate_pincode(company_address.pincode, 'Company Address')
data.fromStateCode = data.actualFromStateCode = validate_state_code(
company_address.gst_state_number, 'Company Address')
if not doc.billing_address_gstin or len(doc.billing_address_gstin) < 15:
data.toGstin = 'URP'
set_gst_state_and_state_number(billing_address)
else:
data.toGstin = doc.billing_address_gstin
data.toPincode = validate_pincode(billing_address.pincode, 'Customer Address')
data.toStateCode = validate_state_code(billing_address.gst_state_number, 'Customer Address')
if doc.customer_address != doc.shipping_address_name:
data.transType = 2
shipping_address = frappe.get_doc('Address', doc.shipping_address_name)
set_gst_state_and_state_number(shipping_address)
data.toPincode = validate_pincode(shipping_address.pincode, 'Shipping Address')
data.actualToStateCode = validate_state_code(shipping_address.gst_state_number, 'Shipping Address')
else:
data.transType = 1
data.actualToStateCode = data.toStateCode
shipping_address = billing_address
return data
def get_item_list(data, doc):
for attr in ['cgstValue', 'sgstValue', 'igstValue', 'cessValue', 'OthValue']:
data[attr] = 0
gst_accounts = get_gst_accounts(doc.company, account_wise=True)
tax_map = {
'sgst_account': ['sgstRate', 'sgstValue'],
'cgst_account': ['cgstRate', 'cgstValue'],
'igst_account': ['igstRate', 'igstValue'],
'cess_account': ['cessRate', 'cessValue']
}
item_data_attrs = ['sgstRate', 'cgstRate', 'igstRate', 'cessRate', 'cessNonAdvol']
hsn_wise_charges, hsn_taxable_amount = get_itemised_tax_breakup_data(doc, account_wise=True)
for hsn_code, taxable_amount in hsn_taxable_amount.items():
item_data = frappe._dict()
if not hsn_code:
frappe.throw(_('GST HSN Code does not exist for one or more items'))
item_data.hsnCode = int(hsn_code)
item_data.taxableAmount = taxable_amount
item_data.qtyUnit = ""
for attr in item_data_attrs:
item_data[attr] = 0
for account, tax_detail in hsn_wise_charges.get(hsn_code, {}).items():
account_type = gst_accounts.get(account, '')
for tax_acc, attrs in tax_map.items():
if account_type == tax_acc:
item_data[attrs[0]] = tax_detail.get('tax_rate')
data[attrs[1]] += tax_detail.get('tax_amount')
break
else:
data.OthValue += tax_detail.get('tax_amount')
data.itemList.append(item_data)
# Tax amounts rounded to 2 decimals to avoid exceeding max character limit
for attr in ['sgstValue', 'cgstValue', 'igstValue', 'cessValue']:
data[attr] = flt(data[attr], 2)
return data
def validate_sales_invoice(doc):
if doc.docstatus != 1:
frappe.throw(_('e-Way Bill JSON can only be generated from submitted document'))
if doc.is_return:
frappe.throw(_('e-Way Bill JSON cannot be generated for Sales Return as of now'))
if doc.ewaybill:
frappe.throw(_('e-Way Bill already exists for this document'))
reqd_fields = ['company_gstin', 'company_address', 'customer_address',
'shipping_address_name', 'mode_of_transport', 'distance']
for fieldname in reqd_fields:
if not doc.get(fieldname):
frappe.throw(_('{} is required to generate e-Way Bill JSON'.format(
doc.meta.get_label(fieldname)
)))
if len(doc.company_gstin) < 15:
frappe.throw(_('You must be a registered supplier to generate e-Way Bill'))
def get_transport_details(data, doc):
if doc.distance > 4000:
frappe.throw(_('Distance cannot be greater than 4000 kms'))
data.transDistance = int(round(doc.distance))
transport_modes = {
'Road': 1,
'Rail': 2,
'Air': 3,
'Ship': 4
}
vehicle_types = {
'Regular': 'R',
'Over Dimensional Cargo (ODC)': 'O'
}
data.transMode = transport_modes.get(doc.mode_of_transport)
if doc.mode_of_transport == 'Road':
if not doc.gst_transporter_id and not doc.vehicle_no:
frappe.throw(_('Either GST Transporter ID or Vehicle No is required if Mode of Transport is Road'))
if doc.vehicle_no:
data.vehicleNo = doc.vehicle_no.replace(' ', '')
if not doc.gst_vehicle_type:
frappe.throw(_('Vehicle Type is required if Mode of Transport is Road'))
else:
data.vehicleType = vehicle_types.get(doc.gst_vehicle_type)
else:
if not doc.lr_no or not doc.lr_date:
frappe.throw(_('Transport Receipt No and Date are mandatory for your chosen Mode of Transport'))
if doc.lr_no:
data.transDocNo = doc.lr_no
if doc.lr_date:
data.transDocDate = frappe.utils.formatdate(doc.lr_date, 'dd/mm/yyyy')
if doc.gst_transporter_id:
validate_gstin_check_digit(doc.gst_transporter_id, label='GST Transporter ID')
data.transporterId = doc.gst_transporter_id
return data
def validate_pincode(pincode, address):
pin_not_found = "Pin Code doesn't exist for {}"
incorrect_pin = "Pin Code for {} is incorrecty formatted. It must be 6 digits (without spaces)"
if not pincode:
frappe.throw(_(pin_not_found.format(address)))
pincode = pincode.replace(' ', '')
if not pincode.isdigit() or len(pincode) != 6:
frappe.throw(_(incorrect_pin.format(address)))
else:
return int(pincode)
def validate_state_code(state_code, address):
no_state_code = "GST State Code not found for {0}. Please set GST State in {0}"
if not state_code:
frappe.throw(_(no_state_code.format(address)))
else:
return int(state_code)
def get_gst_accounts(company, account_wise=False):
gst_accounts = frappe._dict()
gst_settings_accounts = frappe.get_all("GST Account",
filters={"parent": "GST Settings", "company": company},
fields=["cgst_account", "sgst_account", "igst_account", "cess_account"])
if not gst_settings_accounts:
frappe.throw(_("Please set GST Accounts in GST Settings"))
for d in gst_settings_accounts:
for acc, val in d.items():
if not account_wise:
gst_accounts.setdefault(acc, []).append(val)
elif val:
gst_accounts[val] = acc
return gst_accounts

View File

@@ -7,6 +7,7 @@ from frappe import _
from frappe.utils import flt, formatdate
from datetime import date
from six import iteritems
from erpnext.regional.india.utils import get_gst_accounts
def execute(filters=None):
return Gstr1Report(filters).run()
@@ -41,7 +42,7 @@ class Gstr1Report(object):
def run(self):
self.get_columns()
self.get_gst_accounts()
self.gst_accounts = get_gst_accounts(self.filters.company)
self.get_invoice_data()
if self.invoices:
@@ -60,7 +61,7 @@ class Gstr1Report(object):
for inv, items_based_on_rate in self.items_based_on_tax_rate.items():
invoice_details = self.invoices.get(inv)
for rate, items in items_based_on_rate.items():
row, taxable_value = self.get_row_data_for_invoice(inv, invoice_details, rate, items)
row = self.get_row_data_for_invoice(inv, invoice_details, rate, items)
if self.filters.get("type_of_business") == "CDNR":
row.append("Y" if invoice_details.posting_date <= date(2017, 7, 1) else "N")
@@ -117,7 +118,7 @@ class Gstr1Report(object):
for item_code, net_amount in self.invoice_items.get(invoice).items() if item_code in items])
row += [tax_rate or 0, taxable_value]
return row, taxable_value
return row
def get_invoice_data(self):
self.invoices = frappe._dict()
@@ -239,19 +240,6 @@ class Gstr1Report(object):
and frappe.db.get_value(self.doctype, invoice, "export_type") == "Without Payment of Tax":
self.items_based_on_tax_rate.setdefault(invoice, {}).setdefault(0, items.keys())
def get_gst_accounts(self):
self.gst_accounts = frappe._dict()
gst_settings_accounts = frappe.get_all("GST Account",
filters={"parent": "GST Settings", "company": self.filters.company},
fields=["cgst_account", "sgst_account", "igst_account", "cess_account"])
if not gst_settings_accounts:
frappe.throw(_("Please set GST Accounts in GST Settings"))
for d in gst_settings_accounts:
for acc, val in d.items():
self.gst_accounts.setdefault(acc, []).append(val)
def get_columns(self):
self.tax_columns = [
{

View File

@@ -124,10 +124,5 @@ frappe.ui.form.on("Customer", {
validate: function(frm) {
if(frm.doc.lead_name) frappe.model.clear_doc("Lead", frm.doc.lead_name);
var total = 0;
for (var idx in frm.doc.sales_team) {
total += frm.doc.sales_team[idx].allocated_percentage;
if (total > 100) frappe.throw(__("Total contribution percentage can't exceed 100"));
}
},
});

View File

@@ -60,6 +60,10 @@ class Customer(TransactionBase):
if self.loyalty_program == customer.loyalty_program and not self.loyalty_program_tier:
self.loyalty_program_tier = customer.loyalty_program_tier
if self.sales_team:
if sum([member.allocated_percentage or 0 for member in self.sales_team]) != 100:
frappe.throw(_("Total contribution percentage should be equal to 100"))
def check_customer_group_change(self):
frappe.flags.customer_group_changed = False
@@ -104,10 +108,6 @@ class Customer(TransactionBase):
if self.lead_name:
frappe.db.set_value('Lead', self.lead_name, 'status', 'Converted', update_modified=False)
for doctype in ('Opportunity', 'Quotation'):
for d in frappe.get_all(doctype, {'lead': self.lead_name}):
frappe.db.set_value(doctype, d.name, 'customer', self.name, update_modified=False)
def create_lead_address_contact(self):
if self.lead_name:
# assign lead address to customer (if already not set)

View File

@@ -6,6 +6,10 @@ def get_data():
'heatmap': True,
'heatmap_message': _('This is based on transactions against this Customer. See timeline below for details'),
'fieldname': 'customer',
'non_standard_fieldnames': {
'Quotation': 'party_name',
'Opportunity': 'party_name'
},
'transactions': [
{
'label': _('Pre Sales'),

View File

@@ -8,15 +8,27 @@ frappe.ui.form.on('Quotation', {
setup: function(frm) {
frm.custom_make_buttons = {
'Sales Order': 'Make Sales Order'
}
},
frm.set_query("quotation_to", function() {
return{
"filters": {
"name": ["in", ["Customer", "Lead"]],
}
}
});
},
refresh: function(frm) {
frm.trigger("set_label");
frm.trigger("set_dynamic_field_label");
},
quotation_to: function(frm) {
frm.trigger("set_label");
frm.trigger("toggle_reqd_lead_customer");
frm.trigger("set_dynamic_field_label");
},
set_label: function(frm) {
@@ -28,16 +40,18 @@ erpnext.selling.QuotationController = erpnext.selling.SellingController.extend({
onload: function(doc, dt, dn) {
var me = this;
this._super(doc, dt, dn);
if(doc.customer && !doc.quotation_to)
doc.quotation_to = "Customer";
else if(doc.lead && !doc.quotation_to)
doc.quotation_to = "Lead";
},
party_name: function() {
var me = this;
erpnext.utils.get_party_details(this.frm, null, null, function() {
me.apply_price_list();
});
},
refresh: function(doc, dt, dn) {
this._super(doc, dt, dn);
doctype = doc.quotation_to == 'Customer' ? 'Customer':'Lead';
frappe.dynamic_link = {doc: this.frm.doc, fieldname: doctype.toLowerCase(), doctype: doctype}
frappe.dynamic_link = {doc: this.frm.doc, fieldname: 'party_name', doctype: doctype}
var me = this;
@@ -97,33 +111,46 @@ erpnext.selling.QuotationController = erpnext.selling.SellingController.extend({
},
quotation_to: function() {
var me = this;
if (this.frm.doc.quotation_to == "Lead") {
this.frm.set_value("customer", null);
this.frm.set_value("contact_person", null);
} else if (this.frm.doc.quotation_to == "Customer") {
this.frm.set_value("lead", null);
set_dynamic_field_label: function(){
if (this.frm.doc.quotation_to == "Customer")
{
this.frm.set_df_property("party_name", "label", "Customer");
this.frm.fields_dict.party_name.get_query = null;
}
this.toggle_reqd_lead_customer();
if (this.frm.doc.quotation_to == "Lead")
{
this.frm.set_df_property("party_name", "label", "Lead");
this.frm.fields_dict.party_name.get_query = function() {
return{ query: "erpnext.controllers.queries.lead_query" }
}
}
},
toggle_reqd_lead_customer: function() {
var me = this;
this.frm.toggle_reqd("lead", this.frm.doc.quotation_to == "Lead");
this.frm.toggle_reqd("customer", this.frm.doc.quotation_to == "Customer");
// to overwrite the customer_filter trigger from queries.js
this.frm.set_query('customer_address', erpnext.queries.address_query);
this.frm.set_query('shipping_address_name', erpnext.queries.address_query);
this.frm.toggle_reqd("party_name", this.frm.doc.quotation_to);
this.frm.set_query('customer_address', this.address_query);
this.frm.set_query('shipping_address_name', this.address_query);
},
tc_name: function() {
this.get_terms();
},
address_query: function(doc) {
return {
query: 'frappe.contacts.doctype.address.address.address_query',
filters: {
link_doctype: frappe.dynamic_link.doctype,
link_name: doc.party_name
}
};
},
validate_company_and_party: function(party_field) {
if(!this.frm.doc.quotation_to) {
frappe.msgprint(__("Please select a value for {0} quotation_to {1}", [this.frm.doc.doctype, this.frm.doc.name]));
@@ -163,10 +190,6 @@ erpnext.selling.QuotationController = erpnext.selling.SellingController.extend({
cur_frm.script_manager.make(erpnext.selling.QuotationController);
cur_frm.fields_dict.lead.get_query = function(doc,cdt,cdn) {
return{ query: "erpnext.controllers.queries.lead_query" }
}
cur_frm.cscript['Make Sales Order'] = function() {
frappe.model.open_mapped_doc({
method: "erpnext.selling.doctype.quotation.quotation.make_sales_order",

View File

@@ -20,6 +20,7 @@
"bold": 0,
"collapsible": 0,
"columns": 0,
"fetch_if_empty": 0,
"fieldname": "customer_section",
"fieldtype": "Section Break",
"hidden": 0,
@@ -53,6 +54,7 @@
"collapsible": 0,
"columns": 0,
"default": "{customer_name}",
"fetch_if_empty": 0,
"fieldname": "title",
"fieldtype": "Data",
"hidden": 1,
@@ -86,6 +88,7 @@
"collapsible": 0,
"columns": 0,
"default": "",
"fetch_if_empty": 0,
"fieldname": "naming_series",
"fieldtype": "Select",
"hidden": 0,
@@ -121,8 +124,9 @@
"collapsible": 0,
"columns": 0,
"default": "Customer",
"fetch_if_empty": 0,
"fieldname": "quotation_to",
"fieldtype": "Select",
"fieldtype": "Link",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
@@ -135,7 +139,7 @@
"no_copy": 0,
"oldfieldname": "quotation_to",
"oldfieldtype": "Select",
"options": "\nLead\nCustomer",
"options": "DocType",
"permlevel": 0,
"print_hide": 1,
"print_hide_if_no_value": 0,
@@ -155,22 +159,23 @@
"bold": 1,
"collapsible": 0,
"columns": 0,
"depends_on": "eval:doc.quotation_to == \"Customer\"",
"fieldname": "customer",
"fieldtype": "Link",
"depends_on": "",
"fetch_if_empty": 0,
"fieldname": "party_name",
"fieldtype": "Dynamic Link",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 1,
"in_list_view": 0,
"in_standard_filter": 1,
"label": "Customer",
"in_standard_filter": 0,
"label": "Party",
"length": 0,
"no_copy": 0,
"oldfieldname": "customer",
"oldfieldtype": "Link",
"options": "Customer",
"options": "quotation_to",
"permlevel": 0,
"print_hide": 1,
"print_hide_if_no_value": 0,
@@ -190,42 +195,8 @@
"bold": 1,
"collapsible": 0,
"columns": 0,
"depends_on": "eval:doc.quotation_to == \"Lead\"",
"fieldname": "lead",
"fieldtype": "Link",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 0,
"in_standard_filter": 1,
"label": "Lead",
"length": 0,
"no_copy": 0,
"oldfieldname": "lead",
"oldfieldtype": "Link",
"options": "Lead",
"permlevel": 0,
"print_hide": 1,
"print_hide_if_no_value": 0,
"read_only": 0,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"translatable": 0,
"unique": 0
},
{
"allow_bulk_edit": 0,
"allow_in_quick_entry": 0,
"allow_on_submit": 0,
"bold": 1,
"collapsible": 0,
"columns": 0,
"fetch_from": "customer.customer_name",
"fetch_from": "",
"fetch_if_empty": 0,
"fieldname": "customer_name",
"fieldtype": "Data",
"hidden": 1,
@@ -258,6 +229,7 @@
"bold": 0,
"collapsible": 0,
"columns": 0,
"fetch_if_empty": 0,
"fieldname": "column_break1",
"fieldtype": "Column Break",
"hidden": 0,
@@ -290,6 +262,7 @@
"bold": 0,
"collapsible": 0,
"columns": 0,
"fetch_if_empty": 0,
"fieldname": "amended_from",
"fieldtype": "Link",
"hidden": 0,
@@ -326,6 +299,7 @@
"collapsible": 0,
"columns": 0,
"description": "",
"fetch_if_empty": 0,
"fieldname": "company",
"fieldtype": "Link",
"hidden": 0,
@@ -362,6 +336,7 @@
"collapsible": 0,
"columns": 0,
"default": "Today",
"fetch_if_empty": 0,
"fieldname": "transaction_date",
"fieldtype": "Date",
"hidden": 0,
@@ -396,6 +371,7 @@
"bold": 0,
"collapsible": 0,
"columns": 0,
"fetch_if_empty": 0,
"fieldname": "valid_till",
"fieldtype": "Date",
"hidden": 0,
@@ -429,6 +405,7 @@
"collapsible": 0,
"columns": 0,
"default": "Sales",
"fetch_if_empty": 0,
"fieldname": "order_type",
"fieldtype": "Select",
"hidden": 0,
@@ -464,7 +441,8 @@
"collapsible": 1,
"collapsible_depends_on": "",
"columns": 0,
"depends_on": "eval:(doc.customer || doc.lead)",
"depends_on": "eval:doc.party_name",
"fetch_if_empty": 0,
"fieldname": "contact_section",
"fieldtype": "Section Break",
"hidden": 0,
@@ -497,6 +475,7 @@
"bold": 0,
"collapsible": 0,
"columns": 0,
"fetch_if_empty": 0,
"fieldname": "customer_address",
"fieldtype": "Link",
"hidden": 0,
@@ -529,6 +508,7 @@
"bold": 0,
"collapsible": 0,
"columns": 0,
"fetch_if_empty": 0,
"fieldname": "address_display",
"fieldtype": "Small Text",
"hidden": 0,
@@ -562,7 +542,8 @@
"bold": 0,
"collapsible": 0,
"columns": 0,
"depends_on": "eval:doc.customer",
"depends_on": "",
"fetch_if_empty": 0,
"fieldname": "contact_person",
"fieldtype": "Link",
"hidden": 0,
@@ -597,6 +578,7 @@
"bold": 0,
"collapsible": 0,
"columns": 0,
"fetch_if_empty": 0,
"fieldname": "contact_display",
"fieldtype": "Small Text",
"hidden": 0,
@@ -628,6 +610,7 @@
"bold": 0,
"collapsible": 0,
"columns": 0,
"fetch_if_empty": 0,
"fieldname": "contact_mobile",
"fieldtype": "Small Text",
"hidden": 0,
@@ -659,6 +642,7 @@
"bold": 0,
"collapsible": 0,
"columns": 0,
"fetch_if_empty": 0,
"fieldname": "contact_email",
"fieldtype": "Data",
"hidden": 1,
@@ -692,6 +676,7 @@
"collapsible": 0,
"columns": 0,
"depends_on": "customer",
"fetch_if_empty": 0,
"fieldname": "col_break98",
"fieldtype": "Column Break",
"hidden": 0,
@@ -723,6 +708,7 @@
"bold": 0,
"collapsible": 0,
"columns": 0,
"fetch_if_empty": 0,
"fieldname": "shipping_address_name",
"fieldtype": "Link",
"hidden": 0,
@@ -755,6 +741,7 @@
"bold": 0,
"collapsible": 0,
"columns": 0,
"fetch_if_empty": 0,
"fieldname": "shipping_address",
"fieldtype": "Small Text",
"hidden": 0,
@@ -788,6 +775,7 @@
"columns": 0,
"depends_on": "customer",
"description": "",
"fetch_if_empty": 0,
"fieldname": "customer_group",
"fieldtype": "Link",
"hidden": 1,
@@ -823,6 +811,7 @@
"collapsible": 0,
"columns": 0,
"description": "",
"fetch_if_empty": 0,
"fieldname": "territory",
"fieldtype": "Link",
"hidden": 0,
@@ -855,6 +844,7 @@
"bold": 0,
"collapsible": 1,
"columns": 0,
"fetch_if_empty": 0,
"fieldname": "currency_and_price_list",
"fieldtype": "Section Break",
"hidden": 0,
@@ -887,6 +877,7 @@
"bold": 0,
"collapsible": 0,
"columns": 0,
"fetch_if_empty": 0,
"fieldname": "currency",
"fieldtype": "Link",
"hidden": 0,
@@ -923,6 +914,7 @@
"collapsible": 0,
"columns": 0,
"description": "Rate at which customer's currency is converted to company's base currency",
"fetch_if_empty": 0,
"fieldname": "conversion_rate",
"fieldtype": "Float",
"hidden": 0,
@@ -958,6 +950,7 @@
"bold": 0,
"collapsible": 0,
"columns": 0,
"fetch_if_empty": 0,
"fieldname": "column_break2",
"fieldtype": "Column Break",
"hidden": 0,
@@ -989,6 +982,7 @@
"bold": 0,
"collapsible": 0,
"columns": 0,
"fetch_if_empty": 0,
"fieldname": "selling_price_list",
"fieldtype": "Link",
"hidden": 0,
@@ -1024,6 +1018,7 @@
"bold": 0,
"collapsible": 0,
"columns": 0,
"fetch_if_empty": 0,
"fieldname": "price_list_currency",
"fieldtype": "Link",
"hidden": 0,
@@ -1057,6 +1052,7 @@
"collapsible": 0,
"columns": 0,
"description": "Rate at which Price list currency is converted to company's base currency",
"fetch_if_empty": 0,
"fieldname": "plc_conversion_rate",
"fieldtype": "Float",
"hidden": 0,
@@ -1089,6 +1085,7 @@
"bold": 0,
"collapsible": 0,
"columns": 0,
"fetch_if_empty": 0,
"fieldname": "ignore_pricing_rule",
"fieldtype": "Check",
"hidden": 0,
@@ -1120,6 +1117,7 @@
"bold": 0,
"collapsible": 0,
"columns": 0,
"fetch_if_empty": 0,
"fieldname": "items_section",
"fieldtype": "Section Break",
"hidden": 0,
@@ -1153,6 +1151,7 @@
"bold": 0,
"collapsible": 0,
"columns": 0,
"fetch_if_empty": 0,
"fieldname": "items",
"fieldtype": "Table",
"hidden": 0,
@@ -1188,6 +1187,7 @@
"bold": 0,
"collapsible": 0,
"columns": 0,
"fetch_if_empty": 0,
"fieldname": "sec_break23",
"fieldtype": "Section Break",
"hidden": 0,
@@ -1218,6 +1218,7 @@
"bold": 0,
"collapsible": 0,
"columns": 0,
"fetch_if_empty": 0,
"fieldname": "total_qty",
"fieldtype": "Float",
"hidden": 0,
@@ -1250,6 +1251,7 @@
"bold": 0,
"collapsible": 0,
"columns": 0,
"fetch_if_empty": 0,
"fieldname": "base_total",
"fieldtype": "Currency",
"hidden": 0,
@@ -1283,6 +1285,7 @@
"bold": 0,
"collapsible": 0,
"columns": 0,
"fetch_if_empty": 0,
"fieldname": "base_net_total",
"fieldtype": "Currency",
"hidden": 0,
@@ -1318,6 +1321,7 @@
"bold": 0,
"collapsible": 0,
"columns": 0,
"fetch_if_empty": 0,
"fieldname": "column_break_28",
"fieldtype": "Column Break",
"hidden": 0,
@@ -1348,6 +1352,7 @@
"bold": 0,
"collapsible": 0,
"columns": 0,
"fetch_if_empty": 0,
"fieldname": "total",
"fieldtype": "Currency",
"hidden": 0,
@@ -1381,6 +1386,7 @@
"bold": 0,
"collapsible": 0,
"columns": 0,
"fetch_if_empty": 0,
"fieldname": "net_total",
"fieldtype": "Currency",
"hidden": 0,
@@ -1413,6 +1419,7 @@
"bold": 0,
"collapsible": 0,
"columns": 0,
"fetch_if_empty": 0,
"fieldname": "total_net_weight",
"fieldtype": "Float",
"hidden": 0,
@@ -1445,6 +1452,7 @@
"bold": 0,
"collapsible": 0,
"columns": 0,
"fetch_if_empty": 0,
"fieldname": "taxes_section",
"fieldtype": "Section Break",
"hidden": 0,
@@ -1478,6 +1486,7 @@
"bold": 0,
"collapsible": 0,
"columns": 0,
"fetch_if_empty": 0,
"fieldname": "taxes_and_charges",
"fieldtype": "Link",
"hidden": 0,
@@ -1512,6 +1521,7 @@
"bold": 0,
"collapsible": 0,
"columns": 0,
"fetch_if_empty": 0,
"fieldname": "column_break_34",
"fieldtype": "Column Break",
"hidden": 0,
@@ -1542,6 +1552,7 @@
"bold": 0,
"collapsible": 0,
"columns": 0,
"fetch_if_empty": 0,
"fieldname": "shipping_rule",
"fieldtype": "Link",
"hidden": 0,
@@ -1575,6 +1586,7 @@
"bold": 0,
"collapsible": 0,
"columns": 0,
"fetch_if_empty": 0,
"fieldname": "section_break_36",
"fieldtype": "Section Break",
"hidden": 0,
@@ -1605,6 +1617,7 @@
"bold": 0,
"collapsible": 0,
"columns": 0,
"fetch_if_empty": 0,
"fieldname": "taxes",
"fieldtype": "Table",
"hidden": 0,
@@ -1639,6 +1652,7 @@
"bold": 0,
"collapsible": 1,
"columns": 0,
"fetch_if_empty": 0,
"fieldname": "sec_tax_breakup",
"fieldtype": "Section Break",
"hidden": 0,
@@ -1671,6 +1685,7 @@
"bold": 0,
"collapsible": 0,
"columns": 0,
"fetch_if_empty": 0,
"fieldname": "other_charges_calculation",
"fieldtype": "Text",
"hidden": 0,
@@ -1703,6 +1718,7 @@
"bold": 0,
"collapsible": 0,
"columns": 0,
"fetch_if_empty": 0,
"fieldname": "section_break_39",
"fieldtype": "Section Break",
"hidden": 0,
@@ -1733,6 +1749,7 @@
"bold": 0,
"collapsible": 0,
"columns": 0,
"fetch_if_empty": 0,
"fieldname": "base_total_taxes_and_charges",
"fieldtype": "Currency",
"hidden": 0,
@@ -1767,6 +1784,7 @@
"bold": 0,
"collapsible": 0,
"columns": 0,
"fetch_if_empty": 0,
"fieldname": "column_break_42",
"fieldtype": "Column Break",
"hidden": 0,
@@ -1797,6 +1815,7 @@
"bold": 0,
"collapsible": 0,
"columns": 0,
"fetch_if_empty": 0,
"fieldname": "total_taxes_and_charges",
"fieldtype": "Currency",
"hidden": 0,
@@ -1830,6 +1849,7 @@
"collapsible": 1,
"collapsible_depends_on": "discount_amount",
"columns": 0,
"fetch_if_empty": 0,
"fieldname": "section_break_44",
"fieldtype": "Section Break",
"hidden": 0,
@@ -1863,6 +1883,7 @@
"collapsible": 0,
"columns": 0,
"default": "Grand Total",
"fetch_if_empty": 0,
"fieldname": "apply_discount_on",
"fieldtype": "Select",
"hidden": 0,
@@ -1896,6 +1917,7 @@
"bold": 0,
"collapsible": 0,
"columns": 0,
"fetch_if_empty": 0,
"fieldname": "base_discount_amount",
"fieldtype": "Currency",
"hidden": 0,
@@ -1929,6 +1951,7 @@
"bold": 0,
"collapsible": 0,
"columns": 0,
"fetch_if_empty": 0,
"fieldname": "column_break_46",
"fieldtype": "Column Break",
"hidden": 0,
@@ -1960,6 +1983,7 @@
"bold": 0,
"collapsible": 0,
"columns": 0,
"fetch_if_empty": 0,
"fieldname": "additional_discount_percentage",
"fieldtype": "Float",
"hidden": 0,
@@ -1992,6 +2016,7 @@
"bold": 0,
"collapsible": 0,
"columns": 0,
"fetch_if_empty": 0,
"fieldname": "discount_amount",
"fieldtype": "Currency",
"hidden": 0,
@@ -2024,6 +2049,7 @@
"bold": 0,
"collapsible": 0,
"columns": 0,
"fetch_if_empty": 0,
"fieldname": "totals",
"fieldtype": "Section Break",
"hidden": 0,
@@ -2057,6 +2083,7 @@
"bold": 0,
"collapsible": 0,
"columns": 0,
"fetch_if_empty": 0,
"fieldname": "base_grand_total",
"fieldtype": "Currency",
"hidden": 0,
@@ -2092,6 +2119,7 @@
"bold": 0,
"collapsible": 0,
"columns": 0,
"fetch_if_empty": 0,
"fieldname": "base_rounding_adjustment",
"fieldtype": "Currency",
"hidden": 0,
@@ -2126,6 +2154,7 @@
"collapsible": 0,
"columns": 0,
"description": "In Words will be visible once you save the Quotation.",
"fetch_if_empty": 0,
"fieldname": "base_in_words",
"fieldtype": "Data",
"hidden": 0,
@@ -2160,6 +2189,7 @@
"bold": 0,
"collapsible": 0,
"columns": 0,
"fetch_if_empty": 0,
"fieldname": "base_rounded_total",
"fieldtype": "Currency",
"hidden": 0,
@@ -2195,6 +2225,7 @@
"bold": 0,
"collapsible": 0,
"columns": 0,
"fetch_if_empty": 0,
"fieldname": "column_break3",
"fieldtype": "Column Break",
"hidden": 0,
@@ -2227,6 +2258,7 @@
"bold": 0,
"collapsible": 0,
"columns": 0,
"fetch_if_empty": 0,
"fieldname": "grand_total",
"fieldtype": "Currency",
"hidden": 0,
@@ -2262,6 +2294,7 @@
"bold": 0,
"collapsible": 0,
"columns": 0,
"fetch_if_empty": 0,
"fieldname": "rounding_adjustment",
"fieldtype": "Currency",
"hidden": 0,
@@ -2295,6 +2328,7 @@
"bold": 1,
"collapsible": 0,
"columns": 0,
"fetch_if_empty": 0,
"fieldname": "rounded_total",
"fieldtype": "Currency",
"hidden": 0,
@@ -2330,6 +2364,7 @@
"bold": 0,
"collapsible": 0,
"columns": 0,
"fetch_if_empty": 0,
"fieldname": "in_words",
"fieldtype": "Data",
"hidden": 0,
@@ -2366,6 +2401,7 @@
"collapsible_depends_on": "",
"columns": 0,
"depends_on": "",
"fetch_if_empty": 0,
"fieldname": "payment_schedule_section",
"fieldtype": "Section Break",
"hidden": 0,
@@ -2398,6 +2434,7 @@
"bold": 0,
"collapsible": 0,
"columns": 0,
"fetch_if_empty": 0,
"fieldname": "payment_terms_template",
"fieldtype": "Link",
"hidden": 0,
@@ -2431,6 +2468,7 @@
"bold": 0,
"collapsible": 0,
"columns": 0,
"fetch_if_empty": 0,
"fieldname": "payment_schedule",
"fieldtype": "Table",
"hidden": 0,
@@ -2465,6 +2503,7 @@
"collapsible": 1,
"collapsible_depends_on": "terms",
"columns": 0,
"fetch_if_empty": 0,
"fieldname": "terms_section_break",
"fieldtype": "Section Break",
"hidden": 0,
@@ -2498,6 +2537,7 @@
"bold": 0,
"collapsible": 0,
"columns": 0,
"fetch_if_empty": 0,
"fieldname": "tc_name",
"fieldtype": "Link",
"hidden": 0,
@@ -2532,6 +2572,7 @@
"bold": 0,
"collapsible": 0,
"columns": 0,
"fetch_if_empty": 0,
"fieldname": "terms",
"fieldtype": "Text Editor",
"hidden": 0,
@@ -2565,6 +2606,7 @@
"bold": 0,
"collapsible": 1,
"columns": 0,
"fetch_if_empty": 0,
"fieldname": "print_settings",
"fieldtype": "Section Break",
"hidden": 0,
@@ -2597,6 +2639,7 @@
"bold": 0,
"collapsible": 0,
"columns": 0,
"fetch_if_empty": 0,
"fieldname": "letter_head",
"fieldtype": "Link",
"hidden": 0,
@@ -2631,6 +2674,7 @@
"bold": 0,
"collapsible": 0,
"columns": 0,
"fetch_if_empty": 0,
"fieldname": "group_same_items",
"fieldtype": "Check",
"hidden": 0,
@@ -2663,6 +2707,7 @@
"bold": 0,
"collapsible": 0,
"columns": 0,
"fetch_if_empty": 0,
"fieldname": "column_break_73",
"fieldtype": "Column Break",
"hidden": 0,
@@ -2694,6 +2739,7 @@
"bold": 0,
"collapsible": 0,
"columns": 0,
"fetch_if_empty": 0,
"fieldname": "select_print_heading",
"fieldtype": "Link",
"hidden": 0,
@@ -2728,6 +2774,7 @@
"bold": 0,
"collapsible": 0,
"columns": 0,
"fetch_if_empty": 0,
"fieldname": "language",
"fieldtype": "Data",
"hidden": 0,
@@ -2760,6 +2807,7 @@
"bold": 0,
"collapsible": 0,
"columns": 0,
"fetch_if_empty": 0,
"fieldname": "subscription_section",
"fieldtype": "Section Break",
"hidden": 0,
@@ -2792,6 +2840,7 @@
"bold": 0,
"collapsible": 0,
"columns": 0,
"fetch_if_empty": 0,
"fieldname": "auto_repeat",
"fieldtype": "Link",
"hidden": 0,
@@ -2826,6 +2875,7 @@
"collapsible": 0,
"columns": 0,
"depends_on": "eval: doc.auto_repeat",
"fetch_if_empty": 0,
"fieldname": "update_auto_repeat_reference",
"fieldtype": "Button",
"hidden": 0,
@@ -2858,6 +2908,7 @@
"bold": 0,
"collapsible": 1,
"columns": 0,
"fetch_if_empty": 0,
"fieldname": "more_info",
"fieldtype": "Section Break",
"hidden": 0,
@@ -2891,6 +2942,7 @@
"bold": 0,
"collapsible": 0,
"columns": 0,
"fetch_if_empty": 0,
"fieldname": "campaign",
"fieldtype": "Link",
"hidden": 0,
@@ -2925,6 +2977,7 @@
"bold": 0,
"collapsible": 0,
"columns": 0,
"fetch_if_empty": 0,
"fieldname": "source",
"fieldtype": "Link",
"hidden": 0,
@@ -2960,6 +3013,7 @@
"collapsible": 0,
"columns": 0,
"depends_on": "eval:doc.status===\"Lost\"",
"fetch_if_empty": 0,
"fieldname": "order_lost_reason",
"fieldtype": "Small Text",
"hidden": 0,
@@ -2993,6 +3047,7 @@
"bold": 0,
"collapsible": 0,
"columns": 0,
"fetch_if_empty": 0,
"fieldname": "column_break4",
"fieldtype": "Column Break",
"hidden": 0,
@@ -3026,6 +3081,7 @@
"collapsible": 0,
"columns": 0,
"default": "Draft",
"fetch_if_empty": 0,
"fieldname": "status",
"fieldtype": "Select",
"hidden": 0,
@@ -3060,6 +3116,7 @@
"bold": 0,
"collapsible": 0,
"columns": 0,
"fetch_if_empty": 0,
"fieldname": "enq_det",
"fieldtype": "Text",
"hidden": 1,
@@ -3093,6 +3150,7 @@
"bold": 0,
"collapsible": 0,
"columns": 0,
"fetch_if_empty": 0,
"fieldname": "supplier_quotation",
"fieldtype": "Link",
"hidden": 0,
@@ -3126,6 +3184,7 @@
"bold": 0,
"collapsible": 0,
"columns": 0,
"fetch_if_empty": 0,
"fieldname": "opportunity",
"fieldtype": "Link",
"hidden": 0,
@@ -3165,7 +3224,7 @@
"istable": 0,
"max_attachments": 1,
"menu_index": 0,
"modified": "2019-01-07 16:51:55.604845",
"modified": "2019-05-07 14:29:22.565474",
"modified_by": "Administrator",
"module": "Selling",
"name": "Quotation",
@@ -3331,11 +3390,11 @@
"quick_entry": 0,
"read_only": 0,
"read_only_onload": 1,
"search_fields": "status,transaction_date,customer,lead,order_type",
"search_fields": "status,transaction_date,party_name,order_type",
"show_name_in_global_search": 1,
"sort_field": "modified",
"sort_order": "DESC",
"timeline_field": "customer",
"timeline_field": "party_name",
"title_field": "title",
"track_changes": 0,
"track_seen": 0,

View File

@@ -28,11 +28,11 @@ class Quotation(SellingController):
self.update_opportunity()
self.validate_order_type()
self.validate_uom_is_integer("stock_uom", "qty")
self.validate_quotation_to()
self.validate_valid_till()
self.set_customer_name()
if self.items:
self.with_items = 1
def validate_valid_till(self):
if self.valid_till and self.valid_till < self.transaction_date:
frappe.throw(_("Valid till date cannot be before transaction date"))
@@ -43,16 +43,16 @@ class Quotation(SellingController):
def validate_order_type(self):
super(Quotation, self).validate_order_type()
def validate_quotation_to(self):
if self.customer:
self.quotation_to = "Customer"
self.lead = None
elif self.lead:
self.quotation_to = "Lead"
def update_lead(self):
if self.lead:
frappe.get_doc("Lead", self.lead).set_status(update=True)
if self.quotation_to == "Lead" and self.party_name:
frappe.get_doc("Lead", self.party_name).set_status(update=True)
def set_customer_name(self):
if self.party_name and self.quotation_to == 'Customer':
self.customer_name = frappe.db.get_value("Customer", self.party_name, "customer_name")
elif self.party_name and self.quotation_to == 'Lead':
lead_name, company_name = frappe.db.get_value("Lead", self.party_name, ["lead_name", "company_name"])
self.customer_name = company_name or lead_name
def update_opportunity(self):
for opportunity in list(set([d.prevdoc_docname for d in self.get("items")])):
@@ -209,36 +209,41 @@ def _make_sales_invoice(source_name, target_doc=None, ignore_permissions=False):
}
}, target_doc, set_missing_values, ignore_permissions=ignore_permissions)
return doclist
return doclist
def _make_customer(source_name, ignore_permissions=False):
quotation = frappe.db.get_value("Quotation", source_name, ["lead", "order_type", "customer"])
if quotation and quotation[0] and not quotation[2]:
lead_name = quotation[0]
customer_name = frappe.db.get_value("Customer", {"lead_name": lead_name},
["name", "customer_name"], as_dict=True)
if not customer_name:
from erpnext.crm.doctype.lead.lead import _make_customer
customer_doclist = _make_customer(lead_name, ignore_permissions=ignore_permissions)
customer = frappe.get_doc(customer_doclist)
customer.flags.ignore_permissions = ignore_permissions
if quotation[1] == "Shopping Cart":
customer.customer_group = frappe.db.get_value("Shopping Cart Settings", None,
"default_customer_group")
quotation = frappe.db.get_value("Quotation",
source_name, ["order_type", "party_name", "customer_name"], as_dict=1)
try:
customer.insert()
return customer
except frappe.NameError:
if frappe.defaults.get_global_default('cust_master_name') == "Customer Name":
customer.run_method("autoname")
customer.name += "-" + lead_name
if quotation and quotation.get('party_name'):
if not frappe.db.exists("Customer", quotation.get("party_name")):
lead_name = quotation.get("party_name")
customer_name = frappe.db.get_value("Customer", {"lead_name": lead_name},
["name", "customer_name"], as_dict=True)
if not customer_name:
from erpnext.crm.doctype.lead.lead import _make_customer
customer_doclist = _make_customer(lead_name, ignore_permissions=ignore_permissions)
customer = frappe.get_doc(customer_doclist)
customer.flags.ignore_permissions = ignore_permissions
if quotation.get("party_name") == "Shopping Cart":
customer.customer_group = frappe.db.get_value("Shopping Cart Settings", None,
"default_customer_group")
try:
customer.insert()
return customer
else:
raise
except frappe.MandatoryError:
frappe.local.message_log = []
frappe.throw(_("Please create Customer from Lead {0}").format(lead_name))
except frappe.NameError:
if frappe.defaults.get_global_default('cust_master_name') == "Customer Name":
customer.run_method("autoname")
customer.name += "-" + lead_name
customer.insert()
return customer
else:
raise
except frappe.MandatoryError:
frappe.local.message_log = []
frappe.throw(_("Please create Customer from Lead {0}").format(lead_name))
else:
return customer_name
else:
return customer_name
return frappe.get_doc("Customer", quotation.get("party_name"))

View File

@@ -203,15 +203,15 @@ class TestQuotation(unittest.TestCase):
test_records = frappe.get_test_records('Quotation')
def get_quotation_dict(customer=None, item_code=None):
if not customer:
customer = '_Test Customer'
def get_quotation_dict(party_name=None, item_code=None):
if not party_name:
party_name = '_Test Customer'
if not item_code:
item_code = '_Test Item'
return {
'doctype': 'Quotation',
'customer': customer,
'party_name': party_name,
'items': [
{
'item_code': item_code,
@@ -229,7 +229,7 @@ def make_quotation(**args):
qo.transaction_date = args.transaction_date
qo.company = args.company or "_Test Company"
qo.customer = args.customer or "_Test Customer"
qo.party_name = args.party_name or "_Test Customer"
qo.currency = args.currency or "INR"
if args.selling_price_list:
qo.selling_price_list = args.selling_price_list

View File

@@ -1,37 +1,37 @@
[
{
"company": "_Test Company",
"conversion_rate": 1.0,
"currency": "INR",
"customer": "_Test Customer",
"customer_group": "_Test Customer Group",
"customer_name": "_Test Customer",
"doctype": "Quotation",
"base_grand_total": 1000.0,
"grand_total": 1000.0,
"order_type": "Sales",
"plc_conversion_rate": 1.0,
"price_list_currency": "INR",
"items": [
{
"base_amount": 1000.0,
"base_rate": 100.0,
"description": "CPU",
"doctype": "Quotation Item",
"item_code": "_Test Item Home Desktop 100",
"item_name": "CPU",
"parentfield": "items",
"qty": 10.0,
"rate": 100.0,
"uom": "_Test UOM 1",
"stock_uom": "_Test UOM 1",
"conversion_factor": 1.0
}
],
"quotation_to": "Customer",
"selling_price_list": "_Test Price List",
"territory": "_Test Territory",
"transaction_date": "2013-02-21",
"valid_till": "2013-03-21"
}
{
"company": "_Test Company",
"conversion_rate": 1.0,
"currency": "INR",
"party_name": "_Test Customer",
"customer_group": "_Test Customer Group",
"customer_name": "_Test Customer",
"doctype": "Quotation",
"base_grand_total": 1000.0,
"grand_total": 1000.0,
"order_type": "Sales",
"plc_conversion_rate": 1.0,
"price_list_currency": "INR",
"items": [
{
"base_amount": 1000.0,
"base_rate": 100.0,
"description": "CPU",
"doctype": "Quotation Item",
"item_code": "_Test Item Home Desktop 100",
"item_name": "CPU",
"parentfield": "items",
"qty": 10.0,
"rate": 100.0,
"uom": "_Test UOM 1",
"stock_uom": "_Test UOM 1",
"conversion_factor": 1.0
}
],
"quotation_to": "Customer",
"selling_price_list": "_Test Price List",
"territory": "_Test Territory",
"transaction_date": "2013-02-21",
"valid_till": "2013-03-21"
}
]

View File

@@ -544,7 +544,7 @@ def make_project(source_name, target_doc=None):
"Sales Order Item": {
"doctype": "Project Task",
"field_map": {
"description": "title",
"item_code": "title",
},
}
}, target_doc, postprocess)

File diff suppressed because it is too large Load Diff

View File

@@ -33,7 +33,7 @@ def make_sample_data(domains, make_dependent = False):
def make_opportunity(items, customer):
b = frappe.get_doc({
"doctype": "Opportunity",
"enquiry_from": "Customer",
"opportunity_from": "Customer",
"customer": customer,
"opportunity_type": _("Sales"),
"with_items": 1

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