Merge branch 'develop' into loan_manager_amend

This commit is contained in:
Deepesh Garg 2021-04-20 19:08:31 +05:30 committed by GitHub
commit 6d88cf9ede
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
28 changed files with 570 additions and 486 deletions

View File

@ -85,10 +85,9 @@ jobs:
run: | run: |
cp ~/frappe-bench/sites/.coverage ${GITHUB_WORKSPACE} cp ~/frappe-bench/sites/.coverage ${GITHUB_WORKSPACE}
cd ${GITHUB_WORKSPACE} cd ${GITHUB_WORKSPACE}
pip install coveralls==2.2.0 pip install coveralls==3.0.1
pip install coverage==4.5.4 pip install coverage==5.5
coveralls coveralls --service=github
env: env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
COVERALLS_REPO_TOKEN: ${{ secrets.COVERALLS_TOKEN }} COVERALLS_REPO_TOKEN: ${{ secrets.COVERALLS_TOKEN }}

View File

@ -12,6 +12,7 @@
"frozen_accounts_modifier", "frozen_accounts_modifier",
"determine_address_tax_category_from", "determine_address_tax_category_from",
"over_billing_allowance", "over_billing_allowance",
"role_allowed_to_over_bill",
"column_break_4", "column_break_4",
"credit_controller", "credit_controller",
"check_supplier_invoice_uniqueness", "check_supplier_invoice_uniqueness",
@ -226,6 +227,13 @@
"fieldname": "delete_linked_ledger_entries", "fieldname": "delete_linked_ledger_entries",
"fieldtype": "Check", "fieldtype": "Check",
"label": "Delete Accounting and Stock Ledger Entries on deletion of Transaction" "label": "Delete Accounting and Stock Ledger Entries on deletion of Transaction"
},
{
"description": "Users with this role are allowed to over bill above the allowance percentage",
"fieldname": "role_allowed_to_over_bill",
"fieldtype": "Link",
"label": "Role Allowed to Over Bill ",
"options": "Role"
} }
], ],
"icon": "icon-cog", "icon": "icon-cog",
@ -233,7 +241,7 @@
"index_web_pages_for_search": 1, "index_web_pages_for_search": 1,
"issingle": 1, "issingle": 1,
"links": [], "links": [],
"modified": "2021-01-05 13:04:00.118892", "modified": "2021-03-11 18:52:05.601996",
"modified_by": "Administrator", "modified_by": "Administrator",
"module": "Accounts", "module": "Accounts",
"name": "Accounts Settings", "name": "Accounts Settings",

View File

@ -89,8 +89,8 @@ class POSClosingEntry(StatusUpdater):
@frappe.whitelist() @frappe.whitelist()
@frappe.validate_and_sanitize_search_inputs @frappe.validate_and_sanitize_search_inputs
def get_cashiers(doctype, txt, searchfield, start, page_len, filters): def get_cashiers(doctype, txt, searchfield, start, page_len, filters):
cashiers_list = frappe.get_all("POS Profile User", filters=filters, fields=['user']) cashiers_list = frappe.get_all("POS Profile User", filters=filters, fields=['user'], as_list=1)
return [c['user'] for c in cashiers_list] return [c for c in cashiers_list]
@frappe.whitelist() @frappe.whitelist()
def get_pos_invoices(start, end, pos_profile, user): def get_pos_invoices(start, end, pos_profile, user):

View File

@ -173,7 +173,7 @@ def _get_tree_conditions(args, parenttype, table, allow_blank=True):
if parenttype in ["Customer Group", "Item Group", "Territory"]: if parenttype in ["Customer Group", "Item Group", "Territory"]:
parent_field = "parent_{0}".format(frappe.scrub(parenttype)) parent_field = "parent_{0}".format(frappe.scrub(parenttype))
root_name = frappe.db.get_list(parenttype, root_name = frappe.db.get_list(parenttype,
{"is_group": 1, parent_field: ("is", "not set")}, "name", as_list=1) {"is_group": 1, parent_field: ("is", "not set")}, "name", as_list=1, ignore_permissions=True)
if root_name and root_name[0][0]: if root_name and root_name[0][0]:
parent_groups.append(root_name[0][0]) parent_groups.append(root_name[0][0])

View File

@ -717,7 +717,9 @@ class AccountsController(TransactionBase):
total_billed_amt = abs(total_billed_amt) total_billed_amt = abs(total_billed_amt)
max_allowed_amt = abs(max_allowed_amt) max_allowed_amt = abs(max_allowed_amt)
if total_billed_amt - max_allowed_amt > 0.01: role_allowed_to_over_bill = frappe.db.get_single_value('Accounts Settings', 'role_allowed_to_over_bill')
if total_billed_amt - max_allowed_amt > 0.01 and role_allowed_to_over_bill not in frappe.get_roles():
frappe.throw(_("Cannot overbill for Item {0} in row {1} more than {2}. To allow over-billing, please set allowance in Accounts Settings") frappe.throw(_("Cannot overbill for Item {0} in row {1} more than {2}. To allow over-billing, please set allowance in Accounts Settings")
.format(item.item_code, item.idx, max_allowed_amt)) .format(item.item_code, item.idx, max_allowed_amt))

View File

