Merge branch 'develop' of https://github.com/frappe/erpnext into loan_penalty_new_fix
This commit is contained in:
commit
e7efbc1d33
@ -293,6 +293,11 @@ def validate_accounts(file_name):
|
||||
accounts_dict = {}
|
||||
for account in accounts:
|
||||
accounts_dict.setdefault(account["account_name"], account)
|
||||
if not hasattr(account, "parent_account"):
|
||||
msg = _("Please make sure the file you are using has 'Parent Account' column present in the header.")
|
||||
msg += "<br><br>"
|
||||
msg += _("Alternatively, you can download the template and fill your data in.")
|
||||
frappe.throw(msg, title=_("Parent Account Missing"))
|
||||
if account["parent_account"] and accounts_dict.get(account["parent_account"]):
|
||||
accounts_dict[account["parent_account"]]["is_group"] = 1
|
||||
|
||||
|
@ -127,7 +127,6 @@
|
||||
"write_off_cost_center",
|
||||
"advances_section",
|
||||
"allocate_advances_automatically",
|
||||
"adjust_advance_taxes",
|
||||
"get_advances",
|
||||
"advances",
|
||||
"payment_schedule_section",
|
||||
@ -1326,13 +1325,6 @@
|
||||
"label": "Project",
|
||||
"options": "Project"
|
||||
},
|
||||
{
|
||||
"default": "0",
|
||||
"description": "Taxes paid while advance payment will be adjusted against this invoice",
|
||||
"fieldname": "adjust_advance_taxes",
|
||||
"fieldtype": "Check",
|
||||
"label": "Adjust Advance Taxes"
|
||||
},
|
||||
{
|
||||
"depends_on": "eval:doc.is_internal_supplier",
|
||||
"description": "Unrealized Profit / Loss account for intra-company transfers",
|
||||
@ -1378,7 +1370,7 @@
|
||||
"idx": 204,
|
||||
"is_submittable": 1,
|
||||
"links": [],
|
||||
"modified": "2021-03-09 21:12:30.422084",
|
||||
"modified": "2021-03-30 21:45:58.334107",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Accounts",
|
||||
"name": "Purchase Invoice",
|
||||
|
@ -406,9 +406,10 @@ def check_if_advance_entry_modified(args):
|
||||
throw(_("""Payment Entry has been modified after you pulled it. Please pull it again."""))
|
||||
|
||||
def validate_allocated_amount(args):
|
||||
precision = args.get('precision') or frappe.db.get_single_value("System Settings", "currency_precision")
|
||||
if args.get("allocated_amount") < 0:
|
||||
throw(_("Allocated amount cannot be negative"))
|
||||
elif args.get("allocated_amount") > args.get("unadjusted_amount"):
|
||||
elif flt(args.get("allocated_amount"), precision) > flt(args.get("unadjusted_amount"), precision):
|
||||
throw(_("Allocated amount cannot be greater than unadjusted amount"))
|
||||
|
||||
def update_reference_in_journal_entry(d, jv_obj):
|
||||
|
@ -659,6 +659,7 @@ class AccountsController(TransactionBase):
|
||||
'dr_or_cr': dr_or_cr,
|
||||
'unadjusted_amount': flt(d.advance_amount),
|
||||
'allocated_amount': flt(d.allocated_amount),
|
||||
'precision': d.precision('advance_amount'),
|
||||
'exchange_rate': (self.conversion_rate
|
||||
if self.party_account_currency != self.company_currency else 1),
|
||||
'grand_total': (self.base_grand_total
|
||||
|
8
erpnext/crm/doctype/lead_source/lead_source.js
Normal file
8
erpnext/crm/doctype/lead_source/lead_source.js
Normal file
@ -0,0 +1,8 @@
|
||||
// Copyright (c) 2021, Frappe Technologies Pvt. Ltd. and contributors
|
||||
// For license information, please see license.txt
|
||||
|
||||
frappe.ui.form.on('Lead Source', {
|
||||
// refresh: function(frm) {
|
||||
|
||||
// }
|
||||
});
|
62
erpnext/crm/doctype/lead_source/lead_source.json
Normal file
62
erpnext/crm/doctype/lead_source/lead_source.json
Normal file
@ -0,0 +1,62 @@
|
||||
{
|
||||
"actions": [],
|
||||
"allow_rename": 1,
|
||||
"autoname": "field:source_name",
|
||||
"creation": "2016-09-16 01:47:47.382372",
|
||||
"doctype": "DocType",
|
||||
"editable_grid": 1,
|
||||
"engine": "InnoDB",
|
||||
"field_order": [
|
||||
"source_name",
|
||||
"details"
|
||||
],
|
||||
"fields": [
|
||||
{
|
||||
"fieldname": "source_name",
|
||||
"fieldtype": "Data",
|
||||
"in_list_view": 1,
|
||||
"label": "Source Name",
|
||||
"reqd": 1,
|
||||
"unique": 1
|
||||
},
|
||||
{
|
||||
"fieldname": "details",
|
||||
"fieldtype": "Text Editor",
|
||||
"label": "Details"
|
||||
}
|
||||
],
|
||||
"links": [],
|
||||
"modified": "2021-02-08 12:51:48.971517",
|
||||
"modified_by": "Administrator",
|
||||
"module": "CRM",
|
||||
"name": "Lead Source",
|
||||
"owner": "Administrator",
|
||||
"permissions": [
|
||||
{
|
||||
"create": 1,
|
||||
"delete": 1,
|
||||
"email": 1,
|
||||
"export": 1,
|
||||
"print": 1,
|
||||
"read": 1,
|
||||
"report": 1,
|
||||
"role": "Sales Manager",
|
||||
"share": 1,
|
||||
"write": 1
|
||||
},
|
||||
{
|
||||
"create": 1,
|
||||
"email": 1,
|
||||
"export": 1,
|
||||
"print": 1,
|
||||
"read": 1,
|
||||
"report": 1,
|
||||
"role": "Sales User",
|
||||
"share": 1,
|
||||
"write": 1
|
||||
}
|
||||
],
|
||||
"quick_entry": 1,
|
||||
"sort_field": "modified",
|
||||
"sort_order": "DESC"
|
||||
}
|
@ -1,9 +1,9 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and contributors
|
||||
# Copyright (c) 2021, Frappe Technologies Pvt. Ltd. and contributors
|
||||
# For license information, please see license.txt
|
||||
|
||||
from __future__ import unicode_literals
|
||||
import frappe
|
||||
# import frappe
|
||||
from frappe.model.document import Document
|
||||
|
||||
class LeadSource(Document):
|
@ -1,12 +1,10 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors
|
||||
# Copyright (c) 2021, Frappe Technologies Pvt. Ltd. and Contributors
|
||||
# See license.txt
|
||||
from __future__ import unicode_literals
|
||||
|
||||
import frappe
|
||||
# import frappe
|
||||
import unittest
|
||||
|
||||
# test_records = frappe.get_test_records('Lead Source')
|
||||
|
||||
class TestLeadSource(unittest.TestCase):
|
||||
pass
|
@ -260,7 +260,10 @@ doc_events = {
|
||||
"erpnext.regional.italy.utils.sales_invoice_on_cancel",
|
||||
"erpnext.erpnext_integrations.taxjar_integration.delete_transaction"
|
||||
],
|
||||
"on_trash": "erpnext.regional.check_deletion_permission"
|
||||
"on_trash": "erpnext.regional.check_deletion_permission",
|
||||
"validate": [
|
||||
"erpnext.regional.india.utils.validate_document_name"
|
||||
]
|
||||
},
|
||||
"Purchase Invoice": {
|
||||
"validate": [
|
||||
@ -282,9 +285,6 @@ doc_events = {
|
||||
('Sales Invoice', 'Sales Order', 'Delivery Note', 'Purchase Invoice', 'Purchase Order', 'Purchase Receipt'): {
|
||||
'validate': ['erpnext.regional.india.utils.set_place_of_supply']
|
||||
},
|
||||
('Sales Invoice', 'Purchase Invoice'): {
|
||||
'validate': ['erpnext.regional.india.utils.validate_document_name']
|
||||
},
|
||||
"Contact": {
|
||||
"on_trash": "erpnext.support.doctype.issue.issue.update_issue",
|
||||
"after_insert": "erpnext.telephony.doctype.call_log.call_log.link_existing_conversations",
|
||||
|
@ -15,6 +15,7 @@
|
||||
"hide_custom": 0,
|
||||
"icon": "hr",
|
||||
"idx": 0,
|
||||
"is_default": 0,
|
||||
"is_standard": 1,
|
||||
"label": "HR",
|
||||
"links": [
|
||||
@ -226,42 +227,12 @@
|
||||
"onboard": 0,
|
||||
"type": "Card Break"
|
||||
},
|
||||
{
|
||||
"dependencies": "Employee",
|
||||
"hidden": 0,
|
||||
"is_query_report": 0,
|
||||
"label": "Leave Application",
|
||||
"link_to": "Leave Application",
|
||||
"link_type": "DocType",
|
||||
"onboard": 0,
|
||||
"type": "Link"
|
||||
},
|
||||
{
|
||||
"dependencies": "Employee",
|
||||
"hidden": 0,
|
||||
"is_query_report": 0,
|
||||
"label": "Leave Allocation",
|
||||
"link_to": "Leave Allocation",
|
||||
"link_type": "DocType",
|
||||
"onboard": 0,
|
||||
"type": "Link"
|
||||
},
|
||||
{
|
||||
"dependencies": "Leave Type",
|
||||
"hidden": 0,
|
||||
"is_query_report": 0,
|
||||
"label": "Leave Policy",
|
||||
"link_to": "Leave Policy",
|
||||
"link_type": "DocType",
|
||||
"onboard": 0,
|
||||
"type": "Link"
|
||||
},
|
||||
{
|
||||
"dependencies": "",
|
||||
"hidden": 0,
|
||||
"is_query_report": 0,
|
||||
"label": "Leave Period",
|
||||
"link_to": "Leave Period",
|
||||
"label": "Holiday List",
|
||||
"link_to": "Holiday List",
|
||||
"link_type": "DocType",
|
||||
"onboard": 0,
|
||||
"type": "Link"
|
||||
@ -280,8 +251,28 @@
|
||||
"dependencies": "",
|
||||
"hidden": 0,
|
||||
"is_query_report": 0,
|
||||
"label": "Holiday List",
|
||||
"link_to": "Holiday List",
|
||||
"label": "Leave Period",
|
||||
"link_to": "Leave Period",
|
||||
"link_type": "DocType",
|
||||
"onboard": 0,
|
||||
"type": "Link"
|
||||
},
|
||||
{
|
||||
"dependencies": "Leave Type",
|
||||
"hidden": 0,
|
||||
"is_query_report": 0,
|
||||
"label": "Leave Policy",
|
||||
"link_to": "Leave Policy",
|
||||
"link_type": "DocType",
|
||||
"onboard": 0,
|
||||
"type": "Link"
|
||||
},
|
||||
{
|
||||
"dependencies": "Leave Policy",
|
||||
"hidden": 0,
|
||||
"is_query_report": 0,
|
||||
"label": "Leave Policy Assignment",
|
||||
"link_to": "Leave Policy Assignment",
|
||||
"link_type": "DocType",
|
||||
"onboard": 0,
|
||||
"type": "Link"
|
||||
@ -290,8 +281,18 @@
|
||||
"dependencies": "Employee",
|
||||
"hidden": 0,
|
||||
"is_query_report": 0,
|
||||
"label": "Compensatory Leave Request",
|
||||
"link_to": "Compensatory Leave Request",
|
||||
"label": "Leave Application",
|
||||
"link_to": "Leave Application",
|
||||
"link_type": "DocType",
|
||||
"onboard": 0,
|
||||
"type": "Link"
|
||||
},
|
||||
{
|
||||
"dependencies": "Employee",
|
||||
"hidden": 0,
|
||||
"is_query_report": 0,
|
||||
"label": "Leave Allocation",
|
||||
"link_to": "Leave Allocation",
|
||||
"link_type": "DocType",
|
||||
"onboard": 0,
|
||||
"type": "Link"
|
||||
@ -317,12 +318,12 @@
|
||||
"type": "Link"
|
||||
},
|
||||
{
|
||||
"dependencies": "Leave Application",
|
||||
"dependencies": "Employee",
|
||||
"hidden": 0,
|
||||
"is_query_report": 1,
|
||||
"label": "Employee Leave Balance",
|
||||
"link_to": "Employee Leave Balance",
|
||||
"link_type": "Report",
|
||||
"is_query_report": 0,
|
||||
"label": "Compensatory Leave Request",
|
||||
"link_to": "Compensatory Leave Request",
|
||||
"link_type": "DocType",
|
||||
"onboard": 0,
|
||||
"type": "Link"
|
||||
},
|
||||
@ -383,16 +384,6 @@
|
||||
"onboard": 0,
|
||||
"type": "Link"
|
||||
},
|
||||
{
|
||||
"dependencies": "Attendance",
|
||||
"hidden": 0,
|
||||
"is_query_report": 1,
|
||||
"label": "Monthly Attendance Sheet",
|
||||
"link_to": "Monthly Attendance Sheet",
|
||||
"link_type": "Report",
|
||||
"onboard": 0,
|
||||
"type": "Link"
|
||||
},
|
||||
{
|
||||
"hidden": 0,
|
||||
"is_query_report": 0,
|
||||
@ -420,6 +411,15 @@
|
||||
"onboard": 0,
|
||||
"type": "Link"
|
||||
},
|
||||
{
|
||||
"hidden": 0,
|
||||
"is_query_report": 0,
|
||||
"label": "Travel Request",
|
||||
"link_to": "Travel Request",
|
||||
"link_type": "DocType",
|
||||
"onboard": 0,
|
||||
"type": "Link"
|
||||
},
|
||||
{
|
||||
"hidden": 0,
|
||||
"is_query_report": 0,
|
||||
@ -464,6 +464,15 @@
|
||||
"onboard": 0,
|
||||
"type": "Card Break"
|
||||
},
|
||||
{
|
||||
"hidden": 0,
|
||||
"is_query_report": 0,
|
||||
"label": "Driver",
|
||||
"link_to": "Driver",
|
||||
"link_type": "DocType",
|
||||
"onboard": 0,
|
||||
"type": "Link"
|
||||
},
|
||||
{
|
||||
"dependencies": "",
|
||||
"hidden": 0,
|
||||
@ -541,6 +550,24 @@
|
||||
"onboard": 0,
|
||||
"type": "Link"
|
||||
},
|
||||
{
|
||||
"hidden": 0,
|
||||
"is_query_report": 0,
|
||||
"label": "Appointment Letter",
|
||||
"link_to": "Appointment Letter",
|
||||
"link_type": "DocType",
|
||||
"onboard": 0,
|
||||
"type": "Link"
|
||||
},
|
||||
{
|
||||
"hidden": 0,
|
||||
"is_query_report": 0,
|
||||
"label": "Appointment Letter Template",
|
||||
"link_to": "Appointment Letter Template",
|
||||
"link_type": "DocType",
|
||||
"onboard": 0,
|
||||
"type": "Link"
|
||||
},
|
||||
{
|
||||
"hidden": 0,
|
||||
"is_query_report": 0,
|
||||
@ -625,33 +652,6 @@
|
||||
"onboard": 0,
|
||||
"type": "Link"
|
||||
},
|
||||
{
|
||||
"hidden": 0,
|
||||
"is_query_report": 0,
|
||||
"label": "Reports",
|
||||
"onboard": 0,
|
||||
"type": "Card Break"
|
||||
},
|
||||
{
|
||||
"dependencies": "Employee",
|
||||
"hidden": 0,
|
||||
"is_query_report": 1,
|
||||
"label": "Employee Birthday",
|
||||
"link_to": "Employee Birthday",
|
||||
"link_type": "Report",
|
||||
"onboard": 0,
|
||||
"type": "Link"
|
||||
},
|
||||
{
|
||||
"dependencies": "Employee",
|
||||
"hidden": 0,
|
||||
"is_query_report": 1,
|
||||
"label": "Employees working on a holiday",
|
||||
"link_to": "Employees working on a holiday",
|
||||
"link_type": "Report",
|
||||
"onboard": 0,
|
||||
"type": "Link"
|
||||
},
|
||||
{
|
||||
"hidden": 0,
|
||||
"is_query_report": 0,
|
||||
@ -702,7 +702,74 @@
|
||||
{
|
||||
"hidden": 0,
|
||||
"is_query_report": 0,
|
||||
"label": "Employee Tax and Benefits",
|
||||
"label": "Key Reports",
|
||||
"onboard": 0,
|
||||
"type": "Card Break"
|
||||
},
|
||||
{
|
||||
"dependencies": "Attendance",
|
||||
"hidden": 0,
|
||||
"is_query_report": 1,
|
||||
"label": "Monthly Attendance Sheet",
|
||||
"link_to": "Monthly Attendance Sheet",
|
||||
"link_type": "Report",
|
||||
"onboard": 0,
|
||||
"type": "Link"
|
||||
},
|
||||
{
|
||||
"dependencies": "Staffing Plan",
|
||||
"hidden": 0,
|
||||
"is_query_report": 1,
|
||||
"label": "Recruitment Analytics",
|
||||
"link_to": "Recruitment Analytics",
|
||||
"link_type": "Report",
|
||||
"onboard": 0,
|
||||
"type": "Link"
|
||||
},
|
||||
{
|
||||
"dependencies": "Employee",
|
||||
"hidden": 0,
|
||||
"is_query_report": 1,
|
||||
"label": "Employee Analytics",
|
||||
"link_to": "Employee Analytics",
|
||||
"link_type": "Report",
|
||||
"onboard": 0,
|
||||
"type": "Link"
|
||||
},
|
||||
{
|
||||
"dependencies": "Employee",
|
||||
"hidden": 0,
|
||||
"is_query_report": 1,
|
||||
"label": "Employee Leave Balance",
|
||||
"link_to": "Employee Leave Balance",
|
||||
"link_type": "Report",
|
||||
"onboard": 0,
|
||||
"type": "Link"
|
||||
},
|
||||
{
|
||||
"dependencies": "Employee",
|
||||
"hidden": 0,
|
||||
"is_query_report": 1,
|
||||
"label": "Employee Leave Balance Summary",
|
||||
"link_to": "Employee Leave Balance Summary",
|
||||
"link_type": "Report",
|
||||
"onboard": 0,
|
||||
"type": "Link"
|
||||
},
|
||||
{
|
||||
"dependencies": "Employee Advance",
|
||||
"hidden": 0,
|
||||
"is_query_report": 1,
|
||||
"label": "Employee Advance Summary",
|
||||
"link_to": "Employee Advance Summary",
|
||||
"link_type": "Report",
|
||||
"onboard": 0,
|
||||
"type": "Link"
|
||||
},
|
||||
{
|
||||
"hidden": 0,
|
||||
"is_query_report": 0,
|
||||
"label": "Other Reports",
|
||||
"onboard": 0,
|
||||
"type": "Card Break"
|
||||
},
|
||||
@ -710,74 +777,44 @@
|
||||
"dependencies": "Employee",
|
||||
"hidden": 0,
|
||||
"is_query_report": 0,
|
||||
"label": "Employee Tax Exemption Declaration",
|
||||
"link_to": "Employee Tax Exemption Declaration",
|
||||
"link_type": "DocType",
|
||||
"label": "Employee Information",
|
||||
"link_to": "Employee Information",
|
||||
"link_type": "Report",
|
||||
"onboard": 0,
|
||||
"type": "Link"
|
||||
},
|
||||
{
|
||||
"dependencies": "Employee",
|
||||
"hidden": 0,
|
||||
"is_query_report": 0,
|
||||
"label": "Employee Tax Exemption Proof Submission",
|
||||
"link_to": "Employee Tax Exemption Proof Submission",
|
||||
"link_type": "DocType",
|
||||
"onboard": 0,
|
||||
"type": "Link"
|
||||
},
|
||||
{
|
||||
"dependencies": "Employee, Payroll Period",
|
||||
"hidden": 0,
|
||||
"is_query_report": 0,
|
||||
"label": "Employee Other Income",
|
||||
"link_to": "Employee Other Income",
|
||||
"link_type": "DocType",
|
||||
"is_query_report": 1,
|
||||
"label": "Employee Birthday",
|
||||
"link_to": "Employee Birthday",
|
||||
"link_type": "Report",
|
||||
"onboard": 0,
|
||||
"type": "Link"
|
||||
},
|
||||
{
|
||||
"dependencies": "Employee",
|
||||
"hidden": 0,
|
||||
"is_query_report": 0,
|
||||
"label": "Employee Benefit Application",
|
||||
"link_to": "Employee Benefit Application",
|
||||
"link_type": "DocType",
|
||||
"is_query_report": 1,
|
||||
"label": "Employees Working on a Holiday",
|
||||
"link_to": "Employees working on a holiday",
|
||||
"link_type": "Report",
|
||||
"onboard": 0,
|
||||
"type": "Link"
|
||||
},
|
||||
{
|
||||
"dependencies": "Employee",
|
||||
"dependencies": "Daily Work Summary",
|
||||
"hidden": 0,
|
||||
"is_query_report": 0,
|
||||
"label": "Employee Benefit Claim",
|
||||
"link_to": "Employee Benefit Claim",
|
||||
"link_type": "DocType",
|
||||
"onboard": 0,
|
||||
"type": "Link"
|
||||
},
|
||||
{
|
||||
"dependencies": "Employee",
|
||||
"hidden": 0,
|
||||
"is_query_report": 0,
|
||||
"label": "Employee Tax Exemption Category",
|
||||
"link_to": "Employee Tax Exemption Category",
|
||||
"link_type": "DocType",
|
||||
"onboard": 0,
|
||||
"type": "Link"
|
||||
},
|
||||
{
|
||||
"dependencies": "Employee",
|
||||
"hidden": 0,
|
||||
"is_query_report": 0,
|
||||
"label": "Employee Tax Exemption Sub Category",
|
||||
"link_to": "Employee Tax Exemption Sub Category",
|
||||
"link_type": "DocType",
|
||||
"is_query_report": 1,
|
||||
"label": "Daily Work Summary Replies",
|
||||
"link_to": "Daily Work Summary Replies",
|
||||
"link_type": "Report",
|
||||
"onboard": 0,
|
||||
"type": "Link"
|
||||
}
|
||||
],
|
||||
"modified": "2021-01-21 13:38:38.941001",
|
||||
"modified": "2021-03-24 17:35:21.483297",
|
||||
"modified_by": "Administrator",
|
||||
"module": "HR",
|
||||
"name": "HR",
|
||||
|
@ -21,6 +21,7 @@ class LoanRepayment(AccountsController):
|
||||
def validate(self):
|
||||
amounts = calculate_amounts(self.against_loan, self.posting_date)
|
||||
self.set_missing_values(amounts)
|
||||
self.check_future_entries()
|
||||
self.validate_amount()
|
||||
self.allocate_amounts(amounts)
|
||||
|
||||
@ -69,6 +70,13 @@ class LoanRepayment(AccountsController):
|
||||
if amounts.get('due_date'):
|
||||
self.due_date = amounts.get('due_date')
|
||||
|
||||
def check_future_entries(self):
|
||||
future_repayment_date = frappe.db.get_value("Loan Repayment", {"posting_date": (">", self.posting_date),
|
||||
"docstatus": 1, "against_loan": self.against_loan}, 'posting_date')
|
||||
|
||||
if future_repayment_date:
|
||||
frappe.throw("Repayment already made till date {0}".format(getdate(future_repayment_date)))
|
||||
|
||||
def validate_amount(self):
|
||||
precision = cint(frappe.db.get_default("currency_precision")) or 2
|
||||
|
||||
@ -303,7 +311,9 @@ def create_repayment_entry(loan, applicant, company, posting_date, loan_type,
|
||||
|
||||
return lr
|
||||
|
||||
def get_accrued_interest_entries(against_loan):
|
||||
def get_accrued_interest_entries(against_loan, posting_date=None):
|
||||
if not posting_date:
|
||||
posting_date = getdate()
|
||||
|
||||
unpaid_accrued_entries = frappe.db.sql(
|
||||
"""
|
||||
@ -314,12 +324,13 @@ def get_accrued_interest_entries(against_loan):
|
||||
`tabLoan Interest Accrual`
|
||||
WHERE
|
||||
loan = %s
|
||||
AND posting_date <= %s
|
||||
AND (interest_amount - paid_interest_amount > 0 OR
|
||||
payable_principal_amount - paid_principal_amount > 0)
|
||||
AND
|
||||
docstatus = 1
|
||||
ORDER BY posting_date
|
||||
""", (against_loan), as_dict=1)
|
||||
""", (against_loan, posting_date), as_dict=1)
|
||||
|
||||
return unpaid_accrued_entries
|
||||
|
||||
@ -343,7 +354,7 @@ def get_amounts(amounts, against_loan, posting_date):
|
||||
|
||||
against_loan_doc = frappe.get_doc("Loan", against_loan)
|
||||
loan_type_details = frappe.get_doc("Loan Type", against_loan_doc.loan_type)
|
||||
accrued_interest_entries = get_accrued_interest_entries(against_loan_doc.name)
|
||||
accrued_interest_entries = get_accrued_interest_entries(against_loan_doc.name, posting_date)
|
||||
|
||||
computed_penalty_date, pending_penalty_amount = get_penalty_details(against_loan)
|
||||
pending_accrual_entries = {}
|
||||
|
@ -70,7 +70,9 @@
|
||||
{
|
||||
"fieldname": "loan_repayment_entry",
|
||||
"fieldtype": "Link",
|
||||
"hidden": 1,
|
||||
"label": "Loan Repayment Entry",
|
||||
"no_copy": 1,
|
||||
"options": "Loan Repayment",
|
||||
"read_only": 1
|
||||
},
|
||||
@ -83,9 +85,10 @@
|
||||
"read_only": 1
|
||||
}
|
||||
],
|
||||
"index_web_pages_for_search": 1,
|
||||
"istable": 1,
|
||||
"links": [],
|
||||
"modified": "2020-04-16 13:17:04.798335",
|
||||
"modified": "2021-03-14 20:47:11.725818",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Loan Management",
|
||||
"name": "Salary Slip Loan",
|
||||
|
@ -5,7 +5,7 @@
|
||||
from __future__ import unicode_literals
|
||||
import unittest
|
||||
import frappe
|
||||
from frappe.utils import cstr
|
||||
from frappe.utils import cstr, flt
|
||||
from frappe.test_runner import make_test_records
|
||||
from erpnext.stock.doctype.stock_reconciliation.test_stock_reconciliation import create_stock_reconciliation
|
||||
from erpnext.manufacturing.doctype.bom_update_tool.bom_update_tool import update_cost
|
||||
@ -81,15 +81,27 @@ class TestBOM(unittest.TestCase):
|
||||
bom = frappe.copy_doc(test_records[2])
|
||||
bom.insert()
|
||||
|
||||
# test amounts in selected currency
|
||||
self.assertEqual(bom.operating_cost, 100)
|
||||
self.assertEqual(bom.raw_material_cost, 351.68)
|
||||
self.assertEqual(bom.total_cost, 451.68)
|
||||
raw_material_cost = 0.0
|
||||
op_cost = 0.0
|
||||
|
||||
for op_row in bom.operations:
|
||||
op_cost += op_row.operating_cost
|
||||
|
||||
for row in bom.items:
|
||||
raw_material_cost += row.amount
|
||||
|
||||
base_raw_material_cost = raw_material_cost * flt(bom.conversion_rate, bom.precision("conversion_rate"))
|
||||
base_op_cost = op_cost * flt(bom.conversion_rate, bom.precision("conversion_rate"))
|
||||
|
||||
# test amounts in selected currency
|
||||
self.assertEqual(bom.base_operating_cost, 6000)
|
||||
self.assertEqual(bom.base_raw_material_cost, 21100.80)
|
||||
self.assertEqual(bom.base_total_cost, 27100.80)
|
||||
self.assertEqual(bom.operating_cost, op_cost)
|
||||
self.assertEqual(bom.raw_material_cost, raw_material_cost)
|
||||
self.assertEqual(bom.total_cost, raw_material_cost + op_cost)
|
||||
|
||||
# test amounts in selected currency
|
||||
self.assertEqual(bom.base_operating_cost, base_op_cost)
|
||||
self.assertEqual(bom.base_raw_material_cost, base_raw_material_cost)
|
||||
self.assertEqual(bom.base_total_cost, base_raw_material_cost + base_op_cost)
|
||||
|
||||
def test_bom_cost_multi_uom_multi_currency_based_on_price_list(self):
|
||||
frappe.db.set_value("Price List", "_Test Price List", "price_not_uom_dependent", 1)
|
||||
|
@ -47,6 +47,8 @@ class JobCard(Document):
|
||||
if d.completed_qty:
|
||||
self.total_completed_qty += d.completed_qty
|
||||
|
||||
self.total_completed_qty = flt(self.total_completed_qty, self.precision("total_completed_qty"))
|
||||
|
||||
def get_overlap_for(self, args, check_next_available_slot=False):
|
||||
production_capacity = 1
|
||||
|
||||
|
@ -5,7 +5,7 @@ from frappe import _
|
||||
def execute():
|
||||
from erpnext.setup.setup_wizard.operations.install_fixtures import default_lead_sources
|
||||
|
||||
frappe.reload_doc('selling', 'doctype', 'lead_source')
|
||||
frappe.reload_doc('crm', 'doctype', 'lead_source')
|
||||
|
||||
frappe.local.lang = frappe.db.get_default("lang") or 'en'
|
||||
|
||||
|
@ -133,45 +133,59 @@ frappe.ui.form.on('Payroll Entry', {
|
||||
}
|
||||
};
|
||||
});
|
||||
|
||||
frm.set_query('employee', 'employees', () => {
|
||||
if (!frm.doc.company) {
|
||||
frappe.msgprint(__("Please set a Company"));
|
||||
return [];
|
||||
}
|
||||
return {
|
||||
query: "erpnext.payroll.doctype.payroll_entry.payroll_entry.employee_query",
|
||||
filters: frm.events.get_employee_filters(frm)
|
||||
};
|
||||
});
|
||||
},
|
||||
|
||||
get_employee_filters: function (frm) {
|
||||
let filters = {};
|
||||
filters['company'] = frm.doc.company;
|
||||
filters['start_date'] = frm.doc.start_date;
|
||||
filters['end_date'] = frm.doc.end_date;
|
||||
|
||||
if (frm.doc.department) {
|
||||
filters['department'] = frm.doc.department;
|
||||
}
|
||||
if (frm.doc.branch) {
|
||||
filters['branch'] = frm.doc.branch;
|
||||
}
|
||||
if (frm.doc.designation) {
|
||||
filters['designation'] = frm.doc.designation;
|
||||
}
|
||||
if (frm.doc.employees) {
|
||||
filters['employees'] = frm.doc.employees.filter(d => d.employee).map(d => d.employee);
|
||||
}
|
||||
return filters;
|
||||
},
|
||||
|
||||
payroll_frequency: function (frm) {
|
||||
frm.trigger("set_start_end_dates").then( ()=> {
|
||||
frm.events.clear_employee_table(frm);
|
||||
frm.events.get_employee_with_salary_slip_and_set_query(frm);
|
||||
});
|
||||
},
|
||||
|
||||
employee_filters: function (frm, emp_list) {
|
||||
frm.set_query('employee', 'employees', () => {
|
||||
return {
|
||||
filters: {
|
||||
name: ["not in", emp_list]
|
||||
}
|
||||
};
|
||||
});
|
||||
},
|
||||
|
||||
get_employee_with_salary_slip_and_set_query: function (frm) {
|
||||
frappe.db.get_list('Salary Slip', {
|
||||
filters: {
|
||||
start_date: frm.doc.start_date,
|
||||
end_date: frm.doc.end_date,
|
||||
docstatus: 1,
|
||||
},
|
||||
fields: ['employee']
|
||||
}).then((emp) => {
|
||||
var emp_list = [];
|
||||
emp.forEach((employee_data) => {
|
||||
emp_list.push(Object.values(employee_data)[0]);
|
||||
});
|
||||
frm.events.employee_filters(frm, emp_list);
|
||||
});
|
||||
},
|
||||
|
||||
company: function (frm) {
|
||||
frm.events.clear_employee_table(frm);
|
||||
erpnext.accounts.dimensions.update_dimension(frm, frm.doctype);
|
||||
frm.trigger("set_payable_account_and_currency");
|
||||
},
|
||||
|
||||
set_payable_account_and_currency: function (frm) {
|
||||
frappe.db.get_value("Company", {"name": frm.doc.company}, "default_currency", (r) => {
|
||||
frm.set_value('currency', r.default_currency);
|
||||
});
|
||||
frappe.db.get_value("Company", {"name": frm.doc.company}, "default_payroll_payable_account", (r) => {
|
||||
frm.set_value('payroll_payable_account', r.default_payroll_payable_account);
|
||||
});
|
||||
},
|
||||
|
||||
currency: function (frm) {
|
||||
@ -345,11 +359,3 @@ let render_employee_attendance = function (frm, data) {
|
||||
})
|
||||
);
|
||||
};
|
||||
|
||||
frappe.ui.form.on('Payroll Employee Detail', {
|
||||
employee: function(frm) {
|
||||
if (!frm.doc.payroll_frequency) {
|
||||
frappe.throw(__("Please set a Payroll Frequency"));
|
||||
}
|
||||
}
|
||||
});
|
||||
|
@ -10,16 +10,17 @@ from frappe.utils import cint, flt, add_days, getdate, add_to_date, DATE_FORMAT,
|
||||
from frappe import _
|
||||
from erpnext.accounts.utils import get_fiscal_year
|
||||
from erpnext.hr.doctype.employee.employee import get_holiday_list_for_employee
|
||||
from frappe.desk.reportview import get_match_cond, get_filters_cond
|
||||
|
||||
class PayrollEntry(Document):
|
||||
def onload(self):
|
||||
if not self.docstatus==1 or self.salary_slips_submitted:
|
||||
return
|
||||
return
|
||||
|
||||
# check if salary slips were manually submitted
|
||||
entries = frappe.db.count("Salary Slip", {'payroll_entry': self.name, 'docstatus': 1}, ['name'])
|
||||
if cint(entries) == len(self.employees):
|
||||
self.set_onload("submitted_ss", True)
|
||||
self.set_onload("submitted_ss", True)
|
||||
|
||||
def validate(self):
|
||||
self.number_of_employees = len(self.employees)
|
||||
@ -59,16 +60,16 @@ class PayrollEntry(Document):
|
||||
condition = """and payroll_frequency = '%(payroll_frequency)s'"""% {"payroll_frequency": self.payroll_frequency}
|
||||
|
||||
sal_struct = frappe.db.sql_list("""
|
||||
select
|
||||
name from `tabSalary Structure`
|
||||
where
|
||||
docstatus = 1 and
|
||||
is_active = 'Yes'
|
||||
and company = %(company)s
|
||||
and currency = %(currency)s and
|
||||
ifnull(salary_slip_based_on_timesheet,0) = %(salary_slip_based_on_timesheet)s
|
||||
{condition}""".format(condition=condition),
|
||||
{"company": self.company, "currency": self.currency, "salary_slip_based_on_timesheet":self.salary_slip_based_on_timesheet})
|
||||
select
|
||||
name from `tabSalary Structure`
|
||||
where
|
||||
docstatus = 1 and
|
||||
is_active = 'Yes'
|
||||
and company = %(company)s
|
||||
and currency = %(currency)s and
|
||||
ifnull(salary_slip_based_on_timesheet,0) = %(salary_slip_based_on_timesheet)s
|
||||
{condition}""".format(condition=condition),
|
||||
{"company": self.company, "currency": self.currency, "salary_slip_based_on_timesheet":self.salary_slip_based_on_timesheet})
|
||||
|
||||
if sal_struct:
|
||||
cond += "and t2.salary_structure IN %(sal_struct)s "
|
||||
@ -176,15 +177,15 @@ class PayrollEntry(Document):
|
||||
"""
|
||||
Returns list of salary slips based on selected criteria
|
||||
"""
|
||||
cond = self.get_filter_condition()
|
||||
|
||||
ss_list = frappe.db.sql("""
|
||||
select t1.name, t1.salary_structure, t1.payroll_cost_center from `tabSalary Slip` t1
|
||||
where t1.docstatus = %s and t1.start_date >= %s and t1.end_date <= %s
|
||||
and (t1.journal_entry is null or t1.journal_entry = "") and ifnull(salary_slip_based_on_timesheet,0) = %s %s
|
||||
""" % ('%s', '%s', '%s','%s', cond), (ss_status, self.start_date, self.end_date, self.salary_slip_based_on_timesheet), as_dict=as_dict)
|
||||
where t1.docstatus = %s and t1.start_date >= %s and t1.end_date <= %s and t1.payroll_entry = %s
|
||||
and (t1.journal_entry is null or t1.journal_entry = "") and ifnull(salary_slip_based_on_timesheet,0) = %s
|
||||
""", (ss_status, self.start_date, self.end_date, self.name, self.salary_slip_based_on_timesheet), as_dict=as_dict)
|
||||
return ss_list
|
||||
|
||||
@frappe.whitelist()
|
||||
def submit_salary_slips(self):
|
||||
self.check_permission('write')
|
||||
ss_list = self.get_sal_slip_list(ss_status=0)
|
||||
@ -270,26 +271,26 @@ class PayrollEntry(Document):
|
||||
exchange_rate, amt = self.get_amount_and_exchange_rate_for_journal_entry(acc_cc[0], amount, company_currency, currencies)
|
||||
payable_amount += flt(amount, precision)
|
||||
accounts.append({
|
||||
"account": acc_cc[0],
|
||||
"debit_in_account_currency": flt(amt, precision),
|
||||
"exchange_rate": flt(exchange_rate),
|
||||
"party_type": '',
|
||||
"cost_center": acc_cc[1] or self.cost_center,
|
||||
"project": self.project
|
||||
})
|
||||
"account": acc_cc[0],
|
||||
"debit_in_account_currency": flt(amt, precision),
|
||||
"exchange_rate": flt(exchange_rate),
|
||||
"party_type": '',
|
||||
"cost_center": acc_cc[1] or self.cost_center,
|
||||
"project": self.project
|
||||
})
|
||||
|
||||
# Deductions
|
||||
for acc_cc, amount in deductions.items():
|
||||
exchange_rate, amt = self.get_amount_and_exchange_rate_for_journal_entry(acc_cc[0], amount, company_currency, currencies)
|
||||
payable_amount -= flt(amount, precision)
|
||||
accounts.append({
|
||||
"account": acc_cc[0],
|
||||
"credit_in_account_currency": flt(amt, precision),
|
||||
"exchange_rate": flt(exchange_rate),
|
||||
"cost_center": acc_cc[1] or self.cost_center,
|
||||
"party_type": '',
|
||||
"project": self.project
|
||||
})
|
||||
"account": acc_cc[0],
|
||||
"credit_in_account_currency": flt(amt, precision),
|
||||
"exchange_rate": flt(exchange_rate),
|
||||
"cost_center": acc_cc[1] or self.cost_center,
|
||||
"party_type": '',
|
||||
"project": self.project
|
||||
})
|
||||
|
||||
# Payable amount
|
||||
exchange_rate, payable_amt = self.get_amount_and_exchange_rate_for_journal_entry(payroll_payable_account, payable_amount, company_currency, currencies)
|
||||
@ -335,10 +336,9 @@ class PayrollEntry(Document):
|
||||
def make_payment_entry(self):
|
||||
self.check_permission('write')
|
||||
|
||||
cond = self.get_filter_condition()
|
||||
salary_slip_name_list = frappe.db.sql(""" select t1.name from `tabSalary Slip` t1
|
||||
where t1.docstatus = 1 and start_date >= %s and end_date <= %s %s
|
||||
""" % ('%s', '%s', cond), (self.start_date, self.end_date), as_list = True)
|
||||
where t1.docstatus = 1 and start_date >= %s and end_date <= %s and t1.payroll_entry = %s
|
||||
""", (self.start_date, self.end_date, self.name), as_list = True)
|
||||
|
||||
if salary_slip_name_list and len(salary_slip_name_list) > 0:
|
||||
salary_slip_total = 0
|
||||
@ -370,20 +370,20 @@ class PayrollEntry(Document):
|
||||
|
||||
exchange_rate, amount = self.get_amount_and_exchange_rate_for_journal_entry(self.payment_account, je_payment_amount, company_currency, currencies)
|
||||
accounts.append({
|
||||
"account": self.payment_account,
|
||||
"bank_account": self.bank_account,
|
||||
"credit_in_account_currency": flt(amount, precision),
|
||||
"exchange_rate": flt(exchange_rate),
|
||||
})
|
||||
"account": self.payment_account,
|
||||
"bank_account": self.bank_account,
|
||||
"credit_in_account_currency": flt(amount, precision),
|
||||
"exchange_rate": flt(exchange_rate),
|
||||
})
|
||||
|
||||
exchange_rate, amount = self.get_amount_and_exchange_rate_for_journal_entry(payroll_payable_account, je_payment_amount, company_currency, currencies)
|
||||
accounts.append({
|
||||
"account": payroll_payable_account,
|
||||
"debit_in_account_currency": flt(amount, precision),
|
||||
"exchange_rate": flt(exchange_rate),
|
||||
"reference_type": self.doctype,
|
||||
"reference_name": self.name
|
||||
})
|
||||
"account": payroll_payable_account,
|
||||
"debit_in_account_currency": flt(amount, precision),
|
||||
"exchange_rate": flt(exchange_rate),
|
||||
"reference_type": self.doctype,
|
||||
"reference_name": self.name
|
||||
})
|
||||
|
||||
if len(currencies) > 1:
|
||||
multi_currency = 1
|
||||
@ -409,6 +409,7 @@ class PayrollEntry(Document):
|
||||
self.update(get_start_end_dates(self.payroll_frequency,
|
||||
self.start_date or self.posting_date, self.company))
|
||||
|
||||
@frappe.whitelist()
|
||||
def validate_employee_attendance(self):
|
||||
employees_to_mark_attendance = []
|
||||
days_in_payroll, days_holiday, days_attendance_marked = 0, 0, 0
|
||||
@ -424,7 +425,7 @@ class PayrollEntry(Document):
|
||||
employees_to_mark_attendance.append({
|
||||
"employee": employee_detail.employee,
|
||||
"employee_name": employee_detail.employee_name
|
||||
})
|
||||
})
|
||||
return employees_to_mark_attendance
|
||||
|
||||
def get_count_holidays_of_employee(self, employee, start_date):
|
||||
@ -441,11 +442,11 @@ class PayrollEntry(Document):
|
||||
def get_count_employee_attendance(self, employee, start_date):
|
||||
marked_days = 0
|
||||
attendances = frappe.get_all("Attendance",
|
||||
fields = ["count(*)"],
|
||||
filters = {
|
||||
"employee": employee,
|
||||
"attendance_date": ('between', [start_date, self.end_date])
|
||||
}, as_list=1)
|
||||
fields = ["count(*)"],
|
||||
filters = {
|
||||
"employee": employee,
|
||||
"attendance_date": ('between', [start_date, self.end_date])
|
||||
}, as_list=1)
|
||||
if attendances and attendances[0][0]:
|
||||
marked_days = attendances[0][0]
|
||||
return marked_days
|
||||
@ -553,6 +554,7 @@ def payroll_entry_has_bank_entries(name):
|
||||
def create_salary_slips_for_employees(employees, args, publish_progress=True):
|
||||
salary_slips_exists_for = get_existing_salary_slips(employees, args)
|
||||
count=0
|
||||
salary_slips_not_created = []
|
||||
for emp in employees:
|
||||
if emp not in salary_slips_exists_for:
|
||||
args.update({
|
||||
@ -566,33 +568,24 @@ def create_salary_slips_for_employees(employees, args, publish_progress=True):
|
||||
frappe.publish_progress(count*100/len(set(employees) - set(salary_slips_exists_for)),
|
||||
title = _("Creating Salary Slips..."))
|
||||
else:
|
||||
salary_slip_name = frappe.db.sql(
|
||||
'''SELECT
|
||||
name
|
||||
FROM `tabSalary Slip`
|
||||
WHERE company=%s
|
||||
AND start_date >= %s
|
||||
AND end_date <= %s
|
||||
AND employee = %s
|
||||
''', (args.company, args.start_date, args.end_date, emp), as_dict=True)
|
||||
|
||||
salary_slip_doc = frappe.get_doc('Salary Slip', salary_slip_name[0].name)
|
||||
salary_slip_doc.exchange_rate = args.exchange_rate
|
||||
salary_slip_doc.set_totals()
|
||||
salary_slip_doc.db_update()
|
||||
salary_slips_not_created.append(emp)
|
||||
|
||||
payroll_entry = frappe.get_doc("Payroll Entry", args.payroll_entry)
|
||||
payroll_entry.db_set("salary_slips_created", 1)
|
||||
payroll_entry.notify_update()
|
||||
|
||||
if salary_slips_not_created:
|
||||
frappe.msgprint(_("Salary Slips already exists for employees {}, and will not be processed by this payroll.")
|
||||
.format(frappe.bold(", ".join([emp for emp in salary_slips_not_created]))) , title=_("Message"), indicator="orange")
|
||||
|
||||
def get_existing_salary_slips(employees, args):
|
||||
return frappe.db.sql_list("""
|
||||
select distinct employee from `tabSalary Slip`
|
||||
where docstatus!= 2 and company = %s
|
||||
where docstatus!= 2 and company = %s and payroll_entry = %s
|
||||
and start_date >= %s and end_date <= %s
|
||||
and employee in (%s)
|
||||
""" % ('%s', '%s', '%s', ', '.join(['%s']*len(employees))),
|
||||
[args.company, args.start_date, args.end_date] + employees)
|
||||
""" % ('%s', '%s', '%s', '%s', ', '.join(['%s']*len(employees))),
|
||||
[args.company, args.payroll_entry, args.start_date, args.end_date] + employees)
|
||||
|
||||
def submit_salary_slips_for_employees(payroll_entry, salary_slips, publish_progress=True):
|
||||
submitted_ss = []
|
||||
@ -644,3 +637,61 @@ def get_payroll_entries_for_jv(doctype, txt, searchfield, start, page_len, filte
|
||||
'txt': "%%%s%%" % frappe.db.escape(txt),
|
||||
'start': start, 'page_len': page_len
|
||||
})
|
||||
|
||||
def get_employee_with_existing_salary_slip(start_date, end_date, company):
|
||||
return frappe.db.sql_list("""
|
||||
select employee from `tabSalary Slip`
|
||||
where
|
||||
(start_date between %(start_date)s and %(end_date)s
|
||||
or
|
||||
end_date between %(start_date)s and %(end_date)s
|
||||
or
|
||||
%(start_date)s between start_date and end_date)
|
||||
and company = %(company)s
|
||||
and docstatus = 1
|
||||
""", {'start_date': start_date, 'end_date': end_date, 'company': company})
|
||||
|
||||
@frappe.whitelist()
|
||||
@frappe.validate_and_sanitize_search_inputs
|
||||
def employee_query(doctype, txt, searchfield, start, page_len, filters):
|
||||
filters = frappe._dict(filters)
|
||||
conditions = []
|
||||
exclude_employees = []
|
||||
emp_cond = ''
|
||||
if filters.start_date and filters.end_date:
|
||||
employee_list = get_employee_with_existing_salary_slip(filters.start_date, filters.end_date, filters.company)
|
||||
emp = filters.get('employees')
|
||||
filters.pop('start_date')
|
||||
filters.pop('end_date')
|
||||
if filters.employees is not None:
|
||||
filters.pop('employees')
|
||||
if employee_list:
|
||||
exclude_employees.extend(employee_list)
|
||||
if emp:
|
||||
exclude_employees.extend(emp)
|
||||
if exclude_employees:
|
||||
emp_cond += 'and employee not in %(exclude_employees)s'
|
||||
|
||||
return frappe.db.sql("""select name, employee_name from `tabEmployee`
|
||||
where status = 'Active'
|
||||
and docstatus < 2
|
||||
and ({key} like %(txt)s
|
||||
or employee_name like %(txt)s)
|
||||
{emp_cond}
|
||||
{fcond} {mcond}
|
||||
order by
|
||||
if(locate(%(_txt)s, name), locate(%(_txt)s, name), 99999),
|
||||
if(locate(%(_txt)s, employee_name), locate(%(_txt)s, employee_name), 99999),
|
||||
idx desc,
|
||||
name, employee_name
|
||||
limit %(start)s, %(page_len)s""".format(**{
|
||||
'key': searchfield,
|
||||
'fcond': get_filters_cond(doctype, filters, conditions),
|
||||
'mcond': get_match_cond(doctype),
|
||||
'emp_cond': emp_cond
|
||||
}), {
|
||||
'txt': "%%%s%%" % txt,
|
||||
'_txt': txt.replace("%", ""),
|
||||
'start': start,
|
||||
'page_len': page_len,
|
||||
'exclude_employees': exclude_employees})
|
||||
|
@ -51,21 +51,22 @@ class TestPayrollEntry(unittest.TestCase):
|
||||
|
||||
company_doc = frappe.get_doc('Company', company)
|
||||
salary_structure = make_salary_structure("_Test Multi Currency Salary Structure", "Monthly", company=company, currency='USD')
|
||||
create_salary_structure_assignment(employee, salary_structure.name, company=company)
|
||||
create_salary_structure_assignment(employee, salary_structure.name, company=company, currency='USD')
|
||||
frappe.db.sql("""delete from `tabSalary Slip` where employee=%s""",(frappe.db.get_value("Employee", {"user_id": "test_muti_currency_employee@payroll.com"})))
|
||||
salary_slip = get_salary_slip("test_muti_currency_employee@payroll.com", "Monthly", "_Test Multi Currency Salary Structure")
|
||||
dates = get_start_end_dates('Monthly', nowdate())
|
||||
payroll_entry = make_payroll_entry(start_date=dates.start_date, end_date=dates.end_date,
|
||||
payroll_entry = make_payroll_entry(start_date=dates.start_date, end_date=dates.end_date,
|
||||
payable_account=company_doc.default_payroll_payable_account, currency='USD', exchange_rate=70)
|
||||
payroll_entry.make_payment_entry()
|
||||
|
||||
salary_slip.load_from_db()
|
||||
|
||||
payroll_je = salary_slip.journal_entry
|
||||
payroll_je_doc = frappe.get_doc('Journal Entry', payroll_je)
|
||||
if payroll_je:
|
||||
payroll_je_doc = frappe.get_doc('Journal Entry', payroll_je)
|
||||
|
||||
self.assertEqual(salary_slip.base_gross_pay, payroll_je_doc.total_debit)
|
||||
self.assertEqual(salary_slip.base_gross_pay, payroll_je_doc.total_credit)
|
||||
self.assertEqual(salary_slip.base_gross_pay, payroll_je_doc.total_debit)
|
||||
self.assertEqual(salary_slip.base_gross_pay, payroll_je_doc.total_credit)
|
||||
|
||||
payment_entry = frappe.db.sql('''
|
||||
Select ifnull(sum(je.total_debit),0) as total_debit, ifnull(sum(je.total_credit),0) as total_credit from `tabJournal Entry` je, `tabJournal Entry Account` jea
|
||||
|
@ -39,7 +39,8 @@ frappe.ui.form.on("Salary Slip", {
|
||||
|
||||
frm.set_query("employee", function() {
|
||||
return {
|
||||
query: "erpnext.controllers.queries.employee_query"
|
||||
query: "erpnext.controllers.queries.employee_query",
|
||||
filters: frm.doc.company
|
||||
};
|
||||
});
|
||||
},
|
||||
@ -93,28 +94,31 @@ frappe.ui.form.on("Salary Slip", {
|
||||
},
|
||||
|
||||
set_exchange_rate: function(frm, company_currency) {
|
||||
if (frm.doc.currency) {
|
||||
var from_currency = frm.doc.currency;
|
||||
if (from_currency != company_currency) {
|
||||
frm.events.hide_loan_section(frm);
|
||||
frappe.call({
|
||||
method: "erpnext.setup.utils.get_exchange_rate",
|
||||
args: {
|
||||
from_currency: from_currency,
|
||||
to_currency: company_currency,
|
||||
},
|
||||
callback: function(r) {
|
||||
frm.set_value("exchange_rate", flt(r.message));
|
||||
frm.set_df_property("exchange_rate", "hidden", 0);
|
||||
frm.set_df_property("exchange_rate", "description", "1 " + frm.doc.currency
|
||||
+ " = [?] " + company_currency);
|
||||
}
|
||||
});
|
||||
} else {
|
||||
frm.set_value("exchange_rate", 1.0);
|
||||
frm.set_df_property("exchange_rate", "hidden", 1);
|
||||
frm.set_df_property("exchange_rate", "description", "");
|
||||
}
|
||||
if (frm.doc.docstatus === 0) {
|
||||
if (frm.doc.currency) {
|
||||
var from_currency = frm.doc.currency;
|
||||
if (from_currency != company_currency) {
|
||||
frm.events.hide_loan_section(frm);
|
||||
frappe.call({
|
||||
method: "erpnext.setup.utils.get_exchange_rate",
|
||||
args: {
|
||||
from_currency: from_currency,
|
||||
to_currency: company_currency,
|
||||
},
|
||||
callback: function(r) {
|
||||
if (r.message) {
|
||||
frm.set_value("exchange_rate", flt(r.message));
|
||||
frm.set_df_property('exchange_rate', 'hidden', 0);
|
||||
frm.set_df_property("exchange_rate", "description", "1 " + frm.doc.currency
|
||||
+ " = [?] " + company_currency);
|
||||
}
|
||||
}
|
||||
});
|
||||
} else {
|
||||
frm.set_value("exchange_rate", 1.0);
|
||||
frm.set_df_property('exchange_rate', 'hidden', 1);
|
||||
frm.set_df_property("exchange_rate", "description", "" );
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
|
@ -124,9 +124,12 @@ class SalarySlip(TransactionBase):
|
||||
|
||||
def check_existing(self):
|
||||
if not self.salary_slip_based_on_timesheet:
|
||||
cond = ""
|
||||
if self.payroll_entry:
|
||||
cond += "and payroll_entry = '{0}'".format(self.payroll_entry)
|
||||
ret_exist = frappe.db.sql("""select name from `tabSalary Slip`
|
||||
where start_date = %s and end_date = %s and docstatus != 2
|
||||
and employee = %s and name != %s""",
|
||||
and employee = %s and name != %s {0}""".format(cond),
|
||||
(self.start_date, self.end_date, self.employee, self.name))
|
||||
if ret_exist:
|
||||
self.employee = ''
|
||||
@ -1053,7 +1056,7 @@ class SalarySlip(TransactionBase):
|
||||
repayment_entry.save()
|
||||
repayment_entry.submit()
|
||||
|
||||
loan.loan_repayment_entry = repayment_entry.name
|
||||
frappe.db.set_value("Salary Slip Loan", loan.name, "loan_repayment_entry", repayment_entry.name)
|
||||
|
||||
def cancel_loan_repayment_entry(self):
|
||||
for loan in self.loans:
|
||||
|
@ -33,12 +33,16 @@ class TestProject(unittest.TestCase):
|
||||
|
||||
def test_project_template_having_parent_child_tasks(self):
|
||||
project_name = "Test Project with Template - Tasks with Parent-Child Relation"
|
||||
|
||||
if frappe.db.get_value('Project', {'project_name': project_name}, 'name'):
|
||||
project_name = frappe.db.get_value('Project', {'project_name': project_name}, 'name')
|
||||
|
||||
frappe.db.sql(""" delete from tabTask where project = %s """, project_name)
|
||||
frappe.delete_doc('Project', project_name)
|
||||
|
||||
task1 = task_exists("Test Template Task Parent")
|
||||
if not task1:
|
||||
task1 = create_task(subject="Test Template Task Parent", is_group=1, is_template=1, begin=1, duration=4)
|
||||
task1 = create_task(subject="Test Template Task Parent", is_group=1, is_template=1, begin=1, duration=10)
|
||||
|
||||
task2 = task_exists("Test Template Task Child 1")
|
||||
if not task2:
|
||||
@ -53,7 +57,7 @@ class TestProject(unittest.TestCase):
|
||||
tasks = frappe.get_all('Task', ['subject','exp_end_date','depends_on_tasks', 'name', 'parent_task'], dict(project=project.name), order_by='creation asc')
|
||||
|
||||
self.assertEqual(tasks[0].subject, 'Test Template Task Parent')
|
||||
self.assertEqual(getdate(tasks[0].exp_end_date), calculate_end_date(project, 1, 4))
|
||||
self.assertEqual(getdate(tasks[0].exp_end_date), calculate_end_date(project, 1, 10))
|
||||
|
||||
self.assertEqual(tasks[1].subject, 'Test Template Task Child 1')
|
||||
self.assertEqual(getdate(tasks[1].exp_end_date), calculate_end_date(project, 1, 3))
|
||||
|
@ -737,28 +737,34 @@ erpnext.TransactionController = erpnext.taxes_and_totals.extend({
|
||||
this.frm.trigger("item_code", cdt, cdn);
|
||||
}
|
||||
else {
|
||||
var valid_serial_nos = [];
|
||||
var serialnos = [];
|
||||
// Replacing all occurences of comma with carriage return
|
||||
item.serial_no = item.serial_no.replace(/,/g, '\n');
|
||||
serialnos = item.serial_no.split("\n");
|
||||
for (var i = 0; i < serialnos.length; i++) {
|
||||
if (serialnos[i] != "") {
|
||||
valid_serial_nos.push(serialnos[i]);
|
||||
}
|
||||
}
|
||||
item.conversion_factor = item.conversion_factor || 1;
|
||||
|
||||
refresh_field("serial_no", item.name, item.parentfield);
|
||||
if(!doc.is_return && cint(user_defaults.set_qty_in_transactions_based_on_serial_no_input)) {
|
||||
frappe.model.set_value(item.doctype, item.name,
|
||||
"qty", valid_serial_nos.length / item.conversion_factor);
|
||||
frappe.model.set_value(item.doctype, item.name, "stock_qty", valid_serial_nos.length);
|
||||
if (!doc.is_return && cint(frappe.user_defaults.set_qty_in_transactions_based_on_serial_no_input)) {
|
||||
setTimeout(() => {
|
||||
me.update_qty(cdt, cdn);
|
||||
}, 10000);
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
update_qty: function(cdt, cdn) {
|
||||
var valid_serial_nos = [];
|
||||
var serialnos = [];
|
||||
var item = frappe.get_doc(cdt, cdn);
|
||||
serialnos = item.serial_no.split("\n");
|
||||
for (var i = 0; i < serialnos.length; i++) {
|
||||
if (serialnos[i] != "") {
|
||||
valid_serial_nos.push(serialnos[i]);
|
||||
}
|
||||
}
|
||||
frappe.model.set_value(item.doctype, item.name,
|
||||
"qty", valid_serial_nos.length / item.conversion_factor);
|
||||
frappe.model.set_value(item.doctype, item.name, "stock_qty", valid_serial_nos.length);
|
||||
},
|
||||
|
||||
validate: function() {
|
||||
this.calculate_taxes_and_totals(false);
|
||||
},
|
||||
|
@ -787,6 +787,8 @@ class GSPConnector():
|
||||
|
||||
self.invoice.irn = res.get('Irn')
|
||||
self.invoice.ewaybill = res.get('EwbNo')
|
||||
self.invoice.ack_no = res.get('AckNo')
|
||||
self.invoice.ack_date = res.get('AckDt')
|
||||
self.invoice.signed_einvoice = dec_signed_invoice
|
||||
self.invoice.signed_qr_code = res.get('SignedQRCode')
|
||||
|
||||
|
@ -1,8 +0,0 @@
|
||||
// Copyright (c) 2016, Frappe Technologies Pvt. Ltd. and contributors
|
||||
// For license information, please see license.txt
|
||||
|
||||
frappe.ui.form.on('Lead Source', {
|
||||
refresh: function(frm) {
|
||||
|
||||
}
|
||||
});
|
@ -1,131 +0,0 @@
|
||||
{
|
||||
"allow_copy": 0,
|
||||
"allow_import": 0,
|
||||
"allow_rename": 1,
|
||||
"autoname": "field:source_name",
|
||||
"beta": 0,
|
||||
"creation": "2016-09-16 01:47:47.382372",
|
||||
"custom": 0,
|
||||
"docstatus": 0,
|
||||
"doctype": "DocType",
|
||||
"document_type": "",
|
||||
"editable_grid": 1,
|
||||
"fields": [
|
||||
{
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"columns": 0,
|
||||
"fieldname": "source_name",
|
||||
"fieldtype": "Data",
|
||||
"hidden": 0,
|
||||
"ignore_user_permissions": 0,
|
||||
"ignore_xss_filter": 0,
|
||||
"in_filter": 0,
|
||||
"in_list_view": 0,
|
||||
"label": "Source Name",
|
||||
"length": 0,
|
||||
"no_copy": 0,
|
||||
"permlevel": 0,
|
||||
"precision": "",
|
||||
"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,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"columns": 0,
|
||||
"fieldname": "details",
|
||||
"fieldtype": "Text Editor",
|
||||
"hidden": 0,
|
||||
"ignore_user_permissions": 0,
|
||||
"ignore_xss_filter": 0,
|
||||
"in_filter": 0,
|
||||
"in_list_view": 0,
|
||||
"label": "Details",
|
||||
"length": 0,
|
||||
"no_copy": 0,
|
||||
"permlevel": 0,
|
||||
"precision": "",
|
||||
"print_hide": 0,
|
||||
"print_hide_if_no_value": 0,
|
||||
"read_only": 0,
|
||||
"report_hide": 0,
|
||||
"reqd": 0,
|
||||
"search_index": 0,
|
||||
"set_only_once": 0,
|
||||
"unique": 0
|
||||
}
|
||||
],
|
||||
"hide_heading": 0,
|
||||
"hide_toolbar": 0,
|
||||
"idx": 0,
|
||||
"image_view": 0,
|
||||
"in_create": 0,
|
||||
|
||||
"is_submittable": 0,
|
||||
"issingle": 0,
|
||||
"istable": 0,
|
||||
"max_attachments": 0,
|
||||
"modified": "2020-09-16 02:03:01.441622",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Selling",
|
||||
"name": "Lead Source",
|
||||
"name_case": "",
|
||||
"owner": "Administrator",
|
||||
"permissions": [
|
||||
{
|
||||
"amend": 0,
|
||||
"apply_user_permissions": 0,
|
||||
"cancel": 0,
|
||||
"create": 1,
|
||||
"delete": 1,
|
||||
"email": 1,
|
||||
"export": 1,
|
||||
"if_owner": 0,
|
||||
"import": 0,
|
||||
"permlevel": 0,
|
||||
"print": 1,
|
||||
"read": 1,
|
||||
"report": 1,
|
||||
"role": "Sales Manager",
|
||||
"set_user_permissions": 0,
|
||||
"share": 1,
|
||||
"submit": 0,
|
||||
"write": 1
|
||||
},
|
||||
{
|
||||
"amend": 0,
|
||||
"apply_user_permissions": 0,
|
||||
"cancel": 0,
|
||||
"create": 1,
|
||||
"delete": 0,
|
||||
"email": 1,
|
||||
"export": 1,
|
||||
"if_owner": 0,
|
||||
"import": 0,
|
||||
"permlevel": 0,
|
||||
"print": 1,
|
||||
"read": 1,
|
||||
"report": 1,
|
||||
"role": "Sales User",
|
||||
"set_user_permissions": 0,
|
||||
"share": 1,
|
||||
"submit": 0,
|
||||
"write": 1
|
||||
}
|
||||
],
|
||||
"quick_entry": 1,
|
||||
"read_only": 0,
|
||||
"read_only_onload": 0,
|
||||
"sort_field": "modified",
|
||||
"sort_order": "DESC",
|
||||
"track_seen": 0
|
||||
}
|
@ -17,7 +17,7 @@ frappe.ui.form.on('Global Defaults', {
|
||||
method: "frappe.client.get_list",
|
||||
args: {
|
||||
doctype: "UOM Conversion Factor",
|
||||
filters: { "category": "Length" },
|
||||
filters: { "category": __("Length") },
|
||||
fields: ["to_uom"],
|
||||
limit_page_length: 500
|
||||
},
|
||||
|
@ -1,14 +1,14 @@
|
||||
frappe.provide('erpnext.stock');
|
||||
|
||||
erpnext.stock.ItemDashboard = Class.extend({
|
||||
init: function(opts) {
|
||||
init: function (opts) {
|
||||
$.extend(this, opts);
|
||||
this.make();
|
||||
},
|
||||
make: function() {
|
||||
make: function () {
|
||||
var me = this;
|
||||
this.start = 0;
|
||||
if(!this.sort_by) {
|
||||
if (!this.sort_by) {
|
||||
this.sort_by = 'projected_qty';
|
||||
this.sort_order = 'asc';
|
||||
}
|
||||
@ -16,22 +16,25 @@ erpnext.stock.ItemDashboard = Class.extend({
|
||||
this.content = $(frappe.render_template('item_dashboard')).appendTo(this.parent);
|
||||
this.result = this.content.find('.result');
|
||||
|
||||
this.content.on('click', '.btn-move', function() {
|
||||
handle_move_add($(this), "Move")
|
||||
this.content.on('click', '.btn-move', function () {
|
||||
handle_move_add($(this), "Move");
|
||||
});
|
||||
|
||||
this.content.on('click', '.btn-add', function() {
|
||||
handle_move_add($(this), "Add")
|
||||
this.content.on('click', '.btn-add', function () {
|
||||
handle_move_add($(this), "Add");
|
||||
});
|
||||
|
||||
this.content.on('click', '.btn-edit', function() {
|
||||
this.content.on('click', '.btn-edit', function () {
|
||||
let item = unescape($(this).attr('data-item'));
|
||||
let warehouse = unescape($(this).attr('data-warehouse'));
|
||||
let company = unescape($(this).attr('data-company'));
|
||||
frappe.db.get_value('Putaway Rule',
|
||||
{'item_code': item, 'warehouse': warehouse, 'company': company}, 'name', (r) => {
|
||||
frappe.set_route("Form", "Putaway Rule", r.name);
|
||||
});
|
||||
frappe.db.get_value('Putaway Rule', {
|
||||
'item_code': item,
|
||||
'warehouse': warehouse,
|
||||
'company': company
|
||||
}, 'name', (r) => {
|
||||
frappe.set_route("Form", "Putaway Rule", r.name);
|
||||
});
|
||||
});
|
||||
|
||||
function handle_move_add(element, action) {
|
||||
@ -39,23 +42,26 @@ erpnext.stock.ItemDashboard = Class.extend({
|
||||
let warehouse = unescape(element.attr('data-warehouse'));
|
||||
let actual_qty = unescape(element.attr('data-actual_qty'));
|
||||
let disable_quick_entry = Number(unescape(element.attr('data-disable_quick_entry')));
|
||||
let entry_type = action === "Move" ? "Material Transfer": null;
|
||||
let entry_type = action === "Move" ? "Material Transfer" : null;
|
||||
|
||||
if (disable_quick_entry) {
|
||||
open_stock_entry(item, warehouse, entry_type);
|
||||
} else {
|
||||
if (action === "Add") {
|
||||
let rate = unescape($(this).attr('data-rate'));
|
||||
erpnext.stock.move_item(item, null, warehouse, actual_qty, rate, function() { me.refresh(); });
|
||||
}
|
||||
else {
|
||||
erpnext.stock.move_item(item, warehouse, null, actual_qty, null, function() { me.refresh(); });
|
||||
erpnext.stock.move_item(item, null, warehouse, actual_qty, rate, function () {
|
||||
me.refresh();
|
||||
});
|
||||
} else {
|
||||
erpnext.stock.move_item(item, warehouse, null, actual_qty, null, function () {
|
||||
me.refresh();
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function open_stock_entry(item, warehouse, entry_type) {
|
||||
frappe.model.with_doctype('Stock Entry', function() {
|
||||
frappe.model.with_doctype('Stock Entry', function () {
|
||||
var doc = frappe.model.get_new_doc('Stock Entry');
|
||||
if (entry_type) doc.stock_entry_type = entry_type;
|
||||
|
||||
@ -64,18 +70,18 @@ erpnext.stock.ItemDashboard = Class.extend({
|
||||
row.s_warehouse = warehouse;
|
||||
|
||||
frappe.set_route('Form', doc.doctype, doc.name);
|
||||
})
|
||||
});
|
||||
}
|
||||
|
||||
// more
|
||||
this.content.find('.btn-more').on('click', function() {
|
||||
this.content.find('.btn-more').on('click', function () {
|
||||
me.start += me.page_length;
|
||||
me.refresh();
|
||||
});
|
||||
|
||||
},
|
||||
refresh: function() {
|
||||
if(this.before_refresh) {
|
||||
refresh: function () {
|
||||
if (this.before_refresh) {
|
||||
this.before_refresh();
|
||||
}
|
||||
|
||||
@ -94,13 +100,13 @@ erpnext.stock.ItemDashboard = Class.extend({
|
||||
frappe.call({
|
||||
method: this.method,
|
||||
args: args,
|
||||
callback: function(r) {
|
||||
callback: function (r) {
|
||||
me.render(r.message);
|
||||
}
|
||||
});
|
||||
},
|
||||
render: function(data) {
|
||||
if (this.start===0) {
|
||||
render: function (data) {
|
||||
if (this.start === 0) {
|
||||
this.max_count = 0;
|
||||
this.result.empty();
|
||||
}
|
||||
@ -115,7 +121,7 @@ erpnext.stock.ItemDashboard = Class.extend({
|
||||
this.max_count = this.max_count;
|
||||
|
||||
// show more button
|
||||
if (data && data.length===(this.page_length + 1)) {
|
||||
if (data && data.length === (this.page_length + 1)) {
|
||||
this.content.find('.more').removeClass('hidden');
|
||||
|
||||
// remove the last element
|
||||
@ -137,15 +143,15 @@ erpnext.stock.ItemDashboard = Class.extend({
|
||||
}
|
||||
},
|
||||
|
||||
get_item_dashboard_data: function(data, max_count, show_item) {
|
||||
if(!max_count) max_count = 0;
|
||||
if(!data) data = [];
|
||||
get_item_dashboard_data: function (data, max_count, show_item) {
|
||||
if (!max_count) max_count = 0;
|
||||
if (!data) data = [];
|
||||
|
||||
data.forEach(function(d) {
|
||||
data.forEach(function (d) {
|
||||
d.actual_or_pending = d.projected_qty + d.reserved_qty + d.reserved_qty_for_production + d.reserved_qty_for_sub_contract;
|
||||
d.pending_qty = 0;
|
||||
d.total_reserved = d.reserved_qty + d.reserved_qty_for_production + d.reserved_qty_for_sub_contract;
|
||||
if(d.actual_or_pending > d.actual_qty) {
|
||||
if (d.actual_or_pending > d.actual_qty) {
|
||||
d.pending_qty = d.actual_or_pending - d.actual_qty;
|
||||
}
|
||||
|
||||
@ -161,16 +167,16 @@ erpnext.stock.ItemDashboard = Class.extend({
|
||||
return {
|
||||
data: data,
|
||||
max_count: max_count,
|
||||
can_write:can_write,
|
||||
can_write: can_write,
|
||||
show_item: show_item || false
|
||||
};
|
||||
},
|
||||
|
||||
get_capacity_dashboard_data: function(data) {
|
||||
get_capacity_dashboard_data: function (data) {
|
||||
if (!data) data = [];
|
||||
|
||||
data.forEach(function(d) {
|
||||
d.color = d.percent_occupied >=80 ? "#f8814f" : "#2490ef";
|
||||
data.forEach(function (d) {
|
||||
d.color = d.percent_occupied >= 80 ? "#f8814f" : "#2490ef";
|
||||
});
|
||||
|
||||
let can_write = 0;
|
||||
@ -185,53 +191,77 @@ erpnext.stock.ItemDashboard = Class.extend({
|
||||
}
|
||||
});
|
||||
|
||||
erpnext.stock.move_item = function(item, source, target, actual_qty, rate, callback) {
|
||||
erpnext.stock.move_item = function (item, source, target, actual_qty, rate, callback) {
|
||||
var dialog = new frappe.ui.Dialog({
|
||||
title: target ? __('Add Item') : __('Move Item'),
|
||||
fields: [
|
||||
{fieldname: 'item_code', label: __('Item'),
|
||||
fieldtype: 'Link', options: 'Item', read_only: 1},
|
||||
{fieldname: 'source', label: __('Source Warehouse'),
|
||||
fieldtype: 'Link', options: 'Warehouse', read_only: 1},
|
||||
{fieldname: 'target', label: __('Target Warehouse'),
|
||||
fieldtype: 'Link', options: 'Warehouse', reqd: 1},
|
||||
{fieldname: 'qty', label: __('Quantity'), reqd: 1,
|
||||
fieldtype: 'Float', description: __('Available {0}', [actual_qty]) },
|
||||
{fieldname: 'rate', label: __('Rate'), fieldtype: 'Currency', hidden: 1 },
|
||||
fields: [{
|
||||
fieldname: 'item_code',
|
||||
label: __('Item'),
|
||||
fieldtype: 'Link',
|
||||
options: 'Item',
|
||||
read_only: 1
|
||||
},
|
||||
{
|
||||
fieldname: 'source',
|
||||
label: __('Source Warehouse'),
|
||||
fieldtype: 'Link',
|
||||
options: 'Warehouse',
|
||||
read_only: 1
|
||||
},
|
||||
{
|
||||
fieldname: 'target',
|
||||
label: __('Target Warehouse'),
|
||||
fieldtype: 'Link',
|
||||
options: 'Warehouse',
|
||||
reqd: 1
|
||||
},
|
||||
{
|
||||
fieldname: 'qty',
|
||||
label: __('Quantity'),
|
||||
reqd: 1,
|
||||
fieldtype: 'Float',
|
||||
description: __('Available {0}', [actual_qty])
|
||||
},
|
||||
{
|
||||
fieldname: 'rate',
|
||||
label: __('Rate'),
|
||||
fieldtype: 'Currency',
|
||||
hidden: 1
|
||||
},
|
||||
],
|
||||
})
|
||||
});
|
||||
dialog.show();
|
||||
dialog.get_field('item_code').set_input(item);
|
||||
|
||||
if(source) {
|
||||
if (source) {
|
||||
dialog.get_field('source').set_input(source);
|
||||
} else {
|
||||
dialog.get_field('source').df.hidden = 1;
|
||||
dialog.get_field('source').refresh();
|
||||
}
|
||||
|
||||
if(rate) {
|
||||
if (rate) {
|
||||
dialog.get_field('rate').set_value(rate);
|
||||
dialog.get_field('rate').df.hidden = 0;
|
||||
dialog.get_field('rate').refresh();
|
||||
}
|
||||
|
||||
if(target) {
|
||||
if (target) {
|
||||
dialog.get_field('target').df.read_only = 1;
|
||||
dialog.get_field('target').value = target;
|
||||
dialog.get_field('target').refresh();
|
||||
}
|
||||
|
||||
dialog.set_primary_action(__('Submit'), function() {
|
||||
dialog.set_primary_action(__('Submit'), function () {
|
||||
var values = dialog.get_values();
|
||||
if(!values) {
|
||||
if (!values) {
|
||||
return;
|
||||
}
|
||||
if(source && values.qty > actual_qty) {
|
||||
if (source && values.qty > actual_qty) {
|
||||
frappe.msgprint(__('Quantity must be less than or equal to {0}', [actual_qty]));
|
||||
return;
|
||||
}
|
||||
if(values.source === values.target) {
|
||||
if (values.source === values.target) {
|
||||
frappe.msgprint(__('Source and target warehouse must be different'));
|
||||
}
|
||||
|
||||
@ -239,21 +269,21 @@ erpnext.stock.move_item = function(item, source, target, actual_qty, rate, callb
|
||||
method: 'erpnext.stock.doctype.stock_entry.stock_entry_utils.make_stock_entry',
|
||||
args: values,
|
||||
freeze: true,
|
||||
callback: function(r) {
|
||||
callback: function (r) {
|
||||
frappe.show_alert(__('Stock Entry {0} created',
|
||||
['<a href="/app/stock-entry/'+r.message.name+'">' + r.message.name+ '</a>']));
|
||||
['<a href="/app/stock-entry/' + r.message.name + '">' + r.message.name + '</a>']));
|
||||
dialog.hide();
|
||||
callback(r);
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
$('<p style="margin-left: 10px;"><a class="link-open text-muted small">'
|
||||
+ __("Add more items or open full form") + '</a></p>')
|
||||
$('<p style="margin-left: 10px;"><a class="link-open text-muted small">' +
|
||||
__("Add more items or open full form") + '</a></p>')
|
||||
.appendTo(dialog.body)
|
||||
.find('.link-open')
|
||||
.on('click', function() {
|
||||
frappe.model.with_doctype('Stock Entry', function() {
|
||||
.on('click', function () {
|
||||
frappe.model.with_doctype('Stock Entry', function () {
|
||||
var doc = frappe.model.get_new_doc('Stock Entry');
|
||||
doc.from_warehouse = dialog.get_value('source');
|
||||
doc.to_warehouse = dialog.get_value('target');
|
||||
@ -266,6 +296,6 @@ erpnext.stock.move_item = function(item, source, target, actual_qty, rate, callb
|
||||
row.transfer_qty = dialog.get_value('qty');
|
||||
row.basic_rate = dialog.get_value('rate');
|
||||
frappe.set_route('Form', doc.doctype, doc.name);
|
||||
})
|
||||
});
|
||||
});
|
||||
}
|
||||
};
|
||||
|
@ -2,6 +2,7 @@ from __future__ import unicode_literals
|
||||
|
||||
import frappe
|
||||
from frappe.model.db_query import DatabaseQuery
|
||||
from frappe.utils import flt, cint
|
||||
|
||||
@frappe.whitelist()
|
||||
def get_data(item_code=None, warehouse=None, item_group=None,
|
||||
@ -42,11 +43,20 @@ def get_data(item_code=None, warehouse=None, item_group=None,
|
||||
limit_start=start,
|
||||
limit_page_length='21')
|
||||
|
||||
precision = cint(frappe.db.get_single_value("System Settings", "float_precision"))
|
||||
|
||||
for item in items:
|
||||
item.update({
|
||||
'item_name': frappe.get_cached_value("Item", item.item_code, 'item_name'),
|
||||
'disable_quick_entry': frappe.get_cached_value("Item", item.item_code, 'has_batch_no')
|
||||
or frappe.get_cached_value("Item", item.item_code, 'has_serial_no'),
|
||||
'item_name': frappe.get_cached_value(
|
||||
"Item", item.item_code, 'item_name'),
|
||||
'disable_quick_entry': frappe.get_cached_value(
|
||||
"Item", item.item_code, 'has_batch_no')
|
||||
or frappe.get_cached_value(
|
||||
"Item", item.item_code, 'has_serial_no'),
|
||||
'projected_qty': flt(item.projected_qty, precision),
|
||||
'reserved_qty': flt(item.reserved_qty, precision),
|
||||
'reserved_qty_for_production': flt(item.reserved_qty_for_production, precision),
|
||||
'reserved_qty_for_sub_contract': flt(item.reserved_qty_for_sub_contract, precision),
|
||||
'actual_qty': flt(item.actual_qty, precision),
|
||||
})
|
||||
|
||||
return items
|
||||
|
@ -494,7 +494,8 @@ def make_item_variant():
|
||||
|
||||
test_records = frappe.get_test_records('Item')
|
||||
|
||||
def create_item(item_code, is_stock_item=None, valuation_rate=0, warehouse=None, is_customer_provided_item=None, customer=None, is_purchase_item=None, opening_stock=None):
|
||||
def create_item(item_code, is_stock_item=None, valuation_rate=0, warehouse=None, is_customer_provided_item=None,
|
||||
customer=None, is_purchase_item=None, opening_stock=None, company=None):
|
||||
if not frappe.db.exists("Item", item_code):
|
||||
item = frappe.new_doc("Item")
|
||||
item.item_code = item_code
|
||||
@ -509,7 +510,7 @@ def create_item(item_code, is_stock_item=None, valuation_rate=0, warehouse=None,
|
||||
item.customer = customer or ''
|
||||
item.append("item_defaults", {
|
||||
"default_warehouse": warehouse or '_Test Warehouse - _TC',
|
||||
"company": "_Test Company"
|
||||
"company": company or "_Test Company"
|
||||
})
|
||||
item.save()
|
||||
else:
|
||||
|
@ -346,7 +346,7 @@ def create_delivery_note(source_name, target_doc=None):
|
||||
|
||||
if dn_item:
|
||||
dn_item.warehouse = location.warehouse
|
||||
dn_item.qty = location.picked_qty
|
||||
dn_item.qty = flt(location.picked_qty) / (flt(location.conversion_factor) or 1)
|
||||
dn_item.batch_no = location.batch_no
|
||||
dn_item.serial_no = location.serial_no
|
||||
|
||||
|
@ -9,6 +9,7 @@ test_dependencies = ['Item', 'Sales Invoice', 'Stock Entry', 'Batch']
|
||||
|
||||
from erpnext.stock.doctype.purchase_receipt.test_purchase_receipt import make_purchase_receipt
|
||||
from erpnext.stock.doctype.item.test_item import create_item
|
||||
from erpnext.stock.doctype.pick_list.pick_list import create_delivery_note
|
||||
from erpnext.stock.doctype.stock_reconciliation.stock_reconciliation \
|
||||
import EmptyStockReconciliationItemsError
|
||||
|
||||
@ -291,6 +292,61 @@ class TestPickList(unittest.TestCase):
|
||||
self.assertEqual(pick_list.locations[1].qty, 5)
|
||||
self.assertEqual(pick_list.locations[1].sales_order_item, sales_order.items[0].name)
|
||||
|
||||
def test_pick_list_for_items_with_multiple_UOM(self):
|
||||
purchase_receipt = make_purchase_receipt(item_code="_Test Item", qty=10)
|
||||
purchase_receipt.submit()
|
||||
|
||||
sales_order = frappe.get_doc({
|
||||
'doctype': 'Sales Order',
|
||||
'customer': '_Test Customer',
|
||||
'company': '_Test Company',
|
||||
'items': [{
|
||||
'item_code': '_Test Item',
|
||||
'qty': 1,
|
||||
'conversion_factor': 5,
|
||||
'delivery_date': frappe.utils.today()
|
||||
}, {
|
||||
'item_code': '_Test Item',
|
||||
'qty': 1,
|
||||
'conversion_factor': 1,
|
||||
'delivery_date': frappe.utils.today()
|
||||
}],
|
||||
}).insert()
|
||||
sales_order.submit()
|
||||
|
||||
pick_list = frappe.get_doc({
|
||||
'doctype': 'Pick List',
|
||||
'company': '_Test Company',
|
||||
'customer': '_Test Customer',
|
||||
'items_based_on': 'Sales Order',
|
||||
'locations': [{
|
||||
'item_code': '_Test Item',
|
||||
'qty': 1,
|
||||
'stock_qty': 5,
|
||||
'conversion_factor': 5,
|
||||
'sales_order': sales_order.name,
|
||||
'sales_order_item': sales_order.items[0].name ,
|
||||
}, {
|
||||
'item_code': '_Test Item',
|
||||
'qty': 1,
|
||||
'stock_qty': 1,
|
||||
'conversion_factor': 1,
|
||||
'sales_order': sales_order.name,
|
||||
'sales_order_item': sales_order.items[1].name ,
|
||||
}]
|
||||
})
|
||||
pick_list.set_item_locations()
|
||||
pick_list.submit()
|
||||
|
||||
delivery_note = create_delivery_note(pick_list.name)
|
||||
|
||||
self.assertEqual(pick_list.locations[0].qty, delivery_note.items[0].qty)
|
||||
self.assertEqual(pick_list.locations[1].qty, delivery_note.items[1].qty)
|
||||
self.assertEqual(sales_order.items[0].conversion_factor, delivery_note.items[0].conversion_factor)
|
||||
|
||||
pick_list.cancel()
|
||||
sales_order.cancel()
|
||||
purchase_receipt.cancel()
|
||||
|
||||
# def test_pick_list_skips_items_in_expired_batch(self):
|
||||
# pass
|
||||
|
@ -179,11 +179,15 @@ class TestStockEntry(unittest.TestCase):
|
||||
def test_material_transfer_gl_entry(self):
|
||||
company = frappe.db.get_value('Warehouse', 'Stores - TCP1', 'company')
|
||||
|
||||
mtn = make_stock_entry(item_code="_Test Item", source="Stores - TCP1",
|
||||
item_code = 'Hand Sanitizer - 001'
|
||||
create_item(item_code =item_code, is_stock_item = 1,
|
||||
is_purchase_item=1, opening_stock=1000, valuation_rate=10, company=company, warehouse="Stores - TCP1")
|
||||
|
||||
mtn = make_stock_entry(item_code=item_code, source="Stores - TCP1",
|
||||
target="Finished Goods - TCP1", qty=45, company=company)
|
||||
|
||||
self.check_stock_ledger_entries("Stock Entry", mtn.name,
|
||||
[["_Test Item", "Stores - TCP1", -45.0], ["_Test Item", "Finished Goods - TCP1", 45.0]])
|
||||
[[item_code, "Stores - TCP1", -45.0], [item_code, "Finished Goods - TCP1", 45.0]])
|
||||
|
||||
source_warehouse_account = get_inventory_account(mtn.company, mtn.get("items")[0].s_warehouse)
|
||||
|
||||
|
@ -48,44 +48,62 @@ frappe.ui.form.on("Issue", {
|
||||
}
|
||||
},
|
||||
|
||||
refresh: function (frm) {
|
||||
if (frm.doc.status !== "Closed") {
|
||||
if (frm.doc.service_level_agreement && frm.doc.agreement_status === "Ongoing") {
|
||||
frappe.call({
|
||||
"method": "frappe.client.get",
|
||||
args: {
|
||||
doctype: "Service Level Agreement",
|
||||
name: frm.doc.service_level_agreement
|
||||
},
|
||||
callback: function(data) {
|
||||
let statuses = data.message.pause_sla_on;
|
||||
const hold_statuses = [];
|
||||
$.each(statuses, (_i, entry) => {
|
||||
hold_statuses.push(entry.status);
|
||||
});
|
||||
if (hold_statuses.includes(frm.doc.status)) {
|
||||
frm.dashboard.clear_headline();
|
||||
let message = {"indicator": "orange", "msg": __("SLA is on hold since {0}", [moment(frm.doc.on_hold_since).fromNow(true)])};
|
||||
frm.dashboard.set_headline_alert(
|
||||
'<div class="row">' +
|
||||
'<div class="col-xs-12">' +
|
||||
'<span class="indicator whitespace-nowrap '+ message.indicator +'"><span>'+ message.msg +'</span></span> ' +
|
||||
'</div>' +
|
||||
'</div>'
|
||||
);
|
||||
} else {
|
||||
set_time_to_resolve_and_response(frm);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
refresh: function(frm) {
|
||||
|
||||
frm.add_custom_button(__("Close"), function () {
|
||||
// alert messages
|
||||
if (frm.doc.status !== "Closed" && frm.doc.service_level_agreement
|
||||
&& frm.doc.agreement_status === "Ongoing") {
|
||||
frappe.call({
|
||||
"method": "frappe.client.get",
|
||||
args: {
|
||||
doctype: "Service Level Agreement",
|
||||
name: frm.doc.service_level_agreement
|
||||
},
|
||||
callback: function(data) {
|
||||
let statuses = data.message.pause_sla_on;
|
||||
const hold_statuses = [];
|
||||
$.each(statuses, (_i, entry) => {
|
||||
hold_statuses.push(entry.status);
|
||||
});
|
||||
if (hold_statuses.includes(frm.doc.status)) {
|
||||
frm.dashboard.clear_headline();
|
||||
let message = { "indicator": "orange", "msg": __("SLA is on hold since {0}", [moment(frm.doc.on_hold_since).fromNow(true)]) };
|
||||
frm.dashboard.set_headline_alert(
|
||||
'<div class="row">' +
|
||||
'<div class="col-xs-12">' +
|
||||
'<span class="indicator whitespace-nowrap ' + message.indicator + '"><span>' + message.msg + '</span></span> ' +
|
||||
'</div>' +
|
||||
'</div>'
|
||||
);
|
||||
} else {
|
||||
set_time_to_resolve_and_response(frm);
|
||||
}
|
||||
}
|
||||
});
|
||||
} else if (frm.doc.service_level_agreement) {
|
||||
frm.dashboard.clear_headline();
|
||||
|
||||
let agreement_status = (frm.doc.agreement_status == "Fulfilled") ?
|
||||
{ "indicator": "green", "msg": "Service Level Agreement has been fulfilled" } :
|
||||
{ "indicator": "red", "msg": "Service Level Agreement Failed" };
|
||||
|
||||
frm.dashboard.set_headline_alert(
|
||||
'<div class="row">' +
|
||||
'<div class="col-xs-12">' +
|
||||
'<span class="indicator whitespace-nowrap ' + agreement_status.indicator + '"><span class="hidden-xs">' + agreement_status.msg + '</span></span> ' +
|
||||
'</div>' +
|
||||
'</div>'
|
||||
);
|
||||
}
|
||||
|
||||
// buttons
|
||||
if (frm.doc.status !== "Closed") {
|
||||
frm.add_custom_button(__("Close"), function() {
|
||||
frm.set_value("status", "Closed");
|
||||
frm.save();
|
||||
});
|
||||
|
||||
frm.add_custom_button(__("Task"), function () {
|
||||
frm.add_custom_button(__("Task"), function() {
|
||||
frappe.model.open_mapped_doc({
|
||||
method: "erpnext.support.doctype.issue.issue.make_task",
|
||||
frm: frm
|
||||
@ -93,23 +111,7 @@ frappe.ui.form.on("Issue", {
|
||||
}, __("Create"));
|
||||
|
||||
} else {
|
||||
if (frm.doc.service_level_agreement) {
|
||||
frm.dashboard.clear_headline();
|
||||
|
||||
let agreement_status = (frm.doc.agreement_status == "Fulfilled") ?
|
||||
{"indicator": "green", "msg": "Service Level Agreement has been fulfilled"} :
|
||||
{"indicator": "red", "msg": "Service Level Agreement Failed"};
|
||||
|
||||
frm.dashboard.set_headline_alert(
|
||||
'<div class="row">' +
|
||||
'<div class="col-xs-12">' +
|
||||
'<span class="indicator whitespace-nowrap '+ agreement_status.indicator +'"><span class="hidden-xs">'+ agreement_status.msg +'</span></span> ' +
|
||||
'</div>' +
|
||||
'</div>'
|
||||
);
|
||||
}
|
||||
|
||||
frm.add_custom_button(__("Reopen"), function () {
|
||||
frm.add_custom_button(__("Reopen"), function() {
|
||||
frm.set_value("status", "Open");
|
||||
frm.save();
|
||||
});
|
||||
|
@ -7,7 +7,7 @@ import json
|
||||
from frappe import _
|
||||
from frappe import utils
|
||||
from frappe.model.document import Document
|
||||
from frappe.utils import now_datetime, getdate, get_weekdays, add_to_date, get_time, get_datetime, time_diff_in_seconds
|
||||
from frappe.utils import cint, now_datetime, getdate, get_weekdays, add_to_date, get_time, get_datetime, time_diff_in_seconds
|
||||
from datetime import datetime, timedelta
|
||||
from frappe.model.mapper import get_mapped_doc
|
||||
from frappe.utils.user import is_website_user
|
||||
@ -128,8 +128,8 @@ class Issue(Document):
|
||||
|
||||
def update_agreement_status(self):
|
||||
if self.service_level_agreement and self.agreement_status == "Ongoing":
|
||||
if frappe.db.get_value("Issue", self.name, "response_by_variance") < 0 or \
|
||||
frappe.db.get_value("Issue", self.name, "resolution_by_variance") < 0:
|
||||
if cint(frappe.db.get_value("Issue", self.name, "response_by_variance")) < 0 or \
|
||||
cint(frappe.db.get_value("Issue", self.name, "resolution_by_variance")) < 0:
|
||||
|
||||
self.agreement_status = "Failed"
|
||||
else:
|
||||
|
Loading…
x
Reference in New Issue
Block a user