Merge branch 'develop' of https://github.com/frappe/erpnext into project-refresh
This commit is contained in:
commit
e15dc052a2
@ -228,6 +228,8 @@ class PaymentEntry(AccountsController):
|
|||||||
valid_reference_doctypes = ("Purchase Order", "Purchase Invoice", "Journal Entry")
|
valid_reference_doctypes = ("Purchase Order", "Purchase Invoice", "Journal Entry")
|
||||||
elif self.party_type == "Employee":
|
elif self.party_type == "Employee":
|
||||||
valid_reference_doctypes = ("Expense Claim", "Journal Entry", "Employee Advance")
|
valid_reference_doctypes = ("Expense Claim", "Journal Entry", "Employee Advance")
|
||||||
|
elif self.party_type == "Shareholder":
|
||||||
|
valid_reference_doctypes = ("Journal Entry")
|
||||||
|
|
||||||
for d in self.get("references"):
|
for d in self.get("references"):
|
||||||
if not d.allocated_amount:
|
if not d.allocated_amount:
|
||||||
|
@ -162,7 +162,7 @@ def get_default_price_list(party):
|
|||||||
def set_price_list(party_details, party, party_type, given_price_list, pos=None):
|
def set_price_list(party_details, party, party_type, given_price_list, pos=None):
|
||||||
# price list
|
# price list
|
||||||
price_list = get_permitted_documents('Price List')
|
price_list = get_permitted_documents('Price List')
|
||||||
|
|
||||||
# if there is only one permitted document based on user permissions, set it
|
# if there is only one permitted document based on user permissions, set it
|
||||||
if price_list and len(price_list) == 1:
|
if price_list and len(price_list) == 1:
|
||||||
price_list = price_list[0]
|
price_list = price_list[0]
|
||||||
@ -465,23 +465,25 @@ def get_timeline_data(doctype, name):
|
|||||||
from frappe.desk.form.load import get_communication_data
|
from frappe.desk.form.load import get_communication_data
|
||||||
|
|
||||||
out = {}
|
out = {}
|
||||||
fields = 'date(creation), count(name)'
|
fields = 'creation, count(*)'
|
||||||
after = add_years(None, -1).strftime('%Y-%m-%d')
|
after = add_years(None, -1).strftime('%Y-%m-%d')
|
||||||
group_by='group by date(creation)'
|
group_by='group by Date(creation)'
|
||||||
|
|
||||||
data = get_communication_data(doctype, name, after=after, group_by='group by date(creation)',
|
data = get_communication_data(doctype, name, after=after, group_by='group by creation',
|
||||||
fields='date(C.creation) as creation, count(C.name)',as_dict=False)
|
fields='C.creation as creation, count(C.name)',as_dict=False)
|
||||||
|
|
||||||
# fetch and append data from Activity Log
|
# fetch and append data from Activity Log
|
||||||
data += frappe.db.sql("""select {fields}
|
data += frappe.db.sql("""select {fields}
|
||||||
from `tabActivity Log`
|
from `tabActivity Log`
|
||||||
where (reference_doctype="{doctype}" and reference_name="{name}")
|
where (reference_doctype=%(doctype)s and reference_name=%(name)s)
|
||||||
or (timeline_doctype in ("{doctype}") and timeline_name="{name}")
|
or (timeline_doctype in (%(doctype)s) and timeline_name=%(name)s)
|
||||||
or (reference_doctype in ("Quotation", "Opportunity") and timeline_name="{name}")
|
or (reference_doctype in ("Quotation", "Opportunity") and timeline_name=%(name)s)
|
||||||
and status!='Success' and creation > {after}
|
and status!='Success' and creation > {after}
|
||||||
{group_by} order by creation desc
|
{group_by} order by creation desc
|
||||||
""".format(doctype=frappe.db.escape(doctype), name=frappe.db.escape(name), fields=fields,
|
""".format(fields=fields, group_by=group_by, after=after), {
|
||||||
group_by=group_by, after=after), as_dict=False)
|
"doctype": doctype,
|
||||||
|
"name": name
|
||||||
|
}, as_dict=False)
|
||||||
|
|
||||||
timeline_items = dict(data)
|
timeline_items = dict(data)
|
||||||
|
|
||||||
|
@ -296,6 +296,9 @@ def get_accountwise_gle(filters, gl_entries, gle_map):
|
|||||||
data[key].debit_in_account_currency += flt(gle.debit_in_account_currency)
|
data[key].debit_in_account_currency += flt(gle.debit_in_account_currency)
|
||||||
data[key].credit_in_account_currency += flt(gle.credit_in_account_currency)
|
data[key].credit_in_account_currency += flt(gle.credit_in_account_currency)
|
||||||
|
|
||||||
|
if data[key].against_voucher:
|
||||||
|
data[key].against_voucher += ', ' + gle.against_voucher
|
||||||
|
|
||||||
from_date, to_date = getdate(filters.from_date), getdate(filters.to_date)
|
from_date, to_date = getdate(filters.from_date), getdate(filters.to_date)
|
||||||
for gle in gl_entries:
|
for gle in gl_entries:
|
||||||
if (gle.posting_date < from_date or
|
if (gle.posting_date < from_date or
|
||||||
|
@ -32,7 +32,7 @@ class Asset(AccountsController):
|
|||||||
self.validate_in_use_date()
|
self.validate_in_use_date()
|
||||||
self.set_status()
|
self.set_status()
|
||||||
self.make_asset_movement()
|
self.make_asset_movement()
|
||||||
if not self.booked_fixed_asset and is_cwip_accounting_enabled(self.asset_category):
|
if not self.booked_fixed_asset and self.validate_make_gl_entry():
|
||||||
self.make_gl_entries()
|
self.make_gl_entries()
|
||||||
|
|
||||||
def before_cancel(self):
|
def before_cancel(self):
|
||||||
@ -455,18 +455,55 @@ class Asset(AccountsController):
|
|||||||
for d in self.get('finance_books'):
|
for d in self.get('finance_books'):
|
||||||
if d.finance_book == self.default_finance_book:
|
if d.finance_book == self.default_finance_book:
|
||||||
return cint(d.idx) - 1
|
return cint(d.idx) - 1
|
||||||
|
|
||||||
|
def validate_make_gl_entry(self):
|
||||||
|
purchase_document = self.get_purchase_document()
|
||||||
|
asset_bought_with_invoice = purchase_document == self.purchase_invoice
|
||||||
|
fixed_asset_account, cwip_account = self.get_asset_accounts()
|
||||||
|
cwip_enabled = is_cwip_accounting_enabled(self.asset_category)
|
||||||
|
# check if expense already has been booked in case of cwip was enabled after purchasing asset
|
||||||
|
expense_booked = False
|
||||||
|
cwip_booked = False
|
||||||
|
|
||||||
|
if asset_bought_with_invoice:
|
||||||
|
expense_booked = frappe.db.sql("""SELECT name FROM `tabGL Entry` WHERE voucher_no = %s and account = %s""",
|
||||||
|
(purchase_document, fixed_asset_account), as_dict=1)
|
||||||
|
else:
|
||||||
|
cwip_booked = frappe.db.sql("""SELECT name FROM `tabGL Entry` WHERE voucher_no = %s and account = %s""",
|
||||||
|
(purchase_document, cwip_account), as_dict=1)
|
||||||
|
|
||||||
|
if cwip_enabled and (expense_booked or not cwip_booked):
|
||||||
|
# if expense has already booked from invoice or cwip is booked from receipt
|
||||||
|
return False
|
||||||
|
elif not cwip_enabled and (not expense_booked or cwip_booked):
|
||||||
|
# if cwip is disabled but expense hasn't been booked yet
|
||||||
|
return True
|
||||||
|
elif cwip_enabled:
|
||||||
|
# default condition
|
||||||
|
return True
|
||||||
|
|
||||||
|
def get_purchase_document(self):
|
||||||
|
asset_bought_with_invoice = self.purchase_invoice and frappe.db.get_value('Purchase Invoice', self.purchase_invoice, 'update_stock')
|
||||||
|
purchase_document = self.purchase_invoice if asset_bought_with_invoice else self.purchase_receipt
|
||||||
|
|
||||||
|
return purchase_document
|
||||||
|
|
||||||
|
def get_asset_accounts(self):
|
||||||
|
fixed_asset_account = get_asset_category_account('fixed_asset_account', asset=self.name,
|
||||||
|
asset_category = self.asset_category, company = self.company)
|
||||||
|
|
||||||
|
cwip_account = get_asset_account("capital_work_in_progress_account",
|
||||||
|
self.name, self.asset_category, self.company)
|
||||||
|
|
||||||
|
return fixed_asset_account, cwip_account
|
||||||
|
|
||||||
def make_gl_entries(self):
|
def make_gl_entries(self):
|
||||||
gl_entries = []
|
gl_entries = []
|
||||||
|
|
||||||
if ((self.purchase_receipt \
|
purchase_document = self.get_purchase_document()
|
||||||
or (self.purchase_invoice and frappe.db.get_value('Purchase Invoice', self.purchase_invoice, 'update_stock')))
|
fixed_asset_account, cwip_account = self.get_asset_accounts()
|
||||||
and self.purchase_receipt_amount and self.available_for_use_date <= nowdate()):
|
|
||||||
fixed_asset_account = get_asset_category_account('fixed_asset_account', asset=self.name,
|
|
||||||
asset_category = self.asset_category, company = self.company)
|
|
||||||
|
|
||||||
cwip_account = get_asset_account("capital_work_in_progress_account",
|
if (purchase_document and self.purchase_receipt_amount and self.available_for_use_date <= nowdate()):
|
||||||
self.name, self.asset_category, self.company)
|
|
||||||
|
|
||||||
gl_entries.append(self.get_gl_dict({
|
gl_entries.append(self.get_gl_dict({
|
||||||
"account": cwip_account,
|
"account": cwip_account,
|
||||||
|
@ -560,6 +560,81 @@ class TestAsset(unittest.TestCase):
|
|||||||
|
|
||||||
self.assertEqual(gle, expected_gle)
|
self.assertEqual(gle, expected_gle)
|
||||||
|
|
||||||
|
def test_gle_with_cwip_toggling(self):
|
||||||
|
# TEST: purchase an asset with cwip enabled and then disable cwip and try submitting the asset
|
||||||
|
frappe.db.set_value("Asset Category", "Computers", "enable_cwip_accounting", 1)
|
||||||
|
|
||||||
|
pr = make_purchase_receipt(item_code="Macbook Pro",
|
||||||
|
qty=1, rate=5000, do_not_submit=True, location="Test Location")
|
||||||
|
pr.set('taxes', [{
|
||||||
|
'category': 'Total',
|
||||||
|
'add_deduct_tax': 'Add',
|
||||||
|
'charge_type': 'On Net Total',
|
||||||
|
'account_head': '_Test Account Service Tax - _TC',
|
||||||
|
'description': '_Test Account Service Tax',
|
||||||
|
'cost_center': 'Main - _TC',
|
||||||
|
'rate': 5.0
|
||||||
|
}, {
|
||||||
|
'category': 'Valuation and Total',
|
||||||
|
'add_deduct_tax': 'Add',
|
||||||
|
'charge_type': 'On Net Total',
|
||||||
|
'account_head': '_Test Account Shipping Charges - _TC',
|
||||||
|
'description': '_Test Account Shipping Charges',
|
||||||
|
'cost_center': 'Main - _TC',
|
||||||
|
'rate': 5.0
|
||||||
|
}])
|
||||||
|
pr.submit()
|
||||||
|
expected_gle = (
|
||||||
|
("Asset Received But Not Billed - _TC", 0.0, 5250.0),
|
||||||
|
("CWIP Account - _TC", 5250.0, 0.0)
|
||||||
|
)
|
||||||
|
pr_gle = frappe.db.sql("""select account, debit, credit from `tabGL Entry`
|
||||||
|
where voucher_type='Purchase Receipt' and voucher_no = %s
|
||||||
|
order by account""", pr.name)
|
||||||
|
self.assertEqual(pr_gle, expected_gle)
|
||||||
|
|
||||||
|
pi = make_invoice(pr.name)
|
||||||
|
pi.submit()
|
||||||
|
expected_gle = (
|
||||||
|
("_Test Account Service Tax - _TC", 250.0, 0.0),
|
||||||
|
("_Test Account Shipping Charges - _TC", 250.0, 0.0),
|
||||||
|
("Asset Received But Not Billed - _TC", 5250.0, 0.0),
|
||||||
|
("Creditors - _TC", 0.0, 5500.0),
|
||||||
|
("Expenses Included In Asset Valuation - _TC", 0.0, 250.0),
|
||||||
|
)
|
||||||
|
pi_gle = frappe.db.sql("""select account, debit, credit from `tabGL Entry`
|
||||||
|
where voucher_type='Purchase Invoice' and voucher_no = %s
|
||||||
|
order by account""", pi.name)
|
||||||
|
self.assertEqual(pi_gle, expected_gle)
|
||||||
|
|
||||||
|
asset = frappe.db.get_value('Asset', {'purchase_receipt': pr.name, 'docstatus': 0}, 'name')
|
||||||
|
asset_doc = frappe.get_doc('Asset', asset)
|
||||||
|
month_end_date = get_last_day(nowdate())
|
||||||
|
asset_doc.available_for_use_date = nowdate() if nowdate() != month_end_date else add_days(nowdate(), -15)
|
||||||
|
self.assertEqual(asset_doc.gross_purchase_amount, 5250.0)
|
||||||
|
asset_doc.append("finance_books", {
|
||||||
|
"expected_value_after_useful_life": 200,
|
||||||
|
"depreciation_method": "Straight Line",
|
||||||
|
"total_number_of_depreciations": 3,
|
||||||
|
"frequency_of_depreciation": 10,
|
||||||
|
"depreciation_start_date": month_end_date
|
||||||
|
})
|
||||||
|
|
||||||
|
# disable cwip and try submitting
|
||||||
|
frappe.db.set_value("Asset Category", "Computers", "enable_cwip_accounting", 0)
|
||||||
|
asset_doc.submit()
|
||||||
|
# asset should have gl entries even if cwip is disabled
|
||||||
|
expected_gle = (
|
||||||
|
("_Test Fixed Asset - _TC", 5250.0, 0.0),
|
||||||
|
("CWIP Account - _TC", 0.0, 5250.0)
|
||||||
|
)
|
||||||
|
gle = frappe.db.sql("""select account, debit, credit from `tabGL Entry`
|
||||||
|
where voucher_type='Asset' and voucher_no = %s
|
||||||
|
order by account""", asset_doc.name)
|
||||||
|
self.assertEqual(gle, expected_gle)
|
||||||
|
|
||||||
|
frappe.db.set_value("Asset Category", "Computers", "enable_cwip_accounting", 1)
|
||||||
|
|
||||||
def test_expense_head(self):
|
def test_expense_head(self):
|
||||||
pr = make_purchase_receipt(item_code="Macbook Pro",
|
pr = make_purchase_receipt(item_code="Macbook Pro",
|
||||||
qty=2, rate=200000.0, location="Test Location")
|
qty=2, rate=200000.0, location="Test Location")
|
||||||
|
@ -110,6 +110,7 @@ class AssetMovement(Document):
|
|||||||
ORDER BY
|
ORDER BY
|
||||||
asm.transaction_date asc
|
asm.transaction_date asc
|
||||||
""", (d.asset, self.company, 'Receipt'), as_dict=1)
|
""", (d.asset, self.company, 'Receipt'), as_dict=1)
|
||||||
|
|
||||||
if auto_gen_movement_entry and auto_gen_movement_entry[0].get('name') == self.name:
|
if auto_gen_movement_entry and auto_gen_movement_entry[0].get('name') == self.name:
|
||||||
frappe.throw(_('{0} will be cancelled automatically on asset cancellation as it was \
|
frappe.throw(_('{0} will be cancelled automatically on asset cancellation as it was \
|
||||||
auto generated for Asset {1}').format(self.name, d.asset))
|
auto generated for Asset {1}').format(self.name, d.asset))
|
||||||
|
@ -74,7 +74,7 @@ def validate_returned_items(doc):
|
|||||||
for d in doc.get("items"):
|
for d in doc.get("items"):
|
||||||
if d.item_code and (flt(d.qty) < 0 or flt(d.get('received_qty')) < 0):
|
if d.item_code and (flt(d.qty) < 0 or flt(d.get('received_qty')) < 0):
|
||||||
if d.item_code not in valid_items:
|
if d.item_code not in valid_items:
|
||||||
frappe.throw(_("Row # {0}: Returned Item {1} does not exists in {2} {3}")
|
frappe.throw(_("Row # {0}: Returned Item {1} does not exist in {2} {3}")
|
||||||
.format(d.idx, d.item_code, doc.doctype, doc.return_against))
|
.format(d.idx, d.item_code, doc.doctype, doc.return_against))
|
||||||
else:
|
else:
|
||||||
ref = valid_items.get(d.item_code, frappe._dict())
|
ref = valid_items.get(d.item_code, frappe._dict())
|
||||||
@ -266,6 +266,8 @@ def make_return_doc(doctype, source_name, target_doc=None):
|
|||||||
target_doc.purchase_order = source_doc.purchase_order
|
target_doc.purchase_order = source_doc.purchase_order
|
||||||
target_doc.purchase_order_item = source_doc.purchase_order_item
|
target_doc.purchase_order_item = source_doc.purchase_order_item
|
||||||
target_doc.rejected_warehouse = source_doc.rejected_warehouse
|
target_doc.rejected_warehouse = source_doc.rejected_warehouse
|
||||||
|
target_doc.purchase_receipt_item = source_doc.name
|
||||||
|
|
||||||
elif doctype == "Purchase Invoice":
|
elif doctype == "Purchase Invoice":
|
||||||
target_doc.received_qty = -1* source_doc.received_qty
|
target_doc.received_qty = -1* source_doc.received_qty
|
||||||
target_doc.rejected_qty = -1* source_doc.rejected_qty
|
target_doc.rejected_qty = -1* source_doc.rejected_qty
|
||||||
@ -282,6 +284,7 @@ def make_return_doc(doctype, source_name, target_doc=None):
|
|||||||
target_doc.so_detail = source_doc.so_detail
|
target_doc.so_detail = source_doc.so_detail
|
||||||
target_doc.si_detail = source_doc.si_detail
|
target_doc.si_detail = source_doc.si_detail
|
||||||
target_doc.expense_account = source_doc.expense_account
|
target_doc.expense_account = source_doc.expense_account
|
||||||
|
target_doc.dn_detail = source_doc.name
|
||||||
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
|
||||||
elif doctype == "Sales Invoice":
|
elif doctype == "Sales Invoice":
|
||||||
|
@ -165,9 +165,9 @@ class SellingController(StockController):
|
|||||||
d.stock_qty = flt(d.qty) * flt(d.conversion_factor)
|
d.stock_qty = flt(d.qty) * flt(d.conversion_factor)
|
||||||
|
|
||||||
def validate_selling_price(self):
|
def validate_selling_price(self):
|
||||||
def throw_message(item_name, rate, ref_rate_field):
|
def throw_message(idx, item_name, rate, ref_rate_field):
|
||||||
frappe.throw(_("""Selling rate for item {0} is lower than its {1}. Selling rate should be atleast {2}""")
|
frappe.throw(_("""Row #{}: Selling rate for item {} is lower than its {}. Selling rate should be atleast {}""")
|
||||||
.format(item_name, ref_rate_field, rate))
|
.format(idx, item_name, ref_rate_field, rate))
|
||||||
|
|
||||||
if not frappe.db.get_single_value("Selling Settings", "validate_selling_price"):
|
if not frappe.db.get_single_value("Selling Settings", "validate_selling_price"):
|
||||||
return
|
return
|
||||||
@ -181,8 +181,8 @@ class SellingController(StockController):
|
|||||||
|
|
||||||
last_purchase_rate, is_stock_item = frappe.get_cached_value("Item", it.item_code, ["last_purchase_rate", "is_stock_item"])
|
last_purchase_rate, is_stock_item = frappe.get_cached_value("Item", it.item_code, ["last_purchase_rate", "is_stock_item"])
|
||||||
last_purchase_rate_in_sales_uom = last_purchase_rate / (it.conversion_factor or 1)
|
last_purchase_rate_in_sales_uom = last_purchase_rate / (it.conversion_factor or 1)
|
||||||
if flt(it.base_rate) < flt(last_purchase_rate_in_sales_uom) and not self.get('is_internal_customer'):
|
if flt(it.base_rate) < flt(last_purchase_rate_in_sales_uom):
|
||||||
throw_message(it.item_name, last_purchase_rate_in_sales_uom, "last purchase rate")
|
throw_message(it.idx, frappe.bold(it.item_name), last_purchase_rate_in_sales_uom, "last purchase rate")
|
||||||
|
|
||||||
last_valuation_rate = frappe.db.sql("""
|
last_valuation_rate = frappe.db.sql("""
|
||||||
SELECT valuation_rate FROM `tabStock Ledger Entry` WHERE item_code = %s
|
SELECT valuation_rate FROM `tabStock Ledger Entry` WHERE item_code = %s
|
||||||
@ -193,7 +193,7 @@ class SellingController(StockController):
|
|||||||
last_valuation_rate_in_sales_uom = last_valuation_rate[0][0] / (it.conversion_factor or 1)
|
last_valuation_rate_in_sales_uom = last_valuation_rate[0][0] / (it.conversion_factor or 1)
|
||||||
if is_stock_item and flt(it.base_rate) < flt(last_valuation_rate_in_sales_uom) \
|
if is_stock_item and flt(it.base_rate) < flt(last_valuation_rate_in_sales_uom) \
|
||||||
and not self.get('is_internal_customer'):
|
and not self.get('is_internal_customer'):
|
||||||
throw_message(it.name, last_valuation_rate_in_sales_uom, "valuation rate")
|
throw_message(it.idx, frappe.bold(it.item_name), last_valuation_rate_in_sales_uom, "valuation rate")
|
||||||
|
|
||||||
|
|
||||||
def get_item_list(self):
|
def get_item_list(self):
|
||||||
|
@ -47,7 +47,12 @@
|
|||||||
}
|
}
|
||||||
],
|
],
|
||||||
"category": "Domains",
|
"category": "Domains",
|
||||||
"charts": [],
|
"charts": [
|
||||||
|
{
|
||||||
|
"chart_name": "Patient Appointments",
|
||||||
|
"label": "Patient Appointments"
|
||||||
|
}
|
||||||
|
],
|
||||||
"charts_label": "",
|
"charts_label": "",
|
||||||
"creation": "2020-03-02 17:23:17.919682",
|
"creation": "2020-03-02 17:23:17.919682",
|
||||||
"developer_mode_only": 0,
|
"developer_mode_only": 0,
|
||||||
@ -58,7 +63,7 @@
|
|||||||
"idx": 0,
|
"idx": 0,
|
||||||
"is_standard": 1,
|
"is_standard": 1,
|
||||||
"label": "Healthcare",
|
"label": "Healthcare",
|
||||||
"modified": "2020-04-20 11:42:43.889576",
|
"modified": "2020-04-25 22:31:36.576444",
|
||||||
"modified_by": "Administrator",
|
"modified_by": "Administrator",
|
||||||
"module": "Healthcare",
|
"module": "Healthcare",
|
||||||
"name": "Healthcare",
|
"name": "Healthcare",
|
||||||
|
@ -13,5 +13,5 @@ frappe.ui.form.on('Additional Salary', {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
}
|
},
|
||||||
});
|
});
|
||||||
|
@ -13,10 +13,14 @@
|
|||||||
"salary_component",
|
"salary_component",
|
||||||
"overwrite_salary_structure_amount",
|
"overwrite_salary_structure_amount",
|
||||||
"deduct_full_tax_on_selected_payroll_date",
|
"deduct_full_tax_on_selected_payroll_date",
|
||||||
|
"ref_doctype",
|
||||||
|
"ref_docname",
|
||||||
"column_break_5",
|
"column_break_5",
|
||||||
"company",
|
"company",
|
||||||
|
"is_recurring",
|
||||||
|
"from_date",
|
||||||
|
"to_date",
|
||||||
"payroll_date",
|
"payroll_date",
|
||||||
"salary_slip",
|
|
||||||
"type",
|
"type",
|
||||||
"department",
|
"department",
|
||||||
"amount",
|
"amount",
|
||||||
@ -74,12 +78,13 @@
|
|||||||
"fieldtype": "Column Break"
|
"fieldtype": "Column Break"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
"depends_on": "eval:(doc.is_recurring==0)",
|
||||||
"description": "Date on which this component is applied",
|
"description": "Date on which this component is applied",
|
||||||
"fieldname": "payroll_date",
|
"fieldname": "payroll_date",
|
||||||
"fieldtype": "Date",
|
"fieldtype": "Date",
|
||||||
"in_list_view": 1,
|
"in_list_view": 1,
|
||||||
"label": "Payroll Date",
|
"label": "Payroll Date",
|
||||||
"reqd": 1,
|
"mandatory_depends_on": "eval:(doc.is_recurring==0)",
|
||||||
"search_index": 1
|
"search_index": 1
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@ -105,13 +110,6 @@
|
|||||||
"options": "Company",
|
"options": "Company",
|
||||||
"reqd": 1
|
"reqd": 1
|
||||||
},
|
},
|
||||||
{
|
|
||||||
"fieldname": "salary_slip",
|
|
||||||
"fieldtype": "Link",
|
|
||||||
"label": "Salary Slip",
|
|
||||||
"options": "Salary Slip",
|
|
||||||
"read_only": 1
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
"fetch_from": "salary_component.type",
|
"fetch_from": "salary_component.type",
|
||||||
"fieldname": "type",
|
"fieldname": "type",
|
||||||
@ -127,11 +125,45 @@
|
|||||||
"options": "Additional Salary",
|
"options": "Additional Salary",
|
||||||
"print_hide": 1,
|
"print_hide": 1,
|
||||||
"read_only": 1
|
"read_only": 1
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"default": "0",
|
||||||
|
"fieldname": "is_recurring",
|
||||||
|
"fieldtype": "Check",
|
||||||
|
"label": "Is Recurring"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"depends_on": "eval:(doc.is_recurring==1)",
|
||||||
|
"fieldname": "from_date",
|
||||||
|
"fieldtype": "Date",
|
||||||
|
"label": "From Date",
|
||||||
|
"mandatory_depends_on": "eval:(doc.is_recurring==1)"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"depends_on": "eval:(doc.is_recurring==1)",
|
||||||
|
"fieldname": "to_date",
|
||||||
|
"fieldtype": "Date",
|
||||||
|
"label": "To Date",
|
||||||
|
"mandatory_depends_on": "eval:(doc.is_recurring==1)"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "ref_doctype",
|
||||||
|
"fieldtype": "Link",
|
||||||
|
"label": "Reference Document Type",
|
||||||
|
"options": "DocType",
|
||||||
|
"read_only": 1
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "ref_docname",
|
||||||
|
"fieldtype": "Dynamic Link",
|
||||||
|
"label": "Reference Document",
|
||||||
|
"options": "ref_doctype",
|
||||||
|
"read_only": 1
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"is_submittable": 1,
|
"is_submittable": 1,
|
||||||
"links": [],
|
"links": [],
|
||||||
"modified": "2019-12-12 19:07:23.635901",
|
"modified": "2020-04-04 18:06:29.170878",
|
||||||
"modified_by": "Administrator",
|
"modified_by": "Administrator",
|
||||||
"module": "HR",
|
"module": "HR",
|
||||||
"name": "Additional Salary",
|
"name": "Additional Salary",
|
||||||
|
@ -9,6 +9,11 @@ from frappe import _
|
|||||||
from frappe.utils import getdate, date_diff
|
from frappe.utils import getdate, date_diff
|
||||||
|
|
||||||
class AdditionalSalary(Document):
|
class AdditionalSalary(Document):
|
||||||
|
|
||||||
|
def on_submit(self):
|
||||||
|
if self.ref_doctype == "Employee Advance" and self.ref_docname:
|
||||||
|
frappe.db.set_value("Employee Advance", self.ref_docname, "return_amount", self.amount)
|
||||||
|
|
||||||
def before_insert(self):
|
def before_insert(self):
|
||||||
if frappe.db.exists("Additional Salary", {"employee": self.employee, "salary_component": self.salary_component,
|
if frappe.db.exists("Additional Salary", {"employee": self.employee, "salary_component": self.salary_component,
|
||||||
"amount": self.amount, "payroll_date": self.payroll_date, "company": self.company, "docstatus": 1}):
|
"amount": self.amount, "payroll_date": self.payroll_date, "company": self.company, "docstatus": 1}):
|
||||||
@ -21,10 +26,19 @@ class AdditionalSalary(Document):
|
|||||||
frappe.throw(_("Amount should not be less than zero."))
|
frappe.throw(_("Amount should not be less than zero."))
|
||||||
|
|
||||||
def validate_dates(self):
|
def validate_dates(self):
|
||||||
date_of_joining, relieving_date = frappe.db.get_value("Employee", self.employee,
|
date_of_joining, relieving_date = frappe.db.get_value("Employee", self.employee,
|
||||||
["date_of_joining", "relieving_date"])
|
["date_of_joining", "relieving_date"])
|
||||||
if date_of_joining and getdate(self.payroll_date) < getdate(date_of_joining):
|
|
||||||
frappe.throw(_("Payroll date can not be less than employee's joining date"))
|
if getdate(self.from_date) > getdate(self.to_date):
|
||||||
|
frappe.throw(_("From Date can not be greater than To Date."))
|
||||||
|
|
||||||
|
if date_of_joining:
|
||||||
|
if getdate(self.payroll_date) < getdate(date_of_joining):
|
||||||
|
frappe.throw(_("Payroll date can not be less than employee's joining date."))
|
||||||
|
elif getdate(self.from_date) < getdate(date_of_joining):
|
||||||
|
frappe.throw(_("From date can not be less than employee's joining date."))
|
||||||
|
elif getdate(self.to_date) > getdate(relieving_date):
|
||||||
|
frappe.throw(_("To date can not be greater than employee's relieving date."))
|
||||||
|
|
||||||
def get_amount(self, sal_start_date, sal_end_date):
|
def get_amount(self, sal_start_date, sal_end_date):
|
||||||
start_date = getdate(sal_start_date)
|
start_date = getdate(sal_start_date)
|
||||||
@ -40,15 +54,18 @@ class AdditionalSalary(Document):
|
|||||||
|
|
||||||
@frappe.whitelist()
|
@frappe.whitelist()
|
||||||
def get_additional_salary_component(employee, start_date, end_date, component_type):
|
def get_additional_salary_component(employee, start_date, end_date, component_type):
|
||||||
additional_components = frappe.db.sql("""
|
additional_salaries = frappe.db.sql("""
|
||||||
select salary_component, sum(amount) as amount, overwrite_salary_structure_amount, deduct_full_tax_on_selected_payroll_date
|
select name, salary_component, type, amount, overwrite_salary_structure_amount, deduct_full_tax_on_selected_payroll_date
|
||||||
from `tabAdditional Salary`
|
from `tabAdditional Salary`
|
||||||
where employee=%(employee)s
|
where employee=%(employee)s
|
||||||
and docstatus = 1
|
and docstatus = 1
|
||||||
and payroll_date between %(from_date)s and %(to_date)s
|
and (
|
||||||
and type = %(component_type)s
|
payroll_date between %(from_date)s and %(to_date)s
|
||||||
group by salary_component, overwrite_salary_structure_amount
|
or
|
||||||
order by salary_component, overwrite_salary_structure_amount
|
from_date <= %(to_date)s and to_date >= %(to_date)s
|
||||||
|
)
|
||||||
|
and type = %(component_type)s
|
||||||
|
order by salary_component, overwrite_salary_structure_amount DESC
|
||||||
""", {
|
""", {
|
||||||
'employee': employee,
|
'employee': employee,
|
||||||
'from_date': start_date,
|
'from_date': start_date,
|
||||||
@ -56,21 +73,38 @@ def get_additional_salary_component(employee, start_date, end_date, component_ty
|
|||||||
'component_type': "Earning" if component_type == "earnings" else "Deduction"
|
'component_type': "Earning" if component_type == "earnings" else "Deduction"
|
||||||
}, as_dict=1)
|
}, as_dict=1)
|
||||||
|
|
||||||
additional_components_list = []
|
existing_salary_components= []
|
||||||
|
salary_components_details = {}
|
||||||
|
additional_salary_details = []
|
||||||
|
|
||||||
|
overwrites_components = [ele.salary_component for ele in additional_salaries if ele.overwrite_salary_structure_amount == 1]
|
||||||
|
|
||||||
component_fields = ["depends_on_payment_days", "salary_component_abbr", "is_tax_applicable", "variable_based_on_taxable_salary", 'type']
|
component_fields = ["depends_on_payment_days", "salary_component_abbr", "is_tax_applicable", "variable_based_on_taxable_salary", 'type']
|
||||||
for d in additional_components:
|
for d in additional_salaries:
|
||||||
struct_row = frappe._dict({'salary_component': d.salary_component})
|
|
||||||
component = frappe.get_all("Salary Component", filters={'name': d.salary_component}, fields=component_fields)
|
|
||||||
if component:
|
|
||||||
struct_row.update(component[0])
|
|
||||||
|
|
||||||
struct_row['deduct_full_tax_on_selected_payroll_date'] = d.deduct_full_tax_on_selected_payroll_date
|
if d.salary_component not in existing_salary_components:
|
||||||
struct_row['is_additional_component'] = 1
|
component = frappe.get_all("Salary Component", filters={'name': d.salary_component}, fields=component_fields)
|
||||||
|
struct_row = frappe._dict({'salary_component': d.salary_component})
|
||||||
|
if component:
|
||||||
|
struct_row.update(component[0])
|
||||||
|
|
||||||
additional_components_list.append(frappe._dict({
|
struct_row['deduct_full_tax_on_selected_payroll_date'] = d.deduct_full_tax_on_selected_payroll_date
|
||||||
'amount': d.amount,
|
struct_row['is_additional_component'] = 1
|
||||||
'type': component[0].type,
|
|
||||||
'struct_row': struct_row,
|
salary_components_details[d.salary_component] = struct_row
|
||||||
'overwrite': d.overwrite_salary_structure_amount,
|
|
||||||
}))
|
|
||||||
return additional_components_list
|
if overwrites_components.count(d.salary_component) > 1:
|
||||||
|
frappe.throw(_("Multiple Additional Salaries with overwrite property exist for Salary Component: {0} between {1} and {2}.".format(d.salary_component, start_date, end_date)), title=_("Error"))
|
||||||
|
else:
|
||||||
|
additional_salary_details.append({
|
||||||
|
'name': d.name,
|
||||||
|
'component': d.salary_component,
|
||||||
|
'amount': d.amount,
|
||||||
|
'type': d.type,
|
||||||
|
'overwrite': d.overwrite_salary_structure_amount,
|
||||||
|
})
|
||||||
|
|
||||||
|
existing_salary_components.append(d.salary_component)
|
||||||
|
|
||||||
|
return salary_components_details, additional_salary_details
|
@ -3,6 +3,44 @@
|
|||||||
# See license.txt
|
# See license.txt
|
||||||
from __future__ import unicode_literals
|
from __future__ import unicode_literals
|
||||||
import unittest
|
import unittest
|
||||||
|
import frappe, erpnext
|
||||||
|
from frappe.utils import nowdate, add_days
|
||||||
|
from erpnext.hr.doctype.employee.test_employee import make_employee
|
||||||
|
from erpnext.hr.doctype.salary_component.test_salary_component import create_salary_component
|
||||||
|
from erpnext.hr.doctype.salary_slip.test_salary_slip import make_employee_salary_slip, setup_test
|
||||||
|
|
||||||
|
|
||||||
class TestAdditionalSalary(unittest.TestCase):
|
class TestAdditionalSalary(unittest.TestCase):
|
||||||
pass
|
|
||||||
|
def setUp(self):
|
||||||
|
setup_test()
|
||||||
|
|
||||||
|
def test_recurring_additional_salary(self):
|
||||||
|
emp_id = make_employee("test_additional@salary.com")
|
||||||
|
frappe.db.set_value("Employee", emp_id, "relieving_date", add_days(nowdate(), 1800))
|
||||||
|
add_sal = get_additional_salary(emp_id)
|
||||||
|
|
||||||
|
ss = make_employee_salary_slip("test_additional@salary.com", "Monthly")
|
||||||
|
for earning in ss.earnings:
|
||||||
|
if earning.salary_component == "Recurring Salary Component":
|
||||||
|
amount = earning.amount
|
||||||
|
salary_component = earning.salary_component
|
||||||
|
|
||||||
|
self.assertEqual(amount, add_sal.amount)
|
||||||
|
self.assertEqual(salary_component, add_sal.salary_component)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
def get_additional_salary(emp_id):
|
||||||
|
create_salary_component("Recurring Salary Component")
|
||||||
|
add_sal = frappe.new_doc("Additional Salary")
|
||||||
|
add_sal.employee = emp_id
|
||||||
|
add_sal.salary_component = "Recurring Salary Component"
|
||||||
|
add_sal.is_recurring = 1
|
||||||
|
add_sal.from_date = add_days(nowdate(), -50)
|
||||||
|
add_sal.to_date = add_days(nowdate(), 180)
|
||||||
|
add_sal.amount = 5000
|
||||||
|
add_sal.save()
|
||||||
|
add_sal.submit()
|
||||||
|
|
||||||
|
return add_sal
|
||||||
|
@ -23,6 +23,14 @@ frappe.ui.form.on('Employee Advance', {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
|
|
||||||
|
frm.set_query('salary_component', function(doc) {
|
||||||
|
return {
|
||||||
|
filters: {
|
||||||
|
"type": "Deduction"
|
||||||
|
}
|
||||||
|
};
|
||||||
|
});
|
||||||
},
|
},
|
||||||
|
|
||||||
refresh: function(frm) {
|
refresh: function(frm) {
|
||||||
@ -47,19 +55,37 @@ frappe.ui.form.on('Employee Advance', {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (frm.doc.docstatus === 1
|
if (frm.doc.docstatus === 1
|
||||||
&& (flt(frm.doc.claimed_amount) + flt(frm.doc.return_amount) < flt(frm.doc.paid_amount))
|
&& (flt(frm.doc.claimed_amount) < flt(frm.doc.paid_amount) && flt(frm.doc.paid_amount) != flt(frm.doc.return_amount))) {
|
||||||
&& frappe.model.can_create("Journal Entry")) {
|
|
||||||
|
|
||||||
frm.add_custom_button(__("Return"), function() {
|
if (frm.doc.repay_unclaimed_amount_from_salary == 0 && frappe.model.can_create("Journal Entry")){
|
||||||
frm.trigger('make_return_entry');
|
frm.add_custom_button(__("Return"), function() {
|
||||||
}, __('Create'));
|
frm.trigger('make_return_entry');
|
||||||
|
}, __('Create'));
|
||||||
|
}else if (frm.doc.repay_unclaimed_amount_from_salary == 1 && frappe.model.can_create("Additional Salary")){
|
||||||
|
frm.add_custom_button(__("Deduction from salary"), function() {
|
||||||
|
frm.events.make_deduction_via_additional_salary(frm)
|
||||||
|
}, __('Create'));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
|
make_deduction_via_additional_salary: function(frm){
|
||||||
|
frappe.call({
|
||||||
|
method: "erpnext.hr.doctype.employee_advance.employee_advance.create_return_through_additional_salary",
|
||||||
|
args: {
|
||||||
|
doc: frm.doc
|
||||||
|
},
|
||||||
|
callback: function (r){
|
||||||
|
var doclist = frappe.model.sync(r.message);
|
||||||
|
frappe.set_route("Form", doclist[0].doctype, doclist[0].name);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
make_payment_entry: function(frm) {
|
make_payment_entry: function(frm) {
|
||||||
var method = "erpnext.accounts.doctype.payment_entry.payment_entry.get_payment_entry";
|
var method = "erpnext.accounts.doctype.payment_entry.payment_entry.get_payment_entry";
|
||||||
if(frm.doc.__onload && frm.doc.__onload.make_payment_via_journal_entry) {
|
if(frm.doc.__onload && frm.doc.__onload.make_payment_via_journal_entry) {
|
||||||
method = "erpnext.hr.doctype.employee_advance.employee_advance.make_bank_entry"
|
method = "erpnext.hr.doctype.employee_advance.employee_advance.make_bank_entry";
|
||||||
}
|
}
|
||||||
return frappe.call({
|
return frappe.call({
|
||||||
method: method,
|
method: method,
|
||||||
|
@ -10,9 +10,10 @@
|
|||||||
"naming_series",
|
"naming_series",
|
||||||
"employee",
|
"employee",
|
||||||
"employee_name",
|
"employee_name",
|
||||||
|
"department",
|
||||||
"column_break_4",
|
"column_break_4",
|
||||||
"posting_date",
|
"posting_date",
|
||||||
"department",
|
"repay_unclaimed_amount_from_salary",
|
||||||
"section_break_8",
|
"section_break_8",
|
||||||
"purpose",
|
"purpose",
|
||||||
"column_break_11",
|
"column_break_11",
|
||||||
@ -164,16 +165,23 @@
|
|||||||
"options": "Mode of Payment"
|
"options": "Mode of Payment"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
"allow_on_submit": 1,
|
||||||
"fieldname": "return_amount",
|
"fieldname": "return_amount",
|
||||||
"fieldtype": "Currency",
|
"fieldtype": "Currency",
|
||||||
"label": "Returned Amount",
|
"label": "Returned Amount",
|
||||||
"options": "Company:company:default_currency",
|
"options": "Company:company:default_currency",
|
||||||
"read_only": 1
|
"read_only": 1
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"default": "0",
|
||||||
|
"fieldname": "repay_unclaimed_amount_from_salary",
|
||||||
|
"fieldtype": "Check",
|
||||||
|
"label": "Repay unclaimed amount from salary"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"is_submittable": 1,
|
"is_submittable": 1,
|
||||||
"links": [],
|
"links": [],
|
||||||
"modified": "2019-12-15 19:04:07.044505",
|
"modified": "2020-03-06 15:11:33.747535",
|
||||||
"modified_by": "Administrator",
|
"modified_by": "Administrator",
|
||||||
"module": "HR",
|
"module": "HR",
|
||||||
"name": "Employee Advance",
|
"name": "Employee Advance",
|
||||||
@ -210,4 +218,4 @@
|
|||||||
"sort_field": "modified",
|
"sort_field": "modified",
|
||||||
"sort_order": "DESC",
|
"sort_order": "DESC",
|
||||||
"track_changes": 1
|
"track_changes": 1
|
||||||
}
|
}
|
@ -133,8 +133,20 @@ def make_bank_entry(dt, dn):
|
|||||||
return je.as_dict()
|
return je.as_dict()
|
||||||
|
|
||||||
@frappe.whitelist()
|
@frappe.whitelist()
|
||||||
def make_return_entry(employee, company, employee_advance_name,
|
def create_return_through_additional_salary(doc):
|
||||||
return_amount, advance_account, mode_of_payment=None):
|
import json
|
||||||
|
doc = frappe._dict(json.loads(doc))
|
||||||
|
additional_salary = frappe.new_doc('Additional Salary')
|
||||||
|
additional_salary.employee = doc.employee
|
||||||
|
additional_salary.amount = doc.paid_amount - doc.claimed_amount
|
||||||
|
additional_salary.company = doc.company
|
||||||
|
additional_salary.ref_doctype = doc.doctype
|
||||||
|
additional_salary.ref_docname = doc.name
|
||||||
|
|
||||||
|
return additional_salary
|
||||||
|
|
||||||
|
@frappe.whitelist()
|
||||||
|
def make_return_entry(employee_name, company, employee_advance_name, return_amount, mode_of_payment, advance_account):
|
||||||
return_account = get_default_bank_cash_account(company, account_type='Cash', mode_of_payment = mode_of_payment)
|
return_account = get_default_bank_cash_account(company, account_type='Cash', mode_of_payment = mode_of_payment)
|
||||||
|
|
||||||
mode_of_payment_type = ''
|
mode_of_payment_type = ''
|
||||||
|
@ -9,10 +9,9 @@
|
|||||||
"employee",
|
"employee",
|
||||||
"incentive_amount",
|
"incentive_amount",
|
||||||
"employee_name",
|
"employee_name",
|
||||||
"additional_salary",
|
"salary_component",
|
||||||
"column_break_5",
|
"column_break_5",
|
||||||
"payroll_date",
|
"payroll_date",
|
||||||
"salary_component",
|
|
||||||
"department",
|
"department",
|
||||||
"amended_from"
|
"amended_from"
|
||||||
],
|
],
|
||||||
@ -65,14 +64,6 @@
|
|||||||
"options": "Department",
|
"options": "Department",
|
||||||
"read_only": 1
|
"read_only": 1
|
||||||
},
|
},
|
||||||
{
|
|
||||||
"fieldname": "additional_salary",
|
|
||||||
"fieldtype": "Link",
|
|
||||||
"label": "Additional Salary",
|
|
||||||
"no_copy": 1,
|
|
||||||
"options": "Additional Salary",
|
|
||||||
"read_only": 1
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
"fieldname": "salary_component",
|
"fieldname": "salary_component",
|
||||||
"fieldtype": "Link",
|
"fieldtype": "Link",
|
||||||
@ -83,7 +74,7 @@
|
|||||||
],
|
],
|
||||||
"is_submittable": 1,
|
"is_submittable": 1,
|
||||||
"links": [],
|
"links": [],
|
||||||
"modified": "2019-12-12 13:24:44.761540",
|
"modified": "2020-03-05 18:59:40.526014",
|
||||||
"modified_by": "Administrator",
|
"modified_by": "Administrator",
|
||||||
"module": "HR",
|
"module": "HR",
|
||||||
"name": "Employee Incentive",
|
"name": "Employee Incentive",
|
||||||
|
@ -9,37 +9,13 @@ from frappe.model.document import Document
|
|||||||
class EmployeeIncentive(Document):
|
class EmployeeIncentive(Document):
|
||||||
def on_submit(self):
|
def on_submit(self):
|
||||||
company = frappe.db.get_value('Employee', self.employee, 'company')
|
company = frappe.db.get_value('Employee', self.employee, 'company')
|
||||||
additional_salary = frappe.db.exists('Additional Salary', {
|
|
||||||
'employee': self.employee,
|
|
||||||
'salary_component': self.salary_component,
|
|
||||||
'payroll_date': self.payroll_date,
|
|
||||||
'company': company,
|
|
||||||
'docstatus': 1
|
|
||||||
})
|
|
||||||
|
|
||||||
if not additional_salary:
|
additional_salary = frappe.new_doc('Additional Salary')
|
||||||
additional_salary = frappe.new_doc('Additional Salary')
|
additional_salary.employee = self.employee
|
||||||
additional_salary.employee = self.employee
|
additional_salary.salary_component = self.salary_component
|
||||||
additional_salary.salary_component = self.salary_component
|
additional_salary.amount = self.incentive_amount
|
||||||
additional_salary.amount = self.incentive_amount
|
additional_salary.payroll_date = self.payroll_date
|
||||||
additional_salary.payroll_date = self.payroll_date
|
additional_salary.company = company
|
||||||
additional_salary.company = company
|
additional_salary.ref_doctype = self.doctype
|
||||||
additional_salary.submit()
|
additional_salary.ref_docname = self.name
|
||||||
self.db_set('additional_salary', additional_salary.name)
|
additional_salary.submit()
|
||||||
|
|
||||||
else:
|
|
||||||
incentive_added = frappe.db.get_value('Additional Salary', additional_salary, 'amount') + self.incentive_amount
|
|
||||||
frappe.db.set_value('Additional Salary', additional_salary, 'amount', incentive_added)
|
|
||||||
self.db_set('additional_salary', additional_salary)
|
|
||||||
|
|
||||||
def on_cancel(self):
|
|
||||||
if self.additional_salary:
|
|
||||||
incentive_removed = frappe.db.get_value('Additional Salary', self.additional_salary, 'amount') - self.incentive_amount
|
|
||||||
if incentive_removed == 0:
|
|
||||||
frappe.get_doc('Additional Salary', self.additional_salary).cancel()
|
|
||||||
else:
|
|
||||||
frappe.db.set_value('Additional Salary', self.additional_salary, 'amount', incentive_removed)
|
|
||||||
|
|
||||||
self.db_set('additional_salary', '')
|
|
||||||
|
|
||||||
|
|
||||||
|
@ -1,87 +1,60 @@
|
|||||||
{
|
{
|
||||||
"allow_copy": 0,
|
"actions": [],
|
||||||
"allow_import": 0,
|
"creation": "2013-02-22 01:27:46",
|
||||||
"allow_rename": 0,
|
"doctype": "DocType",
|
||||||
"beta": 0,
|
"document_type": "Setup",
|
||||||
"creation": "2013-02-22 01:27:46",
|
"editable_grid": 1,
|
||||||
"custom": 0,
|
"engine": "InnoDB",
|
||||||
"docstatus": 0,
|
"field_order": [
|
||||||
"doctype": "DocType",
|
"holiday_date",
|
||||||
"document_type": "Setup",
|
"column_break_2",
|
||||||
"editable_grid": 1,
|
"weekly_off",
|
||||||
|
"section_break_4",
|
||||||
|
"description"
|
||||||
|
],
|
||||||
"fields": [
|
"fields": [
|
||||||
{
|
{
|
||||||
"allow_on_submit": 0,
|
"fieldname": "holiday_date",
|
||||||
"bold": 0,
|
"fieldtype": "Date",
|
||||||
"collapsible": 0,
|
"in_list_view": 1,
|
||||||
"fieldname": "holiday_date",
|
"label": "Date",
|
||||||
"fieldtype": "Date",
|
"oldfieldname": "holiday_date",
|
||||||
"hidden": 0,
|
"oldfieldtype": "Date",
|
||||||
"ignore_user_permissions": 0,
|
"reqd": 1
|
||||||
"ignore_xss_filter": 0,
|
},
|
||||||
"in_filter": 0,
|
|
||||||
"in_list_view": 1,
|
|
||||||
"label": "Date",
|
|
||||||
"length": 0,
|
|
||||||
"no_copy": 0,
|
|
||||||
"oldfieldname": "holiday_date",
|
|
||||||
"oldfieldtype": "Date",
|
|
||||||
"permlevel": 0,
|
|
||||||
"print_hide": 0,
|
|
||||||
"print_hide_if_no_value": 0,
|
|
||||||
"read_only": 0,
|
|
||||||
"report_hide": 0,
|
|
||||||
"reqd": 1,
|
|
||||||
"search_index": 0,
|
|
||||||
"set_only_once": 0,
|
|
||||||
"unique": 0
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
"allow_on_submit": 0,
|
"fieldname": "description",
|
||||||
"bold": 0,
|
"fieldtype": "Text Editor",
|
||||||
"collapsible": 0,
|
"in_list_view": 1,
|
||||||
"fieldname": "description",
|
"label": "Description",
|
||||||
"fieldtype": "Text Editor",
|
"print_width": "300px",
|
||||||
"hidden": 0,
|
"reqd": 1,
|
||||||
"ignore_user_permissions": 0,
|
|
||||||
"ignore_xss_filter": 0,
|
|
||||||
"in_filter": 0,
|
|
||||||
"in_list_view": 1,
|
|
||||||
"label": "Description",
|
|
||||||
"length": 0,
|
|
||||||
"no_copy": 0,
|
|
||||||
"permlevel": 0,
|
|
||||||
"print_hide": 0,
|
|
||||||
"print_hide_if_no_value": 0,
|
|
||||||
"print_width": "300px",
|
|
||||||
"read_only": 0,
|
|
||||||
"report_hide": 0,
|
|
||||||
"reqd": 1,
|
|
||||||
"search_index": 0,
|
|
||||||
"set_only_once": 0,
|
|
||||||
"unique": 0,
|
|
||||||
"width": "300px"
|
"width": "300px"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"default": "0",
|
||||||
|
"fieldname": "weekly_off",
|
||||||
|
"fieldtype": "Check",
|
||||||
|
"label": "Weekly Off"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "column_break_2",
|
||||||
|
"fieldtype": "Column Break"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "section_break_4",
|
||||||
|
"fieldtype": "Section Break"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"hide_heading": 0,
|
"idx": 1,
|
||||||
"hide_toolbar": 0,
|
"istable": 1,
|
||||||
"idx": 1,
|
"links": [],
|
||||||
"image_view": 0,
|
"modified": "2020-04-18 19:03:23.507845",
|
||||||
"in_create": 0,
|
"modified_by": "Administrator",
|
||||||
|
"module": "HR",
|
||||||
"is_submittable": 0,
|
"name": "Holiday",
|
||||||
"issingle": 0,
|
"owner": "Administrator",
|
||||||
"istable": 1,
|
"permissions": [],
|
||||||
"max_attachments": 0,
|
"sort_field": "modified",
|
||||||
"modified": "2016-07-11 03:28:00.660849",
|
"sort_order": "ASC"
|
||||||
"modified_by": "Administrator",
|
|
||||||
"module": "HR",
|
|
||||||
"name": "Holiday",
|
|
||||||
"owner": "Administrator",
|
|
||||||
"permissions": [],
|
|
||||||
"quick_entry": 0,
|
|
||||||
"read_only": 0,
|
|
||||||
"read_only_onload": 0,
|
|
||||||
"sort_order": "ASC",
|
|
||||||
"track_seen": 0
|
|
||||||
}
|
}
|
@ -23,6 +23,7 @@ class HolidayList(Document):
|
|||||||
ch = self.append('holidays', {})
|
ch = self.append('holidays', {})
|
||||||
ch.description = self.weekly_off
|
ch.description = self.weekly_off
|
||||||
ch.holiday_date = d
|
ch.holiday_date = d
|
||||||
|
ch.weekly_off = 1
|
||||||
ch.idx = last_idx + i + 1
|
ch.idx = last_idx + i + 1
|
||||||
|
|
||||||
def validate_values(self):
|
def validate_values(self):
|
||||||
|
@ -30,13 +30,16 @@ class LeaveEncashment(Document):
|
|||||||
additional_salary = frappe.new_doc("Additional Salary")
|
additional_salary = frappe.new_doc("Additional Salary")
|
||||||
additional_salary.company = frappe.get_value("Employee", self.employee, "company")
|
additional_salary.company = frappe.get_value("Employee", self.employee, "company")
|
||||||
additional_salary.employee = self.employee
|
additional_salary.employee = self.employee
|
||||||
additional_salary.salary_component = frappe.get_value("Leave Type", self.leave_type, "earning_component")
|
earning_component = frappe.get_value("Leave Type", self.leave_type, "earning_component")
|
||||||
|
if not earning_component:
|
||||||
|
frappe.throw(_("Please set Earning Component for Leave type: {0}.".format(self.leave_type)))
|
||||||
|
additional_salary.salary_component = earning_component
|
||||||
additional_salary.payroll_date = self.encashment_date
|
additional_salary.payroll_date = self.encashment_date
|
||||||
additional_salary.amount = self.encashment_amount
|
additional_salary.amount = self.encashment_amount
|
||||||
|
additional_salary.ref_doctype = self.doctype
|
||||||
|
additional_salary.ref_docname = self.name
|
||||||
additional_salary.submit()
|
additional_salary.submit()
|
||||||
|
|
||||||
self.db_set("additional_salary", additional_salary.name)
|
|
||||||
|
|
||||||
# Set encashed leaves in Allocation
|
# Set encashed leaves in Allocation
|
||||||
frappe.db.set_value("Leave Allocation", self.leave_allocation, "total_leaves_encashed",
|
frappe.db.set_value("Leave Allocation", self.leave_allocation, "total_leaves_encashed",
|
||||||
frappe.db.get_value('Leave Allocation', self.leave_allocation, 'total_leaves_encashed') + self.encashable_days)
|
frappe.db.get_value('Leave Allocation', self.leave_allocation, 'total_leaves_encashed') + self.encashable_days)
|
||||||
@ -118,4 +121,4 @@ def create_leave_encashment(leave_allocation):
|
|||||||
leave_type=allocation.leave_type,
|
leave_type=allocation.leave_type,
|
||||||
encashment_date=allocation.to_date
|
encashment_date=allocation.to_date
|
||||||
))
|
))
|
||||||
leave_encashment.insert(ignore_permissions=True)
|
leave_encashment.insert(ignore_permissions=True)
|
@ -53,7 +53,10 @@ class TestLeaveEncashment(unittest.TestCase):
|
|||||||
self.assertEqual(leave_encashment.encashment_amount, 250)
|
self.assertEqual(leave_encashment.encashment_amount, 250)
|
||||||
|
|
||||||
leave_encashment.submit()
|
leave_encashment.submit()
|
||||||
self.assertTrue(frappe.db.get_value("Leave Encashment", leave_encashment.name, "additional_salary"))
|
|
||||||
|
# assert links
|
||||||
|
add_sal = frappe.get_all("Additional Salary", filters = {"ref_docname": leave_encashment.name})[0]
|
||||||
|
self.assertTrue(add_sal)
|
||||||
|
|
||||||
def test_creation_of_leave_ledger_entry_on_submit(self):
|
def test_creation_of_leave_ledger_entry_on_submit(self):
|
||||||
frappe.db.sql('''delete from `tabLeave Encashment`''')
|
frappe.db.sql('''delete from `tabLeave Encashment`''')
|
||||||
@ -75,5 +78,8 @@ class TestLeaveEncashment(unittest.TestCase):
|
|||||||
self.assertEquals(leave_ledger_entry[0].leaves, leave_encashment.encashable_days * -1)
|
self.assertEquals(leave_ledger_entry[0].leaves, leave_encashment.encashable_days * -1)
|
||||||
|
|
||||||
# check if leave ledger entry is deleted on cancellation
|
# check if leave ledger entry is deleted on cancellation
|
||||||
|
|
||||||
|
frappe.db.sql("Delete from `tabAdditional Salary` WHERE ref_docname = %s", (leave_encashment.name) )
|
||||||
|
|
||||||
leave_encashment.cancel()
|
leave_encashment.cancel()
|
||||||
self.assertFalse(frappe.db.exists("Leave Ledger Entry", {'transaction_name':leave_encashment.name}))
|
self.assertFalse(frappe.db.exists("Leave Ledger Entry", {'transaction_name':leave_encashment.name}))
|
||||||
|
@ -17,7 +17,7 @@ from erpnext.loan_management.doctype.process_loan_interest_accrual.process_loan_
|
|||||||
|
|
||||||
class TestPayrollEntry(unittest.TestCase):
|
class TestPayrollEntry(unittest.TestCase):
|
||||||
def setUp(self):
|
def setUp(self):
|
||||||
for dt in ["Salary Slip", "Salary Component", "Salary Component Account", "Payroll Entry"]:
|
for dt in ["Salary Slip", "Salary Component", "Salary Component Account", "Payroll Entry", "Salary Structure"]:
|
||||||
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"])
|
||||||
|
@ -26,6 +26,7 @@
|
|||||||
"tax_on_flexible_benefit",
|
"tax_on_flexible_benefit",
|
||||||
"tax_on_additional_salary",
|
"tax_on_additional_salary",
|
||||||
"section_break_11",
|
"section_break_11",
|
||||||
|
"additional_salary",
|
||||||
"condition_and_formula_help"
|
"condition_and_formula_help"
|
||||||
],
|
],
|
||||||
"fields": [
|
"fields": [
|
||||||
@ -192,6 +193,12 @@
|
|||||||
"label": "Condition and Formula Help",
|
"label": "Condition and Formula Help",
|
||||||
"options": "<h3>Condition and Formula Help</h3>\n\n<p>Notes:</p>\n\n<ol>\n<li>Use field <code>base</code> for using base salary of the Employee</li>\n<li>Use Salary Component abbreviations in conditions and formulas. <code>BS = Basic Salary</code></li>\n<li>Use field name for employee details in conditions and formulas. <code>Employment Type = employment_type</code><code>Branch = branch</code></li>\n<li>Use field name from Salary Slip in conditions and formulas. <code>Payment Days = payment_days</code><code>Leave without pay = leave_without_pay</code></li>\n<li>Direct Amount can also be entered based on Condtion. See example 3</li></ol>\n\n<h4>Examples</h4>\n<ol>\n<li>Calculating Basic Salary based on <code>base</code>\n<pre><code>Condition: base < 10000</code></pre>\n<pre><code>Formula: base * .2</code></pre></li>\n<li>Calculating HRA based on Basic Salary<code>BS</code> \n<pre><code>Condition: BS > 2000</code></pre>\n<pre><code>Formula: BS * .1</code></pre></li>\n<li>Calculating TDS based on Employment Type<code>employment_type</code> \n<pre><code>Condition: employment_type==\"Intern\"</code></pre>\n<pre><code>Amount: 1000</code></pre></li>\n</ol>"
|
"options": "<h3>Condition and Formula Help</h3>\n\n<p>Notes:</p>\n\n<ol>\n<li>Use field <code>base</code> for using base salary of the Employee</li>\n<li>Use Salary Component abbreviations in conditions and formulas. <code>BS = Basic Salary</code></li>\n<li>Use field name for employee details in conditions and formulas. <code>Employment Type = employment_type</code><code>Branch = branch</code></li>\n<li>Use field name from Salary Slip in conditions and formulas. <code>Payment Days = payment_days</code><code>Leave without pay = leave_without_pay</code></li>\n<li>Direct Amount can also be entered based on Condtion. See example 3</li></ol>\n\n<h4>Examples</h4>\n<ol>\n<li>Calculating Basic Salary based on <code>base</code>\n<pre><code>Condition: base < 10000</code></pre>\n<pre><code>Formula: base * .2</code></pre></li>\n<li>Calculating HRA based on Basic Salary<code>BS</code> \n<pre><code>Condition: BS > 2000</code></pre>\n<pre><code>Formula: BS * .1</code></pre></li>\n<li>Calculating TDS based on Employment Type<code>employment_type</code> \n<pre><code>Condition: employment_type==\"Intern\"</code></pre>\n<pre><code>Amount: 1000</code></pre></li>\n</ol>"
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "additional_salary",
|
||||||
|
"fieldtype": "Link",
|
||||||
|
"label": "Additional Salary ",
|
||||||
|
"options": "Additional Salary"
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"default": "0",
|
"default": "0",
|
||||||
"depends_on": "eval:doc.parentfield=='deductions'",
|
"depends_on": "eval:doc.parentfield=='deductions'",
|
||||||
@ -204,7 +211,7 @@
|
|||||||
],
|
],
|
||||||
"istable": 1,
|
"istable": 1,
|
||||||
"links": [],
|
"links": [],
|
||||||
"modified": "2020-04-24 20:00:16.475295",
|
"modified": "2020-04-04 20:00:16.475295",
|
||||||
"modified_by": "Administrator",
|
"modified_by": "Administrator",
|
||||||
"module": "HR",
|
"module": "HR",
|
||||||
"name": "Salary Detail",
|
"name": "Salary Detail",
|
||||||
@ -213,4 +220,4 @@
|
|||||||
"quick_entry": 1,
|
"quick_entry": 1,
|
||||||
"sort_field": "modified",
|
"sort_field": "modified",
|
||||||
"sort_order": "DESC"
|
"sort_order": "DESC"
|
||||||
}
|
}
|
||||||
|
@ -66,7 +66,6 @@ class SalarySlip(TransactionBase):
|
|||||||
else:
|
else:
|
||||||
self.set_status()
|
self.set_status()
|
||||||
self.update_status(self.name)
|
self.update_status(self.name)
|
||||||
self.update_salary_slip_in_additional_salary()
|
|
||||||
self.make_loan_repayment_entry()
|
self.make_loan_repayment_entry()
|
||||||
if (frappe.db.get_single_value("HR Settings", "email_salary_slip_to_employee")) and not frappe.flags.via_payroll_entry:
|
if (frappe.db.get_single_value("HR Settings", "email_salary_slip_to_employee")) and not frappe.flags.via_payroll_entry:
|
||||||
self.email_salary_slip()
|
self.email_salary_slip()
|
||||||
@ -74,7 +73,6 @@ class SalarySlip(TransactionBase):
|
|||||||
def on_cancel(self):
|
def on_cancel(self):
|
||||||
self.set_status()
|
self.set_status()
|
||||||
self.update_status()
|
self.update_status()
|
||||||
self.update_salary_slip_in_additional_salary()
|
|
||||||
self.cancel_loan_repayment_entry()
|
self.cancel_loan_repayment_entry()
|
||||||
|
|
||||||
def on_trash(self):
|
def on_trash(self):
|
||||||
@ -464,14 +462,15 @@ class SalarySlip(TransactionBase):
|
|||||||
self.update_component_row(frappe._dict(last_benefit.struct_row), amount, "earnings")
|
self.update_component_row(frappe._dict(last_benefit.struct_row), amount, "earnings")
|
||||||
|
|
||||||
def add_additional_salary_components(self, component_type):
|
def add_additional_salary_components(self, component_type):
|
||||||
additional_components = get_additional_salary_component(self.employee,
|
salary_components_details, additional_salary_details = get_additional_salary_component(self.employee,
|
||||||
self.start_date, self.end_date, component_type)
|
self.start_date, self.end_date, component_type)
|
||||||
if additional_components:
|
if salary_components_details and additional_salary_details:
|
||||||
for additional_component in additional_components:
|
for additional_salary in additional_salary_details:
|
||||||
amount = additional_component.amount
|
additional_salary =frappe._dict(additional_salary)
|
||||||
overwrite = additional_component.overwrite
|
amount = additional_salary.amount
|
||||||
self.update_component_row(frappe._dict(additional_component.struct_row), amount,
|
overwrite = additional_salary.overwrite
|
||||||
component_type, overwrite=overwrite)
|
self.update_component_row(frappe._dict(salary_components_details[additional_salary.component]), amount,
|
||||||
|
component_type, overwrite=overwrite, additional_salary=additional_salary.name)
|
||||||
|
|
||||||
def add_tax_components(self, payroll_period):
|
def add_tax_components(self, payroll_period):
|
||||||
# Calculate variable_based_on_taxable_salary after all components updated in salary slip
|
# Calculate variable_based_on_taxable_salary after all components updated in salary slip
|
||||||
@ -491,13 +490,12 @@ class SalarySlip(TransactionBase):
|
|||||||
tax_row = self.get_salary_slip_row(d)
|
tax_row = self.get_salary_slip_row(d)
|
||||||
self.update_component_row(tax_row, tax_amount, "deductions")
|
self.update_component_row(tax_row, tax_amount, "deductions")
|
||||||
|
|
||||||
def update_component_row(self, struct_row, amount, key, overwrite=1):
|
def update_component_row(self, struct_row, amount, key, overwrite=1, additional_salary = ''):
|
||||||
component_row = None
|
component_row = None
|
||||||
for d in self.get(key):
|
for d in self.get(key):
|
||||||
if d.salary_component == struct_row.salary_component:
|
if d.salary_component == struct_row.salary_component:
|
||||||
component_row = d
|
component_row = d
|
||||||
|
if not component_row or (struct_row.get("is_additional_component") and not overwrite):
|
||||||
if not component_row:
|
|
||||||
if amount:
|
if amount:
|
||||||
self.append(key, {
|
self.append(key, {
|
||||||
'amount': amount,
|
'amount': amount,
|
||||||
@ -505,6 +503,7 @@ class SalarySlip(TransactionBase):
|
|||||||
'depends_on_payment_days' : struct_row.depends_on_payment_days,
|
'depends_on_payment_days' : struct_row.depends_on_payment_days,
|
||||||
'salary_component' : struct_row.salary_component,
|
'salary_component' : struct_row.salary_component,
|
||||||
'abbr' : struct_row.abbr,
|
'abbr' : struct_row.abbr,
|
||||||
|
'additional_salary': additional_salary,
|
||||||
'do_not_include_in_total' : struct_row.do_not_include_in_total,
|
'do_not_include_in_total' : struct_row.do_not_include_in_total,
|
||||||
'is_tax_applicable': struct_row.is_tax_applicable,
|
'is_tax_applicable': struct_row.is_tax_applicable,
|
||||||
'is_flexible_benefit': struct_row.is_flexible_benefit,
|
'is_flexible_benefit': struct_row.is_flexible_benefit,
|
||||||
@ -517,6 +516,7 @@ class SalarySlip(TransactionBase):
|
|||||||
if struct_row.get("is_additional_component"):
|
if struct_row.get("is_additional_component"):
|
||||||
if overwrite:
|
if overwrite:
|
||||||
component_row.additional_amount = amount - component_row.get("default_amount", 0)
|
component_row.additional_amount = amount - component_row.get("default_amount", 0)
|
||||||
|
component_row.additional_salary = additional_salary
|
||||||
else:
|
else:
|
||||||
component_row.additional_amount = amount
|
component_row.additional_amount = amount
|
||||||
|
|
||||||
@ -936,14 +936,6 @@ class SalarySlip(TransactionBase):
|
|||||||
"repay_from_salary": 1,
|
"repay_from_salary": 1,
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|
||||||
def update_salary_slip_in_additional_salary(self):
|
|
||||||
salary_slip = self.name if self.docstatus==1 else None
|
|
||||||
frappe.db.sql("""
|
|
||||||
update `tabAdditional Salary` set salary_slip=%s
|
|
||||||
where employee=%s and payroll_date between %s and %s and docstatus=1
|
|
||||||
""", (salary_slip, self.employee, self.start_date, self.end_date))
|
|
||||||
|
|
||||||
def make_loan_repayment_entry(self):
|
def make_loan_repayment_entry(self):
|
||||||
for loan in self.loans:
|
for loan in self.loans:
|
||||||
repayment_entry = create_repayment_entry(loan.loan, self.employee,
|
repayment_entry = create_repayment_entry(loan.loan, self.employee,
|
||||||
|
@ -18,19 +18,7 @@ from erpnext.hr.doctype.employee_tax_exemption_declaration.test_employee_tax_exe
|
|||||||
|
|
||||||
class TestSalarySlip(unittest.TestCase):
|
class TestSalarySlip(unittest.TestCase):
|
||||||
def setUp(self):
|
def setUp(self):
|
||||||
make_earning_salary_component(setup=True, company_list=["_Test Company"])
|
setup_test()
|
||||||
make_deduction_salary_component(setup=True, company_list=["_Test Company"])
|
|
||||||
|
|
||||||
for dt in ["Leave Application", "Leave Allocation", "Salary Slip", "Attendance"]:
|
|
||||||
frappe.db.sql("delete from `tab%s`" % dt)
|
|
||||||
|
|
||||||
self.make_holiday_list()
|
|
||||||
|
|
||||||
frappe.db.set_value("Company", erpnext.get_default_company(), "default_holiday_list", "Salary Slip Test Holiday List")
|
|
||||||
frappe.db.set_value("HR Settings", None, "email_salary_slip_to_employee", 0)
|
|
||||||
frappe.db.set_value('HR Settings', None, 'leave_status_notification_template', None)
|
|
||||||
frappe.db.set_value('HR Settings', None, 'leave_approval_notification_template', None)
|
|
||||||
|
|
||||||
def tearDown(self):
|
def tearDown(self):
|
||||||
frappe.db.set_value("HR Settings", None, "include_holidays_in_total_working_days", 0)
|
frappe.db.set_value("HR Settings", None, "include_holidays_in_total_working_days", 0)
|
||||||
frappe.set_user("Administrator")
|
frappe.set_user("Administrator")
|
||||||
@ -374,19 +362,6 @@ class TestSalarySlip(unittest.TestCase):
|
|||||||
# undelete fixture data
|
# undelete fixture data
|
||||||
frappe.db.rollback()
|
frappe.db.rollback()
|
||||||
|
|
||||||
def make_holiday_list(self):
|
|
||||||
fiscal_year = get_fiscal_year(nowdate(), company=erpnext.get_default_company())
|
|
||||||
if not frappe.db.get_value("Holiday List", "Salary Slip Test Holiday List"):
|
|
||||||
holiday_list = frappe.get_doc({
|
|
||||||
"doctype": "Holiday List",
|
|
||||||
"holiday_list_name": "Salary Slip Test Holiday List",
|
|
||||||
"from_date": fiscal_year[1],
|
|
||||||
"to_date": fiscal_year[2],
|
|
||||||
"weekly_off": "Sunday"
|
|
||||||
}).insert()
|
|
||||||
holiday_list.get_weekly_off_dates()
|
|
||||||
holiday_list.save()
|
|
||||||
|
|
||||||
def make_activity_for_employee(self):
|
def make_activity_for_employee(self):
|
||||||
activity_type = frappe.get_doc("Activity Type", "_Test Activity Type")
|
activity_type = frappe.get_doc("Activity Type", "_Test Activity Type")
|
||||||
activity_type.billing_rate = 50
|
activity_type.billing_rate = 50
|
||||||
@ -702,4 +677,31 @@ def make_leave_application(employee, from_date, to_date, leave_type, company=Non
|
|||||||
status = "Approved",
|
status = "Approved",
|
||||||
leave_approver = 'test@example.com'
|
leave_approver = 'test@example.com'
|
||||||
))
|
))
|
||||||
leave_application.submit()
|
leave_application.submit()
|
||||||
|
|
||||||
|
def setup_test():
|
||||||
|
make_earning_salary_component(setup=True, company_list=["_Test Company"])
|
||||||
|
make_deduction_salary_component(setup=True, company_list=["_Test Company"])
|
||||||
|
|
||||||
|
for dt in ["Leave Application", "Leave Allocation", "Salary Slip", "Attendance"]:
|
||||||
|
frappe.db.sql("delete from `tab%s`" % dt)
|
||||||
|
|
||||||
|
make_holiday_list()
|
||||||
|
|
||||||
|
frappe.db.set_value("Company", erpnext.get_default_company(), "default_holiday_list", "Salary Slip Test Holiday List")
|
||||||
|
frappe.db.set_value("HR Settings", None, "email_salary_slip_to_employee", 0)
|
||||||
|
frappe.db.set_value('HR Settings', None, 'leave_status_notification_template', None)
|
||||||
|
frappe.db.set_value('HR Settings', None, 'leave_approval_notification_template', None)
|
||||||
|
|
||||||
|
def make_holiday_list():
|
||||||
|
fiscal_year = get_fiscal_year(nowdate(), company=erpnext.get_default_company())
|
||||||
|
if not frappe.db.get_value("Holiday List", "Salary Slip Test Holiday List"):
|
||||||
|
holiday_list = frappe.get_doc({
|
||||||
|
"doctype": "Holiday List",
|
||||||
|
"holiday_list_name": "Salary Slip Test Holiday List",
|
||||||
|
"from_date": fiscal_year[1],
|
||||||
|
"to_date": fiscal_year[2],
|
||||||
|
"weekly_off": "Sunday"
|
||||||
|
}).insert()
|
||||||
|
holiday_list.get_weekly_off_dates()
|
||||||
|
holiday_list.save()
|
||||||
|
@ -31,6 +31,18 @@ frappe.query_reports["Monthly Attendance Sheet"] = {
|
|||||||
"options": "Company",
|
"options": "Company",
|
||||||
"default": frappe.defaults.get_user_default("Company"),
|
"default": frappe.defaults.get_user_default("Company"),
|
||||||
"reqd": 1
|
"reqd": 1
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname":"group_by",
|
||||||
|
"label": __("Group By"),
|
||||||
|
"fieldtype": "Select",
|
||||||
|
"options": ["","Branch","Grade","Department","Designation"]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname":"summarized_view",
|
||||||
|
"label": __("Summarized View"),
|
||||||
|
"fieldtype": "Check",
|
||||||
|
"Default": 0,
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
|
|
||||||
|
@ -7,65 +7,127 @@ from frappe.utils import cstr, cint, getdate
|
|||||||
from frappe import msgprint, _
|
from frappe import msgprint, _
|
||||||
from calendar import monthrange
|
from calendar import monthrange
|
||||||
|
|
||||||
|
status_map = {
|
||||||
|
"Absent": "A",
|
||||||
|
"Half Day": "HD",
|
||||||
|
"Holiday": "<b>H</b>",
|
||||||
|
"Weekly Off": "<b>WO</b>",
|
||||||
|
"On Leave": "L",
|
||||||
|
"Present": "P",
|
||||||
|
"Work From Home": "WFH"
|
||||||
|
}
|
||||||
|
|
||||||
|
day_abbr = [
|
||||||
|
"Mon",
|
||||||
|
"Tue",
|
||||||
|
"Wed",
|
||||||
|
"Thu",
|
||||||
|
"Fri",
|
||||||
|
"Sat",
|
||||||
|
"Sun"
|
||||||
|
]
|
||||||
|
|
||||||
def execute(filters=None):
|
def execute(filters=None):
|
||||||
if not filters: filters = {}
|
if not filters: filters = {}
|
||||||
|
|
||||||
conditions, filters = get_conditions(filters)
|
conditions, filters = get_conditions(filters)
|
||||||
columns = get_columns(filters)
|
columns = get_columns(filters)
|
||||||
att_map = get_attendance_list(conditions, filters)
|
att_map = get_attendance_list(conditions, filters)
|
||||||
emp_map = get_employee_details(filters)
|
|
||||||
|
|
||||||
holiday_list = [emp_map[d]["holiday_list"] for d in emp_map if emp_map[d]["holiday_list"]]
|
if filters.group_by:
|
||||||
|
emp_map, group_by_parameters = get_employee_details(filters.group_by, filters.company)
|
||||||
|
holiday_list = []
|
||||||
|
for parameter in group_by_parameters:
|
||||||
|
h_list = [emp_map[parameter][d]["holiday_list"] for d in emp_map[parameter] if emp_map[parameter][d]["holiday_list"]]
|
||||||
|
holiday_list += h_list
|
||||||
|
else:
|
||||||
|
emp_map = get_employee_details(filters.group_by, filters.company)
|
||||||
|
holiday_list = [emp_map[d]["holiday_list"] for d in emp_map if emp_map[d]["holiday_list"]]
|
||||||
|
|
||||||
|
|
||||||
default_holiday_list = frappe.get_cached_value('Company', filters.get("company"), "default_holiday_list")
|
default_holiday_list = frappe.get_cached_value('Company', filters.get("company"), "default_holiday_list")
|
||||||
holiday_list.append(default_holiday_list)
|
holiday_list.append(default_holiday_list)
|
||||||
holiday_list = list(set(holiday_list))
|
holiday_list = list(set(holiday_list))
|
||||||
holiday_map = get_holiday(holiday_list, filters["month"])
|
holiday_map = get_holiday(holiday_list, filters["month"])
|
||||||
|
|
||||||
data = []
|
data = []
|
||||||
leave_types = frappe.db.sql("""select name from `tabLeave Type`""", as_list=True)
|
|
||||||
leave_list = [d[0] for d in leave_types]
|
|
||||||
columns.extend(leave_list)
|
|
||||||
columns.extend([_("Total Late Entries") + ":Float:120", _("Total Early Exits") + ":Float:120"])
|
|
||||||
|
|
||||||
for emp in sorted(att_map):
|
leave_list = None
|
||||||
emp_det = emp_map.get(emp)
|
if filters.summarized_view:
|
||||||
if not emp_det:
|
leave_types = frappe.db.sql("""select name from `tabLeave Type`""", as_list=True)
|
||||||
|
leave_list = [d[0] + ":Float:120" for d in leave_types]
|
||||||
|
columns.extend(leave_list)
|
||||||
|
columns.extend([_("Total Late Entries") + ":Float:120", _("Total Early Exits") + ":Float:120"])
|
||||||
|
|
||||||
|
if filters.group_by:
|
||||||
|
for parameter in group_by_parameters:
|
||||||
|
data.append([ "<b>"+ parameter + "</b>"])
|
||||||
|
record = add_data(emp_map[parameter], att_map, filters, holiday_map, conditions, leave_list=leave_list)
|
||||||
|
data += record
|
||||||
|
else:
|
||||||
|
record = add_data(emp_map, att_map, filters, holiday_map, conditions, leave_list=leave_list)
|
||||||
|
data += record
|
||||||
|
|
||||||
|
return columns, data
|
||||||
|
|
||||||
|
|
||||||
|
def add_data(employee_map, att_map, filters, holiday_map, conditions, leave_list=None):
|
||||||
|
|
||||||
|
record = []
|
||||||
|
for emp in employee_map:
|
||||||
|
emp_det = employee_map.get(emp)
|
||||||
|
if not emp_det or emp not in att_map:
|
||||||
continue
|
continue
|
||||||
|
|
||||||
row = [emp, emp_det.employee_name, emp_det.branch, emp_det.department, emp_det.designation,
|
row = []
|
||||||
emp_det.company]
|
if filters.group_by:
|
||||||
|
row += [" "]
|
||||||
|
row += [emp, emp_det.employee_name]
|
||||||
|
|
||||||
total_p = total_a = total_l = 0.0
|
total_p = total_a = total_l = total_h = total_um= 0.0
|
||||||
for day in range(filters["total_days_in_month"]):
|
for day in range(filters["total_days_in_month"]):
|
||||||
|
status = None
|
||||||
status = att_map.get(emp).get(day + 1)
|
status = att_map.get(emp).get(day + 1)
|
||||||
status_map = {
|
|
||||||
"Absent": "A",
|
|
||||||
"Half Day": "HD",
|
|
||||||
"Holiday":"<b>H</b>",
|
|
||||||
"On Leave": "L",
|
|
||||||
"Present": "P",
|
|
||||||
"Work From Home": "WFH"
|
|
||||||
}
|
|
||||||
|
|
||||||
if status is None and holiday_map:
|
if status is None and holiday_map:
|
||||||
emp_holiday_list = emp_det.holiday_list if emp_det.holiday_list else default_holiday_list
|
emp_holiday_list = emp_det.holiday_list if emp_det.holiday_list else default_holiday_list
|
||||||
if emp_holiday_list in holiday_map and (day+1) in holiday_map[emp_holiday_list]:
|
|
||||||
status = "Holiday"
|
|
||||||
|
|
||||||
row.append(status_map.get(status, ""))
|
if emp_holiday_list in holiday_map:
|
||||||
|
for idx, ele in enumerate(holiday_map[emp_holiday_list]):
|
||||||
|
if day+1 == holiday_map[emp_holiday_list][idx][0]:
|
||||||
|
if holiday_map[emp_holiday_list][idx][1]:
|
||||||
|
status = "Weekly Off"
|
||||||
|
else:
|
||||||
|
status = "Holiday"
|
||||||
|
total_h += 1
|
||||||
|
|
||||||
if status == "Present":
|
|
||||||
total_p += 1
|
|
||||||
elif status == "Absent":
|
|
||||||
total_a += 1
|
|
||||||
elif status == "On Leave":
|
|
||||||
total_l += 1
|
|
||||||
elif status == "Half Day":
|
|
||||||
total_p += 0.5
|
|
||||||
total_a += 0.5
|
|
||||||
total_l += 0.5
|
|
||||||
|
|
||||||
row += [total_p, total_l, total_a]
|
# if emp_holiday_list in holiday_map and (day+1) in holiday_map[emp_holiday_list][0]:
|
||||||
|
# if holiday_map[emp_holiday_list][1]:
|
||||||
|
# status= "Weekly Off"
|
||||||
|
# else:
|
||||||
|
# status = "Holiday"
|
||||||
|
|
||||||
|
# += 1
|
||||||
|
|
||||||
|
if not filters.summarized_view:
|
||||||
|
row.append(status_map.get(status, ""))
|
||||||
|
else:
|
||||||
|
if status == "Present":
|
||||||
|
total_p += 1
|
||||||
|
elif status == "Absent":
|
||||||
|
total_a += 1
|
||||||
|
elif status == "On Leave":
|
||||||
|
total_l += 1
|
||||||
|
elif status == "Half Day":
|
||||||
|
total_p += 0.5
|
||||||
|
total_a += 0.5
|
||||||
|
total_l += 0.5
|
||||||
|
elif not status:
|
||||||
|
total_um += 1
|
||||||
|
|
||||||
|
if filters.summarized_view:
|
||||||
|
row += [total_p, total_l, total_a, total_h, total_um]
|
||||||
|
|
||||||
if not filters.get("employee"):
|
if not filters.get("employee"):
|
||||||
filters.update({"employee": emp})
|
filters.update({"employee": emp})
|
||||||
@ -73,43 +135,53 @@ def execute(filters=None):
|
|||||||
elif not filters.get("employee") == emp:
|
elif not filters.get("employee") == emp:
|
||||||
filters.update({"employee": emp})
|
filters.update({"employee": emp})
|
||||||
|
|
||||||
leave_details = frappe.db.sql("""select leave_type, status, count(*) as count from `tabAttendance`\
|
if filters.summarized_view:
|
||||||
where leave_type is not NULL %s group by leave_type, status""" % conditions, filters, as_dict=1)
|
leave_details = frappe.db.sql("""select leave_type, status, count(*) as count from `tabAttendance`\
|
||||||
|
where leave_type is not NULL %s group by leave_type, status""" % conditions, filters, as_dict=1)
|
||||||
|
|
||||||
time_default_counts = frappe.db.sql("""select (select count(*) from `tabAttendance` where \
|
time_default_counts = frappe.db.sql("""select (select count(*) from `tabAttendance` where \
|
||||||
late_entry = 1 %s) as late_entry_count, (select count(*) from tabAttendance where \
|
late_entry = 1 %s) as late_entry_count, (select count(*) from tabAttendance where \
|
||||||
early_exit = 1 %s) as early_exit_count""" % (conditions, conditions), filters)
|
early_exit = 1 %s) as early_exit_count""" % (conditions, conditions), filters)
|
||||||
|
|
||||||
leaves = {}
|
leaves = {}
|
||||||
for d in leave_details:
|
for d in leave_details:
|
||||||
if d.status == "Half Day":
|
if d.status == "Half Day":
|
||||||
d.count = d.count * 0.5
|
d.count = d.count * 0.5
|
||||||
if d.leave_type in leaves:
|
if d.leave_type in leaves:
|
||||||
leaves[d.leave_type] += d.count
|
leaves[d.leave_type] += d.count
|
||||||
else:
|
else:
|
||||||
leaves[d.leave_type] = d.count
|
leaves[d.leave_type] = d.count
|
||||||
|
|
||||||
for d in leave_list:
|
for d in leave_list:
|
||||||
if d in leaves:
|
if d in leaves:
|
||||||
row.append(leaves[d])
|
row.append(leaves[d])
|
||||||
else:
|
else:
|
||||||
row.append("0.0")
|
row.append("0.0")
|
||||||
|
|
||||||
row.extend([time_default_counts[0][0],time_default_counts[0][1]])
|
row.extend([time_default_counts[0][0],time_default_counts[0][1]])
|
||||||
data.append(row)
|
record.append(row)
|
||||||
return columns, data
|
|
||||||
|
|
||||||
|
return record
|
||||||
|
|
||||||
def get_columns(filters):
|
def get_columns(filters):
|
||||||
columns = [
|
|
||||||
_("Employee") + ":Link/Employee:120", _("Employee Name") + "::140", _("Branch")+ ":Link/Branch:120",
|
columns = []
|
||||||
_("Department") + ":Link/Department:120", _("Designation") + ":Link/Designation:120",
|
|
||||||
_("Company") + ":Link/Company:120"
|
if filters.group_by:
|
||||||
|
columns = [_(filters.group_by)+ ":Link/Branch:120"]
|
||||||
|
|
||||||
|
columns += [
|
||||||
|
_("Employee") + ":Link/Employee:120", _("Employee Name") + ":Link/Employee:120"
|
||||||
]
|
]
|
||||||
|
|
||||||
for day in range(filters["total_days_in_month"]):
|
if not filters.summarized_view:
|
||||||
columns.append(cstr(day+1) +"::20")
|
for day in range(filters["total_days_in_month"]):
|
||||||
|
date = str(filters.year) + "-" + str(filters.month)+ "-" + str(day+1)
|
||||||
columns += [_("Total Present") + ":Float:80", _("Total Leaves") + ":Float:80", _("Total Absent") + ":Float:80"]
|
day_name = day_abbr[getdate(date).weekday()]
|
||||||
|
columns.append(cstr(day+1)+ " " +day_name +"::65")
|
||||||
|
else:
|
||||||
|
columns += [_("Total Present") + ":Float:120", _("Total Leaves") + ":Float:120", _("Total Absent") + ":Float:120", _("Total Holidays") + ":Float:120", _("Unmarked Days")+ ":Float:120"]
|
||||||
return columns
|
return columns
|
||||||
|
|
||||||
def get_attendance_list(conditions, filters):
|
def get_attendance_list(conditions, filters):
|
||||||
@ -140,19 +212,43 @@ def get_conditions(filters):
|
|||||||
|
|
||||||
return conditions, filters
|
return conditions, filters
|
||||||
|
|
||||||
def get_employee_details(filters):
|
def get_employee_details(group_by, company):
|
||||||
emp_map = frappe._dict()
|
emp_map = {}
|
||||||
for d in frappe.db.sql("""select name, employee_name, designation, department, branch, company,
|
query = """select name, employee_name, designation, department, branch, company,
|
||||||
holiday_list from tabEmployee where company = %s""", (filters.get("company")), as_dict=1):
|
holiday_list from `tabEmployee` where company = '%s' """ % frappe.db.escape(company)
|
||||||
emp_map.setdefault(d.name, d)
|
|
||||||
|
|
||||||
return emp_map
|
if group_by:
|
||||||
|
group_by = group_by.lower()
|
||||||
|
query += " order by " + group_by + " ASC"
|
||||||
|
|
||||||
|
employee_details = frappe.db.sql(query , as_dict=1)
|
||||||
|
|
||||||
|
group_by_parameters = []
|
||||||
|
if group_by:
|
||||||
|
|
||||||
|
group_by_parameters = list(set(detail.get(group_by, "") for detail in employee_details if detail.get(group_by, "")))
|
||||||
|
for parameter in group_by_parameters:
|
||||||
|
emp_map[parameter] = {}
|
||||||
|
|
||||||
|
|
||||||
|
for d in employee_details:
|
||||||
|
if group_by and len(group_by_parameters):
|
||||||
|
if d.get(group_by, None):
|
||||||
|
|
||||||
|
emp_map[d.get(group_by)][d.name] = d
|
||||||
|
else:
|
||||||
|
emp_map[d.name] = d
|
||||||
|
|
||||||
|
if not group_by:
|
||||||
|
return emp_map
|
||||||
|
else:
|
||||||
|
return emp_map, group_by_parameters
|
||||||
|
|
||||||
def get_holiday(holiday_list, month):
|
def get_holiday(holiday_list, month):
|
||||||
holiday_map = frappe._dict()
|
holiday_map = frappe._dict()
|
||||||
for d in holiday_list:
|
for d in holiday_list:
|
||||||
if d:
|
if d:
|
||||||
holiday_map.setdefault(d, frappe.db.sql_list('''select day(holiday_date) from `tabHoliday`
|
holiday_map.setdefault(d, frappe.db.sql('''select day(holiday_date), weekly_off from `tabHoliday`
|
||||||
where parent=%s and month(holiday_date)=%s''', (d, month)))
|
where parent=%s and month(holiday_date)=%s''', (d, month)))
|
||||||
|
|
||||||
return holiday_map
|
return holiday_map
|
||||||
|
@ -233,7 +233,7 @@ def make_repayment_entry(loan, applicant_type, applicant, loan_type, company, as
|
|||||||
return repayment_entry
|
return repayment_entry
|
||||||
|
|
||||||
@frappe.whitelist()
|
@frappe.whitelist()
|
||||||
def create_loan_security_unpledge(loan, applicant_type, applicant, company):
|
def create_loan_security_unpledge(loan, applicant_type, applicant, company, as_dict=1):
|
||||||
loan_security_pledge_details = frappe.db.sql("""
|
loan_security_pledge_details = frappe.db.sql("""
|
||||||
SELECT p.parent, p.loan_security, p.qty as qty FROM `tabLoan Security Pledge` lsp , `tabPledge` p
|
SELECT p.parent, p.loan_security, p.qty as qty FROM `tabLoan Security Pledge` lsp , `tabPledge` p
|
||||||
WHERE p.parent = lsp.name AND lsp.loan = %s AND lsp.docstatus = 1
|
WHERE p.parent = lsp.name AND lsp.loan = %s AND lsp.docstatus = 1
|
||||||
@ -252,7 +252,10 @@ def create_loan_security_unpledge(loan, applicant_type, applicant, company):
|
|||||||
"against_pledge": loan_security.parent
|
"against_pledge": loan_security.parent
|
||||||
})
|
})
|
||||||
|
|
||||||
return unpledge_request.as_dict()
|
if as_dict:
|
||||||
|
return unpledge_request.as_dict()
|
||||||
|
else:
|
||||||
|
return unpledge_request
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
@ -14,6 +14,7 @@ from erpnext.loan_management.doctype.process_loan_interest_accrual.process_loan_
|
|||||||
process_loan_interest_accrual_for_term_loans)
|
process_loan_interest_accrual_for_term_loans)
|
||||||
from erpnext.loan_management.doctype.loan_interest_accrual.loan_interest_accrual import days_in_year
|
from erpnext.loan_management.doctype.loan_interest_accrual.loan_interest_accrual import days_in_year
|
||||||
from erpnext.loan_management.doctype.process_loan_security_shortfall.process_loan_security_shortfall import create_process_loan_security_shortfall
|
from erpnext.loan_management.doctype.process_loan_security_shortfall.process_loan_security_shortfall import create_process_loan_security_shortfall
|
||||||
|
from erpnext.loan_management.doctype.loan.loan import create_loan_security_unpledge
|
||||||
|
|
||||||
class TestLoan(unittest.TestCase):
|
class TestLoan(unittest.TestCase):
|
||||||
def setUp(self):
|
def setUp(self):
|
||||||
@ -276,6 +277,56 @@ class TestLoan(unittest.TestCase):
|
|||||||
frappe.db.sql(""" UPDATE `tabLoan Security Price` SET loan_security_price = 250
|
frappe.db.sql(""" UPDATE `tabLoan Security Price` SET loan_security_price = 250
|
||||||
where loan_security='Test Security 2'""")
|
where loan_security='Test Security 2'""")
|
||||||
|
|
||||||
|
def test_loan_security_unpledge(self):
|
||||||
|
pledges = []
|
||||||
|
pledges.append({
|
||||||
|
"loan_security": "Test Security 1",
|
||||||
|
"qty": 4000.00,
|
||||||
|
"haircut": 50
|
||||||
|
})
|
||||||
|
|
||||||
|
loan_security_pledge = create_loan_security_pledge(self.applicant2, pledges)
|
||||||
|
loan = create_demand_loan(self.applicant2, "Demand Loan", loan_security_pledge.name,
|
||||||
|
posting_date=get_first_day(nowdate()))
|
||||||
|
loan.submit()
|
||||||
|
|
||||||
|
self.assertEquals(loan.loan_amount, 1000000)
|
||||||
|
|
||||||
|
first_date = '2019-10-01'
|
||||||
|
last_date = '2019-10-30'
|
||||||
|
|
||||||
|
no_of_days = date_diff(last_date, first_date) + 1
|
||||||
|
|
||||||
|
no_of_days += 6
|
||||||
|
|
||||||
|
accrued_interest_amount = (loan.loan_amount * loan.rate_of_interest * no_of_days) \
|
||||||
|
/ (days_in_year(get_datetime(first_date).year) * 100)
|
||||||
|
|
||||||
|
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),
|
||||||
|
"Loan Closure", flt(loan.loan_amount + accrued_interest_amount))
|
||||||
|
repayment_entry.submit()
|
||||||
|
|
||||||
|
amounts = frappe.db.get_value('Loan Interest Accrual', {'loan': loan.name}, ['paid_interest_amount',
|
||||||
|
'paid_principal_amount'])
|
||||||
|
|
||||||
|
loan.load_from_db()
|
||||||
|
self.assertEquals(loan.status, "Loan Closure Requested")
|
||||||
|
|
||||||
|
unpledge_request = create_loan_security_unpledge(loan.name, loan.applicant_type, loan.applicant, loan.company, as_dict=0)
|
||||||
|
unpledge_request.submit()
|
||||||
|
unpledge_request.status = 'Approved'
|
||||||
|
unpledge_request.save()
|
||||||
|
|
||||||
|
loan_security_pledge.load_from_db()
|
||||||
|
loan.load_from_db()
|
||||||
|
|
||||||
|
self.assertEqual(loan.status, 'Closed')
|
||||||
|
for security in loan_security_pledge.securities:
|
||||||
|
self.assertEquals(security.qty, 0)
|
||||||
|
|
||||||
|
|
||||||
def create_loan_accounts():
|
def create_loan_accounts():
|
||||||
if not frappe.db.exists("Account", "Loans and Advances (Assets) - _TC"):
|
if not frappe.db.exists("Account", "Loans and Advances (Assets) - _TC"):
|
||||||
|
@ -78,7 +78,10 @@ class LoanRepayment(AccountsController):
|
|||||||
(flt(payment.paid_principal_amount), flt(payment.paid_interest_amount), payment.loan_interest_accrual))
|
(flt(payment.paid_principal_amount), flt(payment.paid_interest_amount), payment.loan_interest_accrual))
|
||||||
|
|
||||||
if flt(loan.total_principal_paid + self.principal_amount_paid, 2) >= flt(loan.total_payment, 2):
|
if flt(loan.total_principal_paid + self.principal_amount_paid, 2) >= flt(loan.total_payment, 2):
|
||||||
frappe.db.set_value("Loan", self.against_loan, "status", "Loan Closure Requested")
|
if loan.is_secured_loan:
|
||||||
|
frappe.db.set_value("Loan", self.against_loan, "status", "Loan Closure Requested")
|
||||||
|
else:
|
||||||
|
frappe.db.set_value("Loan", self.against_loan, "status", "Closed")
|
||||||
|
|
||||||
frappe.db.sql(""" UPDATE `tabLoan` SET total_amount_paid = %s, total_principal_paid = %s
|
frappe.db.sql(""" UPDATE `tabLoan` SET total_amount_paid = %s, total_principal_paid = %s
|
||||||
WHERE name = %s """, (loan.total_amount_paid + self.amount_paid,
|
WHERE name = %s """, (loan.total_amount_paid + self.amount_paid,
|
||||||
|
@ -13,6 +13,7 @@ from erpnext.loan_management.doctype.loan_security_price.loan_security_price imp
|
|||||||
class LoanSecurityPledge(Document):
|
class LoanSecurityPledge(Document):
|
||||||
def validate(self):
|
def validate(self):
|
||||||
self.set_pledge_amount()
|
self.set_pledge_amount()
|
||||||
|
self.validate_duplicate_securities()
|
||||||
|
|
||||||
def on_submit(self):
|
def on_submit(self):
|
||||||
if self.loan:
|
if self.loan:
|
||||||
@ -21,6 +22,15 @@ class LoanSecurityPledge(Document):
|
|||||||
update_shortfall_status(self.loan, self.total_security_value)
|
update_shortfall_status(self.loan, self.total_security_value)
|
||||||
update_loan(self.loan, self.maximum_loan_value)
|
update_loan(self.loan, self.maximum_loan_value)
|
||||||
|
|
||||||
|
def validate_duplicate_securities(self):
|
||||||
|
security_list = []
|
||||||
|
for security in self.securities:
|
||||||
|
if security.loan_security not in security_list:
|
||||||
|
security_list.append(security.loan_security)
|
||||||
|
else:
|
||||||
|
frappe.throw(_('Loan Security {0} added multiple times').format(frappe.bold(
|
||||||
|
security.loan_security)))
|
||||||
|
|
||||||
def set_pledge_amount(self):
|
def set_pledge_amount(self):
|
||||||
total_security_value = 0
|
total_security_value = 0
|
||||||
maximum_loan_value = 0
|
maximum_loan_value = 0
|
||||||
|
@ -53,7 +53,7 @@ def check_for_ltv_shortfall(process_loan_security_shortfall):
|
|||||||
|
|
||||||
loans = frappe.db.sql(""" SELECT l.name, l.loan_amount, l.total_principal_paid, lp.loan_security, lp.haircut, lp.qty, lp.loan_security_type
|
loans = frappe.db.sql(""" SELECT l.name, l.loan_amount, l.total_principal_paid, lp.loan_security, lp.haircut, lp.qty, lp.loan_security_type
|
||||||
FROM `tabLoan` l, `tabPledge` lp , `tabLoan Security Pledge`p WHERE lp.parent = p.name and p.loan = l.name and l.docstatus = 1
|
FROM `tabLoan` l, `tabPledge` lp , `tabLoan Security Pledge`p WHERE lp.parent = p.name and p.loan = l.name and l.docstatus = 1
|
||||||
and l.is_secured_loan and l.status = 'Disbursed' and p.status in ('Pledged', 'Partially Unpledged')""", as_dict=1)
|
and l.is_secured_loan and l.status = 'Disbursed' and p.status = 'Pledged'""", as_dict=1)
|
||||||
|
|
||||||
loan_security_map = {}
|
loan_security_map = {}
|
||||||
|
|
||||||
|
@ -1,4 +1,5 @@
|
|||||||
{
|
{
|
||||||
|
"actions": [],
|
||||||
"autoname": "LSU-.{applicant}.-.#####",
|
"autoname": "LSU-.{applicant}.-.#####",
|
||||||
"creation": "2019-09-21 13:23:16.117028",
|
"creation": "2019-09-21 13:23:16.117028",
|
||||||
"doctype": "DocType",
|
"doctype": "DocType",
|
||||||
@ -15,7 +16,6 @@
|
|||||||
"status",
|
"status",
|
||||||
"loan_security_details_section",
|
"loan_security_details_section",
|
||||||
"securities",
|
"securities",
|
||||||
"unpledge_type",
|
|
||||||
"amended_from"
|
"amended_from"
|
||||||
],
|
],
|
||||||
"fields": [
|
"fields": [
|
||||||
@ -47,6 +47,7 @@
|
|||||||
{
|
{
|
||||||
"allow_on_submit": 1,
|
"allow_on_submit": 1,
|
||||||
"default": "Requested",
|
"default": "Requested",
|
||||||
|
"depends_on": "eval:doc.docstatus == 1",
|
||||||
"fieldname": "status",
|
"fieldname": "status",
|
||||||
"fieldtype": "Select",
|
"fieldtype": "Select",
|
||||||
"label": "Status",
|
"label": "Status",
|
||||||
@ -80,13 +81,6 @@
|
|||||||
"options": "Unpledge",
|
"options": "Unpledge",
|
||||||
"reqd": 1
|
"reqd": 1
|
||||||
},
|
},
|
||||||
{
|
|
||||||
"fieldname": "unpledge_type",
|
|
||||||
"fieldtype": "Data",
|
|
||||||
"hidden": 1,
|
|
||||||
"label": "Unpledge Type",
|
|
||||||
"read_only": 1
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
"fieldname": "company",
|
"fieldname": "company",
|
||||||
"fieldtype": "Link",
|
"fieldtype": "Link",
|
||||||
@ -104,7 +98,8 @@
|
|||||||
}
|
}
|
||||||
],
|
],
|
||||||
"is_submittable": 1,
|
"is_submittable": 1,
|
||||||
"modified": "2019-10-28 07:41:47.084882",
|
"links": [],
|
||||||
|
"modified": "2020-05-05 07:23:18.440058",
|
||||||
"modified_by": "Administrator",
|
"modified_by": "Administrator",
|
||||||
"module": "Loan Management",
|
"module": "Loan Management",
|
||||||
"name": "Loan Security Unpledge",
|
"name": "Loan Security Unpledge",
|
||||||
|
@ -13,31 +13,43 @@ from erpnext.loan_management.doctype.loan_security_price.loan_security_price imp
|
|||||||
class LoanSecurityUnpledge(Document):
|
class LoanSecurityUnpledge(Document):
|
||||||
def validate(self):
|
def validate(self):
|
||||||
self.validate_pledges()
|
self.validate_pledges()
|
||||||
|
self.validate_duplicate_securities()
|
||||||
|
|
||||||
|
def on_cancel(self):
|
||||||
|
self.update_loan_security_pledge(cancel=1)
|
||||||
|
self.update_loan_status(cancel=1)
|
||||||
|
self.db_set('status', 'Requested')
|
||||||
|
|
||||||
|
def validate_duplicate_securities(self):
|
||||||
|
security_list = []
|
||||||
|
for d in self.securities:
|
||||||
|
security = [d.loan_security, d.against_pledge]
|
||||||
|
if security not in security_list:
|
||||||
|
security_list.append(security)
|
||||||
|
else:
|
||||||
|
frappe.throw(_("Row {0}: Loan Security {1} against Loan Security Pledge {2} added multiple times").format(
|
||||||
|
d.idx, frappe.bold(d.loan_security), frappe.bold(d.against_pledge)))
|
||||||
|
|
||||||
def validate_pledges(self):
|
def validate_pledges(self):
|
||||||
pledge_details = self.get_pledge_details()
|
pledge_qty_map = self.get_pledge_details()
|
||||||
|
|
||||||
loan = frappe.get_doc("Loan", self.loan)
|
loan = frappe.get_doc("Loan", self.loan)
|
||||||
|
|
||||||
pledge_qty_map = {}
|
|
||||||
remaining_qty = 0
|
remaining_qty = 0
|
||||||
unpledge_value = 0
|
unpledge_value = 0
|
||||||
|
|
||||||
for pledge in pledge_details:
|
|
||||||
pledge_qty_map.setdefault((pledge.parent, pledge.loan_security), pledge.qty)
|
|
||||||
|
|
||||||
for security in self.securities:
|
for security in self.securities:
|
||||||
pledged_qty = pledge_qty_map.get((security.against_pledge, security.loan_security), 0)
|
pledged_qty = pledge_qty_map.get((security.against_pledge, security.loan_security), 0)
|
||||||
if not pledged_qty:
|
if not pledged_qty:
|
||||||
frappe.throw(_("Zero qty of {0} pledged against loan {0}").format(frappe.bold(security.loan_security),
|
frappe.throw(_("Zero qty of {0} pledged against loan {1}").format(frappe.bold(security.loan_security),
|
||||||
frappe.bold(self.loan)))
|
frappe.bold(self.loan)))
|
||||||
|
|
||||||
unpledge_qty = pledged_qty - security.qty
|
unpledge_qty = pledged_qty - security.qty
|
||||||
security_price = security.qty * get_loan_security_price(security.loan_security)
|
security_price = security.qty * get_loan_security_price(security.loan_security)
|
||||||
|
|
||||||
if unpledge_qty < 0:
|
if unpledge_qty < 0:
|
||||||
frappe.throw(_("Cannot unpledge more than {0} qty of {0}").format(frappe.bold(pledged_qty),
|
frappe.throw(_("""Row {0}: Cannot unpledge more than {1} qty of {2} against
|
||||||
frappe.bold(security.loan_security)))
|
Loan Security Pledge {3}""").format(security.idx, frappe.bold(pledged_qty),
|
||||||
|
frappe.bold(security.loan_security), frappe.bold(security.against_pledge)))
|
||||||
|
|
||||||
remaining_qty += unpledge_qty
|
remaining_qty += unpledge_qty
|
||||||
unpledge_value += security_price - flt(security_price * security.haircut/100)
|
unpledge_value += security_price - flt(security_price * security.haircut/100)
|
||||||
@ -45,41 +57,57 @@ class LoanSecurityUnpledge(Document):
|
|||||||
if unpledge_value > loan.total_principal_paid:
|
if unpledge_value > loan.total_principal_paid:
|
||||||
frappe.throw(_("Cannot Unpledge, loan security value is greater than the repaid amount"))
|
frappe.throw(_("Cannot Unpledge, loan security value is greater than the repaid amount"))
|
||||||
|
|
||||||
if not remaining_qty:
|
|
||||||
self.db_set('unpledge_type', 'Unpledged')
|
|
||||||
else:
|
|
||||||
self.db_set('unpledge_type', 'Partially Pledged')
|
|
||||||
|
|
||||||
|
|
||||||
def get_pledge_details(self):
|
def get_pledge_details(self):
|
||||||
|
pledge_qty_map = {}
|
||||||
|
|
||||||
pledge_details = frappe.db.sql("""
|
pledge_details = frappe.db.sql("""
|
||||||
SELECT p.parent, p.loan_security, p.qty as qty FROM
|
SELECT p.parent, p.loan_security, p.qty FROM
|
||||||
`tabLoan Security Pledge` lsp,
|
`tabLoan Security Pledge` lsp,
|
||||||
`tabPledge` p
|
`tabPledge` p
|
||||||
WHERE
|
WHERE
|
||||||
p.parent = lsp.name
|
p.parent = lsp.name
|
||||||
AND lsp.loan = %s
|
AND lsp.loan = %s
|
||||||
AND lsp.docstatus = 1
|
AND lsp.docstatus = 1
|
||||||
AND lsp.status = "Pledged"
|
AND lsp.status in ('Pledged', 'Partially Pledged')
|
||||||
""",(self.loan), as_dict=1)
|
""", (self.loan), as_dict=1)
|
||||||
|
|
||||||
return pledge_details
|
for pledge in pledge_details:
|
||||||
|
pledge_qty_map.setdefault((pledge.parent, pledge.loan_security), pledge.qty)
|
||||||
|
|
||||||
|
return pledge_qty_map
|
||||||
|
|
||||||
def on_update_after_submit(self):
|
def on_update_after_submit(self):
|
||||||
if self.status == "Approved":
|
if self.status == "Approved":
|
||||||
frappe.db.sql("""
|
self.update_loan_security_pledge()
|
||||||
UPDATE
|
self.update_loan_status()
|
||||||
`tabPledge` p, `tabUnpledge` u, `tabLoan Security Pledge` lsp,
|
|
||||||
`tabLoan Security Unpledge` lsu SET p.qty = (p.qty - u.qty)
|
|
||||||
WHERE
|
|
||||||
lsp.loan = %s
|
|
||||||
AND lsu.status = 'Requested'
|
|
||||||
AND u.parent = %s
|
|
||||||
AND p.parent = u.against_pledge
|
|
||||||
AND p.loan_security = u.loan_security""",(self.loan, self.name))
|
|
||||||
|
|
||||||
frappe.db.sql("""UPDATE `tabLoan Security Pledge`
|
def update_loan_security_pledge(self, cancel=0):
|
||||||
SET status = %s WHERE loan = %s""", (self.unpledge_type, self.loan))
|
if cancel:
|
||||||
|
new_qty = 'p.qty + u.qty'
|
||||||
|
else:
|
||||||
|
new_qty = 'p.qty - u.qty'
|
||||||
|
|
||||||
|
frappe.db.sql("""
|
||||||
|
UPDATE
|
||||||
|
`tabPledge` p, `tabUnpledge` u, `tabLoan Security Pledge` lsp, `tabLoan Security Unpledge` lsu
|
||||||
|
SET p.qty = {new_qty}
|
||||||
|
WHERE
|
||||||
|
lsp.loan = %s
|
||||||
|
AND p.parent = u.against_pledge
|
||||||
|
AND p.parent = lsp.name
|
||||||
|
AND lsp.docstatus = 1
|
||||||
|
AND p.loan_security = u.loan_security""".format(new_qty=new_qty),(self.loan))
|
||||||
|
|
||||||
|
def update_loan_status(self, cancel=0):
|
||||||
|
if cancel:
|
||||||
|
loan_status = frappe.get_value('Loan', self.loan, 'status')
|
||||||
|
if loan_status == 'Closed':
|
||||||
|
frappe.db.set_value('Loan', self.loan, 'status', 'Loan Closure Requested')
|
||||||
|
else:
|
||||||
|
pledge_qty = frappe.db.sql("""SELECT SUM(c.qty)
|
||||||
|
FROM `tabLoan Security Pledge` p, `tabPledge` c
|
||||||
|
WHERE p.loan = %s AND c.parent = p.name""", (self.loan))[0][0]
|
||||||
|
|
||||||
|
if not pledge_qty:
|
||||||
|
frappe.db.set_value('Loan', self.loan, 'status', 'Closed')
|
||||||
|
|
||||||
if self.unpledge_type == 'Unpledged':
|
|
||||||
frappe.db.set_value("Loan", self.loan, 'status', 'Closed')
|
|
||||||
|
@ -662,6 +662,7 @@ erpnext.patches.v12_0.create_irs_1099_field_united_states
|
|||||||
erpnext.patches.v12_0.move_bank_account_swift_number_to_bank
|
erpnext.patches.v12_0.move_bank_account_swift_number_to_bank
|
||||||
erpnext.patches.v12_0.rename_bank_reconciliation
|
erpnext.patches.v12_0.rename_bank_reconciliation
|
||||||
erpnext.patches.v12_0.rename_bank_reconciliation_fields # 2020-01-22
|
erpnext.patches.v12_0.rename_bank_reconciliation_fields # 2020-01-22
|
||||||
|
erpnext.patches.v12_0.set_purchase_receipt_delivery_note_detail
|
||||||
erpnext.patches.v12_0.add_permission_in_lower_deduction
|
erpnext.patches.v12_0.add_permission_in_lower_deduction
|
||||||
erpnext.patches.v12_0.set_received_qty_in_material_request_as_per_stock_uom
|
erpnext.patches.v12_0.set_received_qty_in_material_request_as_per_stock_uom
|
||||||
erpnext.patches.v12_0.rename_account_type_doctype
|
erpnext.patches.v12_0.rename_account_type_doctype
|
||||||
@ -677,3 +678,4 @@ erpnext.patches.v13_0.move_tax_slabs_from_payroll_period_to_income_tax_slab #123
|
|||||||
erpnext.patches.v12_0.fix_quotation_expired_status
|
erpnext.patches.v12_0.fix_quotation_expired_status
|
||||||
erpnext.patches.v12_0.update_appointment_reminder_scheduler_entry
|
erpnext.patches.v12_0.update_appointment_reminder_scheduler_entry
|
||||||
erpnext.patches.v12_0.retain_permission_rules_for_video_doctype
|
erpnext.patches.v12_0.retain_permission_rules_for_video_doctype
|
||||||
|
erpnext.patches.v13_0.patch_to_fix_reverse_linking_in_additional_salary_encashment_and_incentive
|
||||||
|
@ -7,4 +7,5 @@ def execute():
|
|||||||
return
|
return
|
||||||
|
|
||||||
frappe.reload_doc("regional", "doctype", "lower_deduction_certificate")
|
frappe.reload_doc("regional", "doctype", "lower_deduction_certificate")
|
||||||
add_permissions()
|
frappe.reload_doc("regional", "doctype", "gstr_3b_report")
|
||||||
|
add_permissions()
|
||||||
|
@ -6,4 +6,5 @@ from erpnext.setup.setup_wizard.operations.install_fixtures import add_dashboard
|
|||||||
|
|
||||||
def execute():
|
def execute():
|
||||||
frappe.reload_doc("desk", "doctype", "number_card_link")
|
frappe.reload_doc("desk", "doctype", "number_card_link")
|
||||||
|
frappe.reload_doc("healthcare", "doctype", "patient_appointment")
|
||||||
add_dashboards()
|
add_dashboards()
|
||||||
|
@ -0,0 +1,84 @@
|
|||||||
|
from __future__ import unicode_literals
|
||||||
|
import frappe
|
||||||
|
from collections import defaultdict
|
||||||
|
|
||||||
|
def execute():
|
||||||
|
def map_rows(doc_row, return_doc_row, detail_field, doctype):
|
||||||
|
"""Map rows after identifying similar ones."""
|
||||||
|
|
||||||
|
frappe.db.sql(""" UPDATE `tab{doctype} Item` set {detail_field} = '{doc_row_name}'
|
||||||
|
where name = '{return_doc_row_name}'""" \
|
||||||
|
.format(doctype=doctype,
|
||||||
|
detail_field=detail_field,
|
||||||
|
doc_row_name=doc_row.get('name'),
|
||||||
|
return_doc_row_name=return_doc_row.get('name'))) #nosec
|
||||||
|
|
||||||
|
def row_is_mappable(doc_row, return_doc_row, detail_field):
|
||||||
|
"""Checks if two rows are similar enough to be mapped."""
|
||||||
|
|
||||||
|
if doc_row.item_code == return_doc_row.item_code and not return_doc_row.get(detail_field):
|
||||||
|
if doc_row.get('batch_no') and return_doc_row.get('batch_no') and doc_row.batch_no == return_doc_row.batch_no:
|
||||||
|
return True
|
||||||
|
|
||||||
|
elif doc_row.get('serial_no') and return_doc_row.get('serial_no'):
|
||||||
|
doc_sn = doc_row.serial_no.split('\n')
|
||||||
|
return_doc_sn = return_doc_row.serial_no.split('\n')
|
||||||
|
|
||||||
|
if set(doc_sn) & set(return_doc_sn):
|
||||||
|
# if two rows have serial nos in common, map them
|
||||||
|
return True
|
||||||
|
|
||||||
|
elif doc_row.rate == return_doc_row.rate:
|
||||||
|
return True
|
||||||
|
else:
|
||||||
|
return False
|
||||||
|
|
||||||
|
def make_return_document_map(doctype, return_document_map):
|
||||||
|
"""Returns a map of documents and it's return documents.
|
||||||
|
Format => { 'document' : ['return_document_1','return_document_2'] }"""
|
||||||
|
|
||||||
|
return_against_documents = frappe.db.sql("""
|
||||||
|
SELECT
|
||||||
|
return_against as document, name as return_document
|
||||||
|
FROM `tab{doctype}`
|
||||||
|
WHERE
|
||||||
|
is_return = 1 and docstatus = 1""".format(doctype=doctype),as_dict=1) #nosec
|
||||||
|
|
||||||
|
for entry in return_against_documents:
|
||||||
|
return_document_map[entry.document].append(entry.return_document)
|
||||||
|
|
||||||
|
return return_document_map
|
||||||
|
|
||||||
|
def set_document_detail_in_return_document(doctype):
|
||||||
|
"""Map each row of the original document in the return document."""
|
||||||
|
mapped = []
|
||||||
|
return_document_map = defaultdict(list)
|
||||||
|
detail_field = "purchase_receipt_item" if doctype=="Purchase Receipt" else "dn_detail"
|
||||||
|
|
||||||
|
child_doc = frappe.scrub("{0} Item".format(doctype))
|
||||||
|
frappe.reload_doc("stock", "doctype", child_doc)
|
||||||
|
|
||||||
|
return_document_map = make_return_document_map(doctype, return_document_map)
|
||||||
|
|
||||||
|
#iterate through original documents and its return documents
|
||||||
|
for docname in return_document_map:
|
||||||
|
doc_items = frappe.get_doc(doctype, docname).get("items")
|
||||||
|
for return_doc in return_document_map[docname]:
|
||||||
|
return_doc_items = frappe.get_doc(doctype, return_doc).get("items")
|
||||||
|
|
||||||
|
#iterate through return document items and original document items for mapping
|
||||||
|
for return_item in return_doc_items:
|
||||||
|
for doc_item in doc_items:
|
||||||
|
if row_is_mappable(doc_item, return_item, detail_field) and doc_item.get('name') not in mapped:
|
||||||
|
map_rows(doc_item, return_item, detail_field, doctype)
|
||||||
|
mapped.append(doc_item.get('name'))
|
||||||
|
break
|
||||||
|
else:
|
||||||
|
continue
|
||||||
|
|
||||||
|
set_document_detail_in_return_document("Purchase Receipt")
|
||||||
|
set_document_detail_in_return_document("Delivery Note")
|
||||||
|
frappe.db.commit()
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -0,0 +1,52 @@
|
|||||||
|
from __future__ import unicode_literals
|
||||||
|
|
||||||
|
import frappe
|
||||||
|
|
||||||
|
def execute():
|
||||||
|
if not frappe.db.table_exists("Additional Salary"):
|
||||||
|
return
|
||||||
|
|
||||||
|
for doctype in ("Additional Salary", "Leave Encashment", "Employee Incentive", "Salary Detail"):
|
||||||
|
frappe.reload_doc("hr", "doctype", doctype)
|
||||||
|
|
||||||
|
additional_salaries = frappe.get_all("Additional Salary",
|
||||||
|
fields = ['name', "salary_slip", "type", "salary_component"],
|
||||||
|
filters = {'salary_slip': ['!=', '']},
|
||||||
|
group_by = 'salary_slip'
|
||||||
|
)
|
||||||
|
leave_encashments = frappe.get_all("Leave Encashment",
|
||||||
|
fields = ["name","additional_salary"],
|
||||||
|
filters = {'additional_salary': ['!=', '']}
|
||||||
|
)
|
||||||
|
employee_incentives = frappe.get_all("Employee Incentive",
|
||||||
|
fields= ["name", "additional_salary"],
|
||||||
|
filters = {'additional_salary': ['!=', '']}
|
||||||
|
)
|
||||||
|
|
||||||
|
for incentive in employee_incentives:
|
||||||
|
frappe.db.sql(""" UPDATE `tabAdditional Salary`
|
||||||
|
SET ref_doctype = 'Employee Incentive', ref_docname = %s
|
||||||
|
WHERE name = %s
|
||||||
|
""", (incentive['name'], incentive['additional_salary']))
|
||||||
|
|
||||||
|
|
||||||
|
for leave_encashment in leave_encashments:
|
||||||
|
frappe.db.sql(""" UPDATE `tabAdditional Salary`
|
||||||
|
SET ref_doctype = 'Leave Encashment', ref_docname = %s
|
||||||
|
WHERE name = %s
|
||||||
|
""", (leave_encashment['name'], leave_encashment['additional_salary']))
|
||||||
|
|
||||||
|
salary_slips = [sal["salary_slip"] for sal in additional_salaries]
|
||||||
|
|
||||||
|
for salary in additional_salaries:
|
||||||
|
comp_type = "earnings" if salary['type'] == 'Earning' else 'deductions'
|
||||||
|
if salary["salary_slip"] and salary_slips.count(salary["salary_slip"]) == 1:
|
||||||
|
frappe.db.sql("""
|
||||||
|
UPDATE `tabSalary Detail`
|
||||||
|
SET additional_salary = %s
|
||||||
|
WHERE parenttype = 'Salary Slip'
|
||||||
|
and parentfield = %s
|
||||||
|
and parent = %s
|
||||||
|
and salary_component = %s
|
||||||
|
""", (salary["name"], comp_type, salary["salary_slip"], salary["salary_component"]))
|
||||||
|
|
@ -29,7 +29,8 @@ def get_default_dashboards():
|
|||||||
{ "chart": "Incoming Bills (Purchase Invoice)" },
|
{ "chart": "Incoming Bills (Purchase Invoice)" },
|
||||||
{ "chart": "Bank Balance" },
|
{ "chart": "Bank Balance" },
|
||||||
{ "chart": "Income" },
|
{ "chart": "Income" },
|
||||||
{ "chart": "Expenses" }
|
{ "chart": "Expenses" },
|
||||||
|
{ "chart": "Patient Appointments" }
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@ -126,6 +127,21 @@ def get_default_dashboards():
|
|||||||
'type': 'Bar',
|
'type': 'Bar',
|
||||||
'custom_options': '{"type": "bar", "colors": ["#98d85b", "#fc4f51", "#7679fc"], "axisOptions": { "shortenYAxisNumbers": 1}, "barOptions": { "stacked": 1 }}',
|
'custom_options': '{"type": "bar", "colors": ["#98d85b", "#fc4f51", "#7679fc"], "axisOptions": { "shortenYAxisNumbers": 1}, "barOptions": { "stacked": 1 }}',
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"doctype": "Dashboard Chart",
|
||||||
|
"time_interval": "Daily",
|
||||||
|
"chart_name": "Patient Appointments",
|
||||||
|
"timespan": "Last Month",
|
||||||
|
"color": "#77ecca",
|
||||||
|
"filters_json": json.dumps({}),
|
||||||
|
"chart_type": "Count",
|
||||||
|
"timeseries": 1,
|
||||||
|
"based_on": "appointment_datetime",
|
||||||
|
"owner": "Administrator",
|
||||||
|
"document_type": "Patient Appointment",
|
||||||
|
"type": "Line",
|
||||||
|
"width": "Half"
|
||||||
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -388,13 +388,12 @@ def get_invoiced_qty_map(delivery_note):
|
|||||||
|
|
||||||
def get_returned_qty_map(delivery_note):
|
def get_returned_qty_map(delivery_note):
|
||||||
"""returns a map: {so_detail: returned_qty}"""
|
"""returns a map: {so_detail: returned_qty}"""
|
||||||
returned_qty_map = frappe._dict(frappe.db.sql("""select dn_item.item_code, sum(abs(dn_item.qty)) as qty
|
returned_qty_map = frappe._dict(frappe.db.sql("""select dn_item.dn_detail, abs(dn_item.qty) as qty
|
||||||
from `tabDelivery Note Item` dn_item, `tabDelivery Note` dn
|
from `tabDelivery Note Item` dn_item, `tabDelivery Note` dn
|
||||||
where dn.name = dn_item.parent
|
where dn.name = dn_item.parent
|
||||||
and dn.docstatus = 1
|
and dn.docstatus = 1
|
||||||
and dn.is_return = 1
|
and dn.is_return = 1
|
||||||
and dn.return_against = %s
|
and dn.return_against = %s
|
||||||
group by dn_item.item_code
|
|
||||||
""", delivery_note))
|
""", delivery_note))
|
||||||
|
|
||||||
return returned_qty_map
|
return returned_qty_map
|
||||||
@ -413,7 +412,7 @@ def make_sales_invoice(source_name, target_doc=None):
|
|||||||
target.run_method("set_po_nos")
|
target.run_method("set_po_nos")
|
||||||
|
|
||||||
if len(target.get("items")) == 0:
|
if len(target.get("items")) == 0:
|
||||||
frappe.throw(_("All these items have already been invoiced"))
|
frappe.throw(_("All these items have already been Invoiced/Returned"))
|
||||||
|
|
||||||
target.run_method("calculate_taxes_and_totals")
|
target.run_method("calculate_taxes_and_totals")
|
||||||
|
|
||||||
@ -438,9 +437,9 @@ def make_sales_invoice(source_name, target_doc=None):
|
|||||||
pending_qty = item_row.qty - invoiced_qty_map.get(item_row.name, 0)
|
pending_qty = item_row.qty - invoiced_qty_map.get(item_row.name, 0)
|
||||||
|
|
||||||
returned_qty = 0
|
returned_qty = 0
|
||||||
if returned_qty_map.get(item_row.item_code, 0) > 0:
|
if returned_qty_map.get(item_row.name, 0) > 0:
|
||||||
returned_qty = flt(returned_qty_map.get(item_row.item_code, 0))
|
returned_qty = flt(returned_qty_map.get(item_row.name, 0))
|
||||||
returned_qty_map[item_row.item_code] -= pending_qty
|
returned_qty_map[item_row.name] -= pending_qty
|
||||||
|
|
||||||
if returned_qty:
|
if returned_qty:
|
||||||
if returned_qty >= pending_qty:
|
if returned_qty >= pending_qty:
|
||||||
|
@ -612,6 +612,7 @@ class TestDeliveryNote(unittest.TestCase):
|
|||||||
dn1 = create_delivery_note(is_return=1, return_against=dn.name, qty=-1, do_not_submit=True)
|
dn1 = create_delivery_note(is_return=1, return_against=dn.name, qty=-1, do_not_submit=True)
|
||||||
dn1.items[0].against_sales_order = so.name
|
dn1.items[0].against_sales_order = so.name
|
||||||
dn1.items[0].so_detail = so.items[0].name
|
dn1.items[0].so_detail = so.items[0].name
|
||||||
|
dn1.items[0].dn_detail = dn.items[0].name
|
||||||
dn1.submit()
|
dn1.submit()
|
||||||
|
|
||||||
si = make_sales_invoice(dn.name)
|
si = make_sales_invoice(dn.name)
|
||||||
@ -638,7 +639,9 @@ class TestDeliveryNote(unittest.TestCase):
|
|||||||
si1.save()
|
si1.save()
|
||||||
si1.submit()
|
si1.submit()
|
||||||
|
|
||||||
create_delivery_note(is_return=1, return_against=dn.name, qty=-2)
|
dn1 = create_delivery_note(is_return=1, return_against=dn.name, qty=-2, do_not_submit=True)
|
||||||
|
dn1.items[0].dn_detail = dn.items[0].name
|
||||||
|
dn1.submit()
|
||||||
|
|
||||||
si2 = make_sales_invoice(dn.name)
|
si2 = make_sales_invoice(dn.name)
|
||||||
self.assertEquals(si2.items[0].qty, 2)
|
self.assertEquals(si2.items[0].qty, 2)
|
||||||
|
@ -67,6 +67,7 @@
|
|||||||
"so_detail",
|
"so_detail",
|
||||||
"against_sales_invoice",
|
"against_sales_invoice",
|
||||||
"si_detail",
|
"si_detail",
|
||||||
|
"dn_detail",
|
||||||
"section_break_40",
|
"section_break_40",
|
||||||
"batch_no",
|
"batch_no",
|
||||||
"serial_no",
|
"serial_no",
|
||||||
@ -699,6 +700,15 @@
|
|||||||
{
|
{
|
||||||
"fieldname": "dimension_col_break",
|
"fieldname": "dimension_col_break",
|
||||||
"fieldtype": "Column Break"
|
"fieldtype": "Column Break"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "dn_detail",
|
||||||
|
"fieldtype": "Data",
|
||||||
|
"hidden": 1,
|
||||||
|
"label": "Against Delivery Note Item",
|
||||||
|
"no_copy": 1,
|
||||||
|
"print_hide": 1,
|
||||||
|
"read_only": 1
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"idx": 1,
|
"idx": 1,
|
||||||
|
@ -504,7 +504,7 @@ def make_purchase_invoice(source_name, target_doc=None):
|
|||||||
|
|
||||||
def set_missing_values(source, target):
|
def set_missing_values(source, target):
|
||||||
if len(target.get("items")) == 0:
|
if len(target.get("items")) == 0:
|
||||||
frappe.throw(_("All items have already been invoiced"))
|
frappe.throw(_("All items have already been Invoiced/Returned"))
|
||||||
|
|
||||||
doc = frappe.get_doc(target)
|
doc = frappe.get_doc(target)
|
||||||
doc.ignore_pricing_rule = 1
|
doc.ignore_pricing_rule = 1
|
||||||
@ -514,11 +514,11 @@ def make_purchase_invoice(source_name, target_doc=None):
|
|||||||
|
|
||||||
def update_item(source_doc, target_doc, source_parent):
|
def update_item(source_doc, target_doc, source_parent):
|
||||||
target_doc.qty, returned_qty = get_pending_qty(source_doc)
|
target_doc.qty, returned_qty = get_pending_qty(source_doc)
|
||||||
returned_qty_map[source_doc.item_code] = returned_qty
|
returned_qty_map[source_doc.name] = returned_qty
|
||||||
|
|
||||||
def get_pending_qty(item_row):
|
def get_pending_qty(item_row):
|
||||||
pending_qty = item_row.qty - invoiced_qty_map.get(item_row.name, 0)
|
pending_qty = item_row.qty - invoiced_qty_map.get(item_row.name, 0)
|
||||||
returned_qty = flt(returned_qty_map.get(item_row.item_code, 0))
|
returned_qty = flt(returned_qty_map.get(item_row.name, 0))
|
||||||
if returned_qty:
|
if returned_qty:
|
||||||
if returned_qty >= pending_qty:
|
if returned_qty >= pending_qty:
|
||||||
pending_qty = 0
|
pending_qty = 0
|
||||||
@ -576,13 +576,12 @@ def get_invoiced_qty_map(purchase_receipt):
|
|||||||
|
|
||||||
def get_returned_qty_map(purchase_receipt):
|
def get_returned_qty_map(purchase_receipt):
|
||||||
"""returns a map: {so_detail: returned_qty}"""
|
"""returns a map: {so_detail: returned_qty}"""
|
||||||
returned_qty_map = frappe._dict(frappe.db.sql("""select pr_item.item_code, sum(abs(pr_item.qty)) as qty
|
returned_qty_map = frappe._dict(frappe.db.sql("""select pr_item.purchase_receipt_item, abs(pr_item.qty) as qty
|
||||||
from `tabPurchase Receipt Item` pr_item, `tabPurchase Receipt` pr
|
from `tabPurchase Receipt Item` pr_item, `tabPurchase Receipt` pr
|
||||||
where pr.name = pr_item.parent
|
where pr.name = pr_item.parent
|
||||||
and pr.docstatus = 1
|
and pr.docstatus = 1
|
||||||
and pr.is_return = 1
|
and pr.is_return = 1
|
||||||
and pr.return_against = %s
|
and pr.return_against = %s
|
||||||
group by pr_item.item_code
|
|
||||||
""", purchase_receipt))
|
""", purchase_receipt))
|
||||||
|
|
||||||
return returned_qty_map
|
return returned_qty_map
|
||||||
|
@ -475,6 +475,7 @@ class TestPurchaseReceipt(unittest.TestCase):
|
|||||||
pr1 = make_purchase_receipt(is_return=1, return_against=pr.name, qty=-1, do_not_submit=True)
|
pr1 = make_purchase_receipt(is_return=1, return_against=pr.name, qty=-1, do_not_submit=True)
|
||||||
pr1.items[0].purchase_order = po.name
|
pr1.items[0].purchase_order = po.name
|
||||||
pr1.items[0].purchase_order_item = po.items[0].name
|
pr1.items[0].purchase_order_item = po.items[0].name
|
||||||
|
pr1.items[0].purchase_receipt_item = pr.items[0].name
|
||||||
pr1.submit()
|
pr1.submit()
|
||||||
|
|
||||||
pi = make_purchase_invoice(pr.name)
|
pi = make_purchase_invoice(pr.name)
|
||||||
@ -498,7 +499,9 @@ class TestPurchaseReceipt(unittest.TestCase):
|
|||||||
pi1.save()
|
pi1.save()
|
||||||
pi1.submit()
|
pi1.submit()
|
||||||
|
|
||||||
make_purchase_receipt(is_return=1, return_against=pr1.name, qty=-2)
|
pr2 = make_purchase_receipt(is_return=1, return_against=pr1.name, qty=-2, do_not_submit=True)
|
||||||
|
pr2.items[0].purchase_receipt_item = pr1.items[0].name
|
||||||
|
pr2.submit()
|
||||||
|
|
||||||
pi2 = make_purchase_invoice(pr1.name)
|
pi2 = make_purchase_invoice(pr1.name)
|
||||||
self.assertEquals(pi2.items[0].qty, 2)
|
self.assertEquals(pi2.items[0].qty, 2)
|
||||||
|
@ -71,6 +71,7 @@
|
|||||||
"quality_inspection",
|
"quality_inspection",
|
||||||
"purchase_order_item",
|
"purchase_order_item",
|
||||||
"material_request_item",
|
"material_request_item",
|
||||||
|
"purchase_receipt_item",
|
||||||
"section_break_45",
|
"section_break_45",
|
||||||
"allow_zero_valuation_rate",
|
"allow_zero_valuation_rate",
|
||||||
"bom",
|
"bom",
|
||||||
@ -820,6 +821,15 @@
|
|||||||
"label": "Supplier Warehouse",
|
"label": "Supplier Warehouse",
|
||||||
"options": "Warehouse"
|
"options": "Warehouse"
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "purchase_receipt_item",
|
||||||
|
"fieldtype": "Data",
|
||||||
|
"hidden": 1,
|
||||||
|
"label": "Purchase Receipt Item",
|
||||||
|
"no_copy": 1,
|
||||||
|
"print_hide": 1,
|
||||||
|
"read_only": 1
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"collapsible": 1,
|
"collapsible": 1,
|
||||||
"fieldname": "image_column",
|
"fieldname": "image_column",
|
||||||
@ -829,7 +839,7 @@
|
|||||||
"idx": 1,
|
"idx": 1,
|
||||||
"istable": 1,
|
"istable": 1,
|
||||||
"links": [],
|
"links": [],
|
||||||
"modified": "2020-04-10 19:01:21.154963",
|
"modified": "2020-04-28 19:01:21.154963",
|
||||||
"modified_by": "Administrator",
|
"modified_by": "Administrator",
|
||||||
"module": "Stock",
|
"module": "Stock",
|
||||||
"name": "Purchase Receipt Item",
|
"name": "Purchase Receipt Item",
|
||||||
|
@ -4,11 +4,13 @@ import frappe
|
|||||||
from frappe.contacts.address_and_contact import filter_dynamic_link_doctypes
|
from frappe.contacts.address_and_contact import filter_dynamic_link_doctypes
|
||||||
|
|
||||||
class TestSearch(unittest.TestCase):
|
class TestSearch(unittest.TestCase):
|
||||||
#Search for the word "cond", part of the word "conduire" (Lead) in french.
|
# Search for the word "cond", part of the word "conduire" (Lead) in french.
|
||||||
def test_contact_search_in_foreign_language(self):
|
def test_contact_search_in_foreign_language(self):
|
||||||
frappe.local.lang = 'fr'
|
frappe.local.lang = 'fr'
|
||||||
output = filter_dynamic_link_doctypes("DocType", "prospect", "name", 0, 20, {'fieldtype': 'HTML', 'fieldname': 'contact_html'})
|
output = filter_dynamic_link_doctypes("DocType", "cond", "name", 0, 20, {
|
||||||
|
'fieldtype': 'HTML',
|
||||||
|
'fieldname': 'contact_html'
|
||||||
|
})
|
||||||
result = [['found' for x in y if x=="Lead"] for y in output]
|
result = [['found' for x in y if x=="Lead"] for y in output]
|
||||||
self.assertTrue(['found'] in result)
|
self.assertTrue(['found'] in result)
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user