<commit_before># -*- coding: utf-8 -*-
# Copyright (c) 2019, Frappe Technologies Pvt. Ltd. and contributors
# For license information, please see license.txt

from __future__ import unicode_literals
import frappe
from frappe.model.document import Document
from frappe import _
from frappe.utils import add_days, today, flt, DATE_FORMAT, getdate

class LeaveLedgerEntry(Document):
	def validate(self):
		if getdate(self.from_date) > getdate(self.to_date):
			frappe.throw(_("To date needs to be before from date"))

	def on_cancel(self):
		# allow cancellation of expiry leaves
		if self.is_expired:
			frappe.db.set_value("Leave Allocation", self.transaction_name, "expired", 0)
		else:
			frappe.throw(_("Only expired allocation can be cancelled"))

def validate_leave_allocation_against_leave_application(ledger):
	''' Checks that leave allocation has no leave application against it '''
	leave_application_records = frappe.db.sql_list("""
		SELECT transaction_name
		FROM `tabLeave Ledger Entry`
		WHERE
			employee=%s
			AND leave_type=%s
			AND transaction_type='Leave Application'
			AND from_date>=%s
			AND to_date<=%s
	""", (ledger.employee, ledger.leave_type, ledger.from_date, ledger.to_date))

	if leave_application_records:
		frappe.throw(_("Leave allocation %s is linked with leave application %s"
			% (ledger.transaction_name, ', '.join(leave_application_records))))

def create_leave_ledger_entry(ref_doc, args, submit=True):
	ledger = frappe._dict(
		doctype='Leave Ledger Entry',
		employee=ref_doc.employee,
		employee_name=ref_doc.employee_name,
		leave_type=ref_doc.leave_type,
		transaction_type=ref_doc.doctype,
		transaction_name=ref_doc.name,
		is_carry_forward=0,
		is_expired=0,
		is_lwp=0
	)
	ledger.update(args)

	if submit:
		frappe.get_doc(ledger).submit()
	else:
		delete_ledger_entry(ledger)

def delete_ledger_entry(ledger):
	''' Delete ledger entry on cancel of leave application/allocation/encashment '''
	if ledger.transaction_type == "Leave Allocation":
		validate_leave_allocation_against_leave_application(ledger)

	expired_entry = get_previous_expiry_ledger_entry(ledger)
	frappe.db.sql("""DELETE
		FROM `tabLeave Ledger Entry`
		WHERE
			`transaction_name`=%s
			OR `name`=%s""", (ledger.transaction_name, expired_entry))

def get_previous_expiry_ledger_entry(ledger):
	''' Returns the expiry ledger entry having same creation date as the ledger entry to be cancelled '''
	creation_date = frappe.db.get_value("Leave Ledger Entry", filters={
			'transaction_name': ledger.transaction_name,
			'is_expired': 0,
			'transaction_type': 'Leave Allocation'
		}, fieldname=['creation'])

	creation_date = creation_date.strftime(DATE_FORMAT) if creation_date else ''

	return frappe.db.get_value("Leave Ledger Entry", filters={
		'creation': ('like', creation_date+"%"),
		'employee': ledger.employee,
		'leave_type': ledger.leave_type,
		'is_expired': 1,
		'docstatus': 1,
		'is_carry_forward': 0
	}, fieldname=['name'])

def process_expired_allocation():
	''' Check if a carry forwarded allocation has expired and create a expiry ledger entry '''

	# fetch leave type records that has carry forwarded leaves expiry
	leave_type_records = frappe.db.get_values("Leave Type", filters={
			'expire_carry_forwarded_leaves_after_days': (">", 0)
		}, fieldname=['name'])

	if leave_type_records:
		leave_type = [record[0] for record in leave_type_records]

		expired_allocation = frappe.db.sql_list("""SELECT name
			FROM `tabLeave Ledger Entry`
			WHERE
				`transaction_type`='Leave Allocation'
				AND `is_expired`=1""")

		expire_allocation = frappe.get_all("Leave Ledger Entry",
			fields=['leaves', 'to_date', 'employee', 'leave_type', 'is_carry_forward', 'transaction_name as name', 'transaction_type'],
			filters={
				'to_date': ("<", today()),
				'transaction_type': 'Leave Allocation',
				'transaction_name': ('not in', expired_allocation)
			},
			or_filters={
				'is_carry_forward': 0,
				'leave_type': ('in', leave_type)
			})

	if expire_allocation:
		create_expiry_ledger_entry(expire_allocation)

