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: |
cp ~/frappe-bench/sites/.coverage ${GITHUB_WORKSPACE}
cd ${GITHUB_WORKSPACE}
pip install coveralls==2.2.0
pip install coverage==4.5.4
coveralls
pip install coveralls==3.0.1
pip install coverage==5.5
coveralls --service=github
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
COVERALLS_REPO_TOKEN: ${{ secrets.COVERALLS_TOKEN }}

View File

@ -12,6 +12,7 @@
"frozen_accounts_modifier",
"determine_address_tax_category_from",
"over_billing_allowance",
"role_allowed_to_over_bill",
"column_break_4",
"credit_controller",
"check_supplier_invoice_uniqueness",
@ -226,6 +227,13 @@
"fieldname": "delete_linked_ledger_entries",
"fieldtype": "Check",
"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",
@ -233,7 +241,7 @@
"index_web_pages_for_search": 1,
"issingle": 1,
"links": [],
"modified": "2021-01-05 13:04:00.118892",
"modified": "2021-03-11 18:52:05.601996",
"modified_by": "Administrator",
"module": "Accounts",
"name": "Accounts Settings",

View File

@ -89,8 +89,8 @@ class POSClosingEntry(StatusUpdater):
@frappe.whitelist()
@frappe.validate_and_sanitize_search_inputs
def get_cashiers(doctype, txt, searchfield, start, page_len, filters):
cashiers_list = frappe.get_all("POS Profile User", filters=filters, fields=['user'])
return [c['user'] for c in cashiers_list]
cashiers_list = frappe.get_all("POS Profile User", filters=filters, fields=['user'], as_list=1)
return [c for c in cashiers_list]
@frappe.whitelist()
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"]:
parent_field = "parent_{0}".format(frappe.scrub(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]:
parent_groups.append(root_name[0][0])

View File

@ -717,7 +717,9 @@ class AccountsController(TransactionBase):
total_billed_amt = abs(total_billed_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")
.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,
self.global_qty_allowance, self.global_amount_allowance, qty_or_amount)
overflow_percent = ((item[args['target_field']] - item[args['target_ref_field']]) /
item[args['target_ref_field']]) * 100
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
if overflow_percent - allowance > 0.01:
overflow_percent = ((item[args['target_field']] - item[args['target_ref_field']]) /
item[args['target_ref_field']]) * 100
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['reduce_by'] = item[args['target_field']] - item['max_allowed']

View File

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

View File

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

View File

@ -66,7 +66,7 @@ class CompensatoryLeaveRequest(Document):
else:
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:
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)))
@ -124,4 +124,4 @@ class CompensatoryLeaveRequest(Document):
))
allocation.insert(ignore_permissions=True)
allocation.submit()
return allocation
return allocation

View File

