def test_gratuity_based_on_all_previous_slabs_via_payment_entry(self): """ Range | Fraction 0-1 | 0 1-5 | 0.7 5-0 | 1 """ from erpnext.accounts.doctype.payment_entry.payment_entry import get_payment_entry doj = add_days(getdate(), -(6 * 365)) relieving_date = getdate() employee = make_employee( "*****@*****.**", company="_Test Company", date_of_joining=doj, relieving_date=relieving_date, ) sal_slip = create_salary_slip("*****@*****.**") rule = get_gratuity_rule("Rule Under Limited Contract (UAE)") set_mode_of_payment_account() gratuity = create_gratuity(expense_account="Payment Account - _TC", mode_of_payment="Cash", employee=employee) # work experience calculation employee_total_workings_days = (get_datetime(relieving_date) - get_datetime(doj)).days experience = floor(employee_total_workings_days / rule.total_working_days_per_year) self.assertEqual(gratuity.current_work_experience, experience) # amount calculation component_amount = frappe.get_all( "Salary Detail", filters={ "docstatus": 1, "parent": sal_slip, "parentfield": "earnings", "salary_component": "Basic Salary", }, fields=["amount"], limit=1, ) gratuity_amount = ((0 * 1) + (4 * 0.7) + (1 * 1)) * component_amount[0].amount self.assertEqual(flt(gratuity_amount, 2), flt(gratuity.amount, 2)) self.assertEqual(gratuity.status, "Unpaid") pe = get_payment_entry("Gratuity", gratuity.name) pe.reference_no = "123467" pe.reference_date = getdate() pe.submit() gratuity.reload() self.assertEqual(gratuity.status, "Paid") self.assertEqual(flt(gratuity.paid_amount, 2), flt(gratuity.amount, 2))
def test_gratuity_based_on_current_slab_via_additional_salary(self): """ Range | Fraction 5-0 | 1 """ doj = add_days(getdate(), -(6 * 365)) relieving_date = getdate() employee = make_employee( "*****@*****.**", company="_Test Company", date_of_joining=doj, relieving_date=relieving_date, ) sal_slip = create_salary_slip("*****@*****.**") rule = get_gratuity_rule( "Rule Under Unlimited Contract on termination (UAE)") gratuity = create_gratuity(pay_via_salary_slip=1, employee=employee, rule=rule.name) # work experience calculation employee_total_workings_days = (get_datetime(relieving_date) - get_datetime(doj)).days experience = floor(employee_total_workings_days / rule.total_working_days_per_year) self.assertEqual(gratuity.current_work_experience, experience) # amount calculation component_amount = frappe.get_all( "Salary Detail", filters={ "docstatus": 1, "parent": sal_slip, "parentfield": "earnings", "salary_component": "Basic Salary", }, fields=["amount"], limit=1, ) gratuity_amount = component_amount[0].amount * experience self.assertEqual(flt(gratuity_amount, 2), flt(gratuity.amount, 2)) # additional salary creation (Pay via salary slip) self.assertTrue( frappe.db.exists("Additional Salary", {"ref_docname": gratuity.name})) # gratuity should be marked "Paid" on the next salary slip submission salary_slip = make_salary_slip("Test Gratuity", employee=employee) salary_slip.posting_date = getdate() salary_slip.insert() salary_slip.submit() gratuity.reload() self.assertEqual(gratuity.status, "Paid")
def get_items_with_location_and_quantity(item_doc, item_location_map): available_locations = item_location_map.get(item_doc.item_code) locations = [] remaining_stock_qty = item_doc.stock_qty while remaining_stock_qty > 0 and available_locations: item_location = available_locations.pop(0) item_location = frappe._dict(item_location) stock_qty = remaining_stock_qty if item_location.qty >= remaining_stock_qty else item_location.qty qty = stock_qty / (item_doc.conversion_factor or 1) uom_must_be_whole_number = frappe.db.get_value('UOM', item_doc.uom, 'must_be_whole_number') if uom_must_be_whole_number: qty = floor(qty) stock_qty = qty * item_doc.conversion_factor if not stock_qty: break serial_nos = None if item_location.serial_no: serial_nos = '\n'.join(item_location.serial_no[0:cint(stock_qty)]) auto_set_serial_no = frappe.db.get_single_value( "Stock Settings", "automatically_set_serial_nos_based_on_fifo") auto_set_batch_no = frappe.db.get_single_value( "Stock Settings", "automatically_set_batch_nos_based_on_fifo") locations.append( frappe._dict({ 'qty': qty, 'stock_qty': stock_qty, 'warehouse': item_location.warehouse, 'serial_no': serial_nos if auto_set_serial_no else item_doc.serial_no, 'batch_no': item_location.batch_no if auto_set_batch_no else item_doc.batch_no })) remaining_stock_qty -= stock_qty qty_diff = item_location.qty - stock_qty # if extra quantity is available push current warehouse to available locations if qty_diff > 0: item_location.qty = qty_diff if item_location.serial_no: # set remaining serial numbers item_location.serial_no = item_location.serial_no[-qty_diff:] available_locations = [item_location] + available_locations # update available locations for the item item_location_map[item_doc.item_code] = available_locations return locations
def get_items_with_location_and_quantity(item_doc, item_location_map, docstatus): available_locations = item_location_map.get(item_doc.item_code) locations = [] # if stock qty is zero on submitted entry, show positive remaining qty to recalculate in case of restock. remaining_stock_qty = (item_doc.qty if (docstatus == 1 and item_doc.stock_qty == 0) else item_doc.stock_qty) while remaining_stock_qty > 0 and available_locations: item_location = available_locations.pop(0) item_location = frappe._dict(item_location) stock_qty = (remaining_stock_qty if item_location.qty >= remaining_stock_qty else item_location.qty) qty = stock_qty / (item_doc.conversion_factor or 1) uom_must_be_whole_number = frappe.db.get_value("UOM", item_doc.uom, "must_be_whole_number") if uom_must_be_whole_number: qty = floor(qty) stock_qty = qty * item_doc.conversion_factor if not stock_qty: break serial_nos = None if item_location.serial_no: serial_nos = "\n".join(item_location.serial_no[0:cint(stock_qty)]) locations.append( frappe._dict({ "qty": qty, "stock_qty": stock_qty, "warehouse": item_location.warehouse, "serial_no": serial_nos, "batch_no": item_location.batch_no, })) remaining_stock_qty -= stock_qty qty_diff = item_location.qty - stock_qty # if extra quantity is available push current warehouse to available locations if qty_diff > 0: item_location.qty = qty_diff if item_location.serial_no: # set remaining serial numbers item_location.serial_no = item_location.serial_no[-int(qty_diff ):] available_locations = [item_location] + available_locations # update available locations for the item item_location_map[item_doc.item_code] = available_locations return locations
def get_discount_filters(self, discounts): discount_filters = [] # [25.89, 60.5] min max min_discount, max_discount = discounts[0], discounts[1] # [25, 60] rounded min max min_range_absolute, max_range_absolute = floor(min_discount), floor(max_discount) min_range = int(min_discount - (min_range_absolute % 10)) # 20 max_range = int(max_discount - (max_range_absolute % 10)) # 60 min_range = ( (min_range + 10) if min_range != min_range_absolute else min_range ) # 30 (upper limit of 25.89 in range of 10) max_range = (max_range + 10) if max_range != max_range_absolute else max_range # 60 for discount in range(min_range, (max_range + 1), 10): label = f"{discount}% and below" discount_filters.append([discount, label]) return discount_filters
def test_floor(self): from decimal import Decimal self.assertEqual(floor(2), 2) self.assertEqual(floor(12.32904), 12) self.assertEqual(floor(22.7330), 22) self.assertEqual(floor('24.7'), 24) self.assertEqual(floor('26.7'), 26) self.assertEqual(floor(Decimal(29.45)), 29)
def test_floor(self): from decimal import Decimal self.assertEqual(floor(2), 2 ) self.assertEqual(floor(12.32904), 12) self.assertEqual(floor(22.7330), 22) self.assertEqual(floor('24.7'), 24) self.assertEqual(floor('26.7'), 26) self.assertEqual(floor(Decimal(29.45)), 29)
def apply_putaway_rule(doctype, items, company, sync=None, purpose=None): """ Applies Putaway Rule on line items. items: List of Purchase Receipt/Stock Entry Items company: Company in the Purchase Receipt/Stock Entry doctype: Doctype to apply rule on purpose: Purpose of Stock Entry sync (optional): Sync with client side only for client side calls """ if isinstance(items, string_types): items = json.loads(items) items_not_accomodated, updated_table = [], [] item_wise_rules = defaultdict(list) for item in items: if isinstance(item, dict): item = frappe._dict(item) source_warehouse = item.get("s_warehouse") serial_nos = get_serial_nos(item.get("serial_no")) item.conversion_factor = flt(item.conversion_factor) or 1.0 pending_qty, item_code = flt(item.qty), item.item_code pending_stock_qty = flt( item.transfer_qty) if doctype == "Stock Entry" else flt( item.stock_qty) uom_must_be_whole_number = frappe.db.get_value('UOM', item.uom, 'must_be_whole_number') if not pending_qty or not item_code: updated_table = add_row(item, pending_qty, source_warehouse or item.warehouse, updated_table) continue at_capacity, rules = get_ordered_putaway_rules( item_code, company, source_warehouse=source_warehouse) if not rules: warehouse = source_warehouse or item.warehouse if at_capacity: # rules available, but no free space items_not_accomodated.append([item_code, pending_qty]) else: updated_table = add_row(item, pending_qty, warehouse, updated_table) continue # maintain item/item-warehouse wise rules, to handle if item is entered twice # in the table, due to different price, etc. key = item_code if doctype == "Stock Entry" and purpose == "Material Transfer" and source_warehouse: key = (item_code, source_warehouse) if not item_wise_rules[key]: item_wise_rules[key] = rules for rule in item_wise_rules[key]: if pending_stock_qty > 0 and rule.free_space: stock_qty_to_allocate = flt( rule.free_space) if pending_stock_qty >= flt( rule.free_space) else pending_stock_qty qty_to_allocate = stock_qty_to_allocate / item.conversion_factor if uom_must_be_whole_number: qty_to_allocate = floor(qty_to_allocate) stock_qty_to_allocate = qty_to_allocate * item.conversion_factor if not qty_to_allocate: break updated_table = add_row(item, qty_to_allocate, rule.warehouse, updated_table, rule.name, serial_nos=serial_nos) pending_stock_qty -= stock_qty_to_allocate pending_qty -= qty_to_allocate rule["free_space"] -= stock_qty_to_allocate if not pending_stock_qty > 0: break # if pending qty after applying all rules, add row without warehouse if pending_stock_qty > 0: items_not_accomodated.append([item.item_code, pending_qty]) if items_not_accomodated: show_unassigned_items_message(items_not_accomodated) items[:] = updated_table if updated_table else items # modify items table if sync and json.loads(sync): # sync with client side return items
def calculate_monthly_ammount(ammount, default_currency, from_date, to_date, foreign_ammount, foreign_currency=None, filters=None): float_precision = cint(frappe.db.get_default("float_precision")) or 2 months_report_list = [] for month in get_months(filters['from_date'], filters['to_date']): months_report_list.append("{0} {1}".format(month.lower(), default_currency)) if filters.get("foreign_currency") and filters.get( "foreign_currency") != default_currency: months_report_list.append("{0} {1}".format( month.lower(), filters.get("foreign_currency"))) if ammount and from_date and to_date: monthly_ammount_obj = {} days = 0 date = from_date end_date = to_date field_list = [] field_list_foreign = [] first_last = 0 first_last_foreign = 0 sub_ammount = 0 sub_ammount_foreign = 0 # days_list= [] while date <= end_date: start_month = getdate(date).month end_month = getdate(to_date).month if start_month == end_month: last_day = end_date days_diff = date_diff(last_day, date) + 1 if check_full_month(date, last_day): days_diff = 30 days += days_diff # days_list.append(days_diff) if date == last_day: last_day = add_days(last_day, 1) month_filed = (get_months(str(date), str(last_day))[0]).lower() month_len = date_diff(get_last_day(date), get_first_day(date)) field_list.append({ "days_diff": days_diff, "month_filed": "{0} {1}".format(month_filed, default_currency), "month_len": month_len, "foreign": False, }) if foreign_currency and foreign_currency != default_currency: field_list_foreign.append({ "days_diff": days_diff, "month_filed": "{0} {1}".format(month_filed, foreign_currency), "month_len": month_len, "foreign": True, }) date = get_first_day(add_months(date, 1)) else: last_day = get_last_day(date) days_diff = date_diff(last_day, date) + 1 if check_full_month(date, last_day): days_diff = 30 days += days_diff # days_list.append(days_diff) if date == last_day: last_day = add_days(last_day, 1) month_filed = (get_months(str(date), str(last_day))[0]).lower() month_len = date_diff(get_last_day(date), get_first_day(date)) field_list.append({ "days_diff": days_diff, "month_filed": "{0} {1}".format(month_filed, default_currency), "month_len": month_len, "foreign": False, }) if foreign_currency and foreign_currency != default_currency: field_list_foreign.append({ "days_diff": days_diff, "month_filed": "{0} {1}".format(month_filed, foreign_currency), "month_len": month_len, "foreign": True, }) date = get_first_day(add_months(date, 1)) if floor(days / 30) != (days / 30) and (floor(days / 30) * 30 + 6) < days: days = (floor(days / 30) + 1) * 30 elif floor(days / 30) != (days / 30) and floor(days / 30) * 30 < days: days = floor(days / 30) * 30 daily_ammount = ammount / (days) daily_ammount_foreign = foreign_ammount / (days) m = 1 for i in field_list: if m == 1 and i["days_diff"] < 30: first_last += i["days_diff"] elif m == len(field_list) and i["days_diff"] < 30: first_last += i["days_diff"] else: sub_ammount += i["days_diff"] * daily_ammount m += 1 m = 1 for i in field_list_foreign: if m == 1 and i["days_diff"] < 30: first_last_foreign += i["days_diff"] elif m == len(field_list) and i["days_diff"] < 30: first_last_foreign += i["days_diff"] else: sub_ammount_foreign += i["days_diff"] * daily_ammount_foreign m += 1 n = 1 for i in field_list_foreign: if n == 1 and i["days_diff"] < 30: monthly_ammount_obj[i["month_filed"]] = flt( i["days_diff"] * ((foreign_ammount - sub_ammount_foreign) / first_last), float_precision) elif n == len(field_list) and i["days_diff"] < 30: monthly_ammount_obj[i["month_filed"]] = flt( i["days_diff"] * ((foreign_ammount - sub_ammount_foreign) / first_last), float_precision) else: monthly_ammount_obj[i["month_filed"]] = flt( i["days_diff"] * daily_ammount_foreign, float_precision) n += 1 n = 1 for i in field_list: if n == 1 and i["days_diff"] < 30: monthly_ammount_obj[i["month_filed"]] = flt( i["days_diff"] * ((ammount - sub_ammount) / first_last), float_precision) elif n == len(field_list) and i["days_diff"] < 30: monthly_ammount_obj[i["month_filed"]] = flt( i["days_diff"] * ((ammount - sub_ammount) / first_last), float_precision) else: monthly_ammount_obj[i["month_filed"]] = flt( i["days_diff"] * daily_ammount, float_precision) n += 1 advance_before = 0 advance_after = 0 first_month = datetime.strptime( months_report_list[0][:6].replace("-", " 20"), "%b %Y") last_month = datetime.strptime( months_report_list[-1][:6].replace("-", " 20"), "%b %Y") for key, value in monthly_ammount_obj.items(): key_currency = key[7:] currency = foreign_currency or default_currency if key not in months_report_list and key_currency == currency: mydate = datetime.strptime( str(key)[:6].replace("-", " 20"), "%b %Y") if mydate > last_month: advance_after += value elif mydate < first_month: advance_before += value if advance_after: monthly_ammount_obj["advance_after"] = advance_after if advance_before: monthly_ammount_obj["advance_before"] = advance_before return monthly_ammount_obj