Merge branch 'develop' into gratuity

This commit is contained in:
Anurag Mishra 2021-01-05 14:47:38 +05:30 committed by GitHub
commit b864b09d40
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
40 changed files with 671 additions and 320 deletions

View File

@ -159,10 +159,10 @@ class TestBudget(unittest.TestCase):
budget = make_budget(budget_against="Cost Center") budget = make_budget(budget_against="Cost Center")
month = now_datetime().month month = now_datetime().month
if month > 10: if month > 9:
month = 10 month = 9
for i in range(month): for i in range(month+1):
jv = make_journal_entry("_Test Account Cost for Goods Sold - _TC", jv = make_journal_entry("_Test Account Cost for Goods Sold - _TC",
"_Test Bank - _TC", 20000, "_Test Cost Center - _TC", posting_date=nowdate(), submit=True) "_Test Bank - _TC", 20000, "_Test Cost Center - _TC", posting_date=nowdate(), submit=True)
@ -181,10 +181,10 @@ class TestBudget(unittest.TestCase):
budget = make_budget(budget_against="Project") budget = make_budget(budget_against="Project")
month = now_datetime().month month = now_datetime().month
if month > 10: if month > 9:
month = 10 month = 9
for i in range(month): for i in range(month + 1):
jv = make_journal_entry("_Test Account Cost for Goods Sold - _TC", jv = make_journal_entry("_Test Account Cost for Goods Sold - _TC",
"_Test Bank - _TC", 20000, "_Test Cost Center - _TC", posting_date=nowdate(), submit=True, project="_Test Project") "_Test Bank - _TC", 20000, "_Test Cost Center - _TC", posting_date=nowdate(), submit=True, project="_Test Project")

View File

@ -267,6 +267,8 @@ class POSInvoice(SalesInvoice):
from erpnext.stock.get_item_details import get_pos_profile_item_details, get_pos_profile from erpnext.stock.get_item_details import get_pos_profile_item_details, get_pos_profile
if not self.pos_profile: if not self.pos_profile:
pos_profile = get_pos_profile(self.company) or {} pos_profile = get_pos_profile(self.company) or {}
if not pos_profile:
frappe.throw(_("No POS Profile found. Please create a New POS Profile first"))
self.pos_profile = pos_profile.get('name') self.pos_profile = pos_profile.get('name')
profile = {} profile = {}

View File

@ -4,7 +4,7 @@
from __future__ import unicode_literals from __future__ import unicode_literals
import frappe, erpnext import frappe, erpnext
import frappe.defaults import frappe.defaults
from frappe.utils import cint, flt, add_months, today, date_diff, getdate, add_days, cstr, nowdate, get_link_to_form from frappe.utils import cint, flt, getdate, add_days, cstr, nowdate, get_link_to_form, formatdate
from frappe import _, msgprint, throw from frappe import _, msgprint, throw
from erpnext.accounts.party import get_party_account, get_due_date from erpnext.accounts.party import get_party_account, get_due_date
from frappe.model.mapper import get_mapped_doc from frappe.model.mapper import get_mapped_doc
@ -549,7 +549,12 @@ class SalesInvoice(SellingController):
self.against_income_account = ','.join(against_acc) self.against_income_account = ','.join(against_acc)
def add_remarks(self): def add_remarks(self):
if not self.remarks: self.remarks = 'No Remarks' if not self.remarks:
if self.po_no and self.po_date:
self.remarks = _("Against Customer Order {0} dated {1}").format(self.po_no,
formatdate(self.po_date))
else:
self.remarks = _("No Remarks")
def validate_auto_set_posting_time(self): def validate_auto_set_posting_time(self):
# Don't auto set the posting date and time if invoice is amended # Don't auto set the posting date and time if invoice is amended

View File

@ -1885,8 +1885,8 @@ class TestSalesInvoice(unittest.TestCase):
"item_code": "_Test Item", "item_code": "_Test Item",
"uom": "Nos", "uom": "Nos",
"warehouse": "_Test Warehouse - _TC", "warehouse": "_Test Warehouse - _TC",
"qty": 2, "qty": 2000,
"rate": 100, "rate": 12,
"income_account": "Sales - _TC", "income_account": "Sales - _TC",
"expense_account": "Cost of Goods Sold - _TC", "expense_account": "Cost of Goods Sold - _TC",
"cost_center": "_Test Cost Center - _TC", "cost_center": "_Test Cost Center - _TC",
@ -1895,31 +1895,52 @@ class TestSalesInvoice(unittest.TestCase):
"item_code": "_Test Item 2", "item_code": "_Test Item 2",
"uom": "Nos", "uom": "Nos",
"warehouse": "_Test Warehouse - _TC", "warehouse": "_Test Warehouse - _TC",
"qty": 4, "qty": 420,
"rate": 150, "rate": 15,
"income_account": "Sales - _TC", "income_account": "Sales - _TC",
"expense_account": "Cost of Goods Sold - _TC", "expense_account": "Cost of Goods Sold - _TC",
"cost_center": "_Test Cost Center - _TC", "cost_center": "_Test Cost Center - _TC",
}) })
si.discount_amount = 100
si.save() si.save()
einvoice = make_einvoice(si) einvoice = make_einvoice(si)
total_item_ass_value = sum([d['AssAmt'] for d in einvoice['ItemList']]) total_item_ass_value = 0
total_item_cgst_value = sum([d['CgstAmt'] for d in einvoice['ItemList']]) total_item_cgst_value = 0
total_item_sgst_value = sum([d['SgstAmt'] for d in einvoice['ItemList']]) total_item_sgst_value = 0
total_item_igst_value = sum([d['IgstAmt'] for d in einvoice['ItemList']]) total_item_igst_value = 0
total_item_value = sum([d['TotItemVal'] for d in einvoice['ItemList']]) total_item_value = 0
for item in einvoice['ItemList']:
total_item_ass_value += item['AssAmt']
total_item_cgst_value += item['CgstAmt']
total_item_sgst_value += item['SgstAmt']
total_item_igst_value += item['IgstAmt']
total_item_value += item['TotItemVal']
self.assertTrue(item['AssAmt'], item['TotAmt'] - item['Discount'])
self.assertTrue(item['TotItemVal'], item['AssAmt'] + item['CgstAmt'] + item['SgstAmt'] + item['IgstAmt'])
value_details = einvoice['ValDtls']
self.assertEqual(einvoice['Version'], '1.1') self.assertEqual(einvoice['Version'], '1.1')
self.assertEqual(einvoice['ValDtls']['AssVal'], total_item_ass_value) self.assertEqual(value_details['AssVal'], total_item_ass_value)
self.assertEqual(einvoice['ValDtls']['CgstVal'], total_item_cgst_value) self.assertEqual(value_details['CgstVal'], total_item_cgst_value)
self.assertEqual(einvoice['ValDtls']['SgstVal'], total_item_sgst_value) self.assertEqual(value_details['SgstVal'], total_item_sgst_value)
self.assertEqual(einvoice['ValDtls']['IgstVal'], total_item_igst_value) self.assertEqual(value_details['IgstVal'], total_item_igst_value)
self.assertEqual(einvoice['ValDtls']['TotInvVal'], total_item_value)
self.assertEqual(
value_details['TotInvVal'],
value_details['AssVal'] + value_details['CgstVal']
+ value_details['SgstVal'] + value_details['IgstVal']
+ value_details['OthChrg'] - value_details['Discount']
)
self.assertEqual(value_details['TotInvVal'], si.base_grand_total)
self.assertTrue(einvoice['EwbDtls']) self.assertTrue(einvoice['EwbDtls'])
def make_sales_invoice_for_ewaybill(): def make_test_address_for_ewaybill():
if not frappe.db.exists('Address', '_Test Address for Eway bill-Billing'): if not frappe.db.exists('Address', '_Test Address for Eway bill-Billing'):
address = frappe.get_doc({ address = frappe.get_doc({
"address_line1": "_Test Address Line 1", "address_line1": "_Test Address Line 1",
@ -1968,6 +1989,7 @@ def make_sales_invoice_for_ewaybill():
address.save() address.save()
def make_test_transporter_for_ewaybill():
if not frappe.db.exists('Supplier', '_Test Transporter'): if not frappe.db.exists('Supplier', '_Test Transporter'):
frappe.get_doc({ frappe.get_doc({
"doctype": "Supplier", "doctype": "Supplier",
@ -1978,12 +2000,17 @@ def make_sales_invoice_for_ewaybill():
"is_transporter": 1 "is_transporter": 1
}).insert() }).insert()
def make_sales_invoice_for_ewaybill():
make_test_address_for_ewaybill()
make_test_transporter_for_ewaybill()
gst_settings = frappe.get_doc("GST Settings") gst_settings = frappe.get_doc("GST Settings")
gst_account = frappe.get_all( gst_account = frappe.get_all(
"GST Account", "GST Account",
fields=["cgst_account", "sgst_account", "igst_account"], fields=["cgst_account", "sgst_account", "igst_account"],
filters = {"company": "_Test Company"}) filters = {"company": "_Test Company"}
)
if not gst_account: if not gst_account:
gst_settings.append("gst_accounts", { gst_settings.append("gst_accounts", {
@ -1995,7 +2022,7 @@ def make_sales_invoice_for_ewaybill():
gst_settings.save() gst_settings.save()
si = create_sales_invoice(do_not_save =1, rate = '60000') si = create_sales_invoice(do_not_save=1, rate='60000')
si.distance = 2000 si.distance = 2000
si.company_address = "_Test Address for Eway bill-Billing" si.company_address = "_Test Address for Eway bill-Billing"

View File

@ -47,21 +47,22 @@ def get_data(filters):
for d in gl_entries: for d in gl_entries:
asset_data = assets_details.get(d.against_voucher) asset_data = assets_details.get(d.against_voucher)
if not asset_data.get("accumulated_depreciation_amount"): if asset_data:
asset_data.accumulated_depreciation_amount = d.debit if not asset_data.get("accumulated_depreciation_amount"):
else: asset_data.accumulated_depreciation_amount = d.debit
asset_data.accumulated_depreciation_amount += d.debit else:
asset_data.accumulated_depreciation_amount += d.debit
row = frappe._dict(asset_data) row = frappe._dict(asset_data)
row.update({ row.update({
"depreciation_amount": d.debit, "depreciation_amount": d.debit,
"depreciation_date": d.posting_date, "depreciation_date": d.posting_date,
"amount_after_depreciation": (flt(row.gross_purchase_amount) - "amount_after_depreciation": (flt(row.gross_purchase_amount) -
flt(row.accumulated_depreciation_amount)), flt(row.accumulated_depreciation_amount)),
"depreciation_entry": d.voucher_no "depreciation_entry": d.voucher_no
}) })
data.append(row) data.append(row)
return data return data

View File

@ -75,62 +75,70 @@ frappe.query_reports["Purchase Analytics"] = {
return Object.assign(options, { return Object.assign(options, {
checkboxColumn: true, checkboxColumn: true,
events: { events: {
onCheckRow: function(data) { onCheckRow: function (data) {
if (!data) return;
const data_doctype = $(
data[2].html
)[0].attributes.getNamedItem("data-doctype").value;
const tree_type = frappe.query_report.filters[0].value;
if (data_doctype != tree_type) return;
row_name = data[2].content; row_name = data[2].content;
length = data.length; length = data.length;
var tree_type = frappe.query_report.filters[0].value; if (tree_type == "Supplier") {
row_values = data
if(tree_type == "Supplier" || tree_type == "Item") { .slice(4, length - 1)
row_values = data.slice(4,length-1).map(function (column) { .map(function (column) {
return column.content; return column.content;
}) });
} } else if (tree_type == "Item") {
else { row_values = data
row_values = data.slice(3,length-1).map(function (column) { .slice(5, length - 1)
return column.content; .map(function (column) {
}) return column.content;
});
} else {
row_values = data
.slice(3, length - 1)
.map(function (column) {
return column.content;
});
} }
entry = { entry = {
'name':row_name, name: row_name,
'values':row_values values: row_values,
} };
let raw_data = frappe.query_report.chart.data; let raw_data = frappe.query_report.chart.data;
let new_datasets = raw_data.datasets; let new_datasets = raw_data.datasets;
var found = false; let element_found = new_datasets.some((element, index, array)=>{
if(element.name == row_name){
for(var i=0; i < new_datasets.length;i++){ array.splice(index, 1)
if(new_datasets[i].name == row_name){ return true
found = true;
new_datasets.splice(i,1);
break;
} }
} return false
})
if(!found){ if (!element_found) {
new_datasets.push(entry); new_datasets.push(entry);
} }
let new_data = { let new_data = {
labels: raw_data.labels, labels: raw_data.labels,
datasets: new_datasets datasets: new_datasets,
} };
chart_options = {
setTimeout(() => { data: new_data,
frappe.query_report.chart.update(new_data) type: "line",
},500) };
frappe.query_report.render_chart(chart_options);
setTimeout(() => {
frappe.query_report.chart.draw(true);
}, 1000)
frappe.query_report.raw_chart_data = new_data; frappe.query_report.raw_chart_data = new_data;
}, },
} },
}); });
} }
} }

View File

@ -328,6 +328,7 @@ def make_return_doc(doctype, source_name, target_doc=None):
target_doc.po_detail = source_doc.po_detail target_doc.po_detail = source_doc.po_detail
target_doc.pr_detail = source_doc.pr_detail target_doc.pr_detail = source_doc.pr_detail
target_doc.purchase_invoice_item = source_doc.name target_doc.purchase_invoice_item = source_doc.name
target_doc.price_list_rate = 0
elif doctype == "Delivery Note": elif doctype == "Delivery Note":
returned_qty_map = get_returned_qty_map_for_row(source_doc.name, doctype) returned_qty_map = get_returned_qty_map_for_row(source_doc.name, doctype)
@ -353,6 +354,7 @@ def make_return_doc(doctype, source_name, target_doc=None):
target_doc.dn_detail = source_doc.dn_detail target_doc.dn_detail = source_doc.dn_detail
target_doc.expense_account = source_doc.expense_account target_doc.expense_account = source_doc.expense_account
target_doc.sales_invoice_item = source_doc.name target_doc.sales_invoice_item = source_doc.name
target_doc.price_list_rate = 0
if default_warehouse_for_sales_return: if default_warehouse_for_sales_return:
target_doc.warehouse = default_warehouse_for_sales_return target_doc.warehouse = default_warehouse_for_sales_return

View File

@ -233,7 +233,7 @@ class SellingController(StockController):
'allow_zero_valuation': d.allow_zero_valuation_rate, 'allow_zero_valuation': d.allow_zero_valuation_rate,
'sales_invoice_item': d.get("sales_invoice_item"), 'sales_invoice_item': d.get("sales_invoice_item"),
'dn_detail': d.get("dn_detail"), 'dn_detail': d.get("dn_detail"),
'incoming_rate': p.incoming_rate 'incoming_rate': p.get("incoming_rate")
})) }))
else: else:
il.append(frappe._dict({ il.append(frappe._dict({
@ -252,7 +252,7 @@ class SellingController(StockController):
'allow_zero_valuation': d.allow_zero_valuation_rate, 'allow_zero_valuation': d.allow_zero_valuation_rate,
'sales_invoice_item': d.get("sales_invoice_item"), 'sales_invoice_item': d.get("sales_invoice_item"),
'dn_detail': d.get("dn_detail"), 'dn_detail': d.get("dn_detail"),
'incoming_rate': d.incoming_rate 'incoming_rate': d.get("incoming_rate")
})) }))
return il return il