def create_expiry_ledger_entry(allocations):
	''' Create ledger entry for expired allocation '''
	for allocation in allocations:
		if allocation.is_carry_forward:
			expire_carried_forward_allocation(allocation)
		else:
			expire_allocation(allocation)

def get_remaining_leaves(allocation):
	''' Returns remaining leaves from the given allocation '''
	return frappe.db.get_value("Leave Ledger Entry",
		filters={
			'employee': allocation.employee,
			'leave_type': allocation.leave_type,
			'to_date': ('<=', allocation.to_date),
		}, fieldname=['SUM(leaves)'])

@frappe.whitelist()
def expire_allocation(allocation, expiry_date=None):
	''' expires non-carry forwarded allocation '''
	leaves = get_remaining_leaves(allocation)
	expiry_date = expiry_date if expiry_date else allocation.to_date

	if leaves:
		args = dict(
			leaves=flt(leaves) * -1,
			transaction_name=allocation.name,
			transaction_type='Leave Allocation',
			from_date=expiry_date,
			to_date=expiry_date,
			is_carry_forward=0,
			is_expired=1
		)
		create_leave_ledger_entry(allocation, args)

	frappe.db.set_value("Leave Allocation", allocation.name, "expired", 1)

def expire_carried_forward_allocation(allocation):
	''' Expires remaining leaves in the on carried forward allocation '''
	from erpnext.hr.doctype.leave_application.leave_application import get_leaves_for_period
	leaves_taken = get_leaves_for_period(allocation.employee, allocation.leave_type, allocation.from_date, allocation.to_date)
	leaves = flt(allocation.leaves) + flt(leaves_taken)
	if leaves > 0:
		args = frappe._dict(
			transaction_name=allocation.name,
			transaction_type="Leave Allocation",
			leaves=allocation.leaves * -1,
			is_carry_forward=allocation.is_carry_forward,
			is_expired=1,
			from_date=allocation.to_date,
			to_date=allocation.to_date
		)
		create_leave_ledger_entry(allocation, args)<commit_msg>fix: process allocation expiry<commit_after># -*- coding: utf-8 -*-
# Copyright (c) 2019, Frappe Technologies Pvt. Ltd. and contributors
# For license information, please see license.txt

from __future__ import unicode_literals
import frappe
from frappe.model.document import Document
from frappe import _
from frappe.utils import add_days, today, flt, DATE_FORMAT, getdate

class LeaveLedgerEntry(Document):
	def validate(self):
		if getdate(self.from_date) > getdate(self.to_date):
			frappe.throw(_("To date needs to be before from date"))

	def on_cancel(self):
		# allow cancellation of expiry leaves
		if self.is_expired:
			frappe.db.set_value("Leave Allocation", self.transaction_name, "expired", 0)
		else:
			frappe.throw(_("Only expired allocation can be cancelled"))

def validate_leave_allocation_against_leave_application(ledger):
	''' Checks that leave allocation has no leave application against it '''
	leave_application_records = frappe.db.sql_list("""
		SELECT transaction_name
		FROM `tabLeave Ledger Entry`
		WHERE
			employee=%s
			AND leave_type=%s
			AND transaction_type='Leave Application'
			AND from_date>=%s
			AND to_date<=%s
	""", (ledger.employee, ledger.leave_type, ledger.from_date, ledger.to_date))

	if leave_application_records:
		frappe.throw(_("Leave allocation %s is linked with leave application %s"
			% (ledger.transaction_name, ', '.join(leave_application_records))))

def create_leave_ledger_entry(ref_doc, args, submit=True):
	ledger = frappe._dict(
		doctype='Leave Ledger Entry',
		employee=ref_doc.employee,
		employee_name=ref_doc.employee_name,
		leave_type=ref_doc.leave_type,
		transaction_type=ref_doc.doctype,
		transaction_name=ref_doc.name,
		is_carry_forward=0,
		is_expired=0,
		is_lwp=0
	)
	ledger.update(args)

	if submit:
		frappe.get_doc(ledger).submit()
	else:
		delete_ledger_entry(ledger)

def delete_ledger_entry(ledger):
	''' Delete ledger entry on cancel of leave application/allocation/encashment '''
	if ledger.transaction_type == "Leave Allocation":
		validate_leave_allocation_against_leave_application(ledger)

	expired_entry = get_previous_expiry_ledger_entry(ledger)
	frappe.db.sql("""DELETE
		FROM `tabLeave Ledger Entry`
		WHERE
			`transaction_name`=%s
			OR `name`=%s""", (ledger.transaction_name, expired_entry))