@ -201,10 +201,14 @@ class StatusUpdater(Document):
get_allowance_for(item['item_code'], self.item_allowance, get_allowance_for(item['item_code'], self.item_allowance,
self.global_qty_allowance, self.global_amount_allowance, qty_or_amount) self.global_qty_allowance, self.global_amount_allowance, qty_or_amount)
role_allowed_to_over_deliver_receive = frappe.db.get_single_value('Stock Settings', 'role_allowed_to_over_deliver_receive')
role_allowed_to_over_bill = frappe.db.get_single_value('Accounts Settings', 'role_allowed_to_over_bill')
role = role_allowed_to_over_deliver_receive if qty_or_amount == 'qty' else role_allowed_to_over_bill
overflow_percent = ((item[args['target_field']] - item[args['target_ref_field']]) / overflow_percent = ((item[args['target_field']] - item[args['target_ref_field']]) /
item[args['target_ref_field']]) * 100 item[args['target_ref_field']]) * 100
if overflow_percent - allowance > 0.01: if overflow_percent - allowance > 0.01 and role not in frappe.get_roles():
item['max_allowed'] = flt(item[args['target_ref_field']] * (100+allowance)/100) item['max_allowed'] = flt(item[args['target_ref_field']] * (100+allowance)/100)
item['reduce_by'] = item[args['target_field']] - item['max_allowed'] item['reduce_by'] = item[args['target_field']] - item['max_allowed']

View File

@ -1,206 +1,64 @@
{ {
"allow_copy": 0, "actions": [],
"allow_events_in_timeline": 0,
"allow_guest_to_view": 0,
"allow_import": 0,
"allow_rename": 0,
"beta": 0,
"creation": "2018-07-12 12:07:36.932333", "creation": "2018-07-12 12:07:36.932333",
"custom": 0,
"docstatus": 0,
"doctype": "DocType", "doctype": "DocType",
"document_type": "",
"editable_grid": 1, "editable_grid": 1,
"engine": "InnoDB", "engine": "InnoDB",
"field_order": [
"service_unit",
"check_in",
"left",
"check_out",
"invoiced"
],
"fields": [ "fields": [
{ {
"allow_bulk_edit": 0,
"allow_in_quick_entry": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "service_unit", "fieldname": "service_unit",
"fieldtype": "Link", "fieldtype": "Link",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 1, "in_list_view": 1,
"in_standard_filter": 0,
"label": "Healthcare Service Unit", "label": "Healthcare Service Unit",
"length": 0,
"no_copy": 0,
"options": "Healthcare Service Unit", "options": "Healthcare Service Unit",
"permlevel": 0, "reqd": 1
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 1,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 1,
"search_index": 0,
"set_only_once": 0,
"translatable": 0,
"unique": 0
}, },
{ {
"allow_bulk_edit": 0,
"allow_in_quick_entry": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "check_in", "fieldname": "check_in",
"fieldtype": "Datetime", "fieldtype": "Datetime",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 1, "in_list_view": 1,
"in_standard_filter": 0, "label": "Check In"
"label": "Check In",
"length": 0,
"no_copy": 0,
"permlevel": 0,
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 1,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"translatable": 0,
"unique": 0
}, },
{ {
"allow_bulk_edit": 0, "default": "0",
"allow_in_quick_entry": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "left", "fieldname": "left",
"fieldtype": "Check", "fieldtype": "Check",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 0,
"in_standard_filter": 0,
"label": "Left", "label": "Left",
"length": 0,
"no_copy": 0,
"permlevel": 0,
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 1, "read_only": 1,
"remember_last_selected_value": 0, "search_index": 1
"report_hide": 0,
"reqd": 0,
"search_index": 1,
"set_only_once": 0,
"translatable": 0,
"unique": 0
}, },
{ {
"allow_bulk_edit": 0,
"allow_in_quick_entry": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "check_out", "fieldname": "check_out",
"fieldtype": "Datetime", "fieldtype": "Datetime",
"hidden": 0, "label": "Check Out"
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 0,
"in_standard_filter": 0,
"label": "Check Out",
"length": 0,
"no_copy": 0,
"permlevel": 0,
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 1,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"translatable": 0,
"unique": 0
}, },
{ {
"allow_bulk_edit": 0,
"allow_in_quick_entry": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"default": "0", "default": "0",
"fieldname": "invoiced", "fieldname": "invoiced",
"fieldtype": "Check", "fieldtype": "Check",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 0,
"in_standard_filter": 0,
"label": "Invoiced", "label": "Invoiced",
"length": 0, "read_only": 1
"no_copy": 0,
"permlevel": 0,
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 1,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"translatable": 0,
"unique": 0
} }
], ],
"has_web_view": 0, "index_web_pages_for_search": 1,
"hide_heading": 0,
"hide_toolbar": 0,
"idx": 0,
"image_view": 0,
"in_create": 0,
"is_submittable": 0,
"issingle": 0,
"istable": 1, "istable": 1,
"max_attachments": 0, "links": [],
"modified": "2018-11-04 03:33:26.958713", "modified": "2021-03-18 15:08:54.634132",
"modified_by": "Administrator", "modified_by": "Administrator",
"module": "Healthcare", "module": "Healthcare",
"name": "Inpatient Occupancy", "name": "Inpatient Occupancy",
"name_case": "",
"owner": "Administrator", "owner": "Administrator",
"permissions": [], "permissions": [],
"quick_entry": 1, "quick_entry": 1,
"read_only": 0,
"read_only_onload": 0,
"restrict_to_domain": "Healthcare", "restrict_to_domain": "Healthcare",
"show_name_in_global_search": 0,
"sort_field": "modified", "sort_field": "modified",
"sort_order": "DESC", "sort_order": "DESC",
"track_changes": 1, "track_changes": 1
"track_seen": 0,
"track_views": 0
} }

View File