View File

@ -11,6 +11,7 @@
"employee", "employee",
"employee_name", "employee_name",
"department", "department",
"company",
"column_break1", "column_break1",
"leave_type", "leave_type",
"from_date", "from_date",
@ -219,6 +220,15 @@
"label": "Leave Policy Assignment", "label": "Leave Policy Assignment",
"options": "Leave Policy Assignment", "options": "Leave Policy Assignment",
"read_only": 1 "read_only": 1
},
{
"fetch_from": "employee.company",
"fieldname": "company",
"fieldtype": "Link",
"label": "Company",
"options": "Company",
"read_only": 1,
"reqd": 1
} }
], ],
"icon": "fa fa-ok", "icon": "fa fa-ok",
@ -226,7 +236,7 @@
"index_web_pages_for_search": 1, "index_web_pages_for_search": 1,
"is_submittable": 1, "is_submittable": 1,
"links": [], "links": [],
"modified": "2020-08-20 14:25:10.314323", "modified": "2021-01-04 18:46:13.184104",
"modified_by": "Administrator", "modified_by": "Administrator",
"module": "HR", "module": "HR",
"name": "Leave Allocation", "name": "Leave Allocation",

View File

@ -1,4 +1,5 @@
{ {
"actions": [],
"creation": "2019-05-09 15:47:39.760406", "creation": "2019-05-09 15:47:39.760406",
"doctype": "DocType", "doctype": "DocType",
"engine": "InnoDB", "engine": "InnoDB",
@ -8,6 +9,7 @@
"leave_type", "leave_type",
"transaction_type", "transaction_type",
"transaction_name", "transaction_name",
"company",
"leaves", "leaves",
"column_break_7", "column_break_7",
"from_date", "from_date",
@ -106,12 +108,22 @@
"fieldtype": "Link", "fieldtype": "Link",
"label": "Holiday List", "label": "Holiday List",
"options": "Holiday List" "options": "Holiday List"
},
{
"fetch_from": "employee.company",
"fieldname": "company",
"fieldtype": "Link",
"label": "Company",
"options": "Company",
"read_only": 1,
"reqd": 1
} }
], ],
"in_create": 1, "in_create": 1,
"index_web_pages_for_search": 1, "index_web_pages_for_search": 1,
"is_submittable": 1, "is_submittable": 1,
"modified": "2020-09-04 12:16:36.569066", "links": [],
"modified": "2021-01-04 18:47:45.146652",
"modified_by": "Administrator", "modified_by": "Administrator",
"module": "HR", "module": "HR",
"name": "Leave Ledger Entry", "name": "Leave Ledger Entry",

View File