def get_previous_expiry_ledger_entry(ledger):
	''' Returns the expiry ledger entry having same creation date as the ledger entry to be cancelled '''
	creation_date = frappe.db.get_value("Leave Ledger Entry", filters={
			'transaction_name': ledger.transaction_name,
			'is_expired': 0,
			'transaction_type': 'Leave Allocation'
		}, fieldname=['creation'])

	creation_date = creation_date.strftime(DATE_FORMAT) if creation_date else ''

	return frappe.db.get_value("Leave Ledger Entry", filters={
		'creation': ('like', creation_date+"%"),
		'employee': ledger.employee,
		'leave_type': ledger.leave_type,
		'is_expired': 1,
		'docstatus': 1,
		'is_carry_forward': 0
	}, fieldname=['name'])

def process_expired_allocation():
	''' Check if a carry forwarded allocation has expired and create a expiry ledger entry '''

	# fetch leave type records that has carry forwarded leaves expiry
	leave_type_records = frappe.db.get_values("Leave Type", filters={
			'expire_carry_forwarded_leaves_after_days': (">", 0)
		}, fieldname=['name'])

	leave_type = [record[0] for record in leave_type_records]

	expired_allocation = frappe.db.sql_list("""SELECT name
		FROM `tabLeave Ledger Entry`
		WHERE
			`transaction_type`='Leave Allocation'
			AND `is_expired`=1""")

	expire_allocation = frappe.get_all("Leave Ledger Entry",
		fields=['leaves', 'to_date', 'employee', 'leave_type', 'is_carry_forward', 'transaction_name as name', 'transaction_type'],
		filters={
			'to_date': ("<", today()),
			'transaction_type': 'Leave Allocation',
			'transaction_name': ('not in', expired_allocation)
		},
		or_filters={
			'is_carry_forward': 0,
			'leave_type': ('in', leave_type)
		})

	if expire_allocation:
			create_expiry_ledger_entry(expire_allocation)

def create_expiry_ledger_entry(allocations):
	''' Create ledger entry for expired allocation '''
	for allocation in allocations:
		if allocation.is_carry_forward:
			expire_carried_forward_allocation(allocation)
		else:
			expire_allocation(allocation)

def get_remaining_leaves(allocation):
	''' Returns remaining leaves from the given allocation '''
	return frappe.db.get_value("Leave Ledger Entry",
		filters={
			'employee': allocation.employee,
			'leave_type': allocation.leave_type,
			'to_date': ('<=', allocation.to_date),
		}, fieldname=['SUM(leaves)'])

@frappe.whitelist()
def expire_allocation(allocation, expiry_date=None):
	''' expires non-carry forwarded allocation '''
	leaves = get_remaining_leaves(allocation)
	expiry_date = expiry_date if expiry_date else allocation.to_date

	if leaves:
		args = dict(
			leaves=flt(leaves) * -1,
			transaction_name=allocation.name,
			transaction_type='Leave Allocation',
			from_date=expiry_date,
			to_date=expiry_date,
			is_carry_forward=0,
			is_expired=1
		)
		create_leave_ledger_entry(allocation, args)

	frappe.db.set_value("Leave Allocation", allocation.name, "expired", 1)

def expire_carried_forward_allocation(allocation):
	''' Expires remaining leaves in the on carried forward allocation '''
	from erpnext.hr.doctype.leave_application.leave_application import get_leaves_for_period
	leaves_taken = get_leaves_for_period(allocation.employee, allocation.leave_type, allocation.from_date, allocation.to_date)
	leaves = flt(allocation.leaves) + flt(leaves_taken)
	if leaves > 0:
		args = frappe._dict(
			transaction_name=allocation.name,
			transaction_type="Leave Allocation",
			leaves=allocation.leaves * -1,
			is_carry_forward=allocation.is_carry_forward,
			is_expired=1,
			from_date=allocation.to_date,
			to_date=allocation.to_date
		)
		create_leave_ledger_entry(allocation, args)
{'fraction_non_alphanumeric': 0.11174130195547279, 'fraction_numerical': 0.004063320071108102, 'mean_word_length': 5.817080207732256, 'pattern_counts': {'":': 2, '<': 9, '<?xml version=': 0, '>': 11, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '6718203', 'n_tokens_mistral': 4350, 'n_tokens_neox': 3953, 'n_words': 854}