@ -185,7 +185,7 @@
"fieldtype": "Datetime", "fieldtype": "Datetime",
"in_list_view": 1, "in_list_view": 1,
"label": "Admitted Datetime", "label": "Admitted Datetime",
"read_only": 1 "permlevel": 2
}, },
{ {
"depends_on": "eval:(doc.expected_length_of_stay > 0)", "depends_on": "eval:(doc.expected_length_of_stay > 0)",
@ -312,7 +312,7 @@
"fieldname": "inpatient_occupancies", "fieldname": "inpatient_occupancies",
"fieldtype": "Table", "fieldtype": "Table",
"options": "Inpatient Occupancy", "options": "Inpatient Occupancy",
"read_only": 1 "permlevel": 2
}, },
{ {
"fieldname": "btn_transfer", "fieldname": "btn_transfer",
@ -407,12 +407,12 @@
"fieldname": "discharge_datetime", "fieldname": "discharge_datetime",
"fieldtype": "Datetime", "fieldtype": "Datetime",
"label": "Discharge Date", "label": "Discharge Date",
"read_only": 1 "permlevel": 2
} }
], ],
"index_web_pages_for_search": 1, "index_web_pages_for_search": 1,
"links": [], "links": [],
"modified": "2021-03-18 14:44:11.689956", "modified": "2021-03-18 15:59:17.318988",
"modified_by": "Administrator", "modified_by": "Administrator",
"module": "Healthcare", "module": "Healthcare",
"name": "Inpatient Record", "name": "Inpatient Record",
@ -465,6 +465,37 @@
"read": 1, "read": 1,
"report": 1, "report": 1,
"role": "Nursing User" "role": "Nursing User"
},
{
"email": 1,
"export": 1,
"permlevel": 2,
"print": 1,
"read": 1,
"report": 1,
"role": "Healthcare Administrator",
"share": 1,
"write": 1
},
{
"email": 1,
"export": 1,
"permlevel": 2,
"print": 1,
"read": 1,
"report": 1,
"role": "Physician",
"share": 1
},
{
"email": 1,
"export": 1,
"permlevel": 2,
"print": 1,
"read": 1,
"report": 1,
"role": "Nursing User",
"share": 1
} }
], ],
"restrict_to_domain": "Healthcare", "restrict_to_domain": "Healthcare",

View File

@ -66,7 +66,7 @@ class CompensatoryLeaveRequest(Document):
else: else:
leave_allocation = self.create_leave_allocation(leave_period, date_difference) leave_allocation = self.create_leave_allocation(leave_period, date_difference)
self.leave_allocation=leave_allocation.name self.db_set("leave_allocation", leave_allocation.name)
else: else:
frappe.throw(_("There is no leave period in between {0} and {1}").format(format_date(self.work_from_date), format_date(self.work_end_date))) frappe.throw(_("There is no leave period in between {0} and {1}").format(format_date(self.work_from_date), format_date(self.work_end_date)))

View File