@ -111,13 +111,14 @@
], ],
"is_submittable": 1, "is_submittable": 1,
"links": [], "links": [],
"modified": "2020-12-17 16:27:20.311060", "modified": "2020-12-31 16:43:30.695206",
"modified_by": "Administrator", "modified_by": "Administrator",
"module": "HR", "module": "HR",
"name": "Leave Policy Assignment", "name": "Leave Policy Assignment",
"owner": "Administrator", "owner": "Administrator",
"permissions": [ "permissions": [
{ {
"cancel": 1,
"create": 1, "create": 1,
"delete": 1, "delete": 1,
"email": 1, "email": 1,
@ -131,6 +132,7 @@
"write": 1 "write": 1
}, },
{ {
"cancel": 1,
"create": 1, "create": 1,
"delete": 1, "delete": 1,
"email": 1, "email": 1,
@ -144,6 +146,7 @@
"write": 1 "write": 1
}, },
{ {
"cancel": 1,
"create": 1, "create": 1,
"delete": 1, "delete": 1,
"email": 1, "email": 1,

View File

@ -6,6 +6,7 @@ from __future__ import unicode_literals
import frappe, math, json import frappe, math, json
import erpnext import erpnext
from frappe import _ from frappe import _
from six import string_types
from frappe.utils import flt, rounded, add_months, nowdate, getdate, now_datetime from frappe.utils import flt, rounded, add_months, nowdate, getdate, now_datetime
from erpnext.loan_management.doctype.loan_security_unpledge.loan_security_unpledge import get_pledged_security_qty from erpnext.loan_management.doctype.loan_security_unpledge.loan_security_unpledge import get_pledged_security_qty
from erpnext.controllers.accounts_controller import AccountsController from erpnext.controllers.accounts_controller import AccountsController
@ -280,10 +281,13 @@ def make_loan_write_off(loan, company=None, posting_date=None, amount=0, as_dict
return write_off return write_off
@frappe.whitelist() @frappe.whitelist()
def unpledge_security(loan=None, loan_security_pledge=None, as_dict=0, save=0, submit=0, approve=0): def unpledge_security(loan=None, loan_security_pledge=None, security_map=None, as_dict=0, save=0, submit=0, approve=0):
# if loan is passed it will be considered as full unpledge # if no security_map is passed it will be considered as full unpledge
if security_map and isinstance(security_map, string_types):
security_map = json.loads(security_map)
if loan: if loan:
pledge_qty_map = get_pledged_security_qty(loan) pledge_qty_map = security_map or get_pledged_security_qty(loan)
loan_doc = frappe.get_doc('Loan', loan) loan_doc = frappe.get_doc('Loan', loan)
unpledge_request = create_loan_security_unpledge(pledge_qty_map, loan_doc.name, loan_doc.company, unpledge_request = create_loan_security_unpledge(pledge_qty_map, loan_doc.name, loan_doc.company,
loan_doc.applicant_type, loan_doc.applicant) loan_doc.applicant_type, loan_doc.applicant)

View File

@ -45,7 +45,7 @@ class TestLoan(unittest.TestCase):
create_loan_security_price("Test Security 2", 250, "Nos", get_datetime() , get_datetime(add_to_date(nowdate(), hours=24))) create_loan_security_price("Test Security 2", 250, "Nos", get_datetime() , get_datetime(add_to_date(nowdate(), hours=24)))
self.applicant1 = make_employee("robert_loan@loan.com") self.applicant1 = make_employee("robert_loan@loan.com")
make_salary_structure("Test Salary Structure Loan", "Monthly", employee=self.applicant1, currency='INR') make_salary_structure("Test Salary Structure Loan", "Monthly", employee=self.applicant1, currency='INR', company="_Test Company")
if not frappe.db.exists("Customer", "_Test Loan Customer"): if not frappe.db.exists("Customer", "_Test Loan Customer"):
frappe.get_doc(get_customer_dict('_Test Loan Customer')).insert(ignore_permissions=True) frappe.get_doc(get_customer_dict('_Test Loan Customer')).insert(ignore_permissions=True)
@ -325,6 +325,43 @@ class TestLoan(unittest.TestCase):
self.assertEquals(amounts['payable_principal_amount'], 0.0) self.assertEquals(amounts['payable_principal_amount'], 0.0)
self.assertEqual(amounts['interest_amount'], 0) self.assertEqual(amounts['interest_amount'], 0)
def test_partial_loan_security_unpledge(self):
pledge = [{
"loan_security": "Test Security 1",
"qty": 2000.00
},
{
"loan_security": "Test Security 2",
"qty": 4000.00
}]
loan_application = create_loan_application('_Test Company', self.applicant2, 'Demand Loan', pledge)
create_pledge(loan_application)
loan = create_demand_loan(self.applicant2, "Demand Loan", loan_application, posting_date='2019-10-01')
loan.submit()
self.assertEquals(loan.loan_amount, 1000000)
first_date = '2019-10-01'
last_date = '2019-10-30'
make_loan_disbursement_entry(loan.name, loan.loan_amount, disbursement_date=first_date)
process_loan_interest_accrual_for_demand_loans(posting_date = last_date)
repayment_entry = create_repayment_entry(loan.name, self.applicant2, add_days(last_date, 5), 600000)
repayment_entry.submit()
unpledge_map = {'Test Security 2': 2000}
unpledge_request = unpledge_security(loan=loan.name, security_map = unpledge_map, save=1)
unpledge_request.submit()
unpledge_request.status = 'Approved'
unpledge_request.save()
unpledge_request.submit()
unpledge_request.load_from_db()
self.assertEqual(unpledge_request.docstatus, 1)
def test_disbursal_check_with_shortfall(self): def test_disbursal_check_with_shortfall(self):
pledges = [{ pledges = [{
"loan_security": "Test Security 2", "loan_security": "Test Security 2",

View File

@ -81,7 +81,6 @@ def check_for_ltv_shortfall(process_loan_security_shortfall):
process_loan_security_shortfall) process_loan_security_shortfall)
def create_loan_security_shortfall(loan, loan_amount, security_value, shortfall_amount, process_loan_security_shortfall): def create_loan_security_shortfall(loan, loan_amount, security_value, shortfall_amount, process_loan_security_shortfall):
existing_shortfall = frappe.db.get_value("Loan Security Shortfall", {"loan": loan, "status": "Pending"}, "name") existing_shortfall = frappe.db.get_value("Loan Security Shortfall", {"loan": loan, "status": "Pending"}, "name")
if existing_shortfall: if existing_shortfall:

View File

@ -30,6 +30,8 @@ class LoanSecurityUnpledge(Document):
d.idx, frappe.bold(d.loan_security))) d.idx, frappe.bold(d.loan_security)))
def validate_unpledge_qty(self): def validate_unpledge_qty(self):
from erpnext.loan_management.doctype.loan_security_shortfall.loan_security_shortfall import get_ltv_ratio
pledge_qty_map = get_pledged_security_qty(self.loan) pledge_qty_map = get_pledged_security_qty(self.loan)
ltv_ratio_map = frappe._dict(frappe.get_all("Loan Security Type", ltv_ratio_map = frappe._dict(frappe.get_all("Loan Security Type",
@ -47,6 +49,8 @@ class LoanSecurityUnpledge(Document):
pending_principal_amount = flt(total_payment) - flt(interest_payable) - flt(principal_paid) - flt(written_off_amount) pending_principal_amount = flt(total_payment) - flt(interest_payable) - flt(principal_paid) - flt(written_off_amount)
security_value = 0 security_value = 0
unpledge_qty_map = {}
ltv_ratio = 0
for security in self.securities: for security in self.securities:
pledged_qty = pledge_qty_map.get(security.loan_security, 0) pledged_qty = pledge_qty_map.get(security.loan_security, 0)
@ -57,13 +61,15 @@ class LoanSecurityUnpledge(Document):
msg += _("You are trying to unpledge more.") msg += _("You are trying to unpledge more.")
frappe.throw(msg, title=_("Loan Security Unpledge Error")) frappe.throw(msg, title=_("Loan Security Unpledge Error"))
qty_after_unpledge = pledged_qty - security.qty unpledge_qty_map.setdefault(security.loan_security, 0)
ltv_ratio = ltv_ratio_map.get(security.loan_security_type) unpledge_qty_map[security.loan_security] += security.qty
current_price = loan_security_price_map.get(security.loan_security) for security in pledge_qty_map:
if not current_price: if not ltv_ratio:
frappe.throw(_("No valid Loan Security Price found for {0}").format(frappe.bold(security.loan_security))) ltv_ratio = get_ltv_ratio(security)
qty_after_unpledge = pledge_qty_map.get(security, 0) - unpledge_qty_map.get(security, 0)
current_price = loan_security_price_map.get(security)
security_value += qty_after_unpledge * current_price security_value += qty_after_unpledge * current_price
if not security_value and flt(pending_principal_amount, 2) > 0: if not security_value and flt(pending_principal_amount, 2) > 0:

View File

@ -711,6 +711,7 @@ erpnext.patches.v13_0.delete_old_sales_reports
execute:frappe.delete_doc_if_exists("DocType", "Bank Reconciliation") execute:frappe.delete_doc_if_exists("DocType", "Bank Reconciliation")
erpnext.patches.v13_0.move_doctype_reports_and_notification_from_hr_to_payroll #22-06-2020 erpnext.patches.v13_0.move_doctype_reports_and_notification_from_hr_to_payroll #22-06-2020
erpnext.patches.v13_0.move_payroll_setting_separately_from_hr_settings #22-06-2020 erpnext.patches.v13_0.move_payroll_setting_separately_from_hr_settings #22-06-2020
execute:frappe.reload_doc("regional", "doctype", "e_invoice_settings")
erpnext.patches.v13_0.check_is_income_tax_component #22-06-2020 erpnext.patches.v13_0.check_is_income_tax_component #22-06-2020
erpnext.patches.v13_0.loyalty_points_entry_for_pos_invoice #22-07-2020 erpnext.patches.v13_0.loyalty_points_entry_for_pos_invoice #22-07-2020
erpnext.patches.v12_0.add_taxjar_integration_field erpnext.patches.v12_0.add_taxjar_integration_field
@ -742,3 +743,4 @@ erpnext.patches.v13_0.create_leave_policy_assignment_based_on_employee_current_l
erpnext.patches.v13_0.add_po_to_global_search erpnext.patches.v13_0.add_po_to_global_search
erpnext.patches.v13_0.update_returned_qty_in_pr_dn erpnext.patches.v13_0.update_returned_qty_in_pr_dn
erpnext.patches.v13_0.setup_gratuity_rule_for_india_and_uae erpnext.patches.v13_0.setup_gratuity_rule_for_india_and_uae
erpnext.patches.v13_0.set_company_in_leave_ledger_entry

View File

@ -8,6 +8,7 @@ def execute():
if not company: if not company:
return return
frappe.reload_doc("custom", "doctype", "custom_field")
frappe.reload_doc("regional", "doctype", "e_invoice_settings") frappe.reload_doc("regional", "doctype", "e_invoice_settings")
custom_fields = { custom_fields = {
'Sales Invoice': [ 'Sales Invoice': [

View File

@ -0,0 +1,7 @@
import frappe
def execute():
frappe.reload_doc('HR', 'doctype', 'Leave Allocation')
frappe.reload_doc('HR', 'doctype', 'Leave Ledger Entry')
frappe.db.sql("""update `tabLeave Ledger Entry` as lle set company = (select company from `tabEmployee` where employee = lle.employee)""")
frappe.db.sql("""update `tabLeave Allocation` as la set company = (select company from `tabEmployee` where employee = la.employee)""")

View File

@ -1,7 +1,7 @@
from __future__ import unicode_literals from __future__ import unicode_literals
import frappe import frappe
from frappe import _ from frappe import _
from frappe.utils import nowdate from frappe.utils import nowdate, flt
from erpnext.accounts.doctype.account.test_account import create_account from erpnext.accounts.doctype.account.test_account import create_account
from erpnext.loan_management.doctype.process_loan_interest_accrual.process_loan_interest_accrual import process_loan_interest_accrual_for_term_loans from erpnext.loan_management.doctype.process_loan_interest_accrual.process_loan_interest_accrual import process_loan_interest_accrual_for_term_loans
from erpnext.loan_management.doctype.loan.loan import make_repayment_entry from erpnext.loan_management.doctype.loan.loan import make_repayment_entry
@ -113,15 +113,15 @@ def execute():
interest_paid = 0 interest_paid = 0
principal_paid = 0 principal_paid = 0
if total_interest > entry.interest_amount: if flt(total_interest) > flt(entry.interest_amount):
interest_paid = entry.interest_amount interest_paid = flt(entry.interest_amount)
else: else:
interest_paid = total_interest interest_paid = flt(total_interest)
if total_principal > entry.payable_principal_amount: if flt(total_principal) > flt(entry.payable_principal_amount):
principal_paid = entry.payable_principal_amount principal_paid = flt(entry.payable_principal_amount)
else: else:
principal_paid = total_principal principal_paid = flt(total_principal)
frappe.db.sql(""" UPDATE `tabLoan Interest Accrual` frappe.db.sql(""" UPDATE `tabLoan Interest Accrual`
SET paid_principal_amount = `paid_principal_amount` + %s, SET paid_principal_amount = `paid_principal_amount` + %s,
@ -129,8 +129,8 @@ def execute():
WHERE name = %s""", WHERE name = %s""",
(principal_paid, interest_paid, entry.name)) (principal_paid, interest_paid, entry.name))
total_principal -= principal_paid total_principal = flt(total_principal) - principal_paid
total_interest -= interest_paid total_interest = flt(total_interest) - interest_paid
def create_loan_type(loan, loan_type_name, penalty_account): def create_loan_type(loan, loan_type_name, penalty_account):
loan_type_doc = frappe.new_doc('Loan Type') loan_type_doc = frappe.new_doc('Loan Type')

View File

@ -86,19 +86,21 @@ class TestEmployeeTaxExemptionDeclaration(unittest.TestCase):
self.assertEqual(declaration.total_exemption_amount, 100000) self.assertEqual(declaration.total_exemption_amount, 100000)
def create_payroll_period(): def create_payroll_period(**args):
if not frappe.db.exists("Payroll Period", "_Test Payroll Period"): args = frappe._dict(args)
name = args.name or "_Test Payroll Period"
if not frappe.db.exists("Payroll Period", name):
from datetime import date from datetime import date
payroll_period = frappe.get_doc(dict( payroll_period = frappe.get_doc(dict(
doctype = 'Payroll Period', doctype = 'Payroll Period',
name = "_Test Payroll Period", name = name,
company = erpnext.get_default_company(), company = args.company or erpnext.get_default_company(),
start_date = date(date.today().year, 1, 1), start_date = args.start_date or date(date.today().year, 1, 1),
end_date = date(date.today().year, 12, 31) end_date = args.end_date or date(date.today().year, 12, 31)
)).insert() )).insert()
return payroll_period return payroll_period
else: else:
return frappe.get_doc("Payroll Period", "_Test Payroll Period") return frappe.get_doc("Payroll Period", name)
def create_exemption_category(): def create_exemption_category():
if not frappe.db.exists("Employee Tax Exemption Category", "_Test Category"): if not frappe.db.exists("Employee Tax Exemption Category", "_Test Category"):

View File

@ -3,8 +3,11 @@
# For license information, please see license.txt # For license information, please see license.txt
from __future__ import unicode_literals from __future__ import unicode_literals
# import frappe #import frappe
import erpnext
from frappe.model.document import Document from frappe.model.document import Document
class IncomeTaxSlab(Document): class IncomeTaxSlab(Document):
pass def validate(self):
if self.company:
self.currency = erpnext.get_company_currency(self.company)

View File

@ -17,8 +17,7 @@
"fieldtype": "Link", "fieldtype": "Link",
"in_list_view": 1, "in_list_view": 1,
"label": "Employee", "label": "Employee",
"options": "Employee", "options": "Employee"
"read_only": 1
}, },
{ {
"fetch_from": "employee.employee_name", "fetch_from": "employee.employee_name",
@ -52,7 +51,7 @@
], ],
"istable": 1, "istable": 1,
"links": [], "links": [],
"modified": "2020-09-30 12:40:07.999878", "modified": "2020-12-17 15:43:29.542977",
"modified_by": "Administrator", "modified_by": "Administrator",
"module": "Payroll", "module": "Payroll",
"name": "Payroll Employee Detail", "name": "Payroll Employee Detail",

View File

@ -10,15 +10,22 @@ frappe.ui.form.on('Payroll Entry', {
} }
frm.toggle_reqd(['payroll_frequency'], !frm.doc.salary_slip_based_on_timesheet); frm.toggle_reqd(['payroll_frequency'], !frm.doc.salary_slip_based_on_timesheet);
frm.set_query("department", function() { frm.events.department_filters(frm);
frm.events.payroll_payable_account_filters(frm);
},
department_filters: function (frm) {
frm.set_query("department", function () {
return { return {
"filters": { "filters": {
"company": frm.doc.company, "company": frm.doc.company,
} }
}; };
}); });
},
frm.set_query("payroll_payable_account", function() { payroll_payable_account_filters: function (frm) {
frm.set_query("payroll_payable_account", function () {
return { return {
filters: { filters: {
"company": frm.doc.company, "company": frm.doc.company,
@ -29,12 +36,12 @@ frappe.ui.form.on('Payroll Entry', {
}); });
}, },
refresh: function(frm) { refresh: function (frm) {
if (frm.doc.docstatus == 0) { if (frm.doc.docstatus == 0) {
if(!frm.is_new()) { if (!frm.is_new()) {
frm.page.clear_primary_action(); frm.page.clear_primary_action();
frm.add_custom_button(__("Get Employees"), frm.add_custom_button(__("Get Employees"),
function() { function () {
frm.events.get_employee_details(frm); frm.events.get_employee_details(frm);
} }
).toggleClass('btn-primary', !(frm.doc.employees || []).length); ).toggleClass('btn-primary', !(frm.doc.employees || []).length);
@ -42,7 +49,7 @@ frappe.ui.form.on('Payroll Entry', {
if ((frm.doc.employees || []).length) { if ((frm.doc.employees || []).length) {
frm.page.clear_primary_action(); frm.page.clear_primary_action();
frm.page.set_primary_action(__('Create Salary Slips'), () => { frm.page.set_primary_action(__('Create Salary Slips'), () => {
frm.save('Submit').then(()=>{ frm.save('Submit').then(() => {
frm.page.clear_primary_action(); frm.page.clear_primary_action();
frm.refresh(); frm.refresh();
frm.events.refresh(frm); frm.events.refresh(frm);
@ -61,48 +68,48 @@ frappe.ui.form.on('Payroll Entry', {
doc: frm.doc, doc: frm.doc,
method: 'fill_employee_details', method: 'fill_employee_details',
}).then(r => { }).then(r => {
if (r.docs && r.docs[0].employees){ if (r.docs && r.docs[0].employees) {
frm.employees = r.docs[0].employees; frm.employees = r.docs[0].employees;
frm.dirty(); frm.dirty();
frm.save(); frm.save();
frm.refresh(); frm.refresh();
if(r.docs[0].validate_attendance){ if (r.docs[0].validate_attendance) {
render_employee_attendance(frm, r.message); render_employee_attendance(frm, r.message);
} }
} }
}) });
}, },
create_salary_slips: function(frm) { create_salary_slips: function (frm) {
frm.call({ frm.call({
doc: frm.doc, doc: frm.doc,
method: "create_salary_slips", method: "create_salary_slips",
callback: function(r) { callback: function () {
frm.refresh(); frm.refresh();
frm.toolbar.refresh(); frm.toolbar.refresh();
} }
}) });
}, },
add_context_buttons: function(frm) { add_context_buttons: function (frm) {
if(frm.doc.salary_slips_submitted || (frm.doc.__onload && frm.doc.__onload.submitted_ss)) { if (frm.doc.salary_slips_submitted || (frm.doc.__onload && frm.doc.__onload.submitted_ss)) {
frm.events.add_bank_entry_button(frm); frm.events.add_bank_entry_button(frm);
} else if(frm.doc.salary_slips_created) { } else if (frm.doc.salary_slips_created) {
frm.add_custom_button(__("Submit Salary Slip"), function() { frm.add_custom_button(__("Submit Salary Slip"), function () {
submit_salary_slip(frm); submit_salary_slip(frm);
}).addClass("btn-primary"); }).addClass("btn-primary");
} }
}, },
add_bank_entry_button: function(frm) { add_bank_entry_button: function (frm) {
frappe.call({ frappe.call({
method: 'erpnext.payroll.doctype.payroll_entry.payroll_entry.payroll_entry_has_bank_entries', method: 'erpnext.payroll.doctype.payroll_entry.payroll_entry.payroll_entry_has_bank_entries',
args: { args: {
'name': frm.doc.name 'name': frm.doc.name
}, },
callback: function(r) { callback: function (r) {
if (r.message && !r.message.submitted) { if (r.message && !r.message.submitted) {
frm.add_custom_button("Make Bank Entry", function() { frm.add_custom_button("Make Bank Entry", function () {
make_bank_entry(frm); make_bank_entry(frm);
}).addClass("btn-primary"); }).addClass("btn-primary");
} }
@ -141,8 +148,37 @@ frappe.ui.form.on('Payroll Entry', {
}, },
payroll_frequency: function (frm) { payroll_frequency: function (frm) {
frm.trigger("set_start_end_dates"); frm.trigger("set_start_end_dates").then( ()=> {
frm.events.clear_employee_table(frm); frm.events.clear_employee_table(frm);
frm.events.get_employee_with_salary_slip_and_set_query(frm);
});
},
employee_filters: function (frm, emp_list) {
frm.set_query('employee', 'employees', () => {
return {
filters: {
name: ["not in", emp_list]
}
};
});
},
get_employee_with_salary_slip_and_set_query: function (frm) {
frappe.db.get_list('Salary Slip', {
filters: {
start_date: frm.doc.start_date,
end_date: frm.doc.end_date,
docstatus: 1,
},
fields: ['employee']
}).then((emp) => {
var emp_list = [];
emp.forEach((employee_data) => {
emp_list.push(Object.values(employee_data)[0]);
});
frm.events.employee_filters(frm, emp_list);
});
}, },
company: function (frm) { company: function (frm) {
@ -164,17 +200,17 @@ frappe.ui.form.on('Payroll Entry', {
from_currency: frm.doc.currency, from_currency: frm.doc.currency,
to_currency: company_currency, to_currency: company_currency,
}, },
callback: function(r) { callback: function (r) {
frm.set_value("exchange_rate", flt(r.message)); frm.set_value("exchange_rate", flt(r.message));
frm.set_df_property('exchange_rate', 'hidden', 0); frm.set_df_property('exchange_rate', 'hidden', 0);
frm.set_df_property("exchange_rate", "description", "1 " + frm.doc.currency frm.set_df_property("exchange_rate", "description", "1 " + frm.doc.currency +
+ " = [?] " + company_currency); " = [?] " + company_currency);
} }
}); });
} else { } else {
frm.set_value("exchange_rate", 1.0); frm.set_value("exchange_rate", 1.0);
frm.set_df_property('exchange_rate', 'hidden', 1); frm.set_df_property('exchange_rate', 'hidden', 1);
frm.set_df_property("exchange_rate", "description", "" ); frm.set_df_property("exchange_rate", "description", "");
} }
} }
}, },
@ -192,9 +228,9 @@ frappe.ui.form.on('Payroll Entry', {
}, },
start_date: function (frm) { start_date: function (frm) {
if(!in_progress && frm.doc.start_date){ if (!in_progress && frm.doc.start_date) {
frm.trigger("set_end_date"); frm.trigger("set_end_date");
}else{ } else {
// reset flag // reset flag
in_progress = false; in_progress = false;
} }
@ -228,7 +264,7 @@ frappe.ui.form.on('Payroll Entry', {
} }
}, },
set_end_date: function(frm){ set_end_date: function (frm) {
frappe.call({ frappe.call({
method: 'erpnext.payroll.doctype.payroll_entry.payroll_entry.get_end_date', method: 'erpnext.payroll.doctype.payroll_entry.payroll_entry.get_end_date',
args: { args: {
@ -243,19 +279,19 @@ frappe.ui.form.on('Payroll Entry', {
}); });
}, },
validate_attendance: function(frm){ validate_attendance: function (frm) {
if(frm.doc.validate_attendance && frm.doc.employees){ if (frm.doc.validate_attendance && frm.doc.employees) {
frappe.call({ frappe.call({
method: 'validate_employee_attendance', method: 'validate_employee_attendance',
args: {}, args: {},
callback: function(r) { callback: function (r) {
render_employee_attendance(frm, r.message); render_employee_attendance(frm, r.message);
}, },
doc: frm.doc, doc: frm.doc,
freeze: true, freeze: true,
freeze_message: __('Validating Employee Attendance...') freeze_message: __('Validating Employee Attendance...')
}); });
}else{ } else {
frm.fields_dict.attendance_detail_html.html(""); frm.fields_dict.attendance_detail_html.html("");
} }
}, },
@ -270,18 +306,20 @@ frappe.ui.form.on('Payroll Entry', {
const submit_salary_slip = function (frm) { const submit_salary_slip = function (frm) {
frappe.confirm(__('This will submit Salary Slips and create accrual Journal Entry. Do you want to proceed?'), frappe.confirm(__('This will submit Salary Slips and create accrual Journal Entry. Do you want to proceed?'),
function() { function () {
frappe.call({ frappe.call({
method: 'submit_salary_slips', method: 'submit_salary_slips',
args: {}, args: {},
callback: function() {frm.events.refresh(frm);}, callback: function () {
frm.events.refresh(frm);
},
doc: frm.doc, doc: frm.doc,
freeze: true, freeze: true,
freeze_message: __('Submitting Salary Slips and creating Journal Entry...') freeze_message: __('Submitting Salary Slips and creating Journal Entry...')
}); });
}, },
function() { function () {
if(frappe.dom.freeze_count) { if (frappe.dom.freeze_count) {
frappe.dom.unfreeze(); frappe.dom.unfreeze();
frm.events.refresh(frm); frm.events.refresh(frm);
} }
@ -295,9 +333,11 @@ let make_bank_entry = function (frm) {
return frappe.call({ return frappe.call({
doc: cur_frm.doc, doc: cur_frm.doc,
method: "make_payment_entry", method: "make_payment_entry",
callback: function() { callback: function () {
frappe.set_route( frappe.set_route(
'List', 'Journal Entry', {"Journal Entry Account.reference_name": frm.doc.name} 'List', 'Journal Entry', {
"Journal Entry Account.reference_name": frm.doc.name
}
); );
}, },
freeze: true, freeze: true,
@ -309,11 +349,19 @@ let make_bank_entry = function (frm) {
} }
}; };
let render_employee_attendance = function (frm, data) {
let render_employee_attendance = function(frm, data) {
frm.fields_dict.attendance_detail_html.html( frm.fields_dict.attendance_detail_html.html(
frappe.render_template('employees_to_mark_attendance', { frappe.render_template('employees_to_mark_attendance', {
data: data data: data
}) })
); );
} };
frappe.ui.form.on('Payroll Employee Detail', {
employee: function(frm) {
frm.events.clear_employee_table(frm);
if (!frm.doc.payroll_frequency) {
frappe.throw(__("Please set a Payroll Frequency"));
}
}
});

View File

@ -129,8 +129,7 @@
"fieldname": "employees", "fieldname": "employees",
"fieldtype": "Table", "fieldtype": "Table",
"label": "Employee Details", "label": "Employee Details",
"options": "Payroll Employee Detail", "options": "Payroll Employee Detail"
"read_only": 1
}, },
{ {
"fieldname": "section_break_13", "fieldname": "section_break_13",
@ -290,7 +289,7 @@
"icon": "fa fa-cog", "icon": "fa fa-cog",
"is_submittable": 1, "is_submittable": 1,
"links": [], "links": [],
"modified": "2020-10-23 13:00:33.753228", "modified": "2020-12-17 15:13:17.766210",
"modified_by": "Administrator", "modified_by": "Administrator",
"module": "Payroll", "module": "Payroll",
"name": "Payroll Entry", "name": "Payroll Entry",

View File

@ -6,7 +6,7 @@ from __future__ import unicode_literals
import frappe, erpnext import frappe, erpnext
from frappe.model.document import Document from frappe.model.document import Document
from dateutil.relativedelta import relativedelta from dateutil.relativedelta import relativedelta
from frappe.utils import cint, flt, nowdate, add_days, getdate, fmt_money, add_to_date, DATE_FORMAT, date_diff from frappe.utils import cint, flt, add_days, getdate, add_to_date, DATE_FORMAT, date_diff, comma_and
from frappe import _ from frappe import _
from erpnext.accounts.utils import get_fiscal_year from erpnext.accounts.utils import get_fiscal_year
from erpnext.hr.doctype.employee.employee import get_holiday_list_for_employee from erpnext.hr.doctype.employee.employee import get_holiday_list_for_employee
@ -19,16 +19,26 @@ class PayrollEntry(Document):
# check if salary slips were manually submitted # check if salary slips were manually submitted
entries = frappe.db.count("Salary Slip", {'payroll_entry': self.name, 'docstatus': 1}, ['name']) entries = frappe.db.count("Salary Slip", {'payroll_entry': self.name, 'docstatus': 1}, ['name'])
if cint(entries) == len(self.employees): if cint(entries) == len(self.employees):
self.set_onload("submitted_ss", True) self.set_onload("submitted_ss", True)
def on_submit(self): def on_submit(self):
self.create_salary_slips() self.create_salary_slips()
def before_submit(self): def before_submit(self):
self.validate_employee_details()
if self.validate_attendance: if self.validate_attendance:
if self.validate_employee_attendance(): if self.validate_employee_attendance():
frappe.throw(_("Cannot Submit, Employees left to mark attendance")) frappe.throw(_("Cannot Submit, Employees left to mark attendance"))
def validate_employee_details(self):
emp_with_sal_slip = []
for employee_details in self.employees:
if frappe.db.exists("Salary Slip", {"employee": employee_details.employee, "start_date": self.start_date, "end_date": self.end_date, "docstatus": 1}):
emp_with_sal_slip.append(employee_details.employee)
if len(emp_with_sal_slip):
frappe.throw(_("Salary Slip already exists for {0} ").format(comma_and(emp_with_sal_slip)))
def on_cancel(self): def on_cancel(self):
frappe.delete_doc("Salary Slip", frappe.db.sql_list("""select name from `tabSalary Slip` frappe.delete_doc("Salary Slip", frappe.db.sql_list("""select name from `tabSalary Slip`
where payroll_entry=%s """, (self.name))) where payroll_entry=%s """, (self.name)))
@ -71,8 +81,17 @@ class PayrollEntry(Document):
and t2.docstatus = 1 and t2.docstatus = 1
%s order by t2.from_date desc %s order by t2.from_date desc
""" % cond, {"sal_struct": tuple(sal_struct), "from_date": self.end_date, "payroll_payable_account": self.payroll_payable_account}, as_dict=True) """ % cond, {"sal_struct": tuple(sal_struct), "from_date": self.end_date, "payroll_payable_account": self.payroll_payable_account}, as_dict=True)
emp_list = self.remove_payrolled_employees(emp_list)
return emp_list return emp_list
def remove_payrolled_employees(self, emp_list):
for employee_details in emp_list:
if frappe.db.exists("Salary Slip", {"employee": employee_details.employee, "start_date": self.start_date, "end_date": self.end_date, "docstatus": 1}):
emp_list.remove(employee_details)
return emp_list
def fill_employee_details(self): def fill_employee_details(self):
self.set('employees', []) self.set('employees', [])
employees = self.get_emp_list() employees = self.get_emp_list()

View File

@ -22,7 +22,7 @@ class TestPayrollEntry(unittest.TestCase):
frappe.db.sql("delete from `tab%s`" % dt) frappe.db.sql("delete from `tab%s`" % dt)
make_earning_salary_component(setup=True, company_list=["_Test Company"]) make_earning_salary_component(setup=True, company_list=["_Test Company"])
make_deduction_salary_component(setup=True, company_list=["_Test Company"]) make_deduction_salary_component(setup=True, test_tax=False, company_list=["_Test Company"])
frappe.db.set_value("Payroll Settings", None, "email_salary_slip_to_employee", 0) frappe.db.set_value("Payroll Settings", None, "email_salary_slip_to_employee", 0)
@ -107,9 +107,9 @@ class TestPayrollEntry(unittest.TestCase):
frappe.db.get_value("Company", "_Test Company", "default_payroll_payable_account") != "_Test Payroll Payable - _TC": frappe.db.get_value("Company", "_Test Company", "default_payroll_payable_account") != "_Test Payroll Payable - _TC":
frappe.db.set_value("Company", "_Test Company", "default_payroll_payable_account", frappe.db.set_value("Company", "_Test Company", "default_payroll_payable_account",
"_Test Payroll Payable - _TC") "_Test Payroll Payable - _TC")
currency=frappe.db.get_value("Company", "_Test Company", "default_currency")
make_salary_structure("_Test Salary Structure 1", "Monthly", employee1, company="_Test Company", currency=frappe.db.get_value("Company", "_Test Company", "default_currency")) make_salary_structure("_Test Salary Structure 1", "Monthly", employee1, company="_Test Company", currency=currency, test_tax=False)
make_salary_structure("_Test Salary Structure 2", "Monthly", employee2, company="_Test Company", currency=frappe.db.get_value("Company", "_Test Company", "default_currency")) make_salary_structure("_Test Salary Structure 2", "Monthly", employee2, company="_Test Company", currency=currency, test_tax=False)
dates = get_start_end_dates('Monthly', nowdate()) dates = get_start_end_dates('Monthly', nowdate())
if not frappe.db.get_value("Salary Slip", {"start_date": dates.start_date, "end_date": dates.end_date}): if not frappe.db.get_value("Salary Slip", {"start_date": dates.start_date, "end_date": dates.end_date}):

View File

@ -125,15 +125,15 @@ frappe.ui.form.on("Salary Slip", {
change_form_labels: function(frm, company_currency) { change_form_labels: function(frm, company_currency) {
frm.set_currency_labels(["base_hour_rate", "base_gross_pay", "base_total_deduction", frm.set_currency_labels(["base_hour_rate", "base_gross_pay", "base_total_deduction",
"base_net_pay", "base_rounded_total", "base_total_in_words"], "base_net_pay", "base_rounded_total", "base_total_in_words", "base_year_to_date", "base_month_to_date"],
company_currency); company_currency);
frm.set_currency_labels(["hour_rate", "gross_pay", "total_deduction", "net_pay", "rounded_total", "total_in_words"], frm.set_currency_labels(["hour_rate", "gross_pay", "total_deduction", "net_pay", "rounded_total", "total_in_words", "year_to_date", "month_to_date"],
frm.doc.currency); frm.doc.currency);
// toggle fields // toggle fields
frm.toggle_display(["exchange_rate", "base_hour_rate", "base_gross_pay", "base_total_deduction", frm.toggle_display(["exchange_rate", "base_hour_rate", "base_gross_pay", "base_total_deduction",
"base_net_pay", "base_rounded_total", "base_total_in_words"], "base_net_pay", "base_rounded_total", "base_total_in_words", "base_year_to_date", "base_month_to_date"],
frm.doc.currency != company_currency); frm.doc.currency != company_currency);
}, },
@ -214,14 +214,16 @@ frappe.ui.form.on('Salary Slip Timesheet', {
}); });
var calculate_totals = function(frm) { var calculate_totals = function(frm) {
if (frm.doc.earnings || frm.doc.deductions) { if (frm.doc.docstatus === 0) {
frappe.call({ if (frm.doc.earnings || frm.doc.deductions) {
method: "set_totals", frappe.call({
doc: frm.doc, method: "set_totals",
callback: function() { doc: frm.doc,
frm.refresh_fields(); callback: function() {
} frm.refresh_fields();
}); }
});
}
} }
}; };

View File

@ -69,9 +69,13 @@
"net_pay_info", "net_pay_info",
"net_pay", "net_pay",
"base_net_pay", "base_net_pay",
"year_to_date",
"base_year_to_date",
"column_break_53", "column_break_53",
"rounded_total", "rounded_total",
"base_rounded_total", "base_rounded_total",
"month_to_date",
"base_month_to_date",
"section_break_55", "section_break_55",
"total_in_words", "total_in_words",
"column_break_69", "column_break_69",
@ -578,13 +582,41 @@
{ {
"fieldname": "column_break_69", "fieldname": "column_break_69",
"fieldtype": "Column Break" "fieldtype": "Column Break"
},
{
"fieldname": "year_to_date",
"fieldtype": "Currency",
"label": "Year To Date",
"options": "currency",
"read_only": 1
},
{
"fieldname": "month_to_date",
"fieldtype": "Currency",
"label": "Month To Date",
"options": "currency",
"read_only": 1
},
{
"fieldname": "base_year_to_date",
"fieldtype": "Currency",
"label": "Year To Date(Company Currency)",
"options": "Company:company:default_currency",
"read_only": 1
},
{
"fieldname": "base_month_to_date",
"fieldtype": "Currency",
"label": "Month To Date(Company Currency)",
"options": "Company:company:default_currency",
"read_only": 1
} }
], ],
"icon": "fa fa-file-text", "icon": "fa fa-file-text",
"idx": 9, "idx": 9,
"is_submittable": 1, "is_submittable": 1,
"links": [], "links": [],
"modified": "2020-10-21 23:02:59.400249", "modified": "2020-12-21 23:43:44.959840",
"modified_by": "Administrator", "modified_by": "Administrator",
"module": "Payroll", "module": "Payroll",
"name": "Salary Slip", "name": "Salary Slip",

View File

@ -5,7 +5,7 @@ from __future__ import unicode_literals
import frappe, erpnext import frappe, erpnext
import datetime, math import datetime, math
from frappe.utils import add_days, cint, cstr, flt, getdate, rounded, date_diff, money_in_words, formatdate from frappe.utils import add_days, cint, cstr, flt, getdate, rounded, date_diff, money_in_words, formatdate, get_first_day
from frappe.model.naming import make_autoname from frappe.model.naming import make_autoname
from frappe import msgprint, _ from frappe import msgprint, _
@ -18,6 +18,7 @@ from erpnext.payroll.doctype.payroll_period.payroll_period import get_period_fac
from erpnext.payroll.doctype.employee_benefit_application.employee_benefit_application import get_benefit_component_amount from erpnext.payroll.doctype.employee_benefit_application.employee_benefit_application import get_benefit_component_amount
from erpnext.payroll.doctype.employee_benefit_claim.employee_benefit_claim import get_benefit_claim_amount, get_last_payroll_period_benefits from erpnext.payroll.doctype.employee_benefit_claim.employee_benefit_claim import get_benefit_claim_amount, get_last_payroll_period_benefits
from erpnext.loan_management.doctype.loan_repayment.loan_repayment import calculate_amounts, create_repayment_entry from erpnext.loan_management.doctype.loan_repayment.loan_repayment import calculate_amounts, create_repayment_entry
from erpnext.accounts.utils import get_fiscal_year
class SalarySlip(TransactionBase): class SalarySlip(TransactionBase):
def __init__(self, *args, **kwargs): def __init__(self, *args, **kwargs):
@ -49,6 +50,8 @@ class SalarySlip(TransactionBase):
self.get_working_days_details(lwp = self.leave_without_pay) self.get_working_days_details(lwp = self.leave_without_pay)
self.calculate_net_pay() self.calculate_net_pay()
self.compute_year_to_date()
self.compute_month_to_date()
if frappe.db.get_single_value("Payroll Settings", "max_working_hours_against_timesheet"): if frappe.db.get_single_value("Payroll Settings", "max_working_hours_against_timesheet"):
max_working_hours = frappe.db.get_single_value("Payroll Settings", "max_working_hours_against_timesheet") max_working_hours = frappe.db.get_single_value("Payroll Settings", "max_working_hours_against_timesheet")
@ -1143,6 +1146,46 @@ class SalarySlip(TransactionBase):
self.gross_pay += self.earnings[i].amount self.gross_pay += self.earnings[i].amount
self.net_pay = flt(self.gross_pay) - flt(self.total_deduction) self.net_pay = flt(self.gross_pay) - flt(self.total_deduction)
def compute_year_to_date(self):
year_to_date = 0
payroll_period = get_payroll_period(self.start_date, self.end_date, self.company)
if payroll_period:
period_start_date = payroll_period.start_date
period_end_date = payroll_period.end_date
else:
# get dates based on fiscal year if no payroll period exists
fiscal_year = get_fiscal_year(date=self.start_date, company=self.company, as_dict=1)
period_start_date = fiscal_year.year_start_date
period_end_date = fiscal_year.year_end_date
salary_slip_sum = frappe.get_list('Salary Slip',
fields = ['sum(net_pay) as sum'],
filters = {'employee_name' : self.employee_name,
'start_date' : ['>=', period_start_date],
'end_date' : ['<', period_end_date]})
year_to_date = flt(salary_slip_sum[0].sum) if salary_slip_sum else 0.0
year_to_date += self.net_pay
self.year_to_date = year_to_date
def compute_month_to_date(self):
month_to_date = 0
first_day_of_the_month = get_first_day(self.start_date)
salary_slip_sum = frappe.get_list('Salary Slip',
fields = ['sum(net_pay) as sum'],
filters = {'employee_name' : self.employee_name,
'start_date' : ['>=', first_day_of_the_month],
'end_date' : ['<', self.start_date]
})
month_to_date = flt(salary_slip_sum[0].sum) if salary_slip_sum else 0.0
month_to_date += self.net_pay
self.month_to_date = month_to_date
def unlink_ref_doc_from_salary_slip(ref_no): def unlink_ref_doc_from_salary_slip(ref_no):
linked_ss = frappe.db.sql_list("""select name from `tabSalary Slip` linked_ss = frappe.db.sql_list("""select name from `tabSalary Slip`
where journal_entry=%s and docstatus < 2""", (ref_no)) where journal_entry=%s and docstatus < 2""", (ref_no))

View File

@ -9,7 +9,7 @@ import calendar
import random import random
from erpnext.accounts.utils import get_fiscal_year from erpnext.accounts.utils import get_fiscal_year
from frappe.utils.make_random import get_random from frappe.utils.make_random import get_random
from frappe.utils import getdate, nowdate, add_days, add_months, flt, get_first_day, get_last_day from frappe.utils import getdate, nowdate, add_days, add_months, flt, get_first_day, get_last_day, cstr
from erpnext.payroll.doctype.salary_structure.salary_structure import make_salary_slip from erpnext.payroll.doctype.salary_structure.salary_structure import make_salary_slip
from erpnext.payroll.doctype.payroll_entry.payroll_entry import get_month_details from erpnext.payroll.doctype.payroll_entry.payroll_entry import get_month_details
from erpnext.hr.doctype.employee.test_employee import make_employee from erpnext.hr.doctype.employee.test_employee import make_employee
@ -241,7 +241,11 @@ class TestSalarySlip(unittest.TestCase):
interest_income_account='Interest Income Account - _TC', interest_income_account='Interest Income Account - _TC',
penalty_income_account='Penalty Income Account - _TC') penalty_income_account='Penalty Income Account - _TC')
make_salary_structure("Test Loan Repayment Salary Structure", "Monthly", employee=applicant, currency='INR') payroll_period = create_payroll_period(name="_Test Payroll Period 1", company="_Test Company")
make_salary_structure("Test Loan Repayment Salary Structure", "Monthly", employee=applicant, currency='INR',
payroll_period=payroll_period)
frappe.db.sql("""delete from `tabLoan""") frappe.db.sql("""delete from `tabLoan""")
loan = create_loan(applicant, "Car Loan", 11000, "Repay Over Number of Periods", 20, posting_date=add_months(nowdate(), -1)) loan = create_loan(applicant, "Car Loan", 11000, "Repay Over Number of Periods", 20, posting_date=add_months(nowdate(), -1))
loan.repay_from_salary = 1 loan.repay_from_salary = 1
@ -291,6 +295,33 @@ class TestSalarySlip(unittest.TestCase):
self.assertEqual(salary_slip.gross_pay, 78000) self.assertEqual(salary_slip.gross_pay, 78000)
self.assertEqual(salary_slip.base_gross_pay, 78000*70) self.assertEqual(salary_slip.base_gross_pay, 78000*70)
def test_year_to_date_computation(self):
from erpnext.payroll.doctype.salary_structure.test_salary_structure import make_salary_structure
applicant = make_employee("test_ytd@salary.com", company="_Test Company")
payroll_period = create_payroll_period(name="_Test Payroll Period 1", company="_Test Company")
create_tax_slab(payroll_period, allow_tax_exemption=True, currency="INR", effective_date=getdate("2019-04-01"),
company="_Test Company")
salary_structure = make_salary_structure("Monthly Salary Structure Test for Salary Slip YTD",
"Monthly", employee=applicant, company="_Test Company", currency="INR", payroll_period=payroll_period)
# clear salary slip for this employee
frappe.db.sql("DELETE FROM `tabSalary Slip` where employee_name = 'test_ytd@salary.com'")
create_salary_slips_for_payroll_period(applicant, salary_structure.name,
payroll_period, deduct_random=False)
salary_slips = frappe.get_all('Salary Slip', fields=['year_to_date', 'net_pay'], filters={'employee_name':
'test_ytd@salary.com'}, order_by = 'posting_date')
year_to_date = 0
for slip in salary_slips:
year_to_date += slip.net_pay
self.assertEqual(slip.year_to_date, year_to_date)
def test_tax_for_payroll_period(self): def test_tax_for_payroll_period(self):
data = {} data = {}
# test the impact of tax exemption declaration, tax exemption proof submission # test the impact of tax exemption declaration, tax exemption proof submission
@ -411,10 +442,7 @@ def make_employee_salary_slip(user, payroll_frequency, salary_structure=None):
salary_structure = payroll_frequency + " Salary Structure Test for Salary Slip" salary_structure = payroll_frequency + " Salary Structure Test for Salary Slip"
employee = frappe.db.get_value("Employee", {"user_id": user}) employee = frappe.db.get_value("Employee", {"user_id": user})
if not frappe.db.exists('Salary Structure', salary_structure): salary_structure_doc = make_salary_structure(salary_structure, payroll_frequency, employee=employee)
salary_structure_doc = make_salary_structure(salary_structure, payroll_frequency, employee)
else:
salary_structure_doc = frappe.get_doc('Salary Structure', salary_structure)
salary_slip_name = frappe.db.get_value("Salary Slip", {"employee": frappe.db.get_value("Employee", {"user_id": user})}) salary_slip_name = frappe.db.get_value("Salary Slip", {"employee": frappe.db.get_value("Employee", {"user_id": user})})
if not salary_slip_name: if not salary_slip_name:
@ -558,14 +586,6 @@ def make_deduction_salary_component(setup=False, test_tax=False, company_list=No
"amount": 200, "amount": 200,
"exempted_from_income_tax": 1 "exempted_from_income_tax": 1
},
{
"salary_component": 'TDS',
"abbr":'T',
"type": "Deduction",
"depends_on_payment_days": 0,
"variable_based_on_taxable_salary": 1,
"round_to_the_nearest_integer": 1
} }
] ]
if not test_tax: if not test_tax:
@ -576,6 +596,15 @@ def make_deduction_salary_component(setup=False, test_tax=False, company_list=No
"type": "Deduction", "type": "Deduction",
"round_to_the_nearest_integer": 1 "round_to_the_nearest_integer": 1
}) })
else:
data.append({
"salary_component": 'TDS',
"abbr":'T',
"type": "Deduction",
"depends_on_payment_days": 0,
"variable_based_on_taxable_salary": 1,
"round_to_the_nearest_integer": 1
})
if setup or test_tax: if setup or test_tax:
make_salary_component(data, test_tax, company_list) make_salary_component(data, test_tax, company_list)
@ -632,8 +661,13 @@ def create_benefit_claim(employee, payroll_period, amount, component):
}).submit() }).submit()
return claim_date return claim_date
def create_tax_slab(payroll_period, effective_date = None, allow_tax_exemption = False, dont_submit = False, currency=erpnext.get_default_currency()): def create_tax_slab(payroll_period, effective_date = None, allow_tax_exemption = False, dont_submit = False, currency=None,
frappe.db.sql("""delete from `tabIncome Tax Slab`""") company=None):
if not currency:
currency = erpnext.get_default_currency()
if company:
currency = erpnext.get_company_currency(company)
slabs = [ slabs = [
{ {
@ -653,26 +687,33 @@ def create_tax_slab(payroll_period, effective_date = None, allow_tax_exemption =
} }
] ]
income_tax_slab = frappe.new_doc("Income Tax Slab") income_tax_slab_name = frappe.db.get_value("Income Tax Slab", {"currency": currency})
income_tax_slab.name = "Tax Slab: " + payroll_period.name if not income_tax_slab_name:
income_tax_slab.effective_from = effective_date or add_days(payroll_period.start_date, -2) income_tax_slab = frappe.new_doc("Income Tax Slab")
income_tax_slab.currency = currency income_tax_slab.name = "Tax Slab: " + payroll_period.name + " " + cstr(currency)
income_tax_slab.effective_from = effective_date or add_days(payroll_period.start_date, -2)
income_tax_slab.company = company or ''
income_tax_slab.currency = currency
if allow_tax_exemption: if allow_tax_exemption:
income_tax_slab.allow_tax_exemption = 1 income_tax_slab.allow_tax_exemption = 1
income_tax_slab.standard_tax_exemption_amount = 50000 income_tax_slab.standard_tax_exemption_amount = 50000
for item in slabs: for item in slabs:
income_tax_slab.append("slabs", item) income_tax_slab.append("slabs", item)
income_tax_slab.append("other_taxes_and_charges", { income_tax_slab.append("other_taxes_and_charges", {
"description": "cess", "description": "cess",
"percent": 4 "percent": 4
}) })
income_tax_slab.save() income_tax_slab.save()
if not dont_submit: if not dont_submit:
income_tax_slab.submit() income_tax_slab.submit()
return income_tax_slab.name
else:
return income_tax_slab_name
def create_salary_slips_for_payroll_period(employee, salary_structure, payroll_period, deduct_random=True): def create_salary_slips_for_payroll_period(employee, salary_structure, payroll_period, deduct_random=True):
deducted_dates = [] deducted_dates = []

View File

@ -114,7 +114,7 @@ class TestSalaryStructure(unittest.TestCase):
self.assertEqual(sal_struct.currency, 'USD') self.assertEqual(sal_struct.currency, 'USD')
def make_salary_structure(salary_structure, payroll_frequency, employee=None, dont_submit=False, other_details=None, def make_salary_structure(salary_structure, payroll_frequency, employee=None, dont_submit=False, other_details=None,
test_tax=False, company=None, currency=erpnext.get_default_currency()): test_tax=False, company=None, currency=erpnext.get_default_currency(), payroll_period=None):
if test_tax: if test_tax:
frappe.db.sql("""delete from `tabSalary Structure` where name=%s""",(salary_structure)) frappe.db.sql("""delete from `tabSalary Structure` where name=%s""",(salary_structure))
@ -141,16 +141,24 @@ def make_salary_structure(salary_structure, payroll_frequency, employee=None, do
if employee and not frappe.db.get_value("Salary Structure Assignment", if employee and not frappe.db.get_value("Salary Structure Assignment",
{'employee':employee, 'docstatus': 1}) and salary_structure_doc.docstatus==1: {'employee':employee, 'docstatus': 1}) and salary_structure_doc.docstatus==1:
create_salary_structure_assignment(employee, salary_structure, company=company, currency=currency) create_salary_structure_assignment(employee, salary_structure, company=company, currency=currency,
payroll_period=payroll_period)
return salary_structure_doc return salary_structure_doc
def create_salary_structure_assignment(employee, salary_structure, from_date=None, company=None, currency=erpnext.get_default_currency()): def create_salary_structure_assignment(employee, salary_structure, from_date=None, company=None, currency=erpnext.get_default_currency(),
payroll_period=None):
if frappe.db.exists("Salary Structure Assignment", {"employee": employee}): if frappe.db.exists("Salary Structure Assignment", {"employee": employee}):
frappe.db.sql("""delete from `tabSalary Structure Assignment` where employee=%s""",(employee)) frappe.db.sql("""delete from `tabSalary Structure Assignment` where employee=%s""",(employee))
payroll_period = create_payroll_period() if not payroll_period:
create_tax_slab(payroll_period, allow_tax_exemption=True, currency=currency) payroll_period = create_payroll_period()
income_tax_slab = frappe.db.get_value("Income Tax Slab", {"currency": currency})
if not income_tax_slab:
income_tax_slab = create_tax_slab(payroll_period, allow_tax_exemption=True, currency=currency)
salary_structure_assignment = frappe.new_doc("Salary Structure Assignment") salary_structure_assignment = frappe.new_doc("Salary Structure Assignment")
salary_structure_assignment.employee = employee salary_structure_assignment.employee = employee
@ -162,7 +170,7 @@ def create_salary_structure_assignment(employee, salary_structure, from_date=Non
salary_structure_assignment.payroll_payable_account = get_payable_account(company) salary_structure_assignment.payroll_payable_account = get_payable_account(company)
salary_structure_assignment.company = company or erpnext.get_default_company() salary_structure_assignment.company = company or erpnext.get_default_company()
salary_structure_assignment.save(ignore_permissions=True) salary_structure_assignment.save(ignore_permissions=True)
salary_structure_assignment.income_tax_slab = "Tax Slab: _Test Payroll Period" salary_structure_assignment.income_tax_slab = income_tax_slab
salary_structure_assignment.submit() salary_structure_assignment.submit()
return salary_structure_assignment return salary_structure_assignment

View File

@ -43,7 +43,7 @@ class SalaryStructureAssignment(Document):
def set_payroll_payable_account(self): def set_payroll_payable_account(self):
if not self.payroll_payable_account: if not self.payroll_payable_account:
payroll_payable_account = frappe.db.get_value('Company', self.company, 'default_payable_account') payroll_payable_account = frappe.db.get_value('Company', self.company, 'default_payroll_payable_account')
if not payroll_payable_account: if not payroll_payable_account:
payroll_payable_account = frappe.db.get_value( payroll_payable_account = frappe.db.get_value(
"Account", { "Account", {

View File

@ -543,6 +543,7 @@ erpnext.TransactionController = erpnext.taxes_and_totals.extend({
company: me.frm.doc.company, company: me.frm.doc.company,
order_type: me.frm.doc.order_type, order_type: me.frm.doc.order_type,
is_pos: cint(me.frm.doc.is_pos), is_pos: cint(me.frm.doc.is_pos),
is_return: cint(me.frm.doc.is_return),
is_subcontracted: me.frm.doc.is_subcontracted, is_subcontracted: me.frm.doc.is_subcontracted,
transaction_date: me.frm.doc.transaction_date || me.frm.doc.posting_date, transaction_date: me.frm.doc.transaction_date || me.frm.doc.posting_date,
ignore_pricing_rule: me.frm.doc.ignore_pricing_rule, ignore_pricing_rule: me.frm.doc.ignore_pricing_rule,

View File

@ -59,7 +59,7 @@
{item_list} {item_list}
], ],
"ValDtls": {{ "ValDtls": {{
"AssVal": "{invoice_value_details.base_net_total}", "AssVal": "{invoice_value_details.base_total}",
"CgstVal": "{invoice_value_details.total_cgst_amt}", "CgstVal": "{invoice_value_details.total_cgst_amt}",
"SgstVal": "{invoice_value_details.total_sgst_amt}", "SgstVal": "{invoice_value_details.total_sgst_amt}",
"IgstVal": "{invoice_value_details.total_igst_amt}", "IgstVal": "{invoice_value_details.total_igst_amt}",

View File

@ -88,25 +88,22 @@ def get_party_details(address_name):
gstin = address.get('gstin') gstin = address.get('gstin')
gstin_details = get_gstin_details(gstin) gstin_details = get_gstin_details(gstin)
legal_name = gstin_details.get('LegalName') legal_name = gstin_details.get('LegalName') or gstin_details.get('TradeName')
location = gstin_details.get('AddrLoc') or address.get('city') location = gstin_details.get('AddrLoc') or address.get('city')
state_code = gstin_details.get('StateCode') state_code = gstin_details.get('StateCode')
pincode = gstin_details.get('AddrPncd') pincode = gstin_details.get('AddrPncd')
address_line1 = '{} {}'.format(gstin_details.get('AddrBno'), gstin_details.get('AddrFlno')) address_line1 = '{} {}'.format(gstin_details.get('AddrBno') or "", gstin_details.get('AddrFlno') or "")
address_line2 = '{} {}'.format(gstin_details.get('AddrBnm'), gstin_details.get('AddrSt')) address_line2 = '{} {}'.format(gstin_details.get('AddrBnm') or "", gstin_details.get('AddrSt') or "")
email_id = address.get('email_id')
phone = address.get('phone')
# get last 10 digit
phone = phone.replace(" ", "")[-10:] if phone else ''
if state_code == 97: if state_code == 97:
# according to einvoice standard # according to einvoice standard
pincode = 999999 pincode = 999999
return frappe._dict(dict( return frappe._dict(dict(
gstin=gstin, legal_name=legal_name, location=location, gstin=gstin, legal_name=legal_name,
pincode=pincode, state_code=state_code, address_line1=address_line1, location=location, pincode=pincode,
address_line2=address_line2, email=email_id, phone=phone state_code=state_code, address_line1=address_line1,
address_line2=address_line2
)) ))
def get_gstin_details(gstin): def get_gstin_details(gstin):
@ -146,16 +143,18 @@ def get_item_list(invoice):
item.update(d.as_dict()) item.update(d.as_dict())
item.sr_no = d.idx item.sr_no = d.idx
item.description = d.item_name.replace('"', '\\"')
item.qty = abs(item.qty) item.qty = abs(item.qty)
item.description = d.item_name
item.taxable_value = abs(item.base_net_amount)
item.discount_amount = abs(item.discount_amount * item.qty) item.discount_amount = abs(item.discount_amount * item.qty)
item.unit_rate = abs(item.base_price_list_rate) if item.discount_amount else abs(item.base_net_rate) item.unit_rate = abs(item.base_amount / item.qty)
item.gross_amount = abs(item.unit_rate * item.qty) item.gross_amount = abs(item.base_amount)
item.taxable_value = abs(item.base_amount)
item.batch_expiry_date = frappe.db.get_value('Batch', d.batch_no, 'expiry_date') if d.batch_no else None item.batch_expiry_date = frappe.db.get_value('Batch', d.batch_no, 'expiry_date') if d.batch_no else None
item.batch_expiry_date = format_date(item.batch_expiry_date, 'dd/mm/yyyy') if item.batch_expiry_date else None item.batch_expiry_date = format_date(item.batch_expiry_date, 'dd/mm/yyyy') if item.batch_expiry_date else None
item.is_service_item = 'N' if frappe.db.get_value('Item', d.item_code, 'is_stock_item') else 'Y' item.is_service_item = 'N' if frappe.db.get_value('Item', d.item_code, 'is_stock_item') else 'Y'
item.serial_no = ""
item = update_item_taxes(invoice, item) item = update_item_taxes(invoice, item)
@ -180,35 +179,35 @@ def update_item_taxes(invoice, item):
item[attr] = 0 item[attr] = 0
for t in invoice.taxes: for t in invoice.taxes:
# this contains item wise tax rate & tax amount (incl. discount)
item_tax_detail = json.loads(t.item_wise_tax_detail).get(item.item_code) item_tax_detail = json.loads(t.item_wise_tax_detail).get(item.item_code)
if t.account_head in gst_accounts_list: if t.account_head in gst_accounts_list:
item_tax_rate = item_tax_detail[0]
# item tax amount excluding discount amount
item_tax_amount = (item_tax_rate / 100) * item.base_amount
if t.account_head in gst_accounts.cess_account: if t.account_head in gst_accounts.cess_account:
item_tax_amount_after_discount = item_tax_detail[1]
if t.charge_type == 'On Item Quantity': if t.charge_type == 'On Item Quantity':
item.cess_nadv_amount += abs(item_tax_detail[1]) item.cess_nadv_amount += abs(item_tax_amount_after_discount)
else: else:
item.cess_rate += item_tax_detail[0] item.cess_rate += item_tax_rate
item.cess_amount += abs(item_tax_detail[1]) item.cess_amount += abs(item_tax_amount_after_discount)
elif t.account_head in gst_accounts.igst_account:
item.tax_rate += item_tax_detail[0] for tax_type in ['igst', 'cgst', 'sgst']:
item.igst_amount += abs(item_tax_detail[1]) if t.account_head in gst_accounts[f'{tax_type}_account']:
elif t.account_head in gst_accounts.sgst_account: item.tax_rate += item_tax_rate
item.tax_rate += item_tax_detail[0] item[f'{tax_type}_amount'] += abs(item_tax_amount)
item.sgst_amount += abs(item_tax_detail[1])
elif t.account_head in gst_accounts.cgst_account:
item.tax_rate += item_tax_detail[0]
item.cgst_amount += abs(item_tax_detail[1])
return item return item
def get_invoice_value_details(invoice): def get_invoice_value_details(invoice):
invoice_value_details = frappe._dict(dict()) invoice_value_details = frappe._dict(dict())
invoice_value_details.base_net_total = abs(invoice.base_net_total) invoice_value_details.base_total = abs(invoice.base_total)
invoice_value_details.invoice_discount_amt = invoice.discount_amount if invoice.discount_amount and invoice.discount_amount > 0 else 0 invoice_value_details.invoice_discount_amt = invoice.discount_amount
# discount amount cannnot be -ve in an e-invoice, so if -ve include discount in round_off invoice_value_details.round_off = invoice.rounding_adjustment
invoice_value_details.round_off = invoice.rounding_adjustment - (invoice.discount_amount if invoice.discount_amount and invoice.discount_amount < 0 else 0) invoice_value_details.base_grand_total = abs(invoice.base_rounded_total) or abs(invoice.base_grand_total)
disable_rounded = frappe.db.get_single_value('Global Defaults', 'disable_rounded_total') invoice_value_details.grand_total = abs(invoice.rounded_total) or abs(invoice.grand_total)
invoice_value_details.base_grand_total = abs(invoice.base_grand_total) if disable_rounded else abs(invoice.base_rounded_total)
invoice_value_details.grand_total = abs(invoice.grand_total) if disable_rounded else abs(invoice.rounded_total)
invoice_value_details = update_invoice_taxes(invoice, invoice_value_details) invoice_value_details = update_invoice_taxes(invoice, invoice_value_details)
@ -226,15 +225,14 @@ def update_invoice_taxes(invoice, invoice_value_details):
for t in invoice.taxes: for t in invoice.taxes:
if t.account_head in gst_accounts_list: if t.account_head in gst_accounts_list:
if t.account_head in gst_accounts.cess_account: if t.account_head in gst_accounts.cess_account:
# using after discount amt since item also uses after discount amt for cess calc
invoice_value_details.total_cess_amt += abs(t.base_tax_amount_after_discount_amount) invoice_value_details.total_cess_amt += abs(t.base_tax_amount_after_discount_amount)
elif t.account_head in gst_accounts.igst_account:
invoice_value_details.total_igst_amt += abs(t.base_tax_amount_after_discount_amount) for tax_type in ['igst', 'cgst', 'sgst']:
elif t.account_head in gst_accounts.sgst_account: if t.account_head in gst_accounts[f'{tax_type}_account']:
invoice_value_details.total_sgst_amt += abs(t.base_tax_amount_after_discount_amount) invoice_value_details[f'total_{tax_type}_amt'] += abs(t.base_tax_amount)
elif t.account_head in gst_accounts.cgst_account:
invoice_value_details.total_cgst_amt += abs(t.base_tax_amount_after_discount_amount)
else: else:
invoice_value_details.total_other_charges += abs(t.base_tax_amount_after_discount_amount) invoice_value_details.total_other_charges += abs(t.base_tax_amount)
return invoice_value_details return invoice_value_details
@ -273,7 +271,25 @@ def get_eway_bill_details(invoice):
vehicle_type=vehicle_type[invoice.gst_vehicle_type] vehicle_type=vehicle_type[invoice.gst_vehicle_type]
)) ))
def validate_mandatory_fields(invoice):
if not invoice.company_address:
frappe.throw(_('Company Address is mandatory to fetch company GSTIN details.'), title=_('Missing Fields'))
if not invoice.customer_address:
frappe.throw(_('Customer Address is mandatory to fetch customer GSTIN details.'), title=_('Missing Fields'))
if not frappe.db.get_value('Address', invoice.company_address, 'gstin'):
frappe.throw(
_('GSTIN is mandatory to fetch company GSTIN details. Please enter GSTIN in selected company address.'),
title=_('Missing Fields')
)
if not frappe.db.get_value('Address', invoice.customer_address, 'gstin'):
frappe.throw(
_('GSTIN is mandatory to fetch customer GSTIN details. Please enter GSTIN in selected customer address.'),
title=_('Missing Fields')
)
def make_einvoice(invoice): def make_einvoice(invoice):
validate_mandatory_fields(invoice)
schema = read_json('einv_template') schema = read_json('einv_template')
transaction_details = get_transaction_details(invoice) transaction_details = get_transaction_details(invoice)
@ -358,7 +374,8 @@ def validate_einvoice(validations, einvoice, errors=[]):
einvoice[fieldname] = str(value) einvoice[fieldname] = str(value)
elif value_type == 'number': elif value_type == 'number':
is_integer = '.' not in str(field_validation.get('maximum')) is_integer = '.' not in str(field_validation.get('maximum'))
einvoice[fieldname] = flt(value, 2) if not is_integer else cint(value) precision = 3 if '.999' in str(field_validation.get('maximum')) else 2
einvoice[fieldname] = flt(value, precision) if not is_integer else cint(value)
value = einvoice[fieldname] value = einvoice[fieldname]
max_length = field_validation.get('maxLength') max_length = field_validation.get('maxLength')
@ -386,14 +403,14 @@ class GSPConnector():
self.invoice = frappe.get_cached_doc(doctype, docname) if doctype and docname else None self.invoice = frappe.get_cached_doc(doctype, docname) if doctype and docname else None
self.credentials = self.get_credentials() self.credentials = self.get_credentials()
self.base_url = 'https://gsp.adaequare.com/' self.base_url = 'https://gsp.adaequare.com'
self.authenticate_url = self.base_url + 'gsp/authenticate?grant_type=token' self.authenticate_url = self.base_url + '/gsp/authenticate?grant_type=token'
self.gstin_details_url = self.base_url + 'test/enriched/ei/api/master/gstin' self.gstin_details_url = self.base_url + '/enriched/ei/api/master/gstin'
self.generate_irn_url = self.base_url + 'test/enriched/ei/api/invoice' self.generate_irn_url = self.base_url + '/enriched/ei/api/invoice'
self.irn_details_url = self.base_url + 'test/enriched/ei/api/invoice/irn' self.irn_details_url = self.base_url + '/enriched/ei/api/invoice/irn'
self.cancel_irn_url = self.base_url + 'test/enriched/ei/api/invoice/cancel' self.cancel_irn_url = self.base_url + '/enriched/ei/api/invoice/cancel'
self.cancel_ewaybill_url = self.base_url + '/test/enriched/ei/api/ewayapi' self.cancel_ewaybill_url = self.base_url + '/enriched/ei/api/ewayapi'
self.generate_ewaybill_url = self.base_url + 'test/enriched/ei/api/ewaybill' self.generate_ewaybill_url = self.base_url + '/enriched/ei/api/ewaybill'
def get_credentials(self): def get_credentials(self):
if self.invoice: if self.invoice:

View File

@ -74,67 +74,71 @@ frappe.query_reports["Sales Analytics"] = {
return Object.assign(options, { return Object.assign(options, {
checkboxColumn: true, checkboxColumn: true,
events: { events: {
onCheckRow: function(data) { onCheckRow: function (data) {
if (!data) return;
const data_doctype = $(
data[2].html
)[0].attributes.getNamedItem("data-doctype").value;
const tree_type = frappe.query_report.filters[0].value;
if (data_doctype != tree_type) return;
row_name = data[2].content; row_name = data[2].content;
length = data.length; length = data.length;
var tree_type = frappe.query_report.filters[0].value; if (tree_type == "Customer") {
row_values = data
if(tree_type == "Customer") { .slice(4, length - 1)
row_values = data.slice(4,length-1).map(function (column) { .map(function (column) {
return column.content; return column.content;
}) });
} else if (tree_type == "Item") { } else if (tree_type == "Item") {
row_values = data.slice(5,length-1).map(function (column) { row_values = data
return column.content; .slice(5, length - 1)
}) .map(function (column) {
} return column.content;
else { });
row_values = data.slice(3,length-1).map(function (column) { } else {
return column.content; row_values = data
}) .slice(3, length - 1)
.map(function (column) {
return column.content;
});
} }
entry = { entry = {
'name':row_name, name: row_name,
'values':row_values values: row_values,
} };
let raw_data = frappe.query_report.chart.data; let raw_data = frappe.query_report.chart.data;
let new_datasets = raw_data.datasets; let new_datasets = raw_data.datasets;
var found = false; let element_found = new_datasets.some((element, index, array)=>{
if(element.name == row_name){
for(var i=0; i < new_datasets.length;i++){ array.splice(index, 1)
if(new_datasets[i].name == row_name){ return true
found = true;
new_datasets.splice(i,1);
break;
} }
} return false
})
if(!found){ if (!element_found) {
new_datasets.push(entry); new_datasets.push(entry);
} }
let new_data = { let new_data = {
labels: raw_data.labels, labels: raw_data.labels,
datasets: new_datasets datasets: new_datasets,
} };
chart_options = {
setTimeout(() => { data: new_data,
frappe.query_report.chart.update(new_data) type: "line",
}, 500) };
frappe.query_report.render_chart(chart_options);
setTimeout(() => {
frappe.query_report.chart.draw(true);
}, 1000)
frappe.query_report.raw_chart_data = new_data; frappe.query_report.raw_chart_data = new_data;
}, },
} },
}) });
}, },
} }

View File

@ -28,7 +28,7 @@ def delete_company_transactions(company_name):
"Party Account", "Employee", "Sales Taxes and Charges Template", "Party Account", "Employee", "Sales Taxes and Charges Template",
"Purchase Taxes and Charges Template", "POS Profile", "BOM", "Purchase Taxes and Charges Template", "POS Profile", "BOM",
"Company", "Bank Account", "Item Tax Template", "Mode Of Payment", "Company", "Bank Account", "Item Tax Template", "Mode Of Payment",
"Item Default"): "Item Default", "Customer", "Supplier"):
delete_for_doctype(doctype, company_name) delete_for_doctype(doctype, company_name)
# reset company values # reset company values

View File

@ -259,11 +259,16 @@ class StockEntry(StockController):
item_code.append(item.item_code) item_code.append(item.item_code)
def validate_fg_completed_qty(self): def validate_fg_completed_qty(self):
item_wise_qty = {}
if self.purpose == "Manufacture" and self.work_order: if self.purpose == "Manufacture" and self.work_order:
for d in self.items: for d in self.items:
if d.is_finished_item and d.qty != self.fg_completed_qty: if d.is_finished_item:
frappe.throw(_("Finished product quantity <b>{0}</b> and For Quantity <b>{1}</b> cannot be different") item_wise_qty.setdefault(d.item_code, []).append(d.qty)
.format(d.qty, self.fg_completed_qty))
for item_code, qty_list in iteritems(item_wise_qty):
if self.fg_completed_qty != sum(qty_list):
frappe.throw(_("The finished product {0} quantity {1} and For Quantity {2} cannot be different")
.format(frappe.bold(item_code), frappe.bold(sum(qty_list)), frappe.bold(self.fg_completed_qty)))
def validate_difference_account(self): def validate_difference_account(self):
if not cint(erpnext.is_perpetual_inventory_enabled(self.company)): if not cint(erpnext.is_perpetual_inventory_enabled(self.company)):
@ -319,7 +324,7 @@ class StockEntry(StockController):
if self.purpose == "Manufacture": if self.purpose == "Manufacture":
if validate_for_manufacture: if validate_for_manufacture:
if d.bom_no: if d.is_finished_item or d.is_scrap_item:
d.s_warehouse = None d.s_warehouse = None
if not d.t_warehouse: if not d.t_warehouse:
frappe.throw(_("Target warehouse is mandatory for row {0}").format(d.idx)) frappe.throw(_("Target warehouse is mandatory for row {0}").format(d.idx))

View File

@ -217,7 +217,7 @@
"fieldname": "role_allowed_to_create_edit_back_dated_transactions", "fieldname": "role_allowed_to_create_edit_back_dated_transactions",
"fieldtype": "Link", "fieldtype": "Link",
"label": "Role Allowed to Create/Edit Back-dated Transactions", "label": "Role Allowed to Create/Edit Back-dated Transactions",
"options": "User" "options": "Role"
}, },
{ {
"fieldname": "column_break_26", "fieldname": "column_break_26",
@ -234,7 +234,7 @@
"index_web_pages_for_search": 1, "index_web_pages_for_search": 1,
"issingle": 1, "issingle": 1,
"links": [], "links": [],
"modified": "2020-11-23 22:26:54.225608", "modified": "2020-12-29 12:53:31.162247",
"modified_by": "Administrator", "modified_by": "Administrator",
"module": "Stock", "module": "Stock",
"name": "Stock Settings", "name": "Stock Settings",

View File

@ -74,7 +74,9 @@ def get_item_details(args, doc=None, for_validate=False, overwrite_warehouse=Tru
update_party_blanket_order(args, out) update_party_blanket_order(args, out)
get_price_list_rate(args, item, out) if not doc or cint(doc.get('is_return')) == 0:
# get price list rate only if the invoice is not a credit or debit note
get_price_list_rate(args, item, out)
if args.customer and cint(args.is_pos): if args.customer and cint(args.is_pos):
out.update(get_pos_profile_item_details(args.company, args)) out.update(get_pos_profile_item_details(args.company, args))