@ -151,6 +151,10 @@ frappe.ui.form.on('Payroll Entry', {
filters['company'] = frm.doc.company;
filters['start_date'] = frm.doc.start_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) {
filters['department'] = frm.doc.department;

View File

@ -52,49 +52,32 @@ class PayrollEntry(Document):
Returns list of active employees based on selected criteria
and for which salary structure exists
"""
cond = self.get_filter_condition()
cond += self.get_joining_relieving_condition()
self.check_mandatory()
filters = self.make_filters()
cond = get_filter_condition(filters)
cond += get_joining_relieving_condition(self.start_date, self.end_date)
condition = ''
if self.payroll_frequency:
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})
sal_struct = get_sal_struct(self.company, self.currency, self.salary_slip_based_on_timesheet, condition)
if sal_struct:
cond += "and t2.salary_structure IN %(sal_struct)s "
cond += "and t2.payroll_payable_account = %(payroll_payable_account)s "
cond += "and %(from_date)s >= t2.from_date"
emp_list = 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": self.end_date, "payroll_payable_account": self.payroll_payable_account}, as_dict=True)
emp_list = self.remove_payrolled_employees(emp_list)
emp_list = get_emp_list(sal_struct, cond, self.end_date, self.payroll_payable_account)
emp_list = remove_payrolled_employees(emp_list, self.start_date, self.end_date)
return emp_list
def remove_payrolled_employees(self, emp_list):
for employee_details in emp_list:
if frappe.db.exists("Salary Slip", {"employee": employee_details.employee, "start_date": self.start_date, "end_date": self.end_date, "docstatus": 1}):
emp_list.remove(employee_details)
def make_filters(self):
filters = frappe._dict()
filters['company'] = self.company
filters['branch'] = self.branch
filters['department'] = self.department
filters['designation'] = self.designation
return emp_list
return filters
@frappe.whitelist()
def fill_employee_details(self):
@ -122,23 +105,6 @@ class PayrollEntry(Document):
if self.validate_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):
for fieldname in ['company', 'start_date', 'end_date']:
if not self.get(fieldname):
@ -451,6 +417,53 @@ class PayrollEntry(Document):
marked_days = attendances[0][0]
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()
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'''
@ -639,39 +652,41 @@ def get_payroll_entries_for_jv(doctype, txt, searchfield, start, page_len, filte
'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})
def get_employee_list(filters):
cond = get_filter_condition(filters)
cond += get_joining_relieving_condition(filters.start_date, filters.end_date)
condition = """and payroll_frequency = '%(payroll_frequency)s'"""% {"payroll_frequency": filters.payroll_frequency}
sal_struct = get_sal_struct(filters.company, filters.currency, filters.salary_slip_based_on_timesheet, condition)
if sal_struct:
cond += "and t2.salary_structure IN %(sal_struct)s "
cond += "and t2.payroll_payable_account = %(payroll_payable_account)s "
cond += "and %(from_date)s >= t2.from_date"
emp_list = get_emp_list(sal_struct, cond, filters.end_date, filters.payroll_payable_account)
emp_list = remove_payrolled_employees(emp_list, filters.start_date, filters.end_date)
return emp_list
@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 = []
include_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)
employee_list = get_employee_list(filters)
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('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:
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'
if include_employees:
emp_cond += 'and employee in %(include_employees)s'
return frappe.db.sql("""select name, employee_name from `tabEmployee`
where status = 'Active'
@ -695,4 +710,4 @@ def employee_query(doctype, txt, searchfield, start, page_len, filters):
'_txt': txt.replace("%", ""),
'start': start,
'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"),
fields: [
{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:'department', fieldtype:'Link', options: 'Department', label: __('Department')},
{fieldname:'designation', fieldtype:'Link', options: 'Designation', label: __('Designation')},

View File

@ -88,7 +88,7 @@ class SalaryStructure(Document):
return employees
@frappe.whitelist()
def assign_salary_structure(self, grade=None, department=None, designation=None,employee=None,
def assign_salary_structure(self, grade=None, department=None, designation=None, employee=None,
payroll_payable_account=None, from_date=None, base=None, variable=None, income_tax_slab=None):
employees = self.get_employees(company= self.company, grade= grade,department= department,designation= designation,name=employee)

View File

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

View File

@ -36,6 +36,7 @@ class Task(NestedSet):
self.validate_status()
self.update_depends_on()
self.validate_dependencies_for_template_task()
self.validate_completed_on()
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):
@ -100,6 +101,10 @@ class Task(NestedSet):
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))
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):
depends_on_tasks = self.depends_on_tasks or ""
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,
"icon": "project",
"idx": 0,
"is_default": 0,
"is_standard": 1,
"label": "Projects",
"links": [
@ -148,9 +149,19 @@
"link_type": "Report",
"onboard": 0,
"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",
"module": "Projects",
"name": "Projects",

View File

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

View File

@ -159,6 +159,31 @@ erpnext.PointOfSale.ItemSelector = class {
bind_events() {
const me = this;
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: (sScancode) => {
if (this.search_field && this.$component.is(':visible')) {

View File

@ -248,177 +248,9 @@
"link_type": "DocType",
"onboard": 1,
"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",
"module": "Setup",
"name": "Home",

View File

@ -13,6 +13,7 @@
"column_break_4",
"valuation_method",
"over_delivery_receipt_allowance",
"role_allowed_to_over_deliver_receive",
"action_if_quality_inspection_is_not_submitted",
"show_barcode_field",
"clean_description_html",
@ -234,6 +235,13 @@
"fieldname": "disable_serial_no_and_batch_selector",
"fieldtype": "Check",
"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",
@ -241,7 +249,7 @@
"index_web_pages_for_search": 1,
"issingle": 1,
"links": [],
"modified": "2021-01-18 13:15:38.352796",
"modified": "2021-03-11 18:48:14.513055",
"modified_by": "Administrator",
"module": "Stock",
"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)
if meta.get_field("currency") or args.get('currency'):
pl_details = get_price_list_currency_and_exchange_rate(args)
args.update(pl_details)
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)
args.update(pl_details)
if meta.get_field("currency"):
validate_conversion_rate(args, meta)
@ -1000,6 +1004,8 @@ def apply_price_list(args, as_doc=False):
args = process_args(args)
parent = get_price_list_currency_and_exchange_rate(args)
args.update(parent)
children = []
if "items" in args:
@ -1064,7 +1070,7 @@ def get_price_list_currency_and_exchange_rate(args):
return frappe._dict({
"price_list_currency": price_list_currency,
"price_list_uom_dependant": price_list_uom_dependant,
"plc_conversion_rate": plc_conversion_rate
"plc_conversion_rate": plc_conversion_rate or 1
})
@frappe.whitelist()

View File

@ -55,19 +55,31 @@ def get_item_info(filters):
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("""
select item_code, abs(sum(actual_qty)) as consumed_qty
from `tabStock Ledger Entry`
where actual_qty < 0
from `tabStock Ledger Entry` as sle left join `tabStock Entry` as se
on sle.voucher_no = se.name
where
actual_qty < 0
and voucher_type not in ('Delivery Note', 'Sales Invoice')
%s
group by item_code
""" % condition, as_dict=1)
consumed_items_map = {}
for item in consumed_items:
consumed_items_map.setdefault(item.item_code, item.consumed_qty)
group by item_code""" % condition, as_dict=1)
consumed_items_map = {item.item_code : item.consumed_qty for item in consumed_items}
return consumed_items_map
def get_delivered_items(condition):

View File

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