@ -151,6 +151,10 @@ frappe.ui.form.on('Payroll Entry', {
filters['company'] = frm.doc.company; filters['company'] = frm.doc.company;
filters['start_date'] = frm.doc.start_date; filters['start_date'] = frm.doc.start_date;
filters['end_date'] = frm.doc.end_date; filters['end_date'] = frm.doc.end_date;
filters['salary_slip_based_on_timesheet'] = frm.doc.salary_slip_based_on_timesheet;
filters['payroll_frequency'] = frm.doc.payroll_frequency;
filters['payroll_payable_account'] = frm.doc.payroll_payable_account;
filters['currency'] = frm.doc.currency;
if (frm.doc.department) { if (frm.doc.department) {
filters['department'] = frm.doc.department; filters['department'] = frm.doc.department;

View File

@ -52,49 +52,32 @@ class PayrollEntry(Document):
Returns list of active employees based on selected criteria Returns list of active employees based on selected criteria
and for which salary structure exists and for which salary structure exists
""" """
cond = self.get_filter_condition() self.check_mandatory()
cond += self.get_joining_relieving_condition() filters = self.make_filters()
cond = get_filter_condition(filters)
cond += get_joining_relieving_condition(self.start_date, self.end_date)
condition = '' condition = ''
if self.payroll_frequency: if self.payroll_frequency:
condition = """and payroll_frequency = '%(payroll_frequency)s'"""% {"payroll_frequency": self.payroll_frequency} condition = """and payroll_frequency = '%(payroll_frequency)s'"""% {"payroll_frequency": self.payroll_frequency}
sal_struct = frappe.db.sql_list(""" sal_struct = get_sal_struct(self.company, self.currency, self.salary_slip_based_on_timesheet, condition)
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: if sal_struct:
cond += "and t2.salary_structure IN %(sal_struct)s " cond += "and t2.salary_structure IN %(sal_struct)s "
cond += "and t2.payroll_payable_account = %(payroll_payable_account)s " cond += "and t2.payroll_payable_account = %(payroll_payable_account)s "
cond += "and %(from_date)s >= t2.from_date" cond += "and %(from_date)s >= t2.from_date"
emp_list = frappe.db.sql(""" emp_list = get_emp_list(sal_struct, cond, self.end_date, self.payroll_payable_account)
select emp_list = remove_payrolled_employees(emp_list, self.start_date, self.end_date)
distinct t1.name as employee, t1.employee_name, t1.department, t1.designation
from
`tabEmployee` t1, `tabSalary Structure Assignment` t2
where
t1.name = t2.employee
and t2.docstatus = 1
%s order by t2.from_date desc
""" % cond, {"sal_struct": tuple(sal_struct), "from_date": self.end_date, "payroll_payable_account": self.payroll_payable_account}, as_dict=True)
emp_list = self.remove_payrolled_employees(emp_list)
return emp_list return emp_list
def remove_payrolled_employees(self, emp_list): def make_filters(self):
for employee_details in emp_list: filters = frappe._dict()
if frappe.db.exists("Salary Slip", {"employee": employee_details.employee, "start_date": self.start_date, "end_date": self.end_date, "docstatus": 1}): filters['company'] = self.company
emp_list.remove(employee_details) filters['branch'] = self.branch
filters['department'] = self.department
filters['designation'] = self.designation
return emp_list return filters
@frappe.whitelist() @frappe.whitelist()
def fill_employee_details(self): def fill_employee_details(self):
@ -122,23 +105,6 @@ class PayrollEntry(Document):
if self.validate_attendance: if self.validate_attendance:
return self.validate_employee_attendance() return self.validate_employee_attendance()
def get_filter_condition(self):
self.check_mandatory()
cond = ''
for f in ['company', 'branch', 'department', 'designation']:
if self.get(f):
cond += " and t1." + f + " = " + frappe.db.escape(self.get(f))
return cond
def get_joining_relieving_condition(self):
cond = """
and ifnull(t1.date_of_joining, '0000-00-00') <= '%(end_date)s'
and ifnull(t1.relieving_date, '2199-12-31') >= '%(start_date)s'
""" % {"start_date": self.start_date, "end_date": self.end_date}
return cond
def check_mandatory(self): def check_mandatory(self):
for fieldname in ['company', 'start_date', 'end_date']: for fieldname in ['company', 'start_date', 'end_date']:
if not self.get(fieldname): if not self.get(fieldname):
@ -451,6 +417,53 @@ class PayrollEntry(Document):
marked_days = attendances[0][0] marked_days = attendances[0][0]
return marked_days return marked_days
def get_sal_struct(company, currency, salary_slip_based_on_timesheet, condition):
return 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": company, "currency": currency, "salary_slip_based_on_timesheet": salary_slip_based_on_timesheet})
def get_filter_condition(filters):
cond = ''
for f in ['company', 'branch', 'department', 'designation']:
if filters.get(f):
cond += " and t1." + f + " = " + frappe.db.escape(filters.get(f))
return cond
def get_joining_relieving_condition(start_date, end_date):
cond = """
and ifnull(t1.date_of_joining, '0000-00-00') <= '%(end_date)s'
and ifnull(t1.relieving_date, '2199-12-31') >= '%(start_date)s'
""" % {"start_date": start_date, "end_date": end_date}
return cond
def get_emp_list(sal_struct, cond, end_date, payroll_payable_account):
return frappe.db.sql("""
select
distinct t1.name as employee, t1.employee_name, t1.department, t1.designation
from
`tabEmployee` t1, `tabSalary Structure Assignment` t2
where
t1.name = t2.employee
and t2.docstatus = 1
%s order by t2.from_date desc
""" % cond, {"sal_struct": tuple(sal_struct), "from_date": end_date, "payroll_payable_account": payroll_payable_account}, as_dict=True)
def remove_payrolled_employees(emp_list, start_date, end_date):
for employee_details in emp_list:
if frappe.db.exists("Salary Slip", {"employee": employee_details.employee, "start_date": start_date, "end_date": end_date, "docstatus": 1}):
emp_list.remove(employee_details)
return emp_list
@frappe.whitelist() @frappe.whitelist()
def get_start_end_dates(payroll_frequency, start_date=None, company=None): def get_start_end_dates(payroll_frequency, start_date=None, company=None):
'''Returns dict of start and end dates for given payroll frequency based on start_date''' '''Returns dict of start and end dates for given payroll frequency based on start_date'''
@ -639,39 +652,41 @@ def get_payroll_entries_for_jv(doctype, txt, searchfield, start, page_len, filte
'start': start, 'page_len': page_len 'start': start, 'page_len': page_len
}) })
def get_employee_with_existing_salary_slip(start_date, end_date, company): def get_employee_list(filters):
return frappe.db.sql_list(""" cond = get_filter_condition(filters)
select employee from `tabSalary Slip` cond += get_joining_relieving_condition(filters.start_date, filters.end_date)
where condition = """and payroll_frequency = '%(payroll_frequency)s'"""% {"payroll_frequency": filters.payroll_frequency}
(start_date between %(start_date)s and %(end_date)s sal_struct = get_sal_struct(filters.company, filters.currency, filters.salary_slip_based_on_timesheet, condition)
or if sal_struct:
end_date between %(start_date)s and %(end_date)s cond += "and t2.salary_structure IN %(sal_struct)s "
or cond += "and t2.payroll_payable_account = %(payroll_payable_account)s "
%(start_date)s between start_date and end_date) cond += "and %(from_date)s >= t2.from_date"
and company = %(company)s emp_list = get_emp_list(sal_struct, cond, filters.end_date, filters.payroll_payable_account)
and docstatus = 1 emp_list = remove_payrolled_employees(emp_list, filters.start_date, filters.end_date)
""", {'start_date': start_date, 'end_date': end_date, 'company': company}) return emp_list
@frappe.whitelist() @frappe.whitelist()
@frappe.validate_and_sanitize_search_inputs @frappe.validate_and_sanitize_search_inputs
def employee_query(doctype, txt, searchfield, start, page_len, filters): def employee_query(doctype, txt, searchfield, start, page_len, filters):
filters = frappe._dict(filters) filters = frappe._dict(filters)
conditions = [] conditions = []
exclude_employees = [] include_employees = []
emp_cond = '' emp_cond = ''
if filters.start_date and filters.end_date: if filters.start_date and filters.end_date:
employee_list = get_employee_with_existing_salary_slip(filters.start_date, filters.end_date, filters.company) employee_list = get_employee_list(filters)
emp = filters.get('employees') emp = filters.get('employees')
include_employees = [employee.employee for employee in employee_list if employee.employee not in emp]
filters.pop('start_date') filters.pop('start_date')
filters.pop('end_date') filters.pop('end_date')
filters.pop('salary_slip_based_on_timesheet')
filters.pop('payroll_frequency')
filters.pop('payroll_payable_account')
filters.pop('currency')
if filters.employees is not None: if filters.employees is not None:
filters.pop('employees') filters.pop('employees')
if employee_list:
exclude_employees.extend(employee_list) if include_employees:
if emp: emp_cond += 'and employee in %(include_employees)s'
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` return frappe.db.sql("""select name, employee_name from `tabEmployee`
where status = 'Active' where status = 'Active'
@ -695,4 +710,4 @@ def employee_query(doctype, txt, searchfield, start, page_len, filters):
'_txt': txt.replace("%", ""), '_txt': txt.replace("%", ""),
'start': start, 'start': start,
'page_len': page_len, 'page_len': page_len,
'exclude_employees': exclude_employees}) 'include_employees': include_employees})

View File

@ -133,8 +133,6 @@ frappe.ui.form.on('Salary Structure', {
title: __("Assign to Employees"), title: __("Assign to Employees"),
fields: [ fields: [
{fieldname: "sec_break", fieldtype: "Section Break", label: __("Filter Employees By (Optional)")}, {fieldname: "sec_break", fieldtype: "Section Break", label: __("Filter Employees By (Optional)")},
{fieldname: "company", fieldtype: "Link", options: "Company", label: __("Company"), default: frm.doc.company, read_only:1},
{fieldname: "currency", fieldtype: "Link", options: "Currency", label: __("Currency"), default: frm.doc.currency, read_only:1},
{fieldname: "grade", fieldtype: "Link", options: "Employee Grade", label: __("Employee Grade")}, {fieldname: "grade", fieldtype: "Link", options: "Employee Grade", label: __("Employee Grade")},
{fieldname:'department', fieldtype:'Link', options: 'Department', label: __('Department')}, {fieldname:'department', fieldtype:'Link', options: 'Department', label: __('Department')},
{fieldname:'designation', fieldtype:'Link', options: 'Designation', label: __('Designation')}, {fieldname:'designation', fieldtype:'Link', options: 'Designation', label: __('Designation')},

View File

@ -11,15 +11,16 @@
"project", "project",
"issue", "issue",
"type", "type",
"color",
"is_group", "is_group",
"is_template", "is_template",
"column_break0", "column_break0",
"status", "status",
"priority", "priority",
"task_weight", "task_weight",
"completed_by",
"color",
"parent_task", "parent_task",
"completed_by",
"completed_on",
"sb_timeline", "sb_timeline",
"exp_start_date", "exp_start_date",
"expected_time", "expected_time",
@ -358,6 +359,7 @@
"read_only": 1 "read_only": 1
}, },
{ {
"depends_on": "eval: doc.status == \"Completed\"",
"fieldname": "completed_by", "fieldname": "completed_by",
"fieldtype": "Link", "fieldtype": "Link",
"label": "Completed By", "label": "Completed By",
@ -381,6 +383,13 @@
"fieldname": "duration", "fieldname": "duration",
"fieldtype": "Int", "fieldtype": "Int",
"label": "Duration (Days)" "label": "Duration (Days)"
},
{
"depends_on": "eval: doc.status == \"Completed\"",
"fieldname": "completed_on",
"fieldtype": "Date",
"label": "Completed On",
"mandatory_depends_on": "eval: doc.status == \"Completed\""
} }
], ],
"icon": "fa fa-check", "icon": "fa fa-check",
@ -388,7 +397,7 @@
"is_tree": 1, "is_tree": 1,
"links": [], "links": [],
"max_attachments": 5, "max_attachments": 5,
"modified": "2020-12-28 11:32:58.714991", "modified": "2021-04-16 12:46:51.556741",
"modified_by": "Administrator", "modified_by": "Administrator",
"module": "Projects", "module": "Projects",
"name": "Task", "name": "Task",

View File

@ -36,6 +36,7 @@ class Task(NestedSet):
self.validate_status() self.validate_status()
self.update_depends_on() self.update_depends_on()
self.validate_dependencies_for_template_task() self.validate_dependencies_for_template_task()
self.validate_completed_on()
def validate_dates(self): def validate_dates(self):
if self.exp_start_date and self.exp_end_date and getdate(self.exp_start_date) > getdate(self.exp_end_date): if self.exp_start_date and self.exp_end_date and getdate(self.exp_start_date) > getdate(self.exp_end_date):
@ -100,6 +101,10 @@ class Task(NestedSet):
dependent_task_format = """<a href="#Form/Task/{0}">{0}</a>""".format(task.task) dependent_task_format = """<a href="#Form/Task/{0}">{0}</a>""".format(task.task)
frappe.throw(_("Dependent Task {0} is not a Template Task").format(dependent_task_format)) frappe.throw(_("Dependent Task {0} is not a Template Task").format(dependent_task_format))
def validate_completed_on(self):
if self.completed_on and getdate(self.completed_on) > getdate():
frappe.throw(_("Completed On cannot be greater than Today"))
def update_depends_on(self): def update_depends_on(self):
depends_on_tasks = self.depends_on_tasks or "" depends_on_tasks = self.depends_on_tasks or ""
for d in self.depends_on: for d in self.depends_on:

View File

@ -0,0 +1,41 @@
// Copyright (c) 2016, Frappe Technologies Pvt. Ltd. and contributors
// For license information, please see license.txt
/* eslint-disable */
frappe.query_reports["Delayed Tasks Summary"] = {
"filters": [
{
"fieldname": "from_date",
"label": __("From Date"),
"fieldtype": "Date"
},
{
"fieldname": "to_date",
"label": __("To Date"),
"fieldtype": "Date"
},
{
"fieldname": "priority",
"label": __("Priority"),
"fieldtype": "Select",
"options": ["", "Low", "Medium", "High", "Urgent"]
},
{
"fieldname": "status",
"label": __("Status"),
"fieldtype": "Select",
"options": ["", "Open", "Working","Pending Review","Overdue","Completed"]
},
],
"formatter": function(value, row, column, data, default_formatter) {
value = default_formatter(value, row, column, data);
if (column.id == "delay") {
if (data["delay"] > 0) {
value = `<p style="color: red; font-weight: bold">${value}</p>`;
} else {
value = `<p style="color: green; font-weight: bold">${value}</p>`;
}
}
return value
}
};

View File

@ -0,0 +1,29 @@
{
"add_total_row": 0,
"columns": [],
"creation": "2021-03-25 15:03:19.857418",
"disable_prepared_report": 0,
"disabled": 0,
"docstatus": 0,
"doctype": "Report",
"filters": [],
"idx": 0,
"is_standard": "Yes",
"modified": "2021-04-15 15:49:35.432486",
"modified_by": "Administrator",
"module": "Projects",
"name": "Delayed Tasks Summary",
"owner": "Administrator",
"prepared_report": 0,
"ref_doctype": "Task",
"report_name": "Delayed Tasks Summary",
"report_type": "Script Report",
"roles": [
{
"role": "Projects User"
},
{
"role": "Projects Manager"
}
]
}

View File

@ -0,0 +1,133 @@
# Copyright (c) 2013, Frappe Technologies Pvt. Ltd. and contributors
# For license information, please see license.txt
from __future__ import unicode_literals
import frappe
from frappe.utils import date_diff, nowdate
def execute(filters=None):
columns, data = [], []
data = get_data(filters)
columns = get_columns()
charts = get_chart_data(data)
return columns, data, None, charts
def get_data(filters):
conditions = get_conditions(filters)
tasks = frappe.get_all("Task",
filters = conditions,
fields = ["name", "subject", "exp_start_date", "exp_end_date",
"status", "priority", "completed_on", "progress"],
order_by="creation"
)
for task in tasks:
if task.exp_end_date:
if task.completed_on:
task.delay = date_diff(task.completed_on, task.exp_end_date)
elif task.status == "Completed":
# task is completed but completed on is not set (for older tasks)
task.delay = 0
else:
# task not completed
task.delay = date_diff(nowdate(), task.exp_end_date)
else:
# task has no end date, hence no delay
task.delay = 0
# Sort by descending order of delay
tasks.sort(key=lambda x: x["delay"], reverse=True)
return tasks
def get_conditions(filters):
conditions = frappe._dict()
keys = ["priority", "status"]
for key in keys:
if filters.get(key):
conditions[key] = filters.get(key)
if filters.get("from_date"):
conditions.exp_end_date = [">=", filters.get("from_date")]
if filters.get("to_date"):
conditions.exp_start_date = ["<=", filters.get("to_date")]
return conditions
def get_chart_data(data):
delay, on_track = 0, 0
for entry in data:
if entry.get("delay") > 0:
delay = delay + 1
else:
on_track = on_track + 1
charts = {
"data": {
"labels": ["On Track", "Delayed"],
"datasets": [
{
"name": "Delayed",
"values": [on_track, delay]
}
]
},
"type": "percentage",
"colors": ["#84D5BA", "#CB4B5F"]
}
return charts
def get_columns():
columns = [
{
"fieldname": "name",
"fieldtype": "Link",
"label": "Task",
"options": "Task",
"width": 150
},
{
"fieldname": "subject",
"fieldtype": "Data",
"label": "Subject",
"width": 200
},
{
"fieldname": "status",
"fieldtype": "Data",
"label": "Status",
"width": 100
},
{
"fieldname": "priority",
"fieldtype": "Data",
"label": "Priority",
"width": 80
},
{
"fieldname": "progress",
"fieldtype": "Data",
"label": "Progress (%)",
"width": 120
},
{
"fieldname": "exp_start_date",
"fieldtype": "Date",
"label": "Expected Start Date",
"width": 150
},
{
"fieldname": "exp_end_date",
"fieldtype": "Date",
"label": "Expected End Date",
"width": 150
},
{
"fieldname": "completed_on",
"fieldtype": "Date",
"label": "Actual End Date",
"width": 130
},
{
"fieldname": "delay",
"fieldtype": "Data",
"label": "Delay (In Days)",
"width": 120
}
]
return columns

View File

@ -0,0 +1,54 @@
from __future__ import unicode_literals
import unittest
import frappe
from frappe.utils import nowdate, add_days, add_months
from erpnext.projects.doctype.task.test_task import create_task
from erpnext.projects.report.delayed_tasks_summary.delayed_tasks_summary import execute
class TestDelayedTasksSummary(unittest.TestCase):
@classmethod
def setUp(self):
task1 = create_task("_Test Task 98", add_days(nowdate(), -10), nowdate())
create_task("_Test Task 99", add_days(nowdate(), -10), add_days(nowdate(), -1))
task1.status = "Completed"
task1.completed_on = add_days(nowdate(), -1)
task1.save()
def test_delayed_tasks_summary(self):
filters = frappe._dict({
"from_date": add_months(nowdate(), -1),
"to_date": nowdate(),
"priority": "Low",
"status": "Open"
})
expected_data = [
{
"subject": "_Test Task 99",
"status": "Open",
"priority": "Low",
"delay": 1
},
{
"subject": "_Test Task 98",
"status": "Completed",
"priority": "Low",
"delay": -1
}
]
report = execute(filters)
data = list(filter(lambda x: x.subject == "_Test Task 99", report[1]))[0]
for key in ["subject", "status", "priority", "delay"]:
self.assertEqual(expected_data[0].get(key), data.get(key))
filters.status = "Completed"
report = execute(filters)
data = list(filter(lambda x: x.subject == "_Test Task 98", report[1]))[0]
for key in ["subject", "status", "priority", "delay"]:
self.assertEqual(expected_data[1].get(key), data.get(key))
def tearDown(self):
for task in ["_Test Task 98", "_Test Task 99"]:
frappe.get_doc("Task", {"subject": task}).delete()

View File

@ -15,6 +15,7 @@
"hide_custom": 0, "hide_custom": 0,
"icon": "project", "icon": "project",
"idx": 0, "idx": 0,
"is_default": 0,
"is_standard": 1, "is_standard": 1,
"label": "Projects", "label": "Projects",
"links": [ "links": [
@ -148,9 +149,19 @@
"link_type": "Report", "link_type": "Report",
"onboard": 0, "onboard": 0,
"type": "Link" "type": "Link"
},
{
"dependencies": "Task",
"hidden": 0,
"is_query_report": 1,
"label": "Delayed Tasks Summary",
"link_to": "Delayed Tasks Summary",
"link_type": "Report",
"onboard": 0,
"type": "Link"
} }
], ],
"modified": "2020-12-01 13:38:37.856224", "modified": "2021-03-26 16:32:00.628561",
"modified_by": "Administrator", "modified_by": "Administrator",
"module": "Projects", "module": "Projects",
"name": "Projects", "name": "Projects",

