Merge branch 'develop' into qi-ux
diff --git a/erpnext/accounts/doctype/budget/test_budget.py b/erpnext/accounts/doctype/budget/test_budget.py
index 0f115f9..cd88b11 100644
--- a/erpnext/accounts/doctype/budget/test_budget.py
+++ b/erpnext/accounts/doctype/budget/test_budget.py
@@ -159,10 +159,10 @@
budget = make_budget(budget_against="Cost Center")
month = now_datetime().month
- if month > 10:
- month = 10
+ if month > 9:
+ month = 9
- for i in range(month):
+ for i in range(month+1):
jv = make_journal_entry("_Test Account Cost for Goods Sold - _TC",
"_Test Bank - _TC", 20000, "_Test Cost Center - _TC", posting_date=nowdate(), submit=True)
@@ -181,10 +181,10 @@
budget = make_budget(budget_against="Project")
month = now_datetime().month
- if month > 10:
- month = 10
+ if month > 9:
+ month = 9
- for i in range(month):
+ for i in range(month + 1):
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")
diff --git a/erpnext/accounts/doctype/pos_invoice/pos_invoice.py b/erpnext/accounts/doctype/pos_invoice/pos_invoice.py
index d486ff6..ac98dcc 100644
--- a/erpnext/accounts/doctype/pos_invoice/pos_invoice.py
+++ b/erpnext/accounts/doctype/pos_invoice/pos_invoice.py
@@ -267,6 +267,8 @@
from erpnext.stock.get_item_details import get_pos_profile_item_details, get_pos_profile
if not self.pos_profile:
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')
profile = {}
diff --git a/erpnext/accounts/doctype/sales_invoice/test_sales_invoice.py b/erpnext/accounts/doctype/sales_invoice/test_sales_invoice.py
index 3c681ee..eb223ee 100644
--- a/erpnext/accounts/doctype/sales_invoice/test_sales_invoice.py
+++ b/erpnext/accounts/doctype/sales_invoice/test_sales_invoice.py
@@ -1885,8 +1885,8 @@
"item_code": "_Test Item",
"uom": "Nos",
"warehouse": "_Test Warehouse - _TC",
- "qty": 2,
- "rate": 100,
+ "qty": 2000,
+ "rate": 12,
"income_account": "Sales - _TC",
"expense_account": "Cost of Goods Sold - _TC",
"cost_center": "_Test Cost Center - _TC",
@@ -1895,31 +1895,52 @@
"item_code": "_Test Item 2",
"uom": "Nos",
"warehouse": "_Test Warehouse - _TC",
- "qty": 4,
- "rate": 150,
+ "qty": 420,
+ "rate": 15,
"income_account": "Sales - _TC",
"expense_account": "Cost of Goods Sold - _TC",
"cost_center": "_Test Cost Center - _TC",
})
+ si.discount_amount = 100
si.save()
einvoice = make_einvoice(si)
- total_item_ass_value = sum([d['AssAmt'] for d in einvoice['ItemList']])
- total_item_cgst_value = sum([d['CgstAmt'] for d in einvoice['ItemList']])
- total_item_sgst_value = sum([d['SgstAmt'] for d in einvoice['ItemList']])
- total_item_igst_value = sum([d['IgstAmt'] for d in einvoice['ItemList']])
- total_item_value = sum([d['TotItemVal'] for d in einvoice['ItemList']])
+ total_item_ass_value = 0
+ total_item_cgst_value = 0
+ total_item_sgst_value = 0
+ total_item_igst_value = 0
+ 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['ValDtls']['AssVal'], total_item_ass_value)
- self.assertEqual(einvoice['ValDtls']['CgstVal'], total_item_cgst_value)
- self.assertEqual(einvoice['ValDtls']['SgstVal'], total_item_sgst_value)
- self.assertEqual(einvoice['ValDtls']['IgstVal'], total_item_igst_value)
- self.assertEqual(einvoice['ValDtls']['TotInvVal'], total_item_value)
+ self.assertEqual(value_details['AssVal'], total_item_ass_value)
+ self.assertEqual(value_details['CgstVal'], total_item_cgst_value)
+ self.assertEqual(value_details['SgstVal'], total_item_sgst_value)
+ self.assertEqual(value_details['IgstVal'], total_item_igst_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'])
-def make_sales_invoice_for_ewaybill():
+def make_test_address_for_ewaybill():
if not frappe.db.exists('Address', '_Test Address for Eway bill-Billing'):
address = frappe.get_doc({
"address_line1": "_Test Address Line 1",
@@ -1967,7 +1988,8 @@
})
address.save()
-
+
+def make_test_transporter_for_ewaybill():
if not frappe.db.exists('Supplier', '_Test Transporter'):
frappe.get_doc({
"doctype": "Supplier",
@@ -1978,12 +2000,17 @@
"is_transporter": 1
}).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_account = frappe.get_all(
"GST Account",
fields=["cgst_account", "sgst_account", "igst_account"],
- filters = {"company": "_Test Company"})
+ filters = {"company": "_Test Company"}
+ )
if not gst_account:
gst_settings.append("gst_accounts", {
@@ -1995,7 +2022,7 @@
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.company_address = "_Test Address for Eway bill-Billing"
diff --git a/erpnext/accounts/report/asset_depreciation_ledger/asset_depreciation_ledger.py b/erpnext/accounts/report/asset_depreciation_ledger/asset_depreciation_ledger.py
index 16bef56..2162a02 100644
--- a/erpnext/accounts/report/asset_depreciation_ledger/asset_depreciation_ledger.py
+++ b/erpnext/accounts/report/asset_depreciation_ledger/asset_depreciation_ledger.py
@@ -47,21 +47,22 @@
for d in gl_entries:
asset_data = assets_details.get(d.against_voucher)
- if not asset_data.get("accumulated_depreciation_amount"):
- asset_data.accumulated_depreciation_amount = d.debit
- else:
- asset_data.accumulated_depreciation_amount += d.debit
+ if asset_data:
+ if not asset_data.get("accumulated_depreciation_amount"):
+ asset_data.accumulated_depreciation_amount = d.debit
+ else:
+ asset_data.accumulated_depreciation_amount += d.debit
- row = frappe._dict(asset_data)
- row.update({
- "depreciation_amount": d.debit,
- "depreciation_date": d.posting_date,
- "amount_after_depreciation": (flt(row.gross_purchase_amount) -
- flt(row.accumulated_depreciation_amount)),
- "depreciation_entry": d.voucher_no
- })
+ row = frappe._dict(asset_data)
+ row.update({
+ "depreciation_amount": d.debit,
+ "depreciation_date": d.posting_date,
+ "amount_after_depreciation": (flt(row.gross_purchase_amount) -
+ flt(row.accumulated_depreciation_amount)),
+ "depreciation_entry": d.voucher_no
+ })
- data.append(row)
+ data.append(row)
return data
diff --git a/erpnext/hr/doctype/leave_policy_assignment/leave_policy_assignment.json b/erpnext/hr/doctype/leave_policy_assignment/leave_policy_assignment.json
index bbb4222..a0327bd 100644
--- a/erpnext/hr/doctype/leave_policy_assignment/leave_policy_assignment.json
+++ b/erpnext/hr/doctype/leave_policy_assignment/leave_policy_assignment.json
@@ -111,13 +111,14 @@
],
"is_submittable": 1,
"links": [],
- "modified": "2020-12-17 16:27:20.311060",
+ "modified": "2020-12-31 16:43:30.695206",
"modified_by": "Administrator",
"module": "HR",
"name": "Leave Policy Assignment",
"owner": "Administrator",
"permissions": [
{
+ "cancel": 1,
"create": 1,
"delete": 1,
"email": 1,
@@ -131,6 +132,7 @@
"write": 1
},
{
+ "cancel": 1,
"create": 1,
"delete": 1,
"email": 1,
@@ -144,6 +146,7 @@
"write": 1
},
{
+ "cancel": 1,
"create": 1,
"delete": 1,
"email": 1,
diff --git a/erpnext/loan_management/doctype/loan/loan.py b/erpnext/loan_management/doctype/loan/loan.py
index cd40a66..2e0a4d1 100644
--- a/erpnext/loan_management/doctype/loan/loan.py
+++ b/erpnext/loan_management/doctype/loan/loan.py
@@ -6,6 +6,7 @@
import frappe, math, json
import erpnext
from frappe import _
+from six import string_types
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.controllers.accounts_controller import AccountsController
@@ -280,10 +281,13 @@
return write_off
@frappe.whitelist()
-def unpledge_security(loan=None, loan_security_pledge=None, as_dict=0, save=0, submit=0, approve=0):
- # if loan is passed it will be considered as full unpledge
+def unpledge_security(loan=None, loan_security_pledge=None, security_map=None, as_dict=0, save=0, submit=0, approve=0):
+ # 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:
- 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)
unpledge_request = create_loan_security_unpledge(pledge_qty_map, loan_doc.name, loan_doc.company,
loan_doc.applicant_type, loan_doc.applicant)
diff --git a/erpnext/loan_management/doctype/loan/test_loan.py b/erpnext/loan_management/doctype/loan/test_loan.py
index a63d065..8b1f9a2 100644
--- a/erpnext/loan_management/doctype/loan/test_loan.py
+++ b/erpnext/loan_management/doctype/loan/test_loan.py
@@ -45,7 +45,7 @@
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")
- 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"):
frappe.get_doc(get_customer_dict('_Test Loan Customer')).insert(ignore_permissions=True)
@@ -325,6 +325,43 @@
self.assertEquals(amounts['payable_principal_amount'], 0.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):
pledges = [{
"loan_security": "Test Security 2",
diff --git a/erpnext/loan_management/doctype/loan_security_shortfall/loan_security_shortfall.py b/erpnext/loan_management/doctype/loan_security_shortfall/loan_security_shortfall.py
index 8ec0bfb..6469806 100644
--- a/erpnext/loan_management/doctype/loan_security_shortfall/loan_security_shortfall.py
+++ b/erpnext/loan_management/doctype/loan_security_shortfall/loan_security_shortfall.py
@@ -81,7 +81,6 @@
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")
if existing_shortfall:
diff --git a/erpnext/loan_management/doctype/loan_security_unpledge/loan_security_unpledge.py b/erpnext/loan_management/doctype/loan_security_unpledge/loan_security_unpledge.py
index c29f325..61c418d 100644
--- a/erpnext/loan_management/doctype/loan_security_unpledge/loan_security_unpledge.py
+++ b/erpnext/loan_management/doctype/loan_security_unpledge/loan_security_unpledge.py
@@ -30,6 +30,8 @@
d.idx, frappe.bold(d.loan_security)))
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)
ltv_ratio_map = frappe._dict(frappe.get_all("Loan Security Type",
@@ -47,6 +49,8 @@
pending_principal_amount = flt(total_payment) - flt(interest_payable) - flt(principal_paid) - flt(written_off_amount)
security_value = 0
+ unpledge_qty_map = {}
+ ltv_ratio = 0
for security in self.securities:
pledged_qty = pledge_qty_map.get(security.loan_security, 0)
@@ -57,13 +61,15 @@
msg += _("You are trying to unpledge more.")
frappe.throw(msg, title=_("Loan Security Unpledge Error"))
- qty_after_unpledge = pledged_qty - security.qty
- ltv_ratio = ltv_ratio_map.get(security.loan_security_type)
+ unpledge_qty_map.setdefault(security.loan_security, 0)
+ unpledge_qty_map[security.loan_security] += security.qty
- current_price = loan_security_price_map.get(security.loan_security)
- if not current_price:
- frappe.throw(_("No valid Loan Security Price found for {0}").format(frappe.bold(security.loan_security)))
+ for security in pledge_qty_map:
+ if not ltv_ratio:
+ 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
if not security_value and flt(pending_principal_amount, 2) > 0:
diff --git a/erpnext/patches.txt b/erpnext/patches.txt
index 621f417..c5a478e 100644
--- a/erpnext/patches.txt
+++ b/erpnext/patches.txt
@@ -711,6 +711,7 @@
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_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.loyalty_points_entry_for_pos_invoice #22-07-2020
erpnext.patches.v12_0.add_taxjar_integration_field
diff --git a/erpnext/patches/v12_0/setup_einvoice_fields.py b/erpnext/patches/v12_0/setup_einvoice_fields.py
index d078276..2474bc3 100644
--- a/erpnext/patches/v12_0/setup_einvoice_fields.py
+++ b/erpnext/patches/v12_0/setup_einvoice_fields.py
@@ -8,6 +8,7 @@
if not company:
return
+ frappe.reload_doc("custom", "doctype", "custom_field")
frappe.reload_doc("regional", "doctype", "e_invoice_settings")
custom_fields = {
'Sales Invoice': [
diff --git a/erpnext/patches/v13_0/update_old_loans.py b/erpnext/patches/v13_0/update_old_loans.py
index 561e967..8cf09aa 100644
--- a/erpnext/patches/v13_0/update_old_loans.py
+++ b/erpnext/patches/v13_0/update_old_loans.py
@@ -1,7 +1,7 @@
from __future__ import unicode_literals
import frappe
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.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
@@ -113,15 +113,15 @@
interest_paid = 0
principal_paid = 0
- if total_interest > entry.interest_amount:
- interest_paid = entry.interest_amount
+ if flt(total_interest) > flt(entry.interest_amount):
+ interest_paid = flt(entry.interest_amount)
else:
- interest_paid = total_interest
+ interest_paid = flt(total_interest)
- if total_principal > entry.payable_principal_amount:
- principal_paid = entry.payable_principal_amount
+ if flt(total_principal) > flt(entry.payable_principal_amount):
+ principal_paid = flt(entry.payable_principal_amount)
else:
- principal_paid = total_principal
+ principal_paid = flt(total_principal)
frappe.db.sql(""" UPDATE `tabLoan Interest Accrual`
SET paid_principal_amount = `paid_principal_amount` + %s,
@@ -129,8 +129,8 @@
WHERE name = %s""",
(principal_paid, interest_paid, entry.name))
- total_principal -= principal_paid
- total_interest -= interest_paid
+ total_principal = flt(total_principal) - principal_paid
+ total_interest = flt(total_interest) - interest_paid
def create_loan_type(loan, loan_type_name, penalty_account):
loan_type_doc = frappe.new_doc('Loan Type')
diff --git a/erpnext/payroll/doctype/employee_tax_exemption_declaration/test_employee_tax_exemption_declaration.py b/erpnext/payroll/doctype/employee_tax_exemption_declaration/test_employee_tax_exemption_declaration.py
index 0609d19..311f352 100644
--- a/erpnext/payroll/doctype/employee_tax_exemption_declaration/test_employee_tax_exemption_declaration.py
+++ b/erpnext/payroll/doctype/employee_tax_exemption_declaration/test_employee_tax_exemption_declaration.py
@@ -86,19 +86,21 @@
self.assertEqual(declaration.total_exemption_amount, 100000)
-def create_payroll_period():
- if not frappe.db.exists("Payroll Period", "_Test Payroll Period"):
+def create_payroll_period(**args):
+ args = frappe._dict(args)
+ name = args.name or "_Test Payroll Period"
+ if not frappe.db.exists("Payroll Period", name):
from datetime import date
payroll_period = frappe.get_doc(dict(
doctype = 'Payroll Period',
- name = "_Test Payroll Period",
- company = erpnext.get_default_company(),
- start_date = date(date.today().year, 1, 1),
- end_date = date(date.today().year, 12, 31)
+ name = name,
+ company = args.company or erpnext.get_default_company(),
+ start_date = args.start_date or date(date.today().year, 1, 1),
+ end_date = args.end_date or date(date.today().year, 12, 31)
)).insert()
return payroll_period
else:
- return frappe.get_doc("Payroll Period", "_Test Payroll Period")
+ return frappe.get_doc("Payroll Period", name)
def create_exemption_category():
if not frappe.db.exists("Employee Tax Exemption Category", "_Test Category"):
diff --git a/erpnext/payroll/doctype/income_tax_slab/income_tax_slab.py b/erpnext/payroll/doctype/income_tax_slab/income_tax_slab.py
index 253f023..81e3647 100644
--- a/erpnext/payroll/doctype/income_tax_slab/income_tax_slab.py
+++ b/erpnext/payroll/doctype/income_tax_slab/income_tax_slab.py
@@ -3,8 +3,11 @@
# For license information, please see license.txt
from __future__ import unicode_literals
-# import frappe
+#import frappe
+import erpnext
from frappe.model.document import Document
class IncomeTaxSlab(Document):
- pass
+ def validate(self):
+ if self.company:
+ self.currency = erpnext.get_company_currency(self.company)
diff --git a/erpnext/payroll/doctype/payroll_employee_detail/payroll_employee_detail.json b/erpnext/payroll/doctype/payroll_employee_detail/payroll_employee_detail.json
index 8a55224..09c7eb9 100644
--- a/erpnext/payroll/doctype/payroll_employee_detail/payroll_employee_detail.json
+++ b/erpnext/payroll/doctype/payroll_employee_detail/payroll_employee_detail.json
@@ -17,8 +17,7 @@
"fieldtype": "Link",
"in_list_view": 1,
"label": "Employee",
- "options": "Employee",
- "read_only": 1
+ "options": "Employee"
},
{
"fetch_from": "employee.employee_name",
@@ -52,7 +51,7 @@
],
"istable": 1,
"links": [],
- "modified": "2020-09-30 12:40:07.999878",
+ "modified": "2020-12-17 15:43:29.542977",
"modified_by": "Administrator",
"module": "Payroll",
"name": "Payroll Employee Detail",
diff --git a/erpnext/payroll/doctype/payroll_entry/payroll_entry.js b/erpnext/payroll/doctype/payroll_entry/payroll_entry.js
index cb48abb..2288a27 100644
--- a/erpnext/payroll/doctype/payroll_entry/payroll_entry.js
+++ b/erpnext/payroll/doctype/payroll_entry/payroll_entry.js
@@ -10,15 +10,22 @@
}
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 {
"filters": {
"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 {
filters: {
"company": frm.doc.company,
@@ -29,12 +36,12 @@
});
},
- refresh: function(frm) {
+ refresh: function (frm) {
if (frm.doc.docstatus == 0) {
- if(!frm.is_new()) {
+ if (!frm.is_new()) {
frm.page.clear_primary_action();
frm.add_custom_button(__("Get Employees"),
- function() {
+ function () {
frm.events.get_employee_details(frm);
}
).toggleClass('btn-primary', !(frm.doc.employees || []).length);
@@ -42,7 +49,7 @@
if ((frm.doc.employees || []).length) {
frm.page.clear_primary_action();
frm.page.set_primary_action(__('Create Salary Slips'), () => {
- frm.save('Submit').then(()=>{
+ frm.save('Submit').then(() => {
frm.page.clear_primary_action();
frm.refresh();
frm.events.refresh(frm);
@@ -61,48 +68,48 @@
doc: frm.doc,
method: 'fill_employee_details',
}).then(r => {
- if (r.docs && r.docs[0].employees){
+ if (r.docs && r.docs[0].employees) {
frm.employees = r.docs[0].employees;
frm.dirty();
frm.save();
frm.refresh();
- if(r.docs[0].validate_attendance){
+ if (r.docs[0].validate_attendance) {
render_employee_attendance(frm, r.message);
}
}
- })
+ });
},
- create_salary_slips: function(frm) {
+ create_salary_slips: function (frm) {
frm.call({
doc: frm.doc,
method: "create_salary_slips",
- callback: function(r) {
+ callback: function () {
frm.refresh();
frm.toolbar.refresh();
}
- })
+ });
},
- add_context_buttons: function(frm) {
- if(frm.doc.salary_slips_submitted || (frm.doc.__onload && frm.doc.__onload.submitted_ss)) {
+ add_context_buttons: function (frm) {
+ if (frm.doc.salary_slips_submitted || (frm.doc.__onload && frm.doc.__onload.submitted_ss)) {
frm.events.add_bank_entry_button(frm);
- } else if(frm.doc.salary_slips_created) {
- frm.add_custom_button(__("Submit Salary Slip"), function() {
+ } else if (frm.doc.salary_slips_created) {
+ frm.add_custom_button(__("Submit Salary Slip"), function () {
submit_salary_slip(frm);
}).addClass("btn-primary");
}
},
- add_bank_entry_button: function(frm) {
+ add_bank_entry_button: function (frm) {
frappe.call({
method: 'erpnext.payroll.doctype.payroll_entry.payroll_entry.payroll_entry_has_bank_entries',
args: {
'name': frm.doc.name
},
- callback: function(r) {
+ callback: function (r) {
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);
}).addClass("btn-primary");
}
@@ -141,8 +148,37 @@
},
payroll_frequency: function (frm) {
- frm.trigger("set_start_end_dates");
- frm.events.clear_employee_table(frm);
+ frm.trigger("set_start_end_dates").then( ()=> {
+ 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) {
@@ -164,17 +200,17 @@
from_currency: frm.doc.currency,
to_currency: company_currency,
},
- callback: function(r) {
+ callback: function (r) {
frm.set_value("exchange_rate", flt(r.message));
frm.set_df_property('exchange_rate', 'hidden', 0);
- frm.set_df_property("exchange_rate", "description", "1 " + frm.doc.currency
- + " = [?] " + company_currency);
+ frm.set_df_property("exchange_rate", "description", "1 " + frm.doc.currency +
+ " = [?] " + company_currency);
}
});
} else {
frm.set_value("exchange_rate", 1.0);
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 @@
},
start_date: function (frm) {
- if(!in_progress && frm.doc.start_date){
+ if (!in_progress && frm.doc.start_date) {
frm.trigger("set_end_date");
- }else{
+ } else {
// reset flag
in_progress = false;
}
@@ -228,7 +264,7 @@
}
},
- set_end_date: function(frm){
+ set_end_date: function (frm) {
frappe.call({
method: 'erpnext.payroll.doctype.payroll_entry.payroll_entry.get_end_date',
args: {
@@ -243,19 +279,19 @@
});
},
- validate_attendance: function(frm){
- if(frm.doc.validate_attendance && frm.doc.employees){
+ validate_attendance: function (frm) {
+ if (frm.doc.validate_attendance && frm.doc.employees) {
frappe.call({
method: 'validate_employee_attendance',
args: {},
- callback: function(r) {
+ callback: function (r) {
render_employee_attendance(frm, r.message);
},
doc: frm.doc,
freeze: true,
freeze_message: __('Validating Employee Attendance...')
});
- }else{
+ } else {
frm.fields_dict.attendance_detail_html.html("");
}
},
@@ -270,18 +306,20 @@
const submit_salary_slip = function (frm) {
frappe.confirm(__('This will submit Salary Slips and create accrual Journal Entry. Do you want to proceed?'),
- function() {
+ function () {
frappe.call({
method: 'submit_salary_slips',
args: {},
- callback: function() {frm.events.refresh(frm);},
+ callback: function () {
+ frm.events.refresh(frm);
+ },
doc: frm.doc,
freeze: true,
freeze_message: __('Submitting Salary Slips and creating Journal Entry...')
});
},
- function() {
- if(frappe.dom.freeze_count) {
+ function () {
+ if (frappe.dom.freeze_count) {
frappe.dom.unfreeze();
frm.events.refresh(frm);
}
@@ -295,9 +333,11 @@
return frappe.call({
doc: cur_frm.doc,
method: "make_payment_entry",
- callback: function() {
+ callback: function () {
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,
@@ -309,11 +349,19 @@
}
};
-
-let render_employee_attendance = function(frm, data) {
+let render_employee_attendance = function (frm, data) {
frm.fields_dict.attendance_detail_html.html(
frappe.render_template('employees_to_mark_attendance', {
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"));
+ }
+ }
+});
diff --git a/erpnext/payroll/doctype/payroll_entry/payroll_entry.json b/erpnext/payroll/doctype/payroll_entry/payroll_entry.json
index 7a48dd1..0444134 100644
--- a/erpnext/payroll/doctype/payroll_entry/payroll_entry.json
+++ b/erpnext/payroll/doctype/payroll_entry/payroll_entry.json
@@ -129,8 +129,7 @@
"fieldname": "employees",
"fieldtype": "Table",
"label": "Employee Details",
- "options": "Payroll Employee Detail",
- "read_only": 1
+ "options": "Payroll Employee Detail"
},
{
"fieldname": "section_break_13",
@@ -290,7 +289,7 @@
"icon": "fa fa-cog",
"is_submittable": 1,
"links": [],
- "modified": "2020-10-23 13:00:33.753228",
+ "modified": "2020-12-17 15:13:17.766210",
"modified_by": "Administrator",
"module": "Payroll",
"name": "Payroll Entry",
diff --git a/erpnext/payroll/doctype/payroll_entry/payroll_entry.py b/erpnext/payroll/doctype/payroll_entry/payroll_entry.py
index 8c2d974..a25a6e7 100644
--- a/erpnext/payroll/doctype/payroll_entry/payroll_entry.py
+++ b/erpnext/payroll/doctype/payroll_entry/payroll_entry.py
@@ -6,7 +6,7 @@
import frappe, erpnext
from frappe.model.document import Document
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 erpnext.accounts.utils import get_fiscal_year
from erpnext.hr.doctype.employee.employee import get_holiday_list_for_employee
@@ -19,16 +19,26 @@
# check if salary slips were manually submitted
entries = frappe.db.count("Salary Slip", {'payroll_entry': self.name, 'docstatus': 1}, ['name'])
if cint(entries) == len(self.employees):
- self.set_onload("submitted_ss", True)
+ self.set_onload("submitted_ss", True)
def on_submit(self):
self.create_salary_slips()
def before_submit(self):
+ self.validate_employee_details()
if self.validate_attendance:
if self.validate_employee_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):
frappe.delete_doc("Salary Slip", frappe.db.sql_list("""select name from `tabSalary Slip`
where payroll_entry=%s """, (self.name)))
@@ -71,8 +81,17 @@
and t2.docstatus = 1
%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)
+
+ emp_list = self.remove_payrolled_employees(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):
self.set('employees', [])
employees = self.get_emp_list()
@@ -542,7 +561,7 @@
title = _("Creating Salary Slips..."))
else:
salary_slip_name = frappe.db.sql(
- '''SELECT
+ '''SELECT
name
FROM `tabSalary Slip`
WHERE company=%s
diff --git a/erpnext/payroll/doctype/payroll_entry/test_payroll_entry.py b/erpnext/payroll/doctype/payroll_entry/test_payroll_entry.py
index 54106c8..e098ec7 100644
--- a/erpnext/payroll/doctype/payroll_entry/test_payroll_entry.py
+++ b/erpnext/payroll/doctype/payroll_entry/test_payroll_entry.py
@@ -22,7 +22,7 @@
frappe.db.sql("delete from `tab%s`" % dt)
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)
@@ -107,9 +107,9 @@
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",
"_Test Payroll Payable - _TC")
-
- 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 2", "Monthly", employee2, company="_Test Company", currency=frappe.db.get_value("Company", "_Test Company", "default_currency"))
+ 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=currency, test_tax=False)
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}):
diff --git a/erpnext/payroll/doctype/salary_slip/salary_slip.js b/erpnext/payroll/doctype/salary_slip/salary_slip.js
index f7e22c6..8e05bb2 100644
--- a/erpnext/payroll/doctype/salary_slip/salary_slip.js
+++ b/erpnext/payroll/doctype/salary_slip/salary_slip.js
@@ -125,15 +125,15 @@
change_form_labels: function(frm, company_currency) {
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);
- 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);
// toggle fields
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);
},
@@ -214,14 +214,16 @@
});
var calculate_totals = function(frm) {
- if (frm.doc.earnings || frm.doc.deductions) {
- frappe.call({
- method: "set_totals",
- doc: frm.doc,
- callback: function() {
- frm.refresh_fields();
- }
- });
+ if (frm.doc.docstatus === 0) {
+ if (frm.doc.earnings || frm.doc.deductions) {
+ frappe.call({
+ method: "set_totals",
+ doc: frm.doc,
+ callback: function() {
+ frm.refresh_fields();
+ }
+ });
+ }
}
};
diff --git a/erpnext/payroll/doctype/salary_slip/salary_slip.json b/erpnext/payroll/doctype/salary_slip/salary_slip.json
index 386618c..43deee4 100644
--- a/erpnext/payroll/doctype/salary_slip/salary_slip.json
+++ b/erpnext/payroll/doctype/salary_slip/salary_slip.json
@@ -69,9 +69,13 @@
"net_pay_info",
"net_pay",
"base_net_pay",
+ "year_to_date",
+ "base_year_to_date",
"column_break_53",
"rounded_total",
"base_rounded_total",
+ "month_to_date",
+ "base_month_to_date",
"section_break_55",
"total_in_words",
"column_break_69",
@@ -578,13 +582,41 @@
{
"fieldname": "column_break_69",
"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",
"idx": 9,
"is_submittable": 1,
"links": [],
- "modified": "2020-10-21 23:02:59.400249",
+ "modified": "2020-12-21 23:43:44.959840",
"modified_by": "Administrator",
"module": "Payroll",
"name": "Salary Slip",
diff --git a/erpnext/payroll/doctype/salary_slip/salary_slip.py b/erpnext/payroll/doctype/salary_slip/salary_slip.py
index 20365b1..99d8a83 100644
--- a/erpnext/payroll/doctype/salary_slip/salary_slip.py
+++ b/erpnext/payroll/doctype/salary_slip/salary_slip.py
@@ -5,7 +5,7 @@
import frappe, erpnext
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 import msgprint, _
@@ -18,6 +18,7 @@
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.loan_management.doctype.loan_repayment.loan_repayment import calculate_amounts, create_repayment_entry
+from erpnext.accounts.utils import get_fiscal_year
class SalarySlip(TransactionBase):
def __init__(self, *args, **kwargs):
@@ -49,6 +50,8 @@
self.get_working_days_details(lwp = self.leave_without_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"):
max_working_hours = frappe.db.get_single_value("Payroll Settings", "max_working_hours_against_timesheet")
@@ -1125,6 +1128,46 @@
self.gross_pay += self.earnings[i].amount
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):
linked_ss = frappe.db.sql_list("""select name from `tabSalary Slip`
where journal_entry=%s and docstatus < 2""", (ref_no))
@@ -1135,4 +1178,4 @@
def generate_password_for_pdf(policy_template, employee):
employee = frappe.get_doc("Employee", employee)
- return policy_template.format(**employee.as_dict())
+ return policy_template.format(**employee.as_dict())
\ No newline at end of file
diff --git a/erpnext/payroll/doctype/salary_slip/test_salary_slip.py b/erpnext/payroll/doctype/salary_slip/test_salary_slip.py
index 5daf1d4..d6fb419 100644
--- a/erpnext/payroll/doctype/salary_slip/test_salary_slip.py
+++ b/erpnext/payroll/doctype/salary_slip/test_salary_slip.py
@@ -9,7 +9,7 @@
import random
from erpnext.accounts.utils import get_fiscal_year
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.payroll_entry.payroll_entry import get_month_details
from erpnext.hr.doctype.employee.test_employee import make_employee
@@ -240,7 +240,11 @@
interest_income_account='Interest 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""")
loan = create_loan(applicant, "Car Loan", 11000, "Repay Over Number of Periods", 20, posting_date=add_months(nowdate(), -1))
loan.repay_from_salary = 1
@@ -290,6 +294,33 @@
self.assertEqual(salary_slip.gross_pay, 78000)
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):
data = {}
# test the impact of tax exemption declaration, tax exemption proof submission
@@ -410,10 +441,7 @@
salary_structure = payroll_frequency + " Salary Structure Test for Salary Slip"
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)
- else:
- salary_structure_doc = frappe.get_doc('Salary Structure', salary_structure)
+ salary_structure_doc = make_salary_structure(salary_structure, payroll_frequency, employee=employee)
salary_slip_name = frappe.db.get_value("Salary Slip", {"employee": frappe.db.get_value("Employee", {"user_id": user})})
if not salary_slip_name:
@@ -557,14 +585,6 @@
"amount": 200,
"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:
@@ -575,6 +595,15 @@
"type": "Deduction",
"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:
make_salary_component(data, test_tax, company_list)
@@ -631,8 +660,13 @@
}).submit()
return claim_date
-def create_tax_slab(payroll_period, effective_date = None, allow_tax_exemption = False, dont_submit = False, currency=erpnext.get_default_currency()):
- frappe.db.sql("""delete from `tabIncome Tax Slab`""")
+def create_tax_slab(payroll_period, effective_date = None, allow_tax_exemption = False, dont_submit = False, currency=None,
+ company=None):
+ if not currency:
+ currency = erpnext.get_default_currency()
+
+ if company:
+ currency = erpnext.get_company_currency(company)
slabs = [
{
@@ -652,26 +686,33 @@
}
]
- income_tax_slab = frappe.new_doc("Income Tax Slab")
- income_tax_slab.name = "Tax Slab: " + payroll_period.name
- income_tax_slab.effective_from = effective_date or add_days(payroll_period.start_date, -2)
- income_tax_slab.currency = currency
+ income_tax_slab_name = frappe.db.get_value("Income Tax Slab", {"currency": currency})
+ if not income_tax_slab_name:
+ income_tax_slab = frappe.new_doc("Income Tax Slab")
+ 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:
- income_tax_slab.allow_tax_exemption = 1
- income_tax_slab.standard_tax_exemption_amount = 50000
+ if allow_tax_exemption:
+ income_tax_slab.allow_tax_exemption = 1
+ income_tax_slab.standard_tax_exemption_amount = 50000
- for item in slabs:
- income_tax_slab.append("slabs", item)
+ for item in slabs:
+ income_tax_slab.append("slabs", item)
- income_tax_slab.append("other_taxes_and_charges", {
- "description": "cess",
- "percent": 4
- })
+ income_tax_slab.append("other_taxes_and_charges", {
+ "description": "cess",
+ "percent": 4
+ })
- income_tax_slab.save()
- if not dont_submit:
- income_tax_slab.submit()
+ income_tax_slab.save()
+ if not dont_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):
deducted_dates = []
diff --git a/erpnext/payroll/doctype/salary_structure/test_salary_structure.py b/erpnext/payroll/doctype/salary_structure/test_salary_structure.py
index abb6697..f2fb558 100644
--- a/erpnext/payroll/doctype/salary_structure/test_salary_structure.py
+++ b/erpnext/payroll/doctype/salary_structure/test_salary_structure.py
@@ -114,7 +114,7 @@
self.assertEqual(sal_struct.currency, 'USD')
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:
frappe.db.sql("""delete from `tabSalary Structure` where name=%s""",(salary_structure))
@@ -141,16 +141,24 @@
if employee and not frappe.db.get_value("Salary Structure Assignment",
{'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
-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}):
frappe.db.sql("""delete from `tabSalary Structure Assignment` where employee=%s""",(employee))
- payroll_period = create_payroll_period()
- create_tax_slab(payroll_period, allow_tax_exemption=True, currency=currency)
+ if not payroll_period:
+ 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.employee = employee
@@ -162,7 +170,7 @@
salary_structure_assignment.payroll_payable_account = get_payable_account(company)
salary_structure_assignment.company = company or erpnext.get_default_company()
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()
return salary_structure_assignment
diff --git a/erpnext/payroll/doctype/salary_structure_assignment/salary_structure_assignment.py b/erpnext/payroll/doctype/salary_structure_assignment/salary_structure_assignment.py
index dccb5df..a0c3013 100644
--- a/erpnext/payroll/doctype/salary_structure_assignment/salary_structure_assignment.py
+++ b/erpnext/payroll/doctype/salary_structure_assignment/salary_structure_assignment.py
@@ -43,7 +43,7 @@
def set_payroll_payable_account(self):
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:
payroll_payable_account = frappe.db.get_value(
"Account", {
diff --git a/erpnext/public/js/controllers/buying.js b/erpnext/public/js/controllers/buying.js
index fcd7c15..db85a3e 100644
--- a/erpnext/public/js/controllers/buying.js
+++ b/erpnext/public/js/controllers/buying.js
@@ -195,10 +195,6 @@
this._super(doc, cdt, cdn);
},
- batch_no: function(doc, cdt, cdn) {
- this._super(doc, cdt, cdn);
- },
-
received_qty: function(doc, cdt, cdn) {
this.calculate_accepted_qty(doc, cdt, cdn)
},
diff --git a/erpnext/public/js/controllers/transaction.js b/erpnext/public/js/controllers/transaction.js
index 7bd72c6..3bc20f8 100644
--- a/erpnext/public/js/controllers/transaction.js
+++ b/erpnext/public/js/controllers/transaction.js
@@ -1104,11 +1104,6 @@
}
},
- batch_no: function(doc, cdt, cdn) {
- let item = frappe.get_doc(cdt, cdn);
- this.apply_price_list(item, true);
- },
-
toggle_conversion_factor: function(item) {
// toggle read only property for conversion factor field if the uom and stock uom are same
if(this.frm.get_field('items').grid.fields_map.conversion_factor) {
@@ -1413,7 +1408,6 @@
"pricing_rules": d.pricing_rules,
"warehouse": d.warehouse,
"serial_no": d.serial_no,
- "batch_no": d.batch_no,
"price_list_rate": d.price_list_rate,
"conversion_factor": d.conversion_factor || 1.0
});
diff --git a/erpnext/regional/india/e_invoice/einv_template.json b/erpnext/regional/india/e_invoice/einv_template.json
index e5751da..60f490d 100644
--- a/erpnext/regional/india/e_invoice/einv_template.json
+++ b/erpnext/regional/india/e_invoice/einv_template.json
@@ -59,7 +59,7 @@
{item_list}
],
"ValDtls": {{
- "AssVal": "{invoice_value_details.base_net_total}",
+ "AssVal": "{invoice_value_details.base_total}",
"CgstVal": "{invoice_value_details.total_cgst_amt}",
"SgstVal": "{invoice_value_details.total_sgst_amt}",
"IgstVal": "{invoice_value_details.total_igst_amt}",
diff --git a/erpnext/regional/india/e_invoice/utils.py b/erpnext/regional/india/e_invoice/utils.py
index cb92c42..02ce6c1 100644
--- a/erpnext/regional/india/e_invoice/utils.py
+++ b/erpnext/regional/india/e_invoice/utils.py
@@ -88,7 +88,7 @@
gstin = address.get('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')
state_code = gstin_details.get('StateCode')
pincode = gstin_details.get('AddrPncd')
@@ -146,12 +146,12 @@
item.update(d.as_dict())
item.sr_no = d.idx
- 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.unit_rate = abs(item.base_price_list_rate) if item.discount_amount else abs(item.base_net_rate)
- item.gross_amount = abs(item.unit_rate * item.qty)
+ item.description = d.item_name
+ item.qty = abs(item.qty)
+ item.unit_rate = abs(item.base_amount / 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 = format_date(item.batch_expiry_date, 'dd/mm/yyyy') if item.batch_expiry_date else None
@@ -180,35 +180,35 @@
item[attr] = 0
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)
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:
+ item_tax_amount_after_discount = item_tax_detail[1]
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:
- item.cess_rate += item_tax_detail[0]
- item.cess_amount += abs(item_tax_detail[1])
- elif t.account_head in gst_accounts.igst_account:
- item.tax_rate += item_tax_detail[0]
- item.igst_amount += abs(item_tax_detail[1])
- elif t.account_head in gst_accounts.sgst_account:
- item.tax_rate += item_tax_detail[0]
- 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])
-
+ item.cess_rate += item_tax_rate
+ item.cess_amount += abs(item_tax_amount_after_discount)
+
+ for tax_type in ['igst', 'cgst', 'sgst']:
+ if t.account_head in gst_accounts[f'{tax_type}_account']:
+ item.tax_rate += item_tax_rate
+ item[f'{tax_type}_amount'] += abs(item_tax_amount)
+
return item
def get_invoice_value_details(invoice):
invoice_value_details = frappe._dict(dict())
- invoice_value_details.base_net_total = abs(invoice.base_net_total)
- invoice_value_details.invoice_discount_amt = invoice.discount_amount if invoice.discount_amount and invoice.discount_amount > 0 else 0
- # 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.discount_amount if invoice.discount_amount and invoice.discount_amount < 0 else 0)
- disable_rounded = frappe.db.get_single_value('Global Defaults', 'disable_rounded_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.base_total = abs(invoice.base_total)
+ invoice_value_details.invoice_discount_amt = invoice.discount_amount
+ invoice_value_details.round_off = invoice.rounding_adjustment
+ invoice_value_details.base_grand_total = abs(invoice.base_rounded_total) or abs(invoice.base_grand_total)
+ invoice_value_details.grand_total = abs(invoice.rounded_total) or abs(invoice.grand_total)
invoice_value_details = update_invoice_taxes(invoice, invoice_value_details)
@@ -226,15 +226,14 @@
for t in invoice.taxes:
if t.account_head in gst_accounts_list:
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)
- elif t.account_head in gst_accounts.igst_account:
- invoice_value_details.total_igst_amt += abs(t.base_tax_amount_after_discount_amount)
- elif t.account_head in gst_accounts.sgst_account:
- invoice_value_details.total_sgst_amt += abs(t.base_tax_amount_after_discount_amount)
- elif t.account_head in gst_accounts.cgst_account:
- invoice_value_details.total_cgst_amt += abs(t.base_tax_amount_after_discount_amount)
+
+ for tax_type in ['igst', 'cgst', 'sgst']:
+ if t.account_head in gst_accounts[f'{tax_type}_account']:
+ invoice_value_details[f'total_{tax_type}_amt'] += abs(t.base_tax_amount)
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
@@ -358,7 +357,8 @@
einvoice[fieldname] = str(value)
elif value_type == 'number':
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]
max_length = field_validation.get('maxLength')
@@ -386,15 +386,15 @@
self.invoice = frappe.get_cached_doc(doctype, docname) if doctype and docname else None
self.credentials = self.get_credentials()
- self.base_url = 'https://gsp.adaequare.com/'
- 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.generate_irn_url = self.base_url + 'test/enriched/ei/api/invoice'
- self.irn_details_url = self.base_url + 'test/enriched/ei/api/invoice/irn'
- self.cancel_irn_url = self.base_url + 'test/enriched/ei/api/invoice/cancel'
- self.cancel_ewaybill_url = self.base_url + '/test/enriched/ei/api/ewayapi'
- self.generate_ewaybill_url = self.base_url + 'test/enriched/ei/api/ewaybill'
-
+ self.base_url = 'https://gsp.adaequare.com'
+ self.authenticate_url = self.base_url + '/gsp/authenticate?grant_type=token'
+ self.gstin_details_url = self.base_url + '/enriched/ei/api/master/gstin'
+ self.generate_irn_url = self.base_url + '/enriched/ei/api/invoice'
+ self.irn_details_url = self.base_url + '/enriched/ei/api/invoice/irn'
+ self.cancel_irn_url = self.base_url + '/enriched/ei/api/invoice/cancel'
+ self.cancel_ewaybill_url = self.base_url + '/enriched/ei/api/ewayapi'
+ self.generate_ewaybill_url = self.base_url + '/enriched/ei/api/ewaybill'
+
def get_credentials(self):
if self.invoice:
gstin = self.get_seller_gstin()
diff --git a/erpnext/selling/sales_common.js b/erpnext/selling/sales_common.js
index ce08464..7f00fca 100644
--- a/erpnext/selling/sales_common.js
+++ b/erpnext/selling/sales_common.js
@@ -399,10 +399,6 @@
}
},
- batch_no: function(doc, cdt, cdn) {
- this._super(doc, cdt, cdn);
- },
-
qty: function(doc, cdt, cdn) {
this._super(doc, cdt, cdn);
diff --git a/erpnext/setup/doctype/company/delete_company_transactions.py b/erpnext/setup/doctype/company/delete_company_transactions.py
index 566f20c..7a72fe3 100644
--- a/erpnext/setup/doctype/company/delete_company_transactions.py
+++ b/erpnext/setup/doctype/company/delete_company_transactions.py
@@ -28,7 +28,7 @@
"Party Account", "Employee", "Sales Taxes and Charges Template",
"Purchase Taxes and Charges Template", "POS Profile", "BOM",
"Company", "Bank Account", "Item Tax Template", "Mode Of Payment",
- "Item Default"):
+ "Item Default", "Customer", "Supplier"):
delete_for_doctype(doctype, company_name)
# reset company values
diff --git a/erpnext/stock/doctype/batch/test_batch.py b/erpnext/stock/doctype/batch/test_batch.py
index 97f85ba..e41f1a8 100644
--- a/erpnext/stock/doctype/batch/test_batch.py
+++ b/erpnext/stock/doctype/batch/test_batch.py
@@ -8,8 +8,6 @@
from erpnext.stock.doctype.batch.batch import get_batch_qty, UnableToSelectBatchError, get_batch_no
from frappe.utils import cint, flt
-from erpnext.accounts.doctype.purchase_invoice.test_purchase_invoice import make_purchase_invoice
-from erpnext.stock.get_item_details import get_item_details
class TestBatch(unittest.TestCase):
def test_item_has_batch_enabled(self):
@@ -184,7 +182,7 @@
stock_entry.cancel()
current_batch_qty = flt(frappe.db.get_value("Batch", "B100", "batch_qty"))
self.assertEqual(current_batch_qty, existing_batch_qty)
-
+
@classmethod
def make_new_batch_and_entry(cls, item_name, batch_name, warehouse):
'''Make a new stock entry for given target warehouse and batch name of item'''
@@ -254,72 +252,6 @@
return batch
- def test_batch_wise_item_price(self):
- if not frappe.db.get_value('Item', '_Test Batch Price Item'):
- frappe.get_doc({
- 'doctype': 'Item',
- 'is_stock_item': 1,
- 'item_code': '_Test Batch Price Item',
- 'item_group': 'Products',
- 'has_batch_no': 1,
- 'create_new_batch': 1
- }).insert(ignore_permissions=True)
-
- batch1 = create_batch('_Test Batch Price Item', 200, 1)
- batch2 = create_batch('_Test Batch Price Item', 300, 1)
- batch3 = create_batch('_Test Batch Price Item', 400, 0)
-
- args = frappe._dict({
- "item_code": "_Test Batch Price Item",
- "company": "_Test Company with perpetual inventory",
- "price_list": "_Test Price List",
- "currency": "_Test Currency",
- "doctype": "Sales Invoice",
- "conversion_rate": 1,
- "price_list_currency": "_Test Currency",
- "plc_conversion_rate": 1,
- "customer": "_Test Customer",
- "name": None
- })
-
- #test price for batch1
- args.update({'batch_no': batch1})
- details = get_item_details(args)
- self.assertEqual(details.get('price_list_rate'), 200)
-
- #test price for batch2
- args.update({'batch_no': batch2})
- details = get_item_details(args)
- self.assertEqual(details.get('price_list_rate'), 300)
-
- #test price for batch3
- args.update({'batch_no': batch3})
- details = get_item_details(args)
- self.assertEqual(details.get('price_list_rate'), 400)
-
-def create_batch(item_code, rate, create_item_price_for_batch):
- pi = make_purchase_invoice(company="_Test Company with perpetual inventory",
- warehouse= "Stores - TCP1", cost_center = "Main - TCP1", update_stock=1,
- expense_account ="_Test Account Cost for Goods Sold - TCP1", item_code=item_code)
-
- batch = frappe.db.get_value('Batch', {'item': item_code, 'reference_name': pi.name})
-
- if not create_item_price_for_batch:
- create_price_list_for_batch(item_code, None, rate)
- else:
- create_price_list_for_batch(item_code, batch, rate)
-
- return batch
-
-def create_price_list_for_batch(item_code, batch, rate):
- frappe.get_doc({
- 'doctype': 'Item Price',
- 'item_code': '_Test Batch Price Item',
- 'price_list': '_Test Price List',
- 'batch_no': batch,
- 'price_list_rate': rate
- }).insert()
-
def make_new_batch(**args):
args = frappe._dict(args)
diff --git a/erpnext/stock/doctype/item_price/item_price.json b/erpnext/stock/doctype/item_price/item_price.json
index 83177b3..5f62381 100644
--- a/erpnext/stock/doctype/item_price/item_price.json
+++ b/erpnext/stock/doctype/item_price/item_price.json
@@ -18,7 +18,6 @@
"price_list",
"customer",
"supplier",
- "batch_no",
"column_break_3",
"buying",
"selling",
@@ -48,41 +47,31 @@
"oldfieldtype": "Select",
"options": "Item",
"reqd": 1,
- "search_index": 1,
- "show_days": 1,
- "show_seconds": 1
+ "search_index": 1
},
{
"fieldname": "uom",
"fieldtype": "Link",
"label": "UOM",
- "options": "UOM",
- "show_days": 1,
- "show_seconds": 1
+ "options": "UOM"
},
{
"default": "0",
"description": "Quantity that must be bought or sold per UOM",
"fieldname": "packing_unit",
"fieldtype": "Int",
- "label": "Packing Unit",
- "show_days": 1,
- "show_seconds": 1
+ "label": "Packing Unit"
},
{
"fieldname": "column_break_17",
- "fieldtype": "Column Break",
- "show_days": 1,
- "show_seconds": 1
+ "fieldtype": "Column Break"
},
{
"fieldname": "item_name",
"fieldtype": "Data",
"in_list_view": 1,
"label": "Item Name",
- "read_only": 1,
- "show_days": 1,
- "show_seconds": 1
+ "read_only": 1
},
{
"fetch_from": "item_code.brand",
@@ -90,25 +79,19 @@
"fieldtype": "Read Only",
"in_list_view": 1,
"label": "Brand",
- "read_only": 1,
- "show_days": 1,
- "show_seconds": 1
+ "read_only": 1
},
{
"fieldname": "item_description",
"fieldtype": "Text",
"label": "Item Description",
- "read_only": 1,
- "show_days": 1,
- "show_seconds": 1
+ "read_only": 1
},
{
"fieldname": "price_list_details",
"fieldtype": "Section Break",
"label": "Price List",
- "options": "fa fa-tags",
- "show_days": 1,
- "show_seconds": 1
+ "options": "fa fa-tags"
},
{
"fieldname": "price_list",
@@ -117,9 +100,7 @@
"in_standard_filter": 1,
"label": "Price List",
"options": "Price List",
- "reqd": 1,
- "show_days": 1,
- "show_seconds": 1
+ "reqd": 1
},
{
"bold": 1,
@@ -127,49 +108,37 @@
"fieldname": "customer",
"fieldtype": "Link",
"label": "Customer",
- "options": "Customer",
- "show_days": 1,
- "show_seconds": 1
+ "options": "Customer"
},
{
"depends_on": "eval:doc.buying == 1",
"fieldname": "supplier",
"fieldtype": "Link",
"label": "Supplier",
- "options": "Supplier",
- "show_days": 1,
- "show_seconds": 1
+ "options": "Supplier"
},
{
"fieldname": "column_break_3",
- "fieldtype": "Column Break",
- "show_days": 1,
- "show_seconds": 1
+ "fieldtype": "Column Break"
},
{
"default": "0",
"fieldname": "buying",
"fieldtype": "Check",
"label": "Buying",
- "read_only": 1,
- "show_days": 1,
- "show_seconds": 1
+ "read_only": 1
},
{
"default": "0",
"fieldname": "selling",
"fieldtype": "Check",
"label": "Selling",
- "read_only": 1,
- "show_days": 1,
- "show_seconds": 1
+ "read_only": 1
},
{
"fieldname": "item_details",
"fieldtype": "Section Break",
- "options": "fa fa-tag",
- "show_days": 1,
- "show_seconds": 1
+ "options": "fa fa-tag"
},
{
"bold": 1,
@@ -177,15 +146,11 @@
"fieldtype": "Link",
"label": "Currency",
"options": "Currency",
- "read_only": 1,
- "show_days": 1,
- "show_seconds": 1
+ "read_only": 1
},
{
"fieldname": "col_br_1",
- "fieldtype": "Column Break",
- "show_days": 1,
- "show_seconds": 1
+ "fieldtype": "Column Break"
},
{
"fieldname": "price_list_rate",
@@ -197,80 +162,53 @@
"oldfieldname": "ref_rate",
"oldfieldtype": "Currency",
"options": "currency",
- "reqd": 1,
- "show_days": 1,
- "show_seconds": 1
+ "reqd": 1
},
{
"fieldname": "section_break_15",
- "fieldtype": "Section Break",
- "show_days": 1,
- "show_seconds": 1
+ "fieldtype": "Section Break"
},
{
"default": "Today",
"fieldname": "valid_from",
"fieldtype": "Date",
- "label": "Valid From",
- "show_days": 1,
- "show_seconds": 1
+ "label": "Valid From"
},
{
"default": "0",
"fieldname": "lead_time_days",
"fieldtype": "Int",
- "label": "Lead Time in days",
- "show_days": 1,
- "show_seconds": 1
+ "label": "Lead Time in days"
},
{
"fieldname": "column_break_18",
- "fieldtype": "Column Break",
- "show_days": 1,
- "show_seconds": 1
+ "fieldtype": "Column Break"
},
{
"fieldname": "valid_upto",
"fieldtype": "Date",
- "label": "Valid Upto",
- "show_days": 1,
- "show_seconds": 1
+ "label": "Valid Upto"
},
{
"fieldname": "section_break_24",
- "fieldtype": "Section Break",
- "show_days": 1,
- "show_seconds": 1
+ "fieldtype": "Section Break"
},
{
"fieldname": "note",
"fieldtype": "Text",
- "label": "Note",
- "show_days": 1,
- "show_seconds": 1
+ "label": "Note"
},
{
"fieldname": "reference",
"fieldtype": "Data",
"in_list_view": 1,
- "label": "Reference",
- "show_days": 1,
- "show_seconds": 1
- },
- {
- "fieldname": "batch_no",
- "fieldtype": "Link",
- "label": "Batch No",
- "options": "Batch",
- "show_days": 1,
- "show_seconds": 1
+ "label": "Reference"
}
],
"icon": "fa fa-flag",
"idx": 1,
- "index_web_pages_for_search": 1,
"links": [],
- "modified": "2020-12-08 18:12:15.395772",
+ "modified": "2020-07-06 22:31:32.943475",
"modified_by": "Administrator",
"module": "Stock",
"name": "Item Price",
diff --git a/erpnext/stock/doctype/item_price/item_price.py b/erpnext/stock/doctype/item_price/item_price.py
index e82a19b..bed5ea9 100644
--- a/erpnext/stock/doctype/item_price/item_price.py
+++ b/erpnext/stock/doctype/item_price/item_price.py
@@ -54,8 +54,7 @@
"valid_upto",
"packing_unit",
"customer",
- "supplier",
- "batch_no"]:
+ "supplier",]:
if self.get(field):
conditions += " and {0} = %({0})s ".format(field)
else:
@@ -69,7 +68,7 @@
self.as_dict(),)
if price_list_rate:
- frappe.throw(_("Item Price appears multiple times based on Price List, Supplier/Customer, Currency, Item, Batch, UOM, Qty, and Dates."), ItemPriceDuplicateItem,)
+ frappe.throw(_("Item Price appears multiple times based on Price List, Supplier/Customer, Currency, Item, UOM, Qty, and Dates."), ItemPriceDuplicateItem,)
def before_save(self):
if self.selling:
diff --git a/erpnext/stock/doctype/stock_entry/stock_entry.py b/erpnext/stock/doctype/stock_entry/stock_entry.py
index 92d268f..2fc7da8 100644
--- a/erpnext/stock/doctype/stock_entry/stock_entry.py
+++ b/erpnext/stock/doctype/stock_entry/stock_entry.py
@@ -259,11 +259,16 @@
item_code.append(item.item_code)
def validate_fg_completed_qty(self):
+ item_wise_qty = {}
if self.purpose == "Manufacture" and self.work_order:
for d in self.items:
- if d.is_finished_item and d.qty != self.fg_completed_qty:
- frappe.throw(_("Finished product quantity <b>{0}</b> and For Quantity <b>{1}</b> cannot be different")
- .format(d.qty, self.fg_completed_qty))
+ if d.is_finished_item:
+ item_wise_qty.setdefault(d.item_code, []).append(d.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):
if not cint(erpnext.is_perpetual_inventory_enabled(self.company)):
@@ -319,7 +324,7 @@
if self.purpose == "Manufacture":
if validate_for_manufacture:
- if d.bom_no:
+ if d.is_finished_item or d.is_scrap_item:
d.s_warehouse = None
if not d.t_warehouse:
frappe.throw(_("Target warehouse is mandatory for row {0}").format(d.idx))
@@ -699,7 +704,7 @@
# SLE for target warehouse
self.get_sle_for_target_warehouse(sl_entries, finished_item_row)
-
+
# reverse sl entries if cancel
if self.docstatus == 2:
sl_entries.reverse()
@@ -727,9 +732,9 @@
sle.dependant_sle_voucher_detail_no = d.name
elif finished_item_row and (finished_item_row.item_code != d.item_code or finished_item_row.t_warehouse != d.s_warehouse):
sle.dependant_sle_voucher_detail_no = finished_item_row.name
-
+
sl_entries.append(sle)
-
+
def get_sle_for_target_warehouse(self, sl_entries, finished_item_row):
for d in self.get('items'):
if cstr(d.t_warehouse):
diff --git a/erpnext/stock/doctype/stock_settings/stock_settings.json b/erpnext/stock/doctype/stock_settings/stock_settings.json
index 859aea2..3ff396b 100644
--- a/erpnext/stock/doctype/stock_settings/stock_settings.json
+++ b/erpnext/stock/doctype/stock_settings/stock_settings.json
@@ -217,7 +217,7 @@
"fieldname": "role_allowed_to_create_edit_back_dated_transactions",
"fieldtype": "Link",
"label": "Role Allowed to Create/Edit Back-dated Transactions",
- "options": "User"
+ "options": "Role"
},
{
"fieldname": "column_break_26",
@@ -234,7 +234,7 @@
"index_web_pages_for_search": 1,
"issingle": 1,
"links": [],
- "modified": "2020-11-23 22:26:54.225608",
+ "modified": "2020-12-29 12:53:31.162247",
"modified_by": "Administrator",
"module": "Stock",
"name": "Stock Settings",
diff --git a/erpnext/stock/get_item_details.py b/erpnext/stock/get_item_details.py
index 2d2abd7..08f7a83 100644
--- a/erpnext/stock/get_item_details.py
+++ b/erpnext/stock/get_item_details.py
@@ -672,8 +672,6 @@
and price_list=%(price_list)s
and ifnull(uom, '') in ('', %(uom)s)"""
- conditions += "and ifnull(batch_no, '') in ('', %(batch_no)s)"
-
if not ignore_party:
if args.get("customer"):
conditions += " and customer=%(customer)s"
@@ -692,7 +690,7 @@
return frappe.db.sql(""" select name, price_list_rate, uom
from `tabItem Price` {conditions}
- order by valid_from desc, batch_no desc, uom desc """.format(conditions=conditions), args)
+ order by valid_from desc, uom desc """.format(conditions=conditions), args)
def get_price_list_rate_for(args, item_code):
"""
@@ -711,7 +709,6 @@
"uom": args.get('uom'),
"transaction_date": args.get('transaction_date'),
"posting_date": args.get('posting_date'),
- "batch_no": args.get('batch_no')
}
item_price_data = 0