View File

@ -1103,6 +1103,8 @@ erpnext.TransactionController = erpnext.taxes_and_totals.extend({
to_currency: to_currency, to_currency: to_currency,
args: args args: args
}, },
freeze: true,
freeze_message: __("Fetching exchange rates ..."),
callback: function(r) { callback: function(r) {
callback(flt(r.message)); callback(flt(r.message));
} }

View File

@ -159,6 +159,31 @@ erpnext.PointOfSale.ItemSelector = class {
bind_events() { bind_events() {
const me = this; const me = this;
window.onScan = onScan; window.onScan = onScan;
onScan.decodeKeyEvent = function (oEvent) {
var iCode = this._getNormalizedKeyNum(oEvent);
switch (true) {
case iCode >= 48 && iCode <= 90: // numbers and letters
case iCode >= 106 && iCode <= 111: // operations on numeric keypad (+, -, etc.)
case (iCode >= 160 && iCode <= 164) || iCode == 170: // ^ ! # $ *
case iCode >= 186 && iCode <= 194: // (; = , - . / `)
case iCode >= 219 && iCode <= 222: // ([ \ ] ')
if (oEvent.key !== undefined && oEvent.key !== '') {
return oEvent.key;
}
var sDecoded = String.fromCharCode(iCode);
switch (oEvent.shiftKey) {
case false: sDecoded = sDecoded.toLowerCase(); break;
case true: sDecoded = sDecoded.toUpperCase(); break;
}
return sDecoded;
case iCode >= 96 && iCode <= 105: // numbers on numeric keypad
return 0 + (iCode - 96);
}
return '';
};
onScan.attachTo(document, { onScan.attachTo(document, {
onScan: (sScancode) => { onScan: (sScancode) => {
if (this.search_field && this.$component.is(':visible')) { if (this.search_field && this.$component.is(':visible')) {

View File

@ -248,177 +248,9 @@
"link_type": "DocType", "link_type": "DocType",
"onboard": 1, "onboard": 1,
"type": "Link" "type": "Link"
},
{
"hidden": 0,
"is_query_report": 0,
"label": "Healthcare",
"onboard": 0,
"type": "Card Break"
},
{
"dependencies": "",
"hidden": 0,
"is_query_report": 0,
"label": "Patient",
"link_to": "Patient",
"link_type": "DocType",
"onboard": 1,
"type": "Link"
},
{
"dependencies": "",
"hidden": 0,
"is_query_report": 0,
"label": "Diagnosis",
"link_to": "Diagnosis",
"link_type": "DocType",
"onboard": 1,
"type": "Link"
},
{
"hidden": 0,
"is_query_report": 0,
"label": "Education",
"onboard": 0,
"type": "Card Break"
},
{
"dependencies": "",
"hidden": 0,
"is_query_report": 0,
"label": "Student",
"link_to": "Student",
"link_type": "DocType",
"onboard": 1,
"type": "Link"
},
{
"dependencies": "",
"hidden": 0,
"is_query_report": 0,
"label": "Instructor",
"link_to": "Instructor",
"link_type": "DocType",
"onboard": 1,
"type": "Link"
},
{
"dependencies": "",
"hidden": 0,
"is_query_report": 0,
"label": "Course",
"link_to": "Course",
"link_type": "DocType",
"onboard": 1,
"type": "Link"
},
{
"dependencies": "",
"hidden": 0,
"is_query_report": 0,
"label": "Room",
"link_to": "Room",
"link_type": "DocType",
"onboard": 1,
"type": "Link"
},
{
"hidden": 0,
"is_query_report": 0,
"label": "Non Profit",
"onboard": 0,
"type": "Card Break"
},
{
"dependencies": "",
"hidden": 0,
"is_query_report": 0,
"label": "Donor",
"link_to": "Donor",
"link_type": "DocType",
"onboard": 1,
"type": "Link"
},
{
"dependencies": "",
"hidden": 0,
"is_query_report": 0,
"label": "Member",
"link_to": "Member",
"link_type": "DocType",
"onboard": 1,
"type": "Link"
},
{
"dependencies": "",
"hidden": 0,
"is_query_report": 0,
"label": "Volunteer",
"link_to": "Volunteer",
"link_type": "DocType",
"onboard": 1,
"type": "Link"
},
{
"dependencies": "",
"hidden": 0,
"is_query_report": 0,
"label": "Chapter",
"link_to": "Chapter",
"link_type": "DocType",
"onboard": 1,
"type": "Link"
},
{
"hidden": 0,
"is_query_report": 0,
"label": "Agriculture",
"onboard": 0,
"type": "Card Break"
},
{
"dependencies": "",
"hidden": 0,
"is_query_report": 0,
"label": "Location",
"link_to": "Location",
"link_type": "DocType",
"onboard": 1,
"type": "Link"
},
{
"dependencies": "",
"hidden": 0,
"is_query_report": 0,
"label": "Crop",
"link_to": "Crop",
"link_type": "DocType",
"onboard": 1,
"type": "Link"
},
{
"dependencies": "",
"hidden": 0,
"is_query_report": 0,
"label": "Crop Cycle",
"link_to": "Crop Cycle",
"link_type": "DocType",
"onboard": 1,
"type": "Link"
},
{
"dependencies": "",
"hidden": 0,
"is_query_report": 0,
"label": "Fertilizer",
"link_to": "Fertilizer",
"link_type": "DocType",
"onboard": 1,
"type": "Link"
} }
], ],
"modified": "2021-03-16 15:59:58.416154", "modified": "2021-04-19 15:48:44.089927",
"modified_by": "Administrator", "modified_by": "Administrator",
"module": "Setup", "module": "Setup",
"name": "Home", "name": "Home",

View File

@ -13,6 +13,7 @@
"column_break_4", "column_break_4",
"valuation_method", "valuation_method",
"over_delivery_receipt_allowance", "over_delivery_receipt_allowance",
"role_allowed_to_over_deliver_receive",
"action_if_quality_inspection_is_not_submitted", "action_if_quality_inspection_is_not_submitted",
"show_barcode_field", "show_barcode_field",
"clean_description_html", "clean_description_html",
@ -234,6 +235,13 @@
"fieldname": "disable_serial_no_and_batch_selector", "fieldname": "disable_serial_no_and_batch_selector",
"fieldtype": "Check", "fieldtype": "Check",
"label": "Disable Serial No And Batch Selector" "label": "Disable Serial No And Batch Selector"
},
{
"description": "Users with this role are allowed to over deliver/receive against orders above the allowance percentage",
"fieldname": "role_allowed_to_over_deliver_receive",
"fieldtype": "Link",
"label": "Role Allowed to Over Deliver/Receive",
"options": "Role"
} }
], ],
"icon": "icon-cog", "icon": "icon-cog",
@ -241,7 +249,7 @@
"index_web_pages_for_search": 1, "index_web_pages_for_search": 1,
"issingle": 1, "issingle": 1,
"links": [], "links": [],
"modified": "2021-01-18 13:15:38.352796", "modified": "2021-03-11 18:48:14.513055",
"modified_by": "Administrator", "modified_by": "Administrator",
"module": "Stock", "module": "Stock",
"name": "Stock Settings", "name": "Stock Settings",

View File

@ -609,8 +609,12 @@ def get_price_list_rate(args, item_doc, out):
meta = frappe.get_meta(args.parenttype or args.doctype) meta = frappe.get_meta(args.parenttype or args.doctype)
if meta.get_field("currency") or args.get('currency'): if meta.get_field("currency") or args.get('currency'):
if not args.get("price_list_currency") or not args.get("plc_conversion_rate"):
# if currency and plc_conversion_rate exist then
# `get_price_list_currency_and_exchange_rate` has already been called
pl_details = get_price_list_currency_and_exchange_rate(args) pl_details = get_price_list_currency_and_exchange_rate(args)
args.update(pl_details) args.update(pl_details)
if meta.get_field("currency"): if meta.get_field("currency"):
validate_conversion_rate(args, meta) validate_conversion_rate(args, meta)
@ -1000,6 +1004,8 @@ def apply_price_list(args, as_doc=False):
args = process_args(args) args = process_args(args)
parent = get_price_list_currency_and_exchange_rate(args) parent = get_price_list_currency_and_exchange_rate(args)
args.update(parent)
children = [] children = []
if "items" in args: if "items" in args:
@ -1064,7 +1070,7 @@ def get_price_list_currency_and_exchange_rate(args):
return frappe._dict({ return frappe._dict({
"price_list_currency": price_list_currency, "price_list_currency": price_list_currency,
"price_list_uom_dependant": price_list_uom_dependant, "price_list_uom_dependant": price_list_uom_dependant,
"plc_conversion_rate": plc_conversion_rate "plc_conversion_rate": plc_conversion_rate or 1
}) })
@frappe.whitelist() @frappe.whitelist()

View File

@ -55,19 +55,31 @@ def get_item_info(filters):
def get_consumed_items(condition): def get_consumed_items(condition):
purpose_to_exclude = [
"Material Transfer for Manufacture",
"Material Transfer",
"Send to Subcontractor"
]
condition += """
and (
purpose is NULL
or purpose not in ({})
)
""".format(', '.join([f"'{p}'" for p in purpose_to_exclude]))
condition = condition.replace("posting_date", "sle.posting_date")
consumed_items = frappe.db.sql(""" consumed_items = frappe.db.sql("""
select item_code, abs(sum(actual_qty)) as consumed_qty select item_code, abs(sum(actual_qty)) as consumed_qty
from `tabStock Ledger Entry` from `tabStock Ledger Entry` as sle left join `tabStock Entry` as se
where actual_qty < 0 on sle.voucher_no = se.name
where
actual_qty < 0
and voucher_type not in ('Delivery Note', 'Sales Invoice') and voucher_type not in ('Delivery Note', 'Sales Invoice')
%s %s
group by item_code group by item_code""" % condition, as_dict=1)
""" % condition, as_dict=1)
consumed_items_map = {}
for item in consumed_items:
consumed_items_map.setdefault(item.item_code, item.consumed_qty)
consumed_items_map = {item.item_code : item.consumed_qty for item in consumed_items}
return consumed_items_map return consumed_items_map
def get_delivered_items(condition): def get_delivered_items(condition):

View File

@ -1,15 +1,13 @@
braintree==3.57.1
frappe frappe
gocardless-pro==1.11.0 gocardless-pro~=1.22.0
googlemaps==3.1.1 googlemaps # used in ERPNext, but dependency is defined in Frappe
pandas>=1.0.5 pandas~=1.1.5
plaid-python>=7.0.0 plaid-python~=7.2.1
pycountry==19.8.18 pycountry~=20.7.3
PyGithub==1.44.1 PyGithub~=1.54.1
python-stdnum==1.12 python-stdnum~=1.16
python-youtube==0.6.0 python-youtube~=0.8.0
taxjar==1.9.0 taxjar~=1.9.2
tweepy==3.8.0 tweepy~=3.10.0
Unidecode==1.1.1 Unidecode~=1.2.0
WooCommerce==2.1.1 WooCommerce~=3.0.0
pycryptodome==3.9.8