/*
Legacy dashboard controller preserved for rollback.
The active controller now lives in: app/components/dashboard/dashboard_taskify.js

//blank line is required
app.controller('dashboard',['$scope','$rootScope','$http',function($scope,$rootScope,$http)
{
	rootUrl=$rootScope.site_url;
	module="dashboard/";
	$http.get(rootUrl+module+"/index").success (function(data) {if(data==0){window.location.assign('login.html');}});
	
//	$scope.type='';
	$scope.expanse=0;
	$scope.followup=0;
	
	$scope.type=localStorage.getItem("type");
	
	if(localStorage.getItem("type")=='User')
	{
		$('#user_privileges1').remove();
	}
//	$("#l_username1").html(localStorage.getItem("staff_name"));
//	$("#l_usertype").html(localStorage.getItem("type"));
//	$("#l_username2").html(localStorage.getItem("username"));
	
	$scope.filldata=function()
	{
		$scope.type=localStorage.getItem("type");
		
		if(localStorage.getItem("type")=='Administrator')
		{
//			console.log("hide");
		}
	}
	if(!localStorage.getItem("username"))
	{
		$http.get(rootUrl+"dashboard/fetch_userdata").success(function(data)
		{
			if(data.type=='A') data.type='Administrator';
			if(data.type=='U') data.type='User';
			if(data.type=='D') data.type='Developer';
			
			if(data.staff_name) localStorage.setItem("staff_name",data.staff_name);
			if(data.username) localStorage.setItem("username",data.username);
			if(data.grade) localStorage.setItem("grade",data.grade);
			if(data.image) localStorage.setItem("image",data.image);
			if(data.emp_id) localStorage.setItem("emp_id",data.emp_id);
			if(data.type) localStorage.setItem("type",data.type);
			if(data.login) localStorage.setItem("login",data.login);
			$scope.filldata();
		})	
	}
	else
	{
		$scope.filldata();
	}
	
	$('.input-daterange').datepicker({});
	$scope.cs={};
	$scope.qs={};
	$http.get(rootUrl+"dashboard/fetch_emp_bday").success(function(data){
		if(data.length>0)
			$scope.ebd=data;
	})
	$http.get(rootUrl+"dashboard/attendance").success(function(data)
	{
		$scope.$total_strength=data['total'];
		$scope.$today_absent=data['absent'];
		$scope.$today_present=data['present'];
	})
	$http.get(rootUrl+"dashboard/get_task").success(function(data)
	{
		$scope.$month_task=data['total_month_task'];
		$scope.$month_run_task=data['total_month_run'];
	})
	// checking for Members
	$http.get(rootUrl+"dashboard/fetch_admin").success(function(data)
	{
		$scope.$admin_rows=data;
	})
	$http.get(rootUrl+"dashboard/fetch_user").success(function(data)
	{
		$scope.$users_rows=data;
	})
	$http.get(rootUrl+"dashboard/fetch_emp").success(function(data)
	{
		$scope.$employee_rows=data;
	})
	// checking for Members End here
	
	$http.get(rootUrl+"dashboard/fetch_journals_data").success(function(data)
	{
		if(data['debit']>0||data['credit']>0)
		{
			$scope.expanse=1;
			$scope.debit=data['debit'];
			$scope.credit=data['credit'];
			$scope.total_trans=data['total_trans'];
		}
	})
	// checking privilege for each user
	$http.get(rootUrl+"dashboard/check_privilege").success(function(data)
	{
		if(data==1)
			$scope.followup=1;
	})
	
		
	$scope.filter_today=function()//Today's Follow Up
	{
		$scope.cs={};
		$scope.qs={};
		$scope.isLoading=true;
		var today=new Date();
		var date=today.getDate();
		if(date.toString().length==1) date='0'+date;
		var month=today.getMonth()+1;
		if(month.toString().length==1) month='0'+month;
		var year=today.getFullYear();
		tdate=date+"/"+month+"/"+year;
		
		$http.get(rootUrl+"hr_follow_up/dash_data?date="+tdate).success(function(data)
		{
			$scope.foldatas=data;
			$scope.isLoading=false;
		})
	}
	$scope.filter_today();
	
	$scope.filter_date=function()// Reminder Follow
	{
		$scope.isLoading=true;
		$scope.cs={};
		if($scope.qs.esdate && $scope.qs.eedate)
		{
			url=rootUrl+"hr_follow_up/dash_data?join=1&esdate="+$scope.qs.esdate+"&eedate="+$scope.qs.eedate;
			$http.get(url).success(function(data)
			{
				$scope.foldatas=data;
				$scope.rows=data.length;
				$scope.isLoading=false;
			})
		}
	}
			
	$scope.filter_date2=function(x)//Contact Follow
	{
		$scope.isLoading=true;
		$scope.qs={};
		if($scope.cs.csdate && $scope.cs.cedate)
		{
			url=rootUrl+"hr_follow_up/dash_data?csdate="+$scope.cs.csdate+"&cedate="+$scope.cs.cedate;
			$http.get(url).success(function(data)
			{
				$scope.foldatas=data;
				$scope.rows=data.length;
				$scope.isLoading=false;
			})
		}
	}
	
		
}]);
*/
app.factory('dashboardMotivationQuoteBank', [function () {
    var MIN_MOTIVATION_QUOTES = 150;

    // Each entry is a single, standalone quote paired with a short heading that
    // reflects its theme and a supporting note written specifically for that
    // quote. One entry (heading + quote + note) is shown per person per day;
    // quotes are never combined.
    var motivationQuotes = [
        { heading: 'Lead with compassion', text: 'We can’t heal the world today. But we can begin with a voice of compassion, a heart of love, and an act of kindness.', note: 'A single act of compassion today can matter more than a grand plan for tomorrow.' },
        { heading: 'Listen deeply', text: 'Listen with curiosity. Speak with honesty. Act with integrity.', note: 'Let curiosity, honesty, and integrity guide how you show up at work today.' },
        { heading: 'Listen deeply', text: 'The most basic and powerful way to connect to another person is to listen. Just listen.', note: 'Before responding today, give someone your full, unhurried attention.' },
        { heading: 'Listen deeply', text: 'Knowledge speaks, but wisdom listens.', note: 'The wisest move in a tough conversation is often to listen first.' },
        { heading: 'Listen deeply', text: 'Deep listening is the kind of listening that can help relieve the suffering of another person.', note: 'Truly hearing a colleague can ease a burden you didn’t know they carried.' },
        { heading: 'Listen deeply', text: 'Every person in this life has something to teach—and as soon as you accept that, you open yourself to truly listening.', note: 'Stay open today and let even small interactions teach you something new.' },
        { heading: 'Stronger together', text: 'There are no problems we cannot solve together, and very few that we can solve by ourselves.', note: 'Reach out to your team today; shared problems are lighter problems.' },
        { heading: 'Take action', text: 'I always wondered why somebody didn’t do something about that; then I realized that I am somebody.', note: 'If you notice something that needs doing today, be the one who does it.' },
        { heading: 'Lead with compassion', text: 'Let us develop respect for all living things. Let us try to replace violence and intolerance with understanding, compassion and love.', note: 'Meet today’s challenges with understanding rather than frustration.' },
        { heading: 'Take action', text: 'We say "things will turn out," but it’s what we do that actually creates the future.', note: 'Your actions today, not your hopes, are what shape the outcome.' },
        { heading: 'Stronger together', text: 'When one tugs at a single thing in nature, one finds it attached to the rest of the world.', note: 'Remember that one careful effort today can ripple across the whole team.' },
        { heading: 'Stay resilient', text: 'Do not judge me by my success, judge me by how many times I fell down and got back up again.', note: 'Every time you rise after a setback, you’re building real strength.' },
        { heading: 'Stay resilient', text: 'I can be changed by what happens to me. But I refuse to be reduced by it.', note: 'Let today’s difficulties shape you without ever diminishing you.' },
        { heading: 'Stay resilient', text: 'Don’t let a bad day trick you into thinking you have a bad life.', note: 'One rough hour doesn’t define your day, and one rough day doesn’t define your work.' },
        { heading: 'Stay resilient', text: 'Like tiny seeds with potent power to push through tough ground and become mighty trees, we hold innate reserves of unimaginable strength.', note: 'You hold more strength than today’s obstacles can demand of you.' },
        { heading: 'Stay resilient', text: 'Hardships often prepare ordinary people for an extraordinary destiny.', note: 'Today’s hard moments are quietly preparing you for bigger things.' },
        { heading: 'Live with purpose', text: 'Not all those who wander are lost.', note: 'Even an uncertain path today can still be moving you in the right direction.' },
        { heading: 'Live with purpose', text: 'Life can only be understood backwards but it must be lived forwards.', note: 'Focus on the next step today; clarity tends to come later.' },
        { heading: 'Live with purpose', text: 'You have to create your life. You have to carve it, like a sculpture.', note: 'Shape your day deliberately, one intentional choice at a time.' },
        { heading: 'Live with purpose', text: 'The purpose of life is to live it, to taste experience to the utmost.', note: 'Bring your full presence to whatever you take on today.' },
        { heading: 'Keep going', text: 'If you can’t fly then run, if you can’t run then walk, if you can’t walk then crawl, but keep moving forward.', note: 'However slow today feels, just keep moving and progress will follow.' },
        { heading: 'Choose kindness', text: 'Ask yourself: Have you been kind today?', note: 'Look for one simple chance to be kind to someone today.' },
        { heading: 'Choose kindness', text: 'One kind word can warm three winter months.', note: 'A few encouraging words today can stay with someone far longer than you think.' },
        { heading: 'Choose kindness', text: 'You can be rich in spirit, kindness, love and all those things that you can’t put a dollar sign on.', note: 'The value you bring today isn’t only measured in tasks completed.' },
        { heading: 'Choose kindness', text: 'Do things for people not because of who they are, but because of who you are.', note: 'Let your own standards, not other people’s titles, guide how you treat them.' },
        { heading: 'Choose kindness', text: 'Ah, kindness. What a simple way to tell another struggling soul that there is love to be found in the world.', note: 'A small kindness today can remind someone they’re not alone.' },
        { heading: 'Choose kindness', text: 'One of the greatest things you can do is help others discover what they have within themselves.', note: 'Help a teammate see a strength in themselves today.' },
        { heading: 'Stay hopeful', text: 'Believe that there’s light at the end of the tunnel.', note: 'Keep going today, trusting that the hard stretch won’t last forever.' },
        { heading: 'Choose kindness', text: 'Great opportunities to help others seldom come, but small ones surround us every day.', note: 'Watch for the small chances to help that fill an ordinary workday.' },
        { heading: 'Choose kindness', text: 'If you get, give. If you learn, teach.', note: 'Share something you’ve learned with someone who’d benefit today.' },
        { heading: 'Choose kindness', text: 'Look for the helpers. You will always find people who are helping.', note: 'When today feels heavy, notice the people quietly supporting you.' },
        { heading: 'Choose kindness', text: 'If the world seems cold to you, kindle fires to warm it.', note: 'Be the warmth you wish you felt from the room today.' },
        { heading: 'Dream big', text: 'The future belongs to those who believe in the beauty of their dreams.', note: 'Hold on to what you’re working toward, even on the busy days.' },
        { heading: 'Stay hopeful', text: 'If we had no winter, the spring would not be so pleasant.', note: 'Today’s harder moments make the wins ahead feel all the sweeter.' },
        { heading: 'Stay hopeful', text: 'Become a possibilitarian. Always see possibilities.', note: 'Approach today’s problems looking for what’s possible, not just what’s wrong.' },
        { heading: 'Stay hopeful', text: 'Love is better than anger. Hope is better than fear. Optimism is better than despair.', note: 'Choose the calmer, more hopeful response in today’s tense moments.' },
        { heading: 'Choose kindness', text: 'Do all the good you can, by all the means you can, in all the ways you can.', note: 'Find every reasonable way to do a little good today.' },
        { heading: 'Choose kindness', text: 'No one has ever become poor by giving.', note: 'Offering your time or help today costs little and gives much.' },
        { heading: 'Choose kindness', text: 'Wherever there is a human in need, there is an opportunity for kindness and to make a difference.', note: 'Someone near you today may quietly need a hand—stay alert to it.' },
        { heading: 'Choose kindness', text: 'From what we get, we can make a living; what we give makes a life.', note: 'Beyond getting the work done today, look for a way to give.' },
        { heading: 'Choose kindness', text: 'The heart that gives, gathers.', note: 'Generosity today tends to come back to you in unexpected ways.' },
        { heading: 'Stronger together', text: 'Touch the earth, speak of love, walk on common ground.', note: 'Look for the common ground with the people you work alongside today.' },
        { heading: 'Choose happiness', text: 'We all live with the objective of being happy.', note: 'Let a bit of what genuinely lifts you find its way into today.' },
        { heading: 'Stronger together', text: 'We can find common ground only by moving to higher ground.', note: 'Rise above small friction today to find where you and others agree.' },
        { heading: 'Stronger together', text: 'Our similarities bring us to common ground; our differences allow us to be fascinated by each other.', note: 'Let a different perspective on the team interest you today rather than divide you.' },
        { heading: 'Stronger together', text: 'Human beings are more alike than we are unalike.', note: 'Lead with what you share with others today, not what sets you apart.' },
        { heading: 'Take action', text: 'Infuse your life with action. Don’t wait for it to happen. Make it happen.', note: 'Don’t wait for the perfect moment today—start and let it take shape.' },
        { heading: 'Stay hopeful', text: 'Hope sees the invisible, feels the intangible, and achieves the impossible.', note: 'Let a hopeful outlook carry you through today’s tougher tasks.' },
        { heading: 'Stay hopeful', text: 'Hope is the thing with feathers that perches in the soul.', note: 'Keep a little hope close today; it asks for nothing and steadies everything.' },
        { heading: 'Stay hopeful', text: 'It is often in the darkest skies that we see the brightest stars.', note: 'Even a difficult day can reveal a strength you didn’t know you had.' },
        { heading: 'Be grateful', text: 'Gratitude unlocks the fullness of life. It turns what we have into enough.', note: 'Pause today to notice what’s already going right.' },
        { heading: 'Take action', text: 'Start where you are. Use what you have. Do what you can.', note: 'You have enough to begin today—start with exactly what’s in front of you.' },
        { heading: 'Keep going', text: 'Success is not final, failure is not fatal: it is the courage to continue that counts.', note: 'Whatever today’s result, keep the courage to show up tomorrow.' },
        { heading: 'Take action', text: 'The best way to predict the future is to create it.', note: 'Spend today building the outcome you want instead of waiting for it.' },
        { heading: 'Believe in yourself', text: 'What lies behind us and what lies before us are tiny matters compared to what lies within us.', note: 'Your inner resources today outweigh anything behind or ahead of you.' },
        { heading: 'Take action', text: 'A journey of a thousand miles begins with a single step.', note: 'Take just the first step today and the rest gets easier.' },
        { heading: 'Live with purpose', text: 'The only way to do great work is to love what you do.', note: 'Find something to care about in today’s work and the quality follows.' },
        { heading: 'Keep going', text: 'It does not matter how slowly you go as long as you do not stop.', note: 'Slow progress today still counts—just don’t stop.' },
        { heading: 'Take action', text: 'Small deeds done are better than great deeds planned.', note: 'One finished task today beats ten perfectly planned ones.' },
        { heading: 'Be courageous', text: 'Courage is resistance to fear, mastery of fear—not absence of fear.', note: 'Feeling nervous about today’s challenge is fine; do it anyway.' },
        { heading: 'Pursue success', text: 'Success usually comes to those who are too busy to be looking for it.', note: 'Pour yourself into the work today and let results take care of themselves.' },
        { heading: 'Keep going', text: 'Don’t watch the clock; do what it does. Keep going.', note: 'Keep a steady pace today and the hours will work for you.' },
        { heading: 'Pursue success', text: 'The harder you work for something, the greater you’ll feel when you achieve it.', note: 'Lean into the effort today; the satisfaction will match it.' },
        { heading: 'Dream big', text: 'Dream big and dare to fail.', note: 'Aim high today and treat a stumble as proof you reached.' },
        { heading: 'Take action', text: 'Every accomplishment starts with the decision to try.', note: 'Decide to try today—that’s where every win begins.' },
        { heading: 'Keep going', text: 'Difficult roads often lead to beautiful destinations.', note: 'Today’s harder path may be leading somewhere worth it.' },
        { heading: 'Dream big', text: 'You are never too old to set another goal or to dream a new dream.', note: 'It’s never too late to set a fresh goal—even today.' },
        { heading: 'Take action', text: 'Act as if what you do makes a difference. It does.', note: 'Treat today’s work as if it matters, because it does.' },
        { heading: 'Take action', text: 'The secret of getting ahead is getting started.', note: 'Beat today’s hesitation by simply starting.' },
        { heading: 'Mind your attitude', text: 'Excellence is not a skill. It is an attitude.', note: 'Bring an excellent attitude to today and the skill will show.' },
        { heading: 'Live with purpose', text: 'Don’t count the days, make the days count.', note: 'Make today count for something rather than just getting through it.' },
        { heading: 'Pursue success', text: 'The difference between ordinary and extraordinary is that little extra.', note: 'Add a little extra care to one thing today.' },
        { heading: 'Stay resilient', text: 'Fall seven times, stand up eight.', note: 'However many times today knocks you down, get back up once more.' },
        { heading: 'Keep going', text: 'A winner is a dreamer who never gives up.', note: 'Hold on to today’s goal and refuse to quit on it.' },
        { heading: 'Believe in yourself', text: 'The only limit to our realization of tomorrow is our doubts of today.', note: 'Set aside the doubt today and you’ll be surprised what’s possible.' },
        { heading: 'Choose happiness', text: 'Success is getting what you want; happiness is wanting what you get.', note: 'Appreciate what today already gives you, not only what’s next.' },
        { heading: 'Stay resilient', text: 'The greatest glory in living lies not in never falling, but in rising every time we fall.', note: 'Your strength shows in how you rise today, not in never falling.' },
        { heading: 'Stay hopeful', text: 'Keep your face always toward the sunshine—and shadows will fall behind you.', note: 'Face today’s bright side and the worries tend to fall behind you.' },
        { heading: 'Stay resilient', text: 'Challenges are what make life interesting; overcoming them is what makes life meaningful.', note: 'Treat today’s challenge as the part that makes the work meaningful.' },
        { heading: 'Be courageous', text: 'You miss 100% of the shots you don’t take.', note: 'Take the chance today—not trying is the only guaranteed miss.' },
        { heading: 'Be courageous', text: 'Everything you’ve ever wanted is on the other side of fear.', note: 'Push through the thing you’re avoiding today; what you want is just past it.' },
        { heading: 'Dream big', text: 'Don’t be pushed around by the fears in your mind. Be led by the dreams in your heart.', note: 'Let today be guided by what you want, not what you fear.' },
        { heading: 'Stay resilient', text: 'The comeback is always stronger than the setback.', note: 'If today started rough, your comeback can still define it.' },
        { heading: 'Keep growing', text: 'Your life does not get better by chance, it gets better by change.', note: 'Change one small habit today rather than waiting on luck.' },
        { heading: 'Take action', text: 'If opportunity doesn’t knock, build a door.', note: 'If today’s chance isn’t appearing, go make one.' },
        { heading: 'Believe in yourself', text: 'The only person you are destined to become is the person you decide to be.', note: 'Decide today who you want to be, then act like it.' },
        { heading: 'Believe in yourself', text: 'Believe you can and you’re halfway there.', note: 'Start today believing you’ll manage it—you’re already partway.' },
        { heading: 'Take action', text: 'Opportunities don’t happen. You create them.', note: 'Make today’s opportunity instead of waiting to be handed one.' },
        { heading: 'Pursue success', text: 'Be so good they can’t ignore you.', note: 'Let the quality of today’s work speak for itself.' },
        { heading: 'Be courageous', text: 'Never let the fear of striking out keep you from playing the game.', note: 'Don’t sit out today’s chance just because you might fail.' },
        { heading: 'Stay disciplined', text: 'Success is the sum of small efforts repeated day in and day out.', note: 'Today’s small, steady effort is exactly what success is made of.' },
        { heading: 'Keep growing', text: 'The expert in anything was once a beginner.', note: 'Be patient with yourself today—every expert started where you are.' },
        { heading: 'Stay disciplined', text: 'Motivation gets you going, but discipline keeps you growing.', note: 'When motivation dips today, let discipline carry you.' },
        { heading: 'Stay disciplined', text: 'Discipline is choosing between what you want now and what you want most.', note: 'Choose today what you want most over what you want this minute.' },
        { heading: 'Stay disciplined', text: 'Consistency is more important than perfection.', note: 'Aim to show up consistently today, not perfectly.' },
        { heading: 'Stay disciplined', text: 'Great things are done by a series of small things brought together.', note: 'String today’s small tasks together into something that matters.' },
        { heading: 'Stay disciplined', text: 'The pain of discipline weighs ounces; the pain of regret weighs tons.', note: 'A little discipline today saves a lot of regret later.' },
        { heading: 'Keep growing', text: 'Focus on progress, not perfection.', note: 'Measure today by how far you moved, not how flawless it was.' },
        { heading: 'Take action', text: 'You don’t have to be great to start, but you have to start to be great.', note: 'Don’t wait to feel ready today—just begin.' },
        { heading: 'Keep growing', text: 'Every day is a new opportunity to improve yourself.', note: 'Treat today as one more chance to get a little better.' },
        { heading: 'Pursue success', text: 'Success is not how high you have climbed, but how you make a positive difference to the world.', note: 'Measure today by the difference you made, not the height you reached.' },
        { heading: 'Live with purpose', text: 'Make each day your masterpiece.', note: 'Put real care into today as if it were your best work.' },
        { heading: 'Stay focused', text: 'A goal without a plan is just a wish.', note: 'Turn today’s goal into a concrete next step.' },
        { heading: 'Take action', text: 'The future depends on what you do today.', note: 'What you do in these hours is what your future is made of.' },
        { heading: 'Keep going', text: 'Energy and persistence conquer all things.', note: 'Keep applying steady energy today and obstacles give way.' },
        { heading: 'Believe in yourself', text: 'You become what you believe.', note: 'Hold a confident view of yourself today and live up to it.' },
        { heading: 'Take action', text: 'The only impossible journey is the one you never begin.', note: 'The work feels impossible today only until you start it.' },
        { heading: 'Take action', text: 'Well done is better than well said.', note: 'Let today’s work, not your words, make the point.' },
        { heading: 'Keep growing', text: 'Don’t limit your challenges. Challenge your limits.', note: 'Push a little past what feels comfortable today.' },
        { heading: 'Stay disciplined', text: 'Be stronger than your strongest excuse.', note: 'Notice today’s best excuse and do the work anyway.' },
        { heading: 'Keep going', text: 'The best view comes after the hardest climb.', note: 'Stay with today’s hard task; the reward is on the far side.' },
        { heading: 'Stay disciplined', text: 'Success doesn’t come from what you do occasionally; it comes from what you do consistently.', note: 'It’s the routine you keep today, not the rare burst, that pays off.' },
        { heading: 'Stay disciplined', text: 'Push yourself because no one else is going to do it for you.', note: 'Be your own push today—don’t wait to be motivated by someone else.' },
        { heading: 'Keep growing', text: 'Great things never come from comfort zones.', note: 'Step a little outside your comfort zone today.' },
        { heading: 'Keep going', text: 'Stay patient and trust your journey.', note: 'Be patient with today’s pace and trust where it’s leading.' },
        { heading: 'Keep growing', text: 'Your only competition is who you were yesterday.', note: 'Just aim to be a bit better today than you were yesterday.' },
        { heading: 'Pursue success', text: 'Work hard in silence; let success make the noise.', note: 'Put your head down today and let results do the talking.' },
        { heading: 'Keep going', text: 'Nothing worth having comes easy.', note: 'If today feels hard, it’s probably because it’s worth it.' },
        { heading: 'Stay focused', text: 'The key to success is to focus on goals, not obstacles.', note: 'Keep your eyes on today’s goal, not the things in the way.' },
        { heading: 'Keep growing', text: 'If you want something you’ve never had, you must be willing to do something you’ve never done.', note: 'Try a new approach today if you want a new result.' },
        { heading: 'Stay resilient', text: 'Strength grows in the moments when you think you can’t go on but keep going anyway.', note: 'When today feels like too much, one more step is where strength grows.' },
        { heading: 'Keep growing', text: 'Every next level of your life demands a different version of you.', note: 'Grow into today’s challenge instead of shrinking from it.' },
        { heading: 'Keep growing', text: 'Progress is impossible without change.', note: 'Welcome one change today instead of resisting it.' },
        { heading: 'Stay resilient', text: 'The strongest people are not those who show strength in front of us but those who win battles we know nothing about.', note: 'Be gentle with others today; everyone is fighting something unseen.' },
        { heading: 'Keep growing', text: 'Never stop learning because life never stops teaching.', note: 'Stay teachable today—there’s a lesson in it somewhere.' },
        { heading: 'Keep growing', text: 'Success begins at the end of your comfort zone.', note: 'Today’s growth starts right where the comfort ends.' },
        { heading: 'Mind your attitude', text: 'Your attitude determines your direction.', note: 'Set a good attitude this morning and point your whole day with it.' },
        { heading: 'Choose kindness', text: 'Be the reason someone smiles today.', note: 'Give someone a reason to smile before the day is out.' },
        { heading: 'Choose kindness', text: 'Kindness costs nothing but means everything.', note: 'A kind gesture today asks little of you and means a lot to someone.' },
        { heading: 'Choose happiness', text: 'Happiness depends upon ourselves.', note: 'Your mood today is more in your hands than in the day’s events.' },
        { heading: 'Choose happiness', text: 'The happiest people don’t have the best of everything; they make the best of everything.', note: 'Make the best of whatever today brings you.' },
        { heading: 'Be grateful', text: 'A grateful heart is a magnet for miracles.', note: 'Start today with gratitude and watch what opens up.' },
        { heading: 'Stay focused', text: 'Where focus goes, energy flows.', note: 'Aim your focus carefully today—your energy will follow it.' },
        { heading: 'Mind your attitude', text: 'Positive thoughts create positive results.', note: 'Keep your thinking constructive today and your work will reflect it.' },
        { heading: 'Stay disciplined', text: 'Choose progress over excuses.', note: 'When the excuse shows up today, choose progress instead.' },
        { heading: 'Be grateful', text: 'Every day may not be good, but there is something good in every day.', note: 'Find the one good thing in today, even on a hard one.' },
        { heading: 'Keep going', text: 'The sun himself is weak when he first rises and gathers strength and courage as the day gets on.', note: 'A slow start today is fine—you’ll build momentum as you go.' },
        { heading: 'Pursue success', text: 'Success is liking yourself, liking what you do, and liking how you do it.', note: 'Do today’s work in a way you can feel good about.' },
        { heading: 'Believe in yourself', text: 'Your potential is endless.', note: 'Don’t underestimate what you’re capable of today.' },
        { heading: 'Take action', text: 'Don’t wait for opportunity. Create it.', note: 'Make today’s opening rather than waiting for one to arrive.' },
        { heading: 'Keep growing', text: 'The greatest investment you can make is in yourself.', note: 'Spend a little of today on becoming better, not just busier.' },
        { heading: 'Keep growing', text: 'Learn as if you will live forever; live as if you will die tomorrow.', note: 'Keep learning today, and live it fully too.' },
        { heading: 'Believe in yourself', text: 'You are capable of more than you know.', note: 'Trust that you can handle more than today seems to ask.' },
        { heading: 'Take action', text: 'Make today so awesome that yesterday gets jealous.', note: 'Set out to make today better than the one before it.' },
        { heading: 'Take action', text: 'One day or day one. You decide.', note: 'Let today be day one instead of someday.' },
        { heading: 'Keep growing', text: 'It’s never too late to be what you might have been.', note: 'It’s not too late to start becoming who you want to be—today works.' },
        { heading: 'Take action', text: 'Action is the foundational key to all success.', note: 'Whatever today’s plan, action is what turns it real.' },
        { heading: 'Keep going', text: 'If you’re going through hell, keep going.', note: 'If today is rough, the way out is straight through—keep moving.' },
        { heading: 'Take action', text: 'The difference between who you are and who you want to be is what you do.', note: 'Close that gap today with one concrete action.' },
        { heading: 'Stay disciplined', text: 'Success is the product of daily habits, not once-in-a-lifetime transformations.', note: 'Tend to today’s habits and let them compound.' },
        { heading: 'Take action', text: 'The man who moves a mountain begins by carrying away small stones.', note: 'Move today’s small stone; the mountain shrinks one at a time.' },
        { heading: 'Dream big', text: 'Dreams don’t work unless you do.', note: 'Put real work behind today’s goal to make it move.' },
        { heading: 'Keep going', text: 'Every strike brings me closer to the next home run.', note: 'Treat today’s misses as steps toward the win.' },
        { heading: 'Be courageous', text: 'Don’t be afraid to give up the good to go for the great.', note: 'Be willing today to trade a safe option for a better one.' },
        { heading: 'Keep going', text: 'You never fail until you stop trying.', note: 'As long as you keep trying today, you haven’t failed.' },
        { heading: 'Keep growing', text: 'Aim for progress, not perfection.', note: 'Let today be about moving forward, not getting it flawless.' },
        { heading: 'Stay disciplined', text: 'A little progress each day adds up to big results.', note: 'Today’s small step adds to a much bigger total.' },
        { heading: 'Be courageous', text: 'Winners are not afraid of losing. Losers are.', note: 'Don’t let the fear of losing keep you from competing today.' },
        { heading: 'Believe in yourself', text: 'If you can imagine it, you can achieve it.', note: 'Picture today’s win clearly, then go earn it.' },
        { heading: 'Keep going', text: 'Success is walking from failure to failure with no loss of enthusiasm.', note: 'Keep your spirit up through today’s setbacks.' },
        { heading: 'Keep going', text: 'Keep going. Everything you need will come to you at the perfect time.', note: 'Stay the course today; what you need tends to arrive on time.' },
        { heading: 'Be courageous', text: 'The biggest risk is not taking any risk.', note: 'Playing it completely safe today is its own kind of risk.' },
        { heading: 'Believe in yourself', text: 'Nothing can dim the light that shines from within.', note: 'Don’t let today’s noise dim what you bring.' },
        { heading: 'Believe in yourself', text: 'Shine so brightly that others can find their way too.', note: 'Do your best today and you may light the way for someone else.' },
        { heading: 'Dream big', text: 'Every accomplishment was once considered impossible.', note: 'Today’s "impossible" task is just one no one’s finished yet.' },
        { heading: 'Mind your attitude', text: 'Life is 10% what happens to you and 90% how you react to it.', note: 'You can’t control all of today, but you own how you respond.' },
        { heading: 'Stay resilient', text: 'Don’t let yesterday take up too much of today.', note: 'Leave yesterday behind and give today a clean start.' },
        { heading: 'Pursue success', text: 'The best revenge is massive success.', note: 'Answer today’s doubters by doing excellent work.' },
        { heading: 'Take action', text: 'What you do today can improve all your tomorrows.', note: 'Small good choices today pay off for every day after.' },
        { heading: 'Dream big', text: 'Never give up on a dream just because of the time it will take to accomplish it.', note: 'Don’t drop today’s goal just because it’s a long road.' },
        { heading: 'Pursue success', text: 'Success is a journey, not a destination.', note: 'Enjoy today’s stretch of the road, not just the finish line.' },
        { heading: 'Stay resilient', text: 'Turn your wounds into wisdom.', note: 'Let today’s setback teach you something you can use.' },
        { heading: 'Take action', text: 'Start by doing what’s necessary; then what’s possible; suddenly you are doing the impossible.', note: 'Begin with today’s essentials and watch how far it carries you.' },
        { heading: 'Choose happiness', text: 'You don’t find the happy life. You make it.', note: 'Build a bit of happiness into today rather than waiting for it.' },
        { heading: 'Keep growing', text: 'Believe in the power of yet.', note: 'You may not have it today—add "yet" and keep working.' },
        { heading: 'Stay resilient', text: 'The struggle you’re in today is developing the strength you need tomorrow.', note: 'Today’s struggle is quietly building tomorrow’s strength.' },
        { heading: 'Stay hopeful', text: 'Every moment is a fresh beginning.', note: 'You can begin again at any point today.' },
        { heading: 'Be courageous', text: 'Don’t be afraid of being different. Be afraid of being the same as everyone else.', note: 'Bring your own perspective to the table today.' },
        { heading: 'Stay disciplined', text: 'Success is built on consistency.', note: 'Show up the same steady way today as every other day.' },
        { heading: 'Take action', text: 'The best preparation for tomorrow is doing your best today.', note: 'Give today your best and tomorrow is already easier.' },
        { heading: 'Live with purpose', text: 'Make your life a masterpiece; imagine no limitations.', note: 'Approach today without putting limits on yourself.' },
        { heading: 'Keep growing', text: 'The only time you should look back is to see how far you’ve come.', note: 'Glance back today only to notice your progress, then move on.' },
        { heading: 'Take action', text: 'Life rewards action.', note: 'Today rewards the move you actually make.' },
        { heading: 'Keep growing', text: 'What seems impossible today will one day become your warm-up.', note: 'Today’s hardest task will feel routine sooner than you think.' },
        { heading: 'Stay disciplined', text: 'Success starts with self-discipline.', note: 'Today’s results begin with the small disciplines you keep.' },
        { heading: 'Be courageous', text: 'Be fearless in the pursuit of what sets your soul on fire.', note: 'Chase what genuinely energizes you in today’s work.' },
        { heading: 'Keep growing', text: 'Every expert was once a beginner.', note: 'Give yourself room to learn today—mastery comes with reps.' },
        { heading: 'Believe in yourself', text: 'Confidence comes from keeping promises to yourself.', note: 'Keep a small promise to yourself today and confidence follows.' },
        { heading: 'Stay disciplined', text: 'The secret of success is consistency of purpose.', note: 'Stay pointed at the same purpose throughout today.' },
        { heading: 'Be courageous', text: 'The greatest mistake is being afraid to make one.', note: 'Don’t let fear of a mistake stop you from acting today.' },
        { heading: 'Stay resilient', text: 'Difficulties strengthen the mind as labor strengthens the body.', note: 'Treat today’s difficulty as a workout for your resolve.' },
        { heading: 'Take action', text: 'Your future is created by what you do today, not tomorrow.', note: 'Build your future in today’s hours, not in a someday.' },
        { heading: 'Keep growing', text: 'Growth begins when you step outside your comfort zone.', note: 'Take one uncomfortable step today and grow from it.' },
        { heading: 'Believe in yourself', text: 'You are stronger than you think and more capable than you imagine.', note: 'Today will likely ask less of you than you can give.' },
        { heading: 'Stay disciplined', text: 'Small steps every day lead to big results.', note: 'Take today’s small step and trust it adds up.' },
        { heading: 'Keep going', text: 'Don’t stop until you’re proud.', note: 'Keep at today’s work until it’s something you’re proud of.' },
        { heading: 'Pursue success', text: 'Success is earned, not given.', note: 'Earn today’s progress one honest effort at a time.' },
        { heading: 'Keep growing', text: 'The best project you’ll ever work on is yourself.', note: 'Spend a little of today improving yourself, not just your tasks.' },
        { heading: 'Stay focused', text: 'Stay focused, stay humble, and keep moving forward.', note: 'Hold focus, stay humble, and keep moving through today.' },
        { heading: 'Take action', text: 'Make it happen. Shock everyone.', note: 'Decide today to make it happen, then do exactly that.' }
    ];

    function hashString(value) {
        var hash = 0;
        var i;

        for (i = 0; i < value.length; i += 1) {
            hash = ((hash << 5) - hash) + value.charCodeAt(i);
            hash |= 0;
        }

        return Math.abs(hash);
    }

    function getDateKey(dateValue) {
        var year = dateValue.getFullYear();
        var month = dateValue.getMonth() + 1;
        var day = dateValue.getDate();

        return year + '-' + (month < 10 ? '0' + month : month) + '-' + (day < 10 ? '0' + day : day);
    }

    function getMotivationQuoteCount() {
        return motivationQuotes.length;
    }

    // Returns the single entry (heading + quote + note) for a given person on a
    // given day. The same index drives the heading, the text and the note so
    // they always belong to the same quote.
    function getDeterministicQuoteIndex(personKey, dateValue) {
        var safeDate = dateValue instanceof Date ? dateValue : new Date();
        var indexKey = String(personKey || 'team-member') + '|quote|' + getDateKey(safeDate);
        return hashString(indexKey) % motivationQuotes.length;
    }

    if (getMotivationQuoteCount() < MIN_MOTIVATION_QUOTES) {
        throw new Error('Motivation quote pool must contain at least ' + MIN_MOTIVATION_QUOTES + ' quotes.');
    }

    return {
        getQuoteCount: function () {
            return getMotivationQuoteCount();
        },

        getDeterministicHeadline: function (personKey, dateValue) {
            return motivationQuotes[getDeterministicQuoteIndex(personKey, dateValue)].heading;
        },

        getDeterministicQuote: function (personKey, dateValue) {
            return motivationQuotes[getDeterministicQuoteIndex(personKey, dateValue)].text;
        },

        getDeterministicNote: function (personKey, dateValue) {
            return motivationQuotes[getDeterministicQuoteIndex(personKey, dateValue)].note;
        }
    };
}]);
app.factory('dashboardBirthdayHelper', ['$timeout', function ($timeout) {
    var birthdayQuoteOpenings = [
        'Wishing you a birthday filled with joy, gratitude, and bright moments.',
        'Hope your special day brings smiles, confidence, and beautiful memories.',
        'May this birthday surround you with happiness, appreciation, and celebration.',
        'Sending warm birthday wishes for a day full of laughter and positivity.',
        'Celebrating you today with heartfelt wishes for happiness and success.'
    ];

    var birthdayQuoteMiddles = [
        'May the year ahead open new doors, rewarding milestones, and meaningful progress.',
        'May every step ahead feel lighter, brighter, and full of encouraging possibilities.',
        'May your journey this year be guided by confidence, growth, and well-earned wins.',
        'May this new chapter bring good health, strong momentum, and memorable achievements.',
        'May the months ahead shine with fresh energy, peaceful moments, and proud success.'
    ];

    var birthdayQuoteClosings = [
        'With warm birthday wishes from the Groveus Team.',
        'With heartfelt appreciation and birthday wishes from the Groveus Team.',
        'With cheerful wishes and support from the Groveus Team.',
        'With respect, joy, and birthday wishes from the Groveus Team.',
        'With best wishes for a wonderful year from the Groveus Team.'
    ];

    function buildBirthdayQuotes() {
        var quotes = [];

        angular.forEach(birthdayQuoteOpenings, function (opening) {
            angular.forEach(birthdayQuoteMiddles, function (middle) {
                angular.forEach(birthdayQuoteClosings, function (closing) {
                    quotes.push(opening + ' ' + middle + ' ' + closing);
                });
            });
        });

        return quotes;
    }

    var birthdayQuotes = buildBirthdayQuotes();

    function hashString(value) {
        var hash = 0;
        var i;

        for (i = 0; i < value.length; i += 1) {
            hash = ((hash << 5) - hash) + value.charCodeAt(i);
            hash |= 0;
        }

        return Math.abs(hash);
    }

    function getBirthdayDateKey(dateValue) {
        var year = dateValue.getFullYear();
        var month = dateValue.getMonth() + 1;
        var day = dateValue.getDate();

        return year + '-' + (month < 10 ? '0' + month : month) + '-' + (day < 10 ? '0' + day : day);
    }

    function getDeterministicBirthdayQuote(personKey, dateValue) {
        var quoteKey = String(personKey || 'team-member') + '|' + getBirthdayDateKey(dateValue);
        var quoteIndex = hashString(quoteKey) % birthdayQuotes.length;
        return birthdayQuotes[quoteIndex];
    }

    return {
        initScope: function (scope) {
            scope.birthdayMode = {
                isBirthdayToday: false,
                active: false,
                effectVisible: false,
                personName: scope.identity.name || 'Team Member',
                headline: '',
                subline: '',
                accentNote: '',
                quote: ''
            };

            scope.birthdayFx = {
                balloons: [
                    { left: '6%', delay: '0s', duration: '10s', colorClass: 'taskify-balloon--gold', sizeClass: 'taskify-balloon--lg' },
                    { left: '18%', delay: '1.1s', duration: '11.5s', colorClass: 'taskify-balloon--pink', sizeClass: 'taskify-balloon--md' },
                    { left: '31%', delay: '0.4s', duration: '9.8s', colorClass: 'taskify-balloon--blue', sizeClass: 'taskify-balloon--sm' },
                    { left: '46%', delay: '1.8s', duration: '12.2s', colorClass: 'taskify-balloon--gold', sizeClass: 'taskify-balloon--md' },
                    { left: '61%', delay: '0.8s', duration: '10.8s', colorClass: 'taskify-balloon--pink', sizeClass: 'taskify-balloon--lg' },
                    { left: '76%', delay: '1.5s', duration: '11.8s', colorClass: 'taskify-balloon--blue', sizeClass: 'taskify-balloon--md' },
                    { left: '89%', delay: '0.2s', duration: '9.6s', colorClass: 'taskify-balloon--gold', sizeClass: 'taskify-balloon--sm' }
                ],
                bursts: [
                    { left: '12%', top: '88px', delay: '0.4s' },
                    { left: '53%', top: '108px', delay: '1.2s' },
                    { left: '84%', top: '84px', delay: '2s' }
                ]
            };
        },

        syncIdentity: function (scope) {
            scope.birthdayMode.personName = scope.identity.name || 'Team Member';
        },

        refreshMode: function (scope, userData, rawData, helpers) {
            var todayDate = helpers.parseDMY(scope.dashboardMeta.today) || helpers.normalizeDate(new Date());
            var empId = String((scope.identity && scope.identity.emp_id) || localStorage.getItem('emp_id') || '');
            var birthdaySource = null;

            if (userData && userData.dob) {
                birthdaySource = userData;
            } else if (empId) {
                angular.forEach(helpers.asArray(rawData.staffAll), function (item) {
                    var rowEmpId = '';
                    if (birthdaySource) {
                        return;
                    }
                    if (item && item.emp_id !== undefined && item.emp_id !== null && String(item.emp_id).trim() !== '') {
                        rowEmpId = String(item.emp_id).trim();
                    } else if (item && item.eid !== undefined && item.eid !== null && String(item.eid).trim() !== '') {
                        rowEmpId = String(item.eid).trim();
                    }
                    if (rowEmpId === empId) {
                        birthdaySource = item;
                    }
                });
            }

            var dobValue = birthdaySource && birthdaySource.dob ? birthdaySource.dob : '';
            var dobDate = helpers.parseFlexibleDate(dobValue);
            var dayOffset = helpers.birthdayDayOffset(dobDate, todayDate);
            var isBirthdayToday = dayOffset === 0;
            // Celebration window: the day before, the birthday itself, and the day after.
            var isBirthdayWindow = dayOffset !== null && dayOffset >= -1 && dayOffset <= 1;
            var displayName = scope.identity.name || (birthdaySource && (birthdaySource.staff_name || birthdaySource.name)) || 'Team Member';

            scope.birthdayMode.personName = displayName;
            scope.birthdayMode.isBirthdayToday = isBirthdayToday;
            scope.birthdayMode.active = isBirthdayWindow;

            if (dayOffset === 1) {
                scope.birthdayMode.headline = displayName + "'s Birthday is Tomorrow!";
                scope.birthdayMode.subline = 'Getting close! Wishing you an early bit of joy ahead of your big day tomorrow.';
            } else if (dayOffset === -1) {
                scope.birthdayMode.headline = 'Hope You Had a Wonderful Birthday, ' + displayName + '!';
                scope.birthdayMode.subline = 'Still soaking in the celebration - hope yesterday was filled with joy and good memories.';
            } else {
                scope.birthdayMode.headline = 'Happy Birthday, ' + displayName;
                scope.birthdayMode.subline = 'Wishing you a wonderful day filled with appreciation, good energy, and a little celebration from your dashboard.';
            }
            scope.birthdayMode.accentNote = 'Celebration mode is active on your dashboard from the day before to the day after your birthday.';
            scope.birthdayMode.quote = isBirthdayWindow ? getDeterministicBirthdayQuote(empId || displayName, todayDate) : '';

            if (scope._birthdayEffectTimer) {
                $timeout.cancel(scope._birthdayEffectTimer);
                scope._birthdayEffectTimer = null;
            }

            scope.birthdayMode.effectVisible = false;
            if (!scope.birthdayMode.active) {
                return;
            }

            $timeout(function () {
                scope.birthdayMode.effectVisible = true;
                scope._birthdayEffectTimer = $timeout(function () {
                    scope.birthdayMode.effectVisible = false;
                    scope._birthdayEffectTimer = null;
                }, 7000, false);
            }, 0, false);
        },

        applyBirthdayQuickStats: function (scope, activeBirthdays, birthdayNames, helpers) {
            var loggedInEmpId = String((scope.identity && scope.identity.emp_id) || localStorage.getItem('emp_id') || '');
            var otherBirthdayNames = [];

            angular.forEach(activeBirthdays, function (item) {
                var itemEmpId = helpers.getEmpId(item);
                var itemName = helpers.getEmpName(item);
                if (itemName && String(itemEmpId) !== loggedInEmpId) {
                    otherBirthdayNames.push(itemName);
                }
            });

            if (scope.birthdayMode.isBirthdayToday) {
                scope.quickStats.todayBirthdayCardNames = otherBirthdayNames;
            } else {
                scope.quickStats.todayBirthdayCardNames = birthdayNames.slice(0);
            }

            scope.quickStats.todayBirthdayCardCount = scope.quickStats.todayBirthdayCardNames.length;
            scope.quickStats.showBirthdayMiniCard = scope.quickStats.todayBirthdayCardCount > 0;

            if (scope.quickStats.todayBirthdays > 1) {
                scope.quickStats.todayBirthdayWishText = 'Celebrate and Wish Them Today';
            } else if (scope.quickStats.todayBirthdays === 1) {
                var onlyBirthdayGender = helpers.normalizeGender(helpers.readGenderValue(activeBirthdays[0]));
                if (onlyBirthdayGender === 'male') {
                    scope.quickStats.todayBirthdayWishText = 'Celebrate and Wish Him Birthday';
                } else if (onlyBirthdayGender === 'female') {
                    scope.quickStats.todayBirthdayWishText = 'Celebrate and Wish Her Birthday';
                } else {
                    scope.quickStats.todayBirthdayWishText = 'Celebrate and Wish Them Birthday';
                }
            } else {
                scope.quickStats.todayBirthdayWishText = '';
            }

            if (scope.birthdayMode.isBirthdayToday && scope.quickStats.todayBirthdayCardCount > 0) {
                scope.quickStats.todayBirthdayNamesText = scope.quickStats.todayBirthdayCardNames.join(', ');
                scope.quickStats.todayBirthdayWishText = 'Also wish your teammates celebrating today';
            } else if (!scope.birthdayMode.isBirthdayToday && scope.quickStats.todayBirthdayCardCount > 0) {
                scope.quickStats.todayBirthdayNamesText = scope.quickStats.todayBirthdayCardNames.join(', ');
            } else if (scope.quickStats.todayBirthdayCardCount === 0) {
                scope.quickStats.todayBirthdayNamesText = 'No other birthdays to show';
            }
        }
    };
}]);
// Festival/occasion theming for the dashboard hero banner.
// The occasion list itself lives in assets/js/occasions-config.js (shared
// with index.html's top ribbon) so there is only one date window to edit.
app.factory('dashboardOccasionHelper', [function () {
    function findActiveOccasion(dateValue) {
        var occasions = window.OCCASION_CONFIG || [];
        var month = dateValue.getMonth() + 1;
        var day = dateValue.getDate();
        var value = month * 100 + day;
        var found = null;

        angular.forEach(occasions, function (occasion) {
            if (found) {
                return;
            }
            var start = occasion.startMonth * 100 + occasion.startDay;
            var end = occasion.endMonth * 100 + occasion.endDay;
            if (value >= start && value <= end) {
                found = occasion;
            }
        });

        return found;
    }

    return {
        initScope: function (scope) {
            scope.occasionMode = {
                active: false,
                key: '',
                eyebrow: '',
                title: '',
                subtitle: '',
                leftTag: '',
                rightTag: '',
                dateLabel: '',
                accent: '',
                stripe: '',
                soft: ''
            };
        },

        refreshMode: function (scope, dateValue) {
            var todayDate = dateValue instanceof Date ? dateValue : new Date();
            var occasion = findActiveOccasion(todayDate);

            scope.occasionMode.active = !!occasion;
            scope.occasionMode.key = occasion ? occasion.key : '';
            scope.occasionMode.eyebrow = occasion ? occasion.eyebrow : '';
            scope.occasionMode.title = occasion ? occasion.title : '';
            scope.occasionMode.subtitle = occasion ? occasion.subtitle : '';
            scope.occasionMode.leftTag = occasion ? occasion.leftTag : '';
            scope.occasionMode.rightTag = occasion ? occasion.rightTag : '';
            scope.occasionMode.dateLabel = occasion ? (occasion.dateLabel + ' ' + todayDate.getFullYear()) : '';
            scope.occasionMode.accent = occasion ? occasion.accent : '';
            scope.occasionMode.stripe = occasion ? occasion.stripe : '';
            scope.occasionMode.soft = occasion ? occasion.soft : '';

            // ng-style can't reliably set CSS custom properties in this
            // jQuery-backed AngularJS setup, so apply them straight to the
            // DOM instead — every themed card reads these via var(...).
            var rootStyle = document.documentElement.style;
            rootStyle.setProperty('--occasion-accent', scope.occasionMode.accent || '');
            rootStyle.setProperty('--occasion-stripe', scope.occasionMode.stripe || '');
            rootStyle.setProperty('--occasion-soft', scope.occasionMode.soft || '');
        }
    };
}]);
// Taskify-style dashboard controller.
// This is intentionally kept in a separate file so legacy dashboard logic remains preserved.
app.directive('taskifyTodoDragSource', [function () {
    return {
        restrict: 'A',
        link: function (scope, element, attrs) {
            element.attr('draggable', 'true');
            element.on('dragstart', function () {
                var bucket = attrs.taskifyTodoBucket || 'warm';
                var index = parseInt(attrs.taskifyTodoDragSource, 10);
                scope.$applyAsync(function () {
                    scope.todoDragStart(bucket, index);
                });
            });
        }
    };
}]);

app.directive('taskifyTodoDropTarget', [function () {
    return {
        restrict: 'A',
        link: function (scope, element, attrs) {
            element.on('dragover', function (event) {
                event.preventDefault();
            });
            element.on('drop', function (event) {
                event.preventDefault();
                var bucket = attrs.taskifyTodoBucket || 'warm';
                var index = parseInt(attrs.taskifyTodoDropTarget, 10);
                scope.$applyAsync(function () {
                    scope.todoDrop(bucket, index, event);
                });
            });
        }
    };
}]);

app.controller('dashboard', ['$scope', '$rootScope', '$http', '$q', '$timeout', 'dashboardBirthdayHelper', 'dashboardMotivationQuoteBank', 'dashboardOccasionHelper', function ($scope, $rootScope, $http, $q, $timeout, dashboardBirthdayHelper, dashboardMotivationQuoteBank, dashboardOccasionHelper) {
    var rootUrl = $rootScope.site_url;
    var todoWidgetResizeBound = false;
    function initRatingSelect2() {
        if (typeof $ === 'undefined' || !$.fn || !$.fn.select2) {
            return;
        }
        $timeout(function () {
            $('.rating-select2').each(function () {
                var $el = $(this);
                if (!$el.is('select')) return;
                if ($el.data('select2')) {
                    $el.select2('destroy');
                }
                $el.select2({
                    width: '100%',
                    minimumResultsForSearch: 0
                });
            });
        }, 0, false);
    }

    function ensureTodoPrioritySelectNative() {
        if (typeof $ === 'undefined' || !$.fn) {
            return;
        }
        $timeout(function () {
            var $select = $('.taskify-todo-widget__composer-select');
            if (!$select.length) {
                return;
            }
            if ($select.data('select2')) {
                $select.select2('destroy');
            }
            $select.removeClass('select2-hidden-accessible');
            $select.removeAttr('data-select2-id');
            $select.removeAttr('tabindex');
            $select.next('.select2, .select2-container').remove();
        }, 0, false);
    }

    $scope.identity = {
        name: localStorage.getItem('staff_name') || 'Team Member',
        type: localStorage.getItem('type') || 'User',
        grade: localStorage.getItem('grade') || '',
        emp_id: localStorage.getItem('emp_id') || '',
        com_id: localStorage.getItem('com_id') || '',
        branch_id: localStorage.getItem('branch_id') || ''
    };

    $scope.dashboardMeta = {
        today: '',
        loading: true,
        error: ''
    };

    dashboardBirthdayHelper.initScope($scope);
    dashboardOccasionHelper.initScope($scope);

    $scope.motivationMode = {
        active: false,
        personName: $scope.identity.name || 'Team Member',
        eyebrow: 'Daily Motivation',
        headline: '',
        quote: '',
        note: 'Small, steady effort creates strong long-term progress.'
    };

    $scope.summary = {
        adminCount: 0,
        userCount: 0,
        employeeCount: 0
    };

    $scope.roleAccess = {
        isAdmin: false,
        canAttendance: false,
        canTask: false,
        canFollowup: false,
        canStaff: false,
        canPayslip: false,
        canFinance: false,
        canTodo: true,
        canRating: false,
        canCharts: false,
        allowedModules: []
    };

    $scope.filterOptions = [
        { id: 'today', label: 'Today' },
        { id: 'yesterday', label: 'Yesterday' },
        { id: 'weekly', label: 'Weekly' },
        { id: 'monthly', label: 'Monthly' },
        { id: 'quarterly', label: 'Quarterly' },
        { id: 'half_yearly', label: 'Half Yearly' },
        { id: 'yearly', label: 'Yearly' },
        { id: 'date_range', label: 'Date Range' },
        { id: 'all', label: 'All Data' }
    ];

    $scope.filters = {
        preset: 'monthly',
        from: '',
        to: ''
    };

    $scope.filterWindow = {
        label: 'Current Month'
    };

    var rawData = {
        followupsToday: [],
        followupsAll: [],
        tasksAll: [],
        staffAll: [],
        payslipsAll: [],
        birthdaysAll: []
    };

    $scope.stats = {
        attendance: { present: 0, absent: 0, total: 0 },
        tasks: { monthly: 0, running: 0, completed: 0 },
        journals: { debit: 0, credit: 0, total: 0 }
    };

    $scope.progress = {
        attendance: 0,
        runningTasks: 0,
        todayRunningTasks: 0,
        taskCompletion: 0,
        debitShare: 0,
        payslipApproval: 0,
        reminderCoverage: 0
    };

    $scope.quickStats = {
        activeStaff: 0,
        inactiveStaff: 0,
        approvedPayslips: 0,
        pendingPayslips: 0,
        dueReminders: 0,
        todayRunningTasks: 0,
        todayTotalTasks: 0,
        todayBirthdays: 0,
        todayBirthdayNames: [],
        todayBirthdayCardCount: 0,
        todayBirthdayCardNames: [],
        showBirthdayMiniCard: false,
        todayBirthdayNamesText: 'No active birthdays today',
        todayBirthdayWishText: ''
    };

    $scope.todoWidget = {
        tasks: [],
        visibleTasks: [],
        groups: { hot: [], warm: [], cold: [] },
        visibleGroups: { hot: [], warm: [], cold: [] },
        stats: { total: 0, completed: 0, pending: 0 },
        form: { title: '', priority_type: 'warm' },
        loading: false,
        drag: { bucket: '', index: -1 },
        visibleLimit: 6,
        listMaxHeight: 320,
        isMobileBoard: false,
        isBirthdayTodoSideBySide: false,
        hiddenCount: 0
    };

    $scope.followups = [];
    $scope.reminderFeed = [];
    $scope.taskFeed = [];
    $scope.staffFeed = [];
    $scope.payslips = [];
    $scope.birthdays = [];
    $scope.ratingPanel = {
        loading: false,
        error: '',
        isGradeA: false,
        employees: [],
        filters: {
            emp_id: '',
            period: 'all'
        },
        periodOptions: [
            { id: 'all', label: 'All' },
            { id: 'this_month', label: 'This Month' },
            { id: 'last_month', label: 'Last Month' },
            { id: 'months_12', label: '12 Months (Till Last Month)' }
        ],
        summary: {
            average: 0,
            count: 0,
            high: 0,
            mid: 0,
            low: 0,
            windowLabel: 'All Time',
            employeeName: ''
        },
        comments: [],
        currentCommentIndex: 0
    };

    $scope.visuals = {
        donuts: {
            attendance: {},
            tasks: {},
            journals: {},
            payslips: {}
        },
        bars: [],
        gradeMix: [],
        activityMix: []
    };

    function toNumber(value) {
        var parsed = parseFloat(value);
        return isNaN(parsed) ? 0 : parsed;
    }

    function toCount(value) {
        if (angular.isArray(value)) {
            return value.length;
        }
        if (value && typeof value === 'object') {
            if (value.total !== undefined) return toNumber(value.total);
            if (value.count !== undefined) return toNumber(value.count);
            if (value.rows !== undefined) return toNumber(value.rows);
        }
        return toNumber(value);
    }

    function percent(part, whole) {
        if (!whole || whole <= 0) {
            return 0;
        }
        var pct = Math.round((toNumber(part) / toNumber(whole)) * 100);
        if (pct < 0) return 0;
        if (pct > 100) return 100;
        return pct;
    }

    function asArray(value) {
        return angular.isArray(value) ? value : [];
    }

    function sameDay(dateA, dateB) {
        if (!dateA || !dateB) return false;
        return dateA.getFullYear() === dateB.getFullYear() &&
            dateA.getMonth() === dateB.getMonth() &&
            dateA.getDate() === dateB.getDate();
    }

    function parseDMY(value) {
        if (!value || typeof value !== 'string') {
            return null;
        }
        var parts = value.split('/');
        if (parts.length !== 3) {
            return null;
        }
        var day = parseInt(parts[0], 10);
        var month = parseInt(parts[1], 10);
        var year = parseInt(parts[2], 10);
        if (!day || !month || !year) {
            return null;
        }
        return new Date(year, month - 1, day);
    }

    function parseISODate(value) {
        if (!value || typeof value !== 'string') {
            return null;
        }
        var parts = value.split('-');
        if (parts.length !== 3) {
            return null;
        }
        var year = parseInt(parts[0], 10);
        var month = parseInt(parts[1], 10);
        var day = parseInt(parts[2], 10);
        if (!year || !month || !day) {
            return null;
        }
        return new Date(year, month - 1, day);
    }

    function parseDashDMY(value) {
        if (!value || typeof value !== 'string') {
            return null;
        }
        var parts = value.split('-');
        if (parts.length !== 3) {
            return null;
        }
        var day = parseInt(parts[0], 10);
        var month = parseInt(parts[1], 10);
        var year = parseInt(parts[2], 10);
        if (!day || !month || !year) {
            return null;
        }
        return new Date(year, month - 1, day);
    }

    function parseFlexibleDate(value) {
        if (!value && value !== 0) {
            return null;
        }
        if (value instanceof Date) {
            return normalizeDate(value);
        }
        var text = String(value).trim();
        if (!text) {
            return null;
        }

        if (text.indexOf('/') !== -1) {
            return parseDMY(text);
        }

        if (text.indexOf('-') !== -1) {
            if (/^\d{4}-\d{2}-\d{2}$/.test(text)) {
                return parseISODate(text);
            }
            return parseDashDMY(text);
        }

        return normalizeDate(text);
    }

    function normalizeDate(dateValue) {
        if (!dateValue) return null;
        var d = new Date(dateValue);
        if (isNaN(d.getTime())) return null;
        return new Date(d.getFullYear(), d.getMonth(), d.getDate());
    }

    function formatISO(dateValue) {
        var d = normalizeDate(dateValue);
        if (!d) return '';
        var month = d.getMonth() + 1;
        var day = d.getDate();
        var m = month < 10 ? '0' + month : '' + month;
        var dd = day < 10 ? '0' + day : '' + day;
        return d.getFullYear() + '-' + m + '-' + dd;
    }

    function formatDMY(dateValue) {
        var d = normalizeDate(dateValue);
        if (!d) return '';
        var month = d.getMonth() + 1;
        var day = d.getDate();
        var m = month < 10 ? '0' + month : '' + month;
        var dd = day < 10 ? '0' + day : '' + day;
        return dd + '/' + m + '/' + d.getFullYear();
    }

    function inRange(dateValue, range) {
        var d = normalizeDate(dateValue);
        if (!d) return false;
        if (!range || (!range.start && !range.end)) {
            return true;
        }
        if (range.start && d < range.start) {
            return false;
        }
        if (range.end && d > range.end) {
            return false;
        }
        return true;
    }

    function sameMonthDay(dateA, dateB) {
        if (!dateA || !dateB) return false;
        return dateA.getMonth() === dateB.getMonth() && dateA.getDate() === dateB.getDate();
    }

    // Returns how many days away dobDate's next/last anniversary is from todayDate
    // (negative = anniversary was in the past few days, 0 = today, positive = upcoming).
    // Checks the anniversary in the previous, current and next year so it stays
    // correct across year and Feb 29 leap boundaries, and returns the closest one.
    function birthdayDayOffset(dobDate, todayDate) {
        if (!dobDate || !todayDate) return null;
        var month = dobDate.getMonth();
        var day = dobDate.getDate();
        var year = todayDate.getFullYear();
        var bestOffset = null;

        angular.forEach([year - 1, year, year + 1], function (y) {
            var anniversary = new Date(y, month, day);
            if (anniversary.getMonth() !== month) {
                // Feb 29 on a non-leap year rolls into March; clamp to the last day of the month instead.
                anniversary = new Date(y, month + 1, 0);
            }
            var diffDays = Math.round((anniversary.getTime() - todayDate.getTime()) / 86400000);
            if (bestOffset === null || Math.abs(diffDays) < Math.abs(bestOffset)) {
                bestOffset = diffDays;
            }
        });

        return bestOffset;
    }

    function readGenderValue(item) {
        if (!item) return '';
        var raw = '';
        if (item.gender !== undefined && item.gender !== null && String(item.gender).trim() !== '') {
            raw = item.gender;
        } else if (item.gen !== undefined && item.gen !== null && String(item.gen).trim() !== '') {
            raw = item.gen;
        } else if (item.sex !== undefined && item.sex !== null && String(item.sex).trim() !== '') {
            raw = item.sex;
        } else if (item.gndr !== undefined && item.gndr !== null && String(item.gndr).trim() !== '') {
            raw = item.gndr;
        }
        return raw === undefined || raw === null ? '' : String(raw).trim();
    }

    function normalizeGender(value) {
        var v = value === undefined || value === null ? '' : String(value).trim().toLowerCase();
        if (!v) return 'unknown';
        if (v === 'm' || v === 'male') return 'male';
        if (v === 'f' || v === 'female') return 'female';
        if (v.indexOf('female') === 0) return 'female';
        if (v.indexOf('male') === 0) return 'male';
        return 'unknown';
    }

    function getPresetRange(preset) {
        var today = normalizeDate(new Date());
        if (!today) {
            return { start: null, end: null, label: 'All Data' };
        }

        var start = null;
        var end = normalizeDate(today);
        var month = today.getMonth();
        var year = today.getFullYear();

        if (preset === 'today') {
            start = normalizeDate(today);
        } else if (preset === 'yesterday') {
            start = new Date(year, month, today.getDate() - 1);
            end = normalizeDate(start);
        } else if (preset === 'weekly') {
            start = new Date(year, month, today.getDate() - 6);
        } else if (preset === 'monthly') {
            start = new Date(year, month, 1);
        } else if (preset === 'quarterly') {
            var quarterStart = Math.floor(month / 3) * 3;
            start = new Date(year, quarterStart, 1);
        } else if (preset === 'half_yearly') {
            start = new Date(year, month < 6 ? 0 : 6, 1);
        } else if (preset === 'yearly') {
            start = new Date(year, 0, 1);
        } else if (preset === 'date_range') {
            start = parseISODate($scope.filters.from);
            end = parseISODate($scope.filters.to);
            if (!start || !end) {
                return { start: null, end: null, label: 'Invalid Date Range', invalid: true };
            }
            if (start > end) {
                var temp = start;
                start = end;
                end = temp;
            }
        } else if (preset === 'all') {
            start = null;
            end = null;
        }

        if (start && end) {
            return {
                start: normalizeDate(start),
                end: normalizeDate(end),
                label: formatDMY(start) + ' to ' + formatDMY(end)
            };
        }

        return { start: null, end: null, label: 'All Data' };
    }

    function getToday() {
        var now = new Date();
        var dd = now.getDate();
        var mm = now.getMonth() + 1;
        var yyyy = now.getFullYear();
        if (dd < 10) dd = '0' + dd;
        if (mm < 10) mm = '0' + mm;
        return dd + '/' + mm + '/' + yyyy;
    }

    function refreshIdentity(userData) {
        if (!userData) return;
        if (userData.type === 'A') userData.type = 'Administrator';
        if (userData.type === 'U') userData.type = 'User';
        if (userData.type === 'D') userData.type = 'Developer';

        if (userData.staff_name) {
            localStorage.setItem('staff_name', userData.staff_name);
            $scope.identity.name = userData.staff_name;
        }
        if (userData.type) {
            localStorage.setItem('type', userData.type);
            $scope.identity.type = userData.type;
        }
        if (userData.grade !== undefined && userData.grade !== null && userData.grade !== '') {
            localStorage.setItem('grade', userData.grade);
            $scope.identity.grade = userData.grade;
        }
        if (userData.emp_id !== undefined && userData.emp_id !== null && userData.emp_id !== '') {
            localStorage.setItem('emp_id', userData.emp_id);
            $scope.identity.emp_id = userData.emp_id;
        }
        if (userData.com_id !== undefined && userData.com_id !== null && userData.com_id !== '') {
            localStorage.setItem('com_id', userData.com_id);
            $scope.identity.com_id = userData.com_id;
        }
        if (userData.branch_id !== undefined && userData.branch_id !== null && userData.branch_id !== '') {
            localStorage.setItem('branch_id', userData.branch_id);
            $scope.identity.branch_id = userData.branch_id;
        }
        dashboardBirthdayHelper.syncIdentity($scope);
        $scope.motivationMode.personName = $scope.identity.name || 'Team Member';
    }

    function refreshMotivationMode() {
        var todayDate = parseDMY($scope.dashboardMeta.today) || normalizeDate(new Date());
        var displayName = $scope.identity.name || 'Team Member';
        var personKey = String(($scope.identity && $scope.identity.emp_id) || displayName || 'team-member');
        var headlinePrefix = dashboardMotivationQuoteBank.getDeterministicHeadline(personKey, todayDate);

        $scope.motivationMode.personName = displayName;
        $scope.motivationMode.active = !($scope.birthdayMode && $scope.birthdayMode.active);
        $scope.motivationMode.headline = headlinePrefix + ', ' + displayName;
        $scope.motivationMode.quote = dashboardMotivationQuoteBank.getDeterministicQuote(personKey, todayDate);
        $scope.motivationMode.note = dashboardMotivationQuoteBank.getDeterministicNote(personKey, todayDate);
    }

    function refreshOccasionMode() {
        var todayDate = parseDMY($scope.dashboardMeta.today) || normalizeDate(new Date());
        dashboardOccasionHelper.refreshMode($scope, todayDate);
    }

    function setRoleAccess(otherPrivilegesData, moduleData, followupPrivilegeFlag) {
        var moduleList = [];
        angular.forEach(asArray(moduleData), function (item) {
            if (item && item.module) {
                moduleList.push(item.module);
            }
        });

        var other = {};
        if (asArray(otherPrivilegesData).length > 0) {
            other = asArray(otherPrivilegesData)[0] || {};
        }

        var type = ($scope.identity.type || '').toLowerCase();
        var isAdmin = type === 'administrator' || type === 'developer';
        var followupAllowed = String(followupPrivilegeFlag) === '1';

        function hasModule(moduleName) {
            return moduleList.indexOf(moduleName) !== -1;
        }

        $scope.roleAccess.isAdmin = isAdmin;
        $scope.roleAccess.allowedModules = moduleList;
        $scope.roleAccess.canAttendance = isAdmin || hasModule('hr_attendance') || String(other.attendance) === '1';
        $scope.roleAccess.canTask = isAdmin || hasModule('task') || hasModule('task_assigner') || String(other.task) === '1';
        $scope.roleAccess.canFollowup = isAdmin || hasModule('hr_follow_up') || followupAllowed;
        $scope.roleAccess.canStaff = isAdmin || hasModule('hr_staff_details') || String(other.hr_staff_details) === '1';
        $scope.roleAccess.canPayslip = isAdmin || hasModule('hr_payslip');
        $scope.roleAccess.canFinance = isAdmin || hasModule('hr_journals') || hasModule('payments');
        $scope.roleAccess.canTodo = true;
        $scope.roleAccess.canRating = true;
        $scope.roleAccess.canCharts = $scope.roleAccess.canTask || $scope.roleAccess.canFollowup || $scope.roleAccess.canPayslip || $scope.roleAccess.canFinance;
    }

    function taskDate(row) {
        return parseDMY((row && (row.start_date || row.end_date)) || null);
    }

    function followupDate(row) {
        return parseDMY((row && row.date) || null);
    }

    function payslipDate(row) {
        return parseDMY((row && row.pay_date) || null);
    }

    function ratingDate(row) {
        if (!row) {
            return null;
        }
        var raw = row.created_at || row.rating_date || row.date || row.timestamp || '';
        if (!raw) {
            return null;
        }
        var asText = String(raw).trim();
        if (!asText) {
            return null;
        }
        if (asText.indexOf(' ') !== -1 && /^\d{4}-\d{2}-\d{2}/.test(asText)) {
            asText = asText.split(' ')[0];
        }
        var parsed = parseFlexibleDate(asText);
        if (parsed) {
            return parsed;
        }
        var fallback = new Date(raw);
        if (isNaN(fallback.getTime())) {
            return null;
        }
        return new Date(fallback.getFullYear(), fallback.getMonth(), fallback.getDate());
    }

    function getEmployeeId(row) {
        if (!row) return '';
        var id = row.emp_id;
        if (id === undefined || id === null || String(id).trim() === '') {
            id = row.eid;
        }
        return (id === undefined || id === null) ? '' : String(id).trim();
    }

    function getEmployeeName(row) {
        if (!row) return '-';
        var name = row.staff_name;
        if (name === undefined || name === null || String(name).trim() === '') {
            name = row.snam;
        }
        if (name === undefined || name === null || String(name).trim() === '') {
            name = row.name;
        }
        var text = (name === undefined || name === null) ? '' : String(name).trim();
        return text || '-';
    }

    function getCustomerId(row) {
        if (!row) return '';
        var id = row.c_id;
        if (id === undefined || id === null || String(id).trim() === '') {
            id = row.id;
        }
        return (id === undefined || id === null) ? '' : String(id).trim();
    }

    function getCustomerName(row) {
        if (!row) return '-';
        var name = row.customer_name;
        if (name === undefined || name === null || String(name).trim() === '') {
            name = row.company_name;
        }
        if (name === undefined || name === null || String(name).trim() === '') {
            name = row.name;
        }
        if (name === undefined || name === null || String(name).trim() === '') {
            name = row.cname;
        }
        var text = (name === undefined || name === null) ? '' : String(name).trim();
        return text || '-';
    }

    function employeeIsActive(row) {
        if (!row) return false;
        var status = row.st;
        if (status === undefined || status === null || String(status).trim() === '') {
            status = row.status;
        }
        if (status === undefined || status === null || String(status).trim() === '') {
            status = 1;
        }
        return String(status) === '1';
    }

    function isGradeAUser() {
        return String($scope.identity.grade || '').toUpperCase() === 'A';
    }

    function loadRatingCardData(empId, period) {
        $scope.ratingPanel.loading = true;
        $scope.ratingPanel.error = '';

        var params = '?period=' + encodeURIComponent(period || 'all');
        if (empId) {
            params += '&emp_id=' + encodeURIComponent(empId);
        }

        $http.get(rootUrl + 'employee_rating/rating_dashboard_card' + params).then(function (response) {
            var data = response.data || {};

            $scope.ratingPanel.isGradeA = !!data.is_grade_a;

            if (data.is_grade_a && $scope.ratingPanel.employees.length === 0) {
                var empList = asArray(data.employees);
                empList.sort(function (a, b) {
                    return String(a.staff_name).localeCompare(String(b.staff_name));
                });
                $scope.ratingPanel.employees = empList;
                initRatingSelect2();
            }

            if (data.selected_emp_id) {
                $scope.ratingPanel.filters.emp_id = String(data.selected_emp_id);
            } else if (!data.is_grade_a) {
                $scope.ratingPanel.filters.emp_id = String($scope.identity.emp_id || localStorage.getItem('emp_id') || '');
            }

            var sum = data.summary || {};
            $scope.ratingPanel.summary = {
                average: sum.average || 0,
                count: sum.count || 0,
                high: sum.high || 0,
                mid: sum.mid || 0,
                low: sum.low || 0,
                windowLabel: sum.window_label || 'All Time',
                employeeName: data.emp_name || ''
            };

            $scope.ratingPanel.comments = asArray(data.comments);
            $scope.ratingPanel.currentCommentIndex = 0;
            $scope.ratingPanel.loading = false;
        }, function () {
            $scope.ratingPanel.loading = false;
            $scope.ratingPanel.error = 'Unable to load rating data.';
        });
    }

    $scope.onRatingFilterChange = function () {
        loadRatingCardData($scope.ratingPanel.filters.emp_id, $scope.ratingPanel.filters.period);
    };

    $scope.prevRatingComment = function () {
        if ($scope.ratingPanel.currentCommentIndex > 0) {
            $scope.ratingPanel.currentCommentIndex--;
        }
    };

    $scope.nextRatingComment = function () {
        if ($scope.ratingPanel.currentCommentIndex < $scope.ratingPanel.comments.length - 1) {
            $scope.ratingPanel.currentCommentIndex++;
        }
    };

    $scope.onRatingEmployeeChange = function () {
        loadRatingCardData($scope.ratingPanel.filters.emp_id, $scope.ratingPanel.filters.period);
    };

    function sortByDateDesc(rows, extractor) {
        return asArray(rows).slice(0).sort(function (a, b) {
            var da = extractor(a);
            var db = extractor(b);
            if (!da && !db) return 0;
            if (!da) return 1;
            if (!db) return -1;
            return db - da;
        });
    }

    function countByStatus(rows, statusValue) {
        var count = 0;
        angular.forEach(asArray(rows), function (row) {
            var value = (row && row.status !== undefined && row.status !== null) ? row.status : row.st;
            if (String(value) === String(statusValue)) {
                count += 1;
            }
        });
        return count;
    }

    function buildTrendSeries(followupRows, taskRows, payslipRows) {
        var followupMap = {};
        var taskMap = {};
        var payslipMap = {};
        var keyMap = {};

        function pushDate(map, key) {
            if (!key) return;
            if (!map[key]) map[key] = 0;
            map[key] += 1;
            keyMap[key] = true;
        }

        angular.forEach(asArray(followupRows), function (row) {
            var d = followupDate(row);
            pushDate(followupMap, d ? formatISO(d) : null);
        });
        angular.forEach(asArray(taskRows), function (row) {
            var d = taskDate(row);
            pushDate(taskMap, d ? formatISO(d) : null);
        });
        angular.forEach(asArray(payslipRows), function (row) {
            var d = payslipDate(row);
            pushDate(payslipMap, d ? formatISO(d) : null);
        });

        var keys = Object.keys(keyMap).sort();
        if (keys.length > 40) {
            var fm = {};
            var tm = {};
            var pm = {};
            var monthKeysMap = {};
            angular.forEach(keys, function (key) {
                var mKey = key.substring(0, 7);
                monthKeysMap[mKey] = true;
                fm[mKey] = (fm[mKey] || 0) + (followupMap[key] || 0);
                tm[mKey] = (tm[mKey] || 0) + (taskMap[key] || 0);
                pm[mKey] = (pm[mKey] || 0) + (payslipMap[key] || 0);
            });
            keys = Object.keys(monthKeysMap).sort();
            followupMap = fm;
            taskMap = tm;
            payslipMap = pm;
        }

        if (keys.length > 24) {
            keys = keys.slice(keys.length - 24);
        }

        var categories = [];
        var fSeries = [];
        var tSeries = [];
        var pSeries = [];

        angular.forEach(keys, function (key) {
            if (key.length === 7) {
                var mk = key.split('-');
                categories.push(mk[1] + '/' + mk[0]);
            } else {
                var d = parseISODate(key);
                categories.push(d ? formatDMY(d).substring(0, 5) : key);
            }
            fSeries.push(toNumber(followupMap[key] || 0));
            tSeries.push(toNumber(taskMap[key] || 0));
            pSeries.push(toNumber(payslipMap[key] || 0));
        });

        return {
            categories: categories,
            followups: fSeries,
            tasks: tSeries,
            payslips: pSeries
        };
    }

    function getDonutStyle(pct, colorA, colorB) {
        var cleaned = pct;
        if (cleaned < 0) cleaned = 0;
        if (cleaned > 100) cleaned = 100;
        return {
            'background': 'conic-gradient(' + colorA + ' 0% ' + cleaned + '%, ' + colorB + ' ' + cleaned + '% 100%)'
        };
    }

    function buildMix(data, keyName, fallbackLabel, maxItems) {
        var mapped = {};
        var rows = asArray(data);
        angular.forEach(rows, function (row) {
            var label = row[keyName] || fallbackLabel;
            if (!mapped[label]) {
                mapped[label] = 0;
            }
            mapped[label] += 1;
        });

        var total = rows.length;
        var result = [];
        angular.forEach(mapped, function (count, label) {
            result.push({
                label: label,
                count: count,
                percent: percent(count, total)
            });
        });

        result.sort(function (a, b) {
            return b.count - a.count;
        });

        return result.slice(0, maxItems || 6);
    }

    function buildReminderFeed(followupRows, today) {
        var rows = [];
        angular.forEach(asArray(followupRows), function (item) {
            if (!item.rem_date || item.rem_date === ':') {
                return;
            }
            var remDate = parseDMY(item.rem_date);
            if (!remDate) {
                return;
            }
            var state = 'Upcoming';
            if (sameDay(remDate, today)) {
                state = 'Today';
            } else if (remDate < today) {
                state = 'Overdue';
            }
            rows.push({
                f_id: item.f_id,
                title: item.title,
                staff_name: item.staff_name,
                rem_date: item.rem_date,
                rem_time: item.rem_time,
                cname: item.cname,
                reminderDateObj: remDate,
                reminderState: state
            });
        });

        rows.sort(function (a, b) {
            return a.reminderDateObj - b.reminderDateObj;
        });

        return rows.slice(0, 10);
    }

    function buildVisualBars() {
        $scope.visuals.bars = [
            {
                label: 'Attendance Health',
                percent: $scope.progress.attendance,
                value: $scope.stats.attendance.present + ' / ' + $scope.stats.attendance.total,
                tone: 'blue'
            },
            {
                label: 'Task Completion',
                percent: $scope.progress.taskCompletion,
                value: $scope.stats.tasks.completed + ' / ' + $scope.stats.tasks.monthly,
                tone: 'teal'
            },
            {
                label: 'Payslip Approval',
                percent: $scope.progress.payslipApproval,
                value: $scope.quickStats.approvedPayslips + ' / ' + ($scope.quickStats.approvedPayslips + $scope.quickStats.pendingPayslips),
                tone: 'green'
            },
            {
                label: 'Reminder Coverage',
                percent: $scope.progress.reminderCoverage,
                value: $scope.quickStats.dueReminders + ' / ' + ($scope.reminderFeed.length || 0),
                tone: 'orange'
            }
        ];
    }

    function renderCharts(filteredFollowups, filteredTasks, filteredPayslips) {
        if (!window.Highcharts) {
            return;
        }

        $timeout(function () {
            var trendData = buildTrendSeries(filteredFollowups, filteredTasks, filteredPayslips);

            if ($scope.roleAccess.canCharts && document.getElementById('taskifyChartTrend')) {
                var trendSeries = [];
                if ($scope.roleAccess.canFollowup) {
                    trendSeries.push({
                        name: 'Follow-ups',
                        data: trendData.followups,
                        color: '#2f7afe'
                    });
                }
                if ($scope.roleAccess.canTask) {
                    trendSeries.push({
                        name: 'Tasks',
                        data: trendData.tasks,
                        color: '#1cbf9f'
                    });
                }
                if ($scope.roleAccess.canPayslip) {
                    trendSeries.push({
                        name: 'Payslips',
                        data: trendData.payslips,
                        color: '#f08b2e'
                    });
                }

                Highcharts.chart('taskifyChartTrend', {
                    chart: {
                        type: 'areaspline',
                        backgroundColor: 'transparent',
                        height: 320
                    },
                    title: { text: null },
                    credits: { enabled: false },
                    legend: { enabled: true },
                    xAxis: {
                        categories: trendData.categories,
                        tickLength: 0
                    },
                    yAxis: {
                        title: { text: null },
                        allowDecimals: false
                    },
                    tooltip: { shared: true },
                    series: trendSeries
                });
            }

            if ($scope.roleAccess.canTask && document.getElementById('taskifyChartTaskStatus')) {
                Highcharts.chart('taskifyChartTaskStatus', {
                    chart: {
                        type: 'pie',
                        backgroundColor: 'transparent',
                        height: 280
                    },
                    title: { text: null },
                    credits: { enabled: false },
                    tooltip: {
                        pointFormat: '<b>{point.y}</b> ({point.percentage:.1f}%)'
                    },
                    plotOptions: {
                        pie: {
                            innerSize: '55%',
                            dataLabels: { enabled: true }
                        }
                    },
                    series: [{
                        name: 'Tasks',
                        data: [
                            { name: 'Running', y: toNumber($scope.stats.tasks.running), color: '#f08b2e' },
                            { name: 'Completed', y: toNumber($scope.stats.tasks.completed), color: '#1cbf9f' }
                        ]
                    }]
                });
            }

            if ($scope.roleAccess.canFollowup && document.getElementById('taskifyChartActivity')) {
                var categories = [];
                var data = [];
                angular.forEach($scope.visuals.activityMix, function (item) {
                    categories.push(item.label);
                    data.push(item.count);
                });

                Highcharts.chart('taskifyChartActivity', {
                    chart: {
                        type: 'bar',
                        backgroundColor: 'transparent',
                        height: 280
                    },
                    title: { text: null },
                    credits: { enabled: false },
                    xAxis: {
                        categories: categories
                    },
                    yAxis: {
                        title: { text: null },
                        allowDecimals: false
                    },
                    legend: { enabled: false },
                    series: [{
                        name: 'Follow-ups',
                        data: data,
                        color: '#2f7afe'
                    }]
                });
            }

            if ($scope.roleAccess.canPayslip && document.getElementById('taskifyChartPayslip')) {
                Highcharts.chart('taskifyChartPayslip', {
                    chart: {
                        type: 'column',
                        backgroundColor: 'transparent',
                        height: 280
                    },
                    title: { text: null },
                    credits: { enabled: false },
                    xAxis: {
                        categories: ['Approved', 'Pending']
                    },
                    yAxis: {
                        title: { text: null },
                        allowDecimals: false
                    },
                    legend: { enabled: false },
                    series: [{
                        name: 'Payslips',
                        data: [toNumber($scope.quickStats.approvedPayslips), toNumber($scope.quickStats.pendingPayslips)],
                        colorByPoint: true,
                        colors: ['#1c9d5d', '#f08b2e']
                    }]
                });
            }
        }, 60, false);
    }

    function applyDerivedVisuals() {
        $scope.visuals.donuts.attendance = getDonutStyle($scope.progress.attendance, '#2f7afe', '#e2ecfa');
        $scope.visuals.donuts.tasks = getDonutStyle($scope.progress.taskCompletion, '#1cbf9f', '#e0f5ef');
        $scope.visuals.donuts.journals = getDonutStyle($scope.progress.debitShare, '#f08b2e', '#faead8');
        $scope.visuals.donuts.payslips = getDonutStyle($scope.progress.payslipApproval, '#1c9d5d', '#deefe6');
        buildVisualBars();
    }

    function rebuildDataForFilter() {
        var range = getPresetRange($scope.filters.preset);
        if (range.invalid) {
            return;
        }
        $scope.filterWindow.label = range.label;

        var filteredFollowups = asArray(rawData.followupsAll).filter(function (row) {
            if (!range.start && !range.end) return true;
            return inRange(followupDate(row), range);
        });

        var filteredTasks = asArray(rawData.tasksAll).filter(function (row) {
            if (!range.start && !range.end) return true;
            return inRange(taskDate(row), range);
        });

        var filteredPayslips = asArray(rawData.payslipsAll).filter(function (row) {
            if (!range.start && !range.end) return true;
            return inRange(payslipDate(row), range);
        });

        $scope.followups = sortByDateDesc(filteredFollowups, followupDate).slice(0, 8);
        $scope.taskFeed = sortByDateDesc(filteredTasks, taskDate).slice(0, 10);
        $scope.payslips = sortByDateDesc(filteredPayslips, payslipDate).slice(0, 10);

        var activeCount = 0;
        var activeEmpMap = {};
        var activeEmpDetails = {};
        angular.forEach(asArray(rawData.staffAll), function (emp) {
            var empStatus = (emp && emp.status !== undefined && emp.status !== null) ? emp.status : emp.st;
            if (String(empStatus) !== '1') {
                return;
            }
            activeCount += 1;

            var activeEmpId = '';
            if (emp && emp.emp_id !== undefined && emp.emp_id !== null && String(emp.emp_id).trim() !== '') {
                activeEmpId = String(emp.emp_id).trim();
            } else if (emp && emp.eid !== undefined && emp.eid !== null && String(emp.eid).trim() !== '') {
                activeEmpId = String(emp.eid).trim();
            }
            if (activeEmpId) {
                activeEmpMap[activeEmpId] = true;
                activeEmpDetails[activeEmpId] = {
                    gender: readGenderValue(emp)
                };
            }
        });
        $scope.quickStats.activeStaff = activeCount;
        $scope.quickStats.inactiveStaff = asArray(rawData.staffAll).length - activeCount;
        if ($scope.quickStats.inactiveStaff < 0) $scope.quickStats.inactiveStaff = 0;

        $scope.stats.tasks.monthly = filteredTasks.length;
        $scope.stats.tasks.running = countByStatus(filteredTasks, 0);
        $scope.stats.tasks.completed = $scope.stats.tasks.monthly - $scope.stats.tasks.running;
        if ($scope.stats.tasks.completed < 0) $scope.stats.tasks.completed = 0;

        $scope.progress.runningTasks = percent($scope.stats.tasks.running, $scope.stats.tasks.monthly);
        $scope.progress.taskCompletion = percent($scope.stats.tasks.completed, $scope.stats.tasks.monthly);

        $scope.quickStats.approvedPayslips = countByStatus(filteredPayslips, 1);
        $scope.quickStats.pendingPayslips = filteredPayslips.length - $scope.quickStats.approvedPayslips;
        if ($scope.quickStats.pendingPayslips < 0) $scope.quickStats.pendingPayslips = 0;
        $scope.progress.payslipApproval = percent($scope.quickStats.approvedPayslips, filteredPayslips.length);

        var reminderConfigured = 0;
        angular.forEach(filteredFollowups, function (item) {
            if (item.rem_date && item.rem_date !== ':') reminderConfigured += 1;
        });
        $scope.progress.reminderCoverage = percent(reminderConfigured, filteredFollowups.length);

        var todayDate = parseDMY($scope.dashboardMeta.today) || normalizeDate(new Date());
        $scope.quickStats.todayRunningTasks = 0;
        $scope.quickStats.todayTotalTasks = 0;
        angular.forEach(asArray(rawData.tasksAll), function (task) {
            var taskDay = taskDate(task);
            if (!sameDay(taskDay, todayDate)) {
                return;
            }

            $scope.quickStats.todayTotalTasks += 1;

            var taskStatus = (task && task.status !== undefined && task.status !== null) ? task.status : task.st;
            if (String(taskStatus) !== '0') {
                return;
            }
            $scope.quickStats.todayRunningTasks += 1;
        });
        $scope.progress.todayRunningTasks = percent($scope.quickStats.todayRunningTasks, $scope.quickStats.todayTotalTasks);

        $scope.reminderFeed = buildReminderFeed(filteredFollowups, todayDate || normalizeDate(new Date()));
        $scope.quickStats.dueReminders = 0;
        angular.forEach($scope.reminderFeed, function (item) {
            if (item.reminderState === 'Today' || item.reminderState === 'Overdue') {
                $scope.quickStats.dueReminders += 1;
            }
        });

        $scope.visuals.gradeMix = buildMix(rawData.staffAll, 'gr', 'NA', 6);
        $scope.visuals.activityMix = buildMix(filteredFollowups, 'cname', 'Uncategorized', 6);
        var activeBirthdays = [];
        var birthdayNames = [];
        var birthdayNameMap = {};
        var birthdayKeyMap = {};
        function getEmpId(item) {
            if (item && item.emp_id !== undefined && item.emp_id !== null && String(item.emp_id).trim() !== '') {
                return String(item.emp_id).trim();
            }
            if (item && item.eid !== undefined && item.eid !== null && String(item.eid).trim() !== '') {
                return String(item.eid).trim();
            }
            return '';
        }
        function getEmpName(item) {
            var name = (item && item.name !== undefined && item.name !== null) ? item.name :
                ((item && item.staff_name !== undefined && item.staff_name !== null) ? item.staff_name : item.snam);
            return name === undefined || name === null ? '' : String(name).trim();
        }
        function addBirthday(item) {
            var empId = getEmpId(item);
            var name = getEmpName(item);
            var dob = (item && item.dob !== undefined && item.dob !== null) ? String(item.dob).trim() : '';
            var gender = readGenderValue(item);
            if (!gender && empId && activeEmpDetails[empId]) {
                gender = activeEmpDetails[empId].gender || '';
            }
            var key = empId ? ('ID:' + empId) : ('ROW:' + name + '|' + dob);
            if (birthdayKeyMap[key]) {
                return;
            }
            birthdayKeyMap[key] = true;

            var safeItem = angular.extend({}, item || {});
            if (empId && (!safeItem.emp_id || String(safeItem.emp_id).trim() === '')) {
                safeItem.emp_id = empId;
            }
            if (name && (!safeItem.name || String(safeItem.name).trim() === '')) {
                safeItem.name = name;
            }
            if (gender && (!safeItem.gender || String(safeItem.gender).trim() === '')) {
                safeItem.gender = gender;
            }
            activeBirthdays.push(safeItem);

            if (name && !birthdayNameMap[name]) {
                birthdayNameMap[name] = true;
                birthdayNames.push(name);
            }
        }

        angular.forEach(asArray(rawData.birthdaysAll), function (item) {
            var itemStatus = (item && item.status !== undefined && item.status !== null) ? item.status : item.st;
            var empId = getEmpId(item);
            var isActiveBirthday = (String(itemStatus) === '1');
            if (!isActiveBirthday && empId && activeEmpMap[empId]) {
                isActiveBirthday = true;
            }
            if (!isActiveBirthday) {
                return;
            }
            addBirthday(item);
        });

        angular.forEach(asArray(rawData.staffAll), function (emp) {
            var empStatus = (emp && emp.status !== undefined && emp.status !== null) ? emp.status : emp.st;
            if (String(empStatus) !== '1') {
                return;
            }
            var dobValue = (emp && emp.dob !== undefined && emp.dob !== null) ? emp.dob : '';
            var dobDate = parseFlexibleDate(dobValue);
            if (!sameMonthDay(dobDate, todayDate)) {
                return;
            }

            addBirthday({
                emp_id: getEmpId(emp),
                name: getEmpName(emp),
                dob: typeof dobValue === 'string' ? dobValue : formatDMY(dobDate),
                st: 1
            });
        });

        $scope.birthdays = activeBirthdays.slice(0, 8);
        $scope.quickStats.todayBirthdays = activeBirthdays.length;
        $scope.quickStats.todayBirthdayNames = birthdayNames;
        $scope.quickStats.todayBirthdayNamesText = birthdayNames.length ? birthdayNames.join(', ') : 'No active birthdays today';
        dashboardBirthdayHelper.applyBirthdayQuickStats($scope, activeBirthdays, birthdayNames, {
            getEmpId: getEmpId,
            getEmpName: getEmpName,
            readGenderValue: readGenderValue,
            normalizeGender: normalizeGender
        });

        $scope.staffFeed = asArray(rawData.staffAll).slice(0, 10);

        applyDerivedVisuals();
        renderCharts(filteredFollowups, filteredTasks, filteredPayslips);
    }

    $scope.onPresetChange = function () {
        if ($scope.filters.preset !== 'date_range') {
            var range = getPresetRange($scope.filters.preset);
            $scope.filters.from = range.start ? formatISO(range.start) : '';
            $scope.filters.to = range.end ? formatISO(range.end) : '';
            rebuildDataForFilter();
        }
    };

    $scope.applyDateRange = function () {
        if (!$scope.filters.from || !$scope.filters.to) {
            return;
        }
        $scope.filters.preset = 'date_range';
        rebuildDataForFilter();
    };

    function parseJsonResponse(data) {
        if (angular.isObject(data)) return data;
        if (!data) return {};
        try {
            return JSON.parse(data);
        } catch (e) {
            return { error: 1, msg: String(data) };
        }
    }

    function normalizeTodoPriorityType(row) {
        var raw = '';
        if (row && row.priority_type !== undefined && row.priority_type !== null) raw = row.priority_type;
        else if (row && row.priority_level !== undefined && row.priority_level !== null) raw = row.priority_level;
        else if (row && row.task_priority !== undefined && row.task_priority !== null) raw = row.task_priority;
        raw = String(raw || '').trim().toLowerCase();
        if (raw === 'hot' || raw === 'high' || raw === '1') return 'hot';
        if (raw === 'cold' || raw === 'low' || raw === '3') return 'cold';
        return 'warm';
    }

    function todoPriorityRank(type) {
        if (type === 'hot') return 3;
        if (type === 'warm') return 2;
        return 1;
    }

    function sortTodoByPriority(tasks) {
        return asArray(tasks).slice().sort(function (a, b) {
            var pa = todoPriorityRank(normalizeTodoPriorityType(a));
            var pb = todoPriorityRank(normalizeTodoPriorityType(b));
            if (pb !== pa) return pb - pa;
            var ra = parseInt(a && a.priority, 10);
            var rb = parseInt(b && b.priority, 10);
            ra = isNaN(ra) ? 0 : ra;
            rb = isNaN(rb) ? 0 : rb;
            if (rb !== ra) return rb - ra;
            var ia = parseInt(a && a.todo_id, 10);
            var ib = parseInt(b && b.todo_id, 10);
            ia = isNaN(ia) ? 0 : ia;
            ib = isNaN(ib) ? 0 : ib;
            return ib - ia;
        });
    }

    function getTodoRowDate(row, fields) {
        if (!row || !fields || !fields.length) return null;
        var raw = '';
        angular.forEach(fields, function (key) {
            if (!raw && row[key] !== undefined && row[key] !== null && String(row[key]).trim() !== '') {
                raw = String(row[key]).trim();
            }
        });
        if (!raw) return null;
        if (/^\d{4}-\d{2}-\d{2}[ T]/.test(raw)) {
            raw = raw.split(/[ T]/)[0];
        }
        var parsed = parseFlexibleDate(raw);
        return parsed ? normalizeDate(parsed) : null;
    }

    function includeTodoWidgetTask(row, today) {
        if (!today) return true;
        var createdDate = getTodoRowDate(row, ['todo_date', 'task_date', 'created_at', 'date']);
        var updatedDate = getTodoRowDate(row, ['updated_at', 'modified_at', 'updated_date', 'completed_at']);
        var isCompleted = String(row && row.is_completed) === '1';

        if (isCompleted) {
            if (sameDay(updatedDate, today) || sameDay(createdDate, today)) return true;
            return false;
        }

        if (!createdDate) return true;
        return createdDate <= today;
    }

    function groupTodoByPriority(tasks) {
        var groups = { hot: [], warm: [], cold: [] };
        angular.forEach(asArray(tasks), function (task) {
            task.priority_type = normalizeTodoPriorityType(task);
            if (task.priority_type === 'hot') groups.hot.push(task);
            else if (task.priority_type === 'cold') groups.cold.push(task);
            else groups.warm.push(task);
        });
        return groups;
    }

    function buildTodoWidgetStats(tasks) {
        var list = asArray(tasks);
        var completed = 0;
        angular.forEach(list, function (item) {
            if (String(item && item.is_completed) === '1') completed += 1;
        });
        return { total: list.length, completed: completed, pending: list.length - completed };
    }

    function computeTodoVisibleLimit() {
        var ratingHeight = $('.taskify-rating-panel:visible').first().outerHeight();
        if (!ratingHeight || ratingHeight < 260) ratingHeight = 420;
        var reserved = 205; // header + composer + stats + paddings
        var usable = ratingHeight - reserved;
        if (usable < 180) usable = 180;
        var itemHeight = 56;
        var limit = Math.floor(usable / itemHeight);
        if (limit < 3) limit = 3;
        if (limit > 24) limit = 24;
        $scope.todoWidget.visibleLimit = limit;
        $scope.todoWidget.listMaxHeight = usable;
    }

    function updateTodoBoardMode() {
        var viewportWidth = window.innerWidth || document.documentElement.clientWidth || 1024;
        $scope.todoWidget.isMobileBoard = viewportWidth < 900;
        $scope.todoWidget.isBirthdayTodoSideBySide = !!(($scope.birthdayMode && $scope.birthdayMode.active) || ($scope.motivationMode && $scope.motivationMode.active)) &&
            !!$scope.roleAccess.canTodo &&
            viewportWidth > 1200;
    }

    function applyVisibleTodoSegments() {
        var sorted = sortTodoByPriority($scope.todoWidget.tasks);
        var grouped = groupTodoByPriority(sorted);
        $scope.todoWidget.groups = grouped;
        $scope.todoWidget.visibleTasks = sorted;
        $scope.todoWidget.visibleGroups = grouped;
        $scope.todoWidget.hiddenCount = 0;
    }

    function refreshTodoVisibleWindow() {
        $timeout(function () {
            updateTodoBoardMode();
            computeTodoVisibleLimit();
            applyVisibleTodoSegments();
        }, 0, false);
    }

    function getOrderedTodoFromGroups(groups) {
        var ordered = [];
        angular.forEach(['hot', 'warm', 'cold'], function (bucket) {
            angular.forEach(asArray(groups[bucket]), function (task) {
                task.priority_type = bucket;
                ordered.push(task);
            });
        });
        return ordered;
    }

    function persistTodoPriorityType(task, bucket, done) {
        if (!task || !task.todo_id) {
            if (done) done(false);
            return;
        }
        $.ajax({
            type: 'POST',
            url: rootUrl + 'to_do_list/save_data',
            data: {
                todo_id: task.todo_id,
                title: task.title || '',
                priority_type: bucket,
                priority_level: bucket,
                task_priority: bucket
            },
            success: function (data) {
                var response = parseJsonResponse(data);
                if (done) done(response && String(response.error) === '0');
            },
            error: function () {
                if (done) done(false);
            }
        });
    }

    function persistTodoOrder(done) {
        var ordered = getOrderedTodoFromGroups($scope.todoWidget.groups);
        var orderedIds = [];
        var orderedTypes = [];
        var orderedMap = [];
        var rank = ordered.length;
        angular.forEach(ordered, function (task) {
            orderedIds.push(task.todo_id);
            orderedTypes.push(task.priority_type || 'warm');
            orderedMap.push({
                todo_id: task.todo_id,
                priority_type: task.priority_type || 'warm',
                priority_rank: rank
            });
            rank -= 1;
        });

        $.ajax({
            type: 'POST',
            url: rootUrl + 'to_do_list/reorder',
            data: {
                'ordered_ids[]': orderedIds,
                'ordered_priority_types[]': orderedTypes,
                ordered_priority_map: JSON.stringify(orderedMap)
            },
            success: function (data) {
                var response = parseJsonResponse(data);
                if (done) done(response && String(response.error) === '0');
            },
            error: function () {
                if (done) done(false);
            }
        });
    }

    $scope.todoPriorityClass = function (task) {
        return 'taskify-priority--' + normalizeTodoPriorityType(task);
    };

    $scope.loadTodoWidget = function () {
        $scope.todoWidget.loading = true;
        var empId = $scope.identity.emp_id || localStorage.getItem('emp_id') || '';
        var endpoint = rootUrl + 'to_do_list/view/500/1?status=1';
        if (empId) endpoint += '&emp_id=' + encodeURIComponent(empId);

        $http.get(endpoint).success(function (response) {
            response = response || {};
            var today = normalizeDate(new Date());
            var tasks = sortTodoByPriority(asArray(response.data).filter(function (row) {
                return includeTodoWidgetTask(row, today);
            }));
            $scope.todoWidget.tasks = tasks;
            $scope.todoWidget.stats = buildTodoWidgetStats(tasks);
            refreshTodoVisibleWindow();
            ensureTodoPrioritySelectNative();

            if (!todoWidgetResizeBound && typeof $ !== 'undefined') {
                todoWidgetResizeBound = true;
                $(window).on('resize.taskifyTodoWidget', function () {
                    $scope.$applyAsync(function () {
                        refreshTodoVisibleWindow();
                    });
                });
            }
        }).finally(function () {
            $scope.todoWidget.loading = false;
        });
    };

    $scope.addTodoFromWidget = function () {
        var title = (($scope.todoWidget.form && $scope.todoWidget.form.title) || '').trim();
        if (!title) return;

        $.ajax({
            type: 'POST',
            url: rootUrl + 'to_do_list/save_data',
            data: { title: title, priority_type: ($scope.todoWidget.form.priority_type || 'warm') },
            success: function (data) {
                var response = parseJsonResponse(data);
                if (response && String(response.error) === '0') {
                    $scope.$applyAsync(function () {
                        $scope.todoWidget.form.title = '';
                        $scope.todoWidget.form.priority_type = 'warm';
                        $scope.loadTodoWidget();
                    });
                    messages('success', 'Success!', 'Task added successfully.', 2200);
                } else {
                    messages('warning', 'Warning!', response.msg || 'Unable to add task.', 4500);
                }
            }
        });
    };

    $scope.toggleTodoFromWidget = function (task) {
        if (!task || !task.todo_id) return;
        var nextValue = String(task.is_completed) === '1' ? 0 : 1;
        $.ajax({
            type: 'POST',
            url: rootUrl + 'to_do_list/toggle_status',
            data: { todo_id: task.todo_id, is_completed: nextValue },
            success: function (data) {
                var response = parseJsonResponse(data);
                if (response && String(response.error) === '0') {
                    $scope.$applyAsync(function () {
                        $scope.loadTodoWidget();
                    });
                } else {
                    messages('warning', 'Warning!', response.msg || 'Unable to update task.', 4500);
                }
            }
        });
    };

    $scope.deleteTodoFromWidget = function (task) {
        if (!task || !task.todo_id) return;
        if (!confirm('Archive this task?')) return;
        $http.get(rootUrl + 'to_do_list/delete_data?id=' + task.todo_id).success(function (data) {
            var response = parseJsonResponse(data);
            var normalizedData = String(data || '').trim();
            var isSuccess = normalizedData === '1' ||
                (response && (String(response.error) === '0' || String(response.status) === '1' || String(response.success) === '1'));
            if (isSuccess) {
                messages('success', 'Success!', 'Task archived.', 2000);
                $scope.loadTodoWidget();
            } else {
                messages('warning', 'Warning!', (response && response.msg) ? response.msg : 'Unable to archive task.', 4500);
            }
        });
    };

    $scope.todoDragStart = function (bucket, index) {
        $scope.todoWidget.drag.bucket = bucket;
        $scope.todoWidget.drag.index = index;
    };

    $scope.todoDragOver = function ($event) {
        if ($event && $event.preventDefault) $event.preventDefault();
    };

    $scope.todoDrop = function (targetBucket, targetIndex, $event) {
        if ($event && $event.preventDefault) $event.preventDefault();
        var fromBucket = $scope.todoWidget.drag.bucket;
        var fromIndex = $scope.todoWidget.drag.index;
        if (!fromBucket || fromIndex < 0) return;

        if (!targetBucket) targetBucket = fromBucket;
        if (['hot', 'warm', 'cold'].indexOf(targetBucket) === -1) return;

        var groups = $scope.todoWidget.groups || { hot: [], warm: [], cold: [] };
        var source = groups[fromBucket] || [];
        if (!source.length || fromIndex >= source.length) return;

        var moved = source.splice(fromIndex, 1)[0];
        var target = groups[targetBucket] || [];
        var insertAt = parseInt(targetIndex, 10);
        if (isNaN(insertAt) || insertAt < 0 || insertAt > target.length) insertAt = target.length;
        target.splice(insertAt, 0, moved);
        moved.priority_type = targetBucket;

        $scope.todoWidget.drag.bucket = '';
        $scope.todoWidget.drag.index = -1;

        $scope.todoWidget.tasks = getOrderedTodoFromGroups(groups);
        applyVisibleTodoSegments();

        persistTodoOrder(function (orderedOk) {
            if (!orderedOk) {
                messages('warning', 'Warning!', 'Unable to save task order.', 4500);
                return;
            }
            persistTodoPriorityType(moved, targetBucket, function (typeOk) {
                if (!typeOk) {
                    messages('warning', 'Warning!', 'Priority updated in UI but API did not confirm priority type save.', 4500);
                }
            });
        });
    };

    $scope.openTodoActiveModal = function () {
        ensureTodoPrioritySelectNative();
        var $todoModal = $('#todoActiveTaskModal');
        if ($todoModal.length && !$todoModal.parent().is('body')) {
            $todoModal.appendTo('body');
        }
        $todoModal.modal('show');
        refreshTodoVisibleWindow();
    };

    $scope.refreshDashboard = function () {
        $scope.dashboardMeta.loading = true;
        $scope.dashboardMeta.error = '';
        $scope.dashboardMeta.today = getToday();

        $q.all([
            $http.get(rootUrl + 'dashboard/index'),
            $http.get(rootUrl + 'dashboard/fetch_userdata'),
            $http.get(rootUrl + 'dashboard/check_other_privilge').then(function (response) {
                return response;
            }, function () {
                return { data: [] };
            }),
            $http.get(rootUrl + 'dashboard/check_emp_privilge').then(function (response) {
                return response;
            }, function () {
                return { data: [] };
            }),
            $http.get(rootUrl + 'dashboard/check_privilege').then(function (response) {
                return response;
            }, function () {
                return { data: 2 };
            }),
            $http.get(rootUrl + 'dashboard/fetch_admin'),
            $http.get(rootUrl + 'dashboard/fetch_user'),
            $http.get(rootUrl + 'dashboard/attendance'),
            $http.get(rootUrl + 'dashboard/get_task'),
            $http.get(rootUrl + 'dashboard/fetch_journals_data'),
            $http.get(rootUrl + 'hr_follow_up/dash_data?date=' + $scope.dashboardMeta.today),
            $http.get(rootUrl + 'hr_follow_up/dash_data'),
            $http.get(rootUrl + 'task/view/2000/1?join=1'),
            $http.get(rootUrl + 'hr_staff_details/view'),
            $http.get(rootUrl + 'hr_payslip/view'),
            $http.get(rootUrl + 'employee/fetch_emp_bday').then(function (response) {
                return response;
            }, function () {
                return { data: [] };
            })
        ]).then(function (responses) {
            var auth = responses[0].data;
            if (auth === 0 || auth === '0') {
                window.location.assign('login.html');
                return;
            }

            refreshIdentity(responses[1].data);
            setRoleAccess(responses[2].data, responses[3].data, responses[4].data);
            $scope.summary.adminCount = toCount(responses[5].data);
            $scope.summary.userCount = toCount(responses[6].data);
            $scope.summary.employeeCount = $scope.summary.adminCount + $scope.summary.userCount;

            var attendance = responses[7].data || {};
            $scope.stats.attendance.present = toNumber(attendance.present);
            $scope.stats.attendance.absent = toNumber(attendance.absent);
            $scope.stats.attendance.total = toNumber(attendance.total);
            $scope.progress.attendance = percent($scope.stats.attendance.present, $scope.stats.attendance.total);

            var tasks = responses[8].data || {};
            $scope.stats.tasks.monthly = toNumber(tasks.total_month_task);
            $scope.stats.tasks.running = toNumber(tasks.total_month_run);
            $scope.stats.tasks.completed = $scope.stats.tasks.monthly - $scope.stats.tasks.running;
            if ($scope.stats.tasks.completed < 0) {
                $scope.stats.tasks.completed = 0;
            }
            $scope.progress.runningTasks = percent($scope.stats.tasks.running, $scope.stats.tasks.monthly);
            $scope.progress.taskCompletion = percent($scope.stats.tasks.completed, $scope.stats.tasks.monthly);

            var journals = responses[9].data || {};
            $scope.stats.journals.debit = toNumber(journals.debit);
            $scope.stats.journals.credit = toNumber(journals.credit);
            $scope.stats.journals.total = toNumber(journals.total_trans);
            $scope.progress.debitShare = percent($scope.stats.journals.debit, $scope.stats.journals.total);

            rawData.followupsToday = asArray(responses[10].data);
            rawData.followupsAll = asArray(responses[11].data);

            var taskPayload = responses[12].data || {};
            rawData.tasksAll = asArray(taskPayload.data || taskPayload);

            rawData.staffAll = asArray(responses[13].data);

            rawData.payslipsAll = asArray(responses[14].data);
            rawData.birthdaysAll = asArray(responses[15].data);
            dashboardBirthdayHelper.refreshMode($scope, responses[1].data || {}, rawData, {
                parseDMY: parseDMY,
                normalizeDate: normalizeDate,
                parseFlexibleDate: parseFlexibleDate,
                sameMonthDay: sameMonthDay,
                birthdayDayOffset: birthdayDayOffset,
                asArray: asArray
            });
            refreshMotivationMode();
            refreshOccasionMode();

            $scope.onPresetChange();
            $scope.loadTodoWidget();
            if ($scope.roleAccess.canRating) {
                $scope.ratingPanel.filters.period = 'all';
                $scope.ratingPanel.employees = [];
                loadRatingCardData(null, 'all');
            }
            $scope.dashboardMeta.loading = false;
            refreshTodoVisibleWindow();
        }, function () {
            $scope.dashboardMeta.loading = false;
            $scope.dashboardMeta.error = 'Unable to load dashboard data. Please verify API/session and retry.';
        });
    };

    $scope.refreshDashboard();

}]);
//blank line is required
app.controller('change_password',['$scope','$rootScope','$http',function($scope,$rootScope,$http)
{
	rootUrl=$rootScope.site_url;
	ch_module="change_password";
//	$http.get(rootUrl+module+"/index").success (function(data) {if(data==0){window.location.assign('login.html');} else if(data==2){messages("success", "Privilege not assigned.", 1000);window.location.assign('index.html');}});
	
	$scope.x={};
	$scope.filter_new=function()
	{
		$scope.x={};
	}
	$scope.save_data=function(x)
	{
		$('#submitbtn').attr('disabled',true);
		$.ajax({
			type: "POST",
			url: rootUrl+"login/change_password_submit",
			data: $("#changeform1").serialize(),
			beforeSend: function()
			{
				$('#loader').css('display','inline');
			},
			success: function(data)
			{
				if(data=="1")
				{
					$scope.filter_new();
					messages("success", "Success!","Password Changed Successfully", 3000);
				}
				else if(data=="0")
				{
					messages("warning", "Info!","No Data Affected", 3000);
				}
				else
				{
					messages("danger", "Warning!",data, 6000);
				}
				$('#loader').css('display','none');
				$('#submitbtn').attr('disabled',false);
			}
		});
	}
	
}]);//blank line is required
app.controller('grade',['$scope','$rootScope','$http',function($scope,$rootScope,$http){
	rootUrl=$rootScope.site_url;
	module="hr_grades/";
	$http.get(rootUrl+module+"/index").success (function(data) {if(data==0){window.location.assign('login.html');} else if(data==2){messages("success", "Privilege not assigned.", 1000);window.location.assign('index.html');}});

	function initGradeSelect2() {
		if (typeof $ === 'undefined' || !$.fn || !$.fn.select2) {
			return;
		}

		setTimeout(function () {
			$('.grade-master-root select').each(function () {
				var $el = $(this);

				if ($el.data('select2')) {
					$el.select2('destroy');
				}

				if ($el.prop('disabled')) {
					return;
				}

				$el.select2({
					width: '100%',
					allowClear: false,
					minimumResultsForSearch: 0
				});
			});
		}, 0);
	}

	$scope.pageno = 1;
	$scope.total_count = 0;
	$scope.itemsPerPage = '15';
	$scope.qx = {};
	$scope.x = {};
	$scope.datadb = [];
	$scope.grade_modal_title = "Add Grade";

	$scope.loader=function(pageno)
	{
		if(!pageno)
			pageno = 1;
		$scope.pageno = pageno;

		var params = [];
		if($scope.qx.grade)
			params.push("search=" + encodeURIComponent($scope.qx.grade));
		if($scope.qx.status!==undefined && $scope.qx.status!=="")
			params.push("st=" + encodeURIComponent($scope.qx.status));
		params.push("per_page=" + encodeURIComponent($scope.itemsPerPage));
		params.push("page=" + encodeURIComponent(pageno));

		var url = rootUrl + module + "view?" + params.join("&");
		$http.get(url).success(function(response){
			if(response && response.data !== undefined)
			{
				$scope.datadb = response.data;
				$scope.total_count = response.total_count || 0;
			}
			else
			{
				$scope.datadb = response || [];
				$scope.total_count = ($scope.datadb || []).length;
			}
			initGradeSelect2();
		});
	};

	$scope.apply_filters = function()
	{
		$scope.loader(1);
	};

	$scope.clear_filters = function()
	{
		$scope.qx = {};
		$scope.itemsPerPage = '15';
		$scope.$applyAsync();
		$('#gradeFilterStatus').val('').trigger('change.select2');
		$('#gradeFilterPerPage').val('15').trigger('change.select2');
		$scope.loader(1);
	};

	$scope.on_items_per_page_change = function()
	{
		$scope.loader(1);
	};

	$scope.update_call=function(y){
		$scope.x = angular.copy(y);
		$scope.x.status = String($scope.x.status);
	}

	$scope.open_grade_modal=function(mode,y)
	{
		if(mode=="edit" && y)
		{
			$scope.grade_modal_title = "Edit Grade";
			$scope.update_call(y);
		}
		else
		{
			$scope.grade_modal_title = "Add Grade";
			$scope.filter_new(false);
		}
		$('#gradeModal').modal('show');
		initGradeSelect2();
	}
	
	$scope.filter_new=function(refreshList){
		$scope.x={};
		if(refreshList!==false)
			$scope.loader($scope.pageno || 1);
	}
	
	$scope.save_data=function(y){
		$('#submitbtn').attr('disabled',true);
		$.ajax({
			type: "POST",
			url: rootUrl+module+"save",
			data: $("#catform").serialize(),
			beforeSend: function()
			{
				$('#loader').css('display','inline');
			},
			success: function(data){
				data = (data || '').trim();
				if(data=="1")
				{
					$scope.x={};
					messages("success", "Success!","Grade Saved Successfully", 3000);
					$scope.loader($scope.pageno || 1);
					$('#gradeModal').modal('hide');
				}
				else if(data=="0")
				{
					messages("warning", "Info!","No Data Affected", .3000);
				}
				else
				{
					messages("danger", "Warning!",data, 8000);
				}
				$('#loader').css('display','none');
				$('#submitbtn').attr('disabled',false);
			}
		});
	}
	
	$scope.delete_data=function(id)
	{
		if(confirm("Deleting Grade may hamper your data associated with it."))
		{
			if(confirm("Are you Sure to DELETE ??"))
			{
				$http.get(rootUrl+module+"delete?id="+id).success(function(data){
					if(data=="1")
					{
						messages("success", "Success!","Grade Deleted Successfully", 3000);
					}
					else
					{
						messages("danger", "Warning!",data+", Grade not Deleted", 4000);
					}
					$scope.loader($scope.pageno || 1);
				})
			}
		}
	}

	$scope.filter_new(false);
	$scope.loader(1);
	initGradeSelect2();
	
}]);
//blank line is required
app.controller('hr_designation',['$scope','$rootScope','$http',function($scope,$rootScope,$http){
	module='hr_designation';
	rootUrl=$rootScope.site_url;
	$http.get(rootUrl+module+"/index").success (function(data) {if(data==0){window.location.assign('login.html');} else if(data==2){messages("success", "Privilege not assigned.", 1000);window.location.assign('index.html');}});

	function initDesignationSelect2() {
		if (typeof $ === 'undefined' || !$.fn || !$.fn.select2) {
			return;
		}

		setTimeout(function () {
			var $modal = $('#designationModal');
			var isSelect2V4 = !!($.fn.select2 && $.fn.select2.amd);

			$('.designation-select2').each(function () {
				var $el = $(this);
				var inModal = $el.closest('#designationModal').length > 0;

				if (!$el.is('select')) {
					return;
				}

				if ($el.data('select2')) {
					$el.select2('destroy');
				}

				var options = {
					width: '100%',
					allowClear: false,
					minimumResultsForSearch: 0
				};

				if (isSelect2V4) {
					options.dropdownParent = inModal ? $modal : $(document.body);
				}

				$el.select2(options);
			});
		}, 0);
	}

	function allowSelect2TypingInsideModal() {
		if (typeof $ === 'undefined' || !$.fn || !$.fn.modal || !$.fn.modal.Constructor) {
			return;
		}

		var ModalConstructor = $.fn.modal.Constructor;
		if (ModalConstructor.prototype._designationSelect2FocusPatched) {
			return;
		}

		ModalConstructor.prototype.enforceFocus = function () {
			var modalThis = this;
			$(document)
				.off('focusin.bs.modal')
				.on('focusin.bs.modal', function (e) {
					var $target = $(e.target);
					var isInsideModal = modalThis.$element[0] === e.target || modalThis.$element.has(e.target).length;
					var isSelect2Input =
						$target.closest('.select2-container, .select2-dropdown, .select2-drop, .select2-search').length > 0 ||
						$target.is('.select2-input, .select2-search__field');

					if (!isInsideModal && !isSelect2Input) {
						modalThis.$element.trigger('focus');
					}
				});
		};

		ModalConstructor.prototype._designationSelect2FocusPatched = true;

		$(document).off('select2:open.designation select2-open.designation');
		$(document).on('select2:open.designation select2-open.designation', function () {
			setTimeout(function () {
				var $search = $('.select2-container--open .select2-search__field, .select2-drop-active .select2-input');
				if ($search.length) {
					$search.focus();
				}
			}, 0);
		});
	}

	function getSelectValue(selector) {
		var $el = $(selector);
		if (!$el.length) {
			return '';
		}
		if ($el.data('select2') && typeof $el.select2 === 'function') {
			var select2Value = $el.select2('val');
			return select2Value || '';
		}
		return $el.val() || '';
	}
	
	$scope.pageno = 1;
	$scope.total_count = 0;
	$scope.itemsPerPage = '15';
	$scope.qx = {name:'', grade:'', status:''};
	$scope.x = {};
	$scope.datadb = [];
	$scope.salary = "";
	$scope.designation_modal_title = "Add Designation";

	$http.get(rootUrl+"hr_departments/view").success(function(data)
	{
		$scope.departments=data;
		initDesignationSelect2();
	});
	$http.get(rootUrl+"hr_grades/view?st=1").success(function(data)
	{
		$scope.grades=data;
		initDesignationSelect2();
	});

	$scope.loader=function(pageno)
	{
		if(!pageno)
			pageno = 1;
		$scope.pageno = pageno;

		var params = ['join=1'];
		if($scope.qx.name)
			params.push("search=" + encodeURIComponent($scope.qx.name));
		if($scope.qx.grade)
			params.push("grade=" + encodeURIComponent($scope.qx.grade));
		if($scope.qx.status!==undefined && $scope.qx.status!=="")
			params.push("st=" + encodeURIComponent($scope.qx.status));
		params.push("per_page=" + encodeURIComponent($scope.itemsPerPage));
		params.push("page=" + encodeURIComponent(pageno));

		var url = rootUrl+module+"/view?" + params.join("&");
		$http.get(url).success(function(response){
			if(response && response.data !== undefined)
			{
				$scope.datadb = response.data;
				$scope.total_count = response.total_count || 0;
			}
			else
			{
				$scope.datadb = response || [];
				$scope.total_count = ($scope.datadb || []).length;
			}
		});
	};

	$scope.apply_filters = function()
	{
		$scope.qx.name = $('#designationSearchText').val() || '';
		$scope.qx.grade = getSelectValue('#designationFilterGrade');
		$scope.qx.status = getSelectValue('#designationFilterStatus');
		$scope.itemsPerPage = getSelectValue('#designationFilterPerPage') || $scope.itemsPerPage;
		$scope.loader(1);
	};

	$scope.clear_filters = function()
	{
		// $scope.qx = {name:'', grade:'', status:''};
		$scope.qx.name = '';
		$scope.qx.grade = '';
		$scope.qx.status = '';
		$scope.itemsPerPage = '15';
		$scope.$applyAsync();
		$('#designationSearchText').val('');
		$('#designationFilterGrade').val('').trigger('change.select2');
		$('#designationFilterStatus').val('').trigger('change.select2');
		$('#designationFilterPerPage').val('15').trigger('change.select2');
		$scope.loader(1);
	};

	$scope.on_items_per_page_change = function()
	{
		$scope.loader(1);
	};
	
	$scope.update_call=function(y)
	{
		$scope.x = angular.copy(y);
		$scope.x.status = String($scope.x.status);
		$scope.fetch_salary($scope.x.grade);
	}

	$scope.fetch_salary=function(g)
	{
		if(g)
		{
			$scope.salary="loading......";
			$http.get(rootUrl+"hr_grades/view?grade="+g).success(function(data)
			{
				if(data && data.length > 0)
					$scope.salary=data[0].min_sal+" - "+ data[0].max_sal;
				else
					$scope.salary="";
			});
		}
		else $scope.salary="";
	}

	$scope.open_designation_modal=function(mode,y)
	{
		if(mode=="edit" && y)
		{
			$scope.designation_modal_title = "Edit Designation";
			$scope.update_call(y);
		}
		else
		{
			$scope.designation_modal_title = "Add Designation";
			$scope.filter_new(false);
		}
		$('#designationModal').modal('show');
		allowSelect2TypingInsideModal();
		initDesignationSelect2();
	}
	
	$scope.filter_new=function(refreshList)
	{
		$scope.x={};
		$scope.salary="";
		if(refreshList!==false)
			$scope.loader($scope.pageno || 1);
		initDesignationSelect2();
	}
	
	$scope.save_data=function()
	{
		$('#submitbtn').attr('disabled',true);
		$.ajax({
			type: "POST",
			url: rootUrl+module+"/save",
			data: $("#desform").serialize(),
			beforeSend: function()
			{
				$('#loader').css('display','inline');
			},
			success: function(data)
			{
				data = (data || '').trim();
				if(data=="1")
				{
					messages("success", "Success!","Designation Saved Successfully", 3000);
					$scope.loader($scope.pageno || 1);
					$('#designationModal').modal('hide');
					$scope.filter_new(false);
				}
				else if(data=="0")
				{
					messages("warning", "Info!","No Data Affected", 3000);
				}
				else
				{
					messages("danger", "Warning!",data, 6000);
				}
				$('#loader').css('display','none');
				$('#submitbtn').attr('disabled',false);
			}
		});
	}
	
	$scope.delete_data=function(id)
	{
		if(confirm("Deleting Designation may hamper your data associated with it."))
		{
			if(confirm("Are you Sure to DELETE ??"))
			{
				$http.get(rootUrl+module+"/delete?dg_id="+id).success(function(data){
					if(data=="1")
					{
						messages("success", "Success!","Designation Deleted Successfully", 3000);
					}
					else
					{
						messages("danger", "Warning!","Designation not Deleted", 4000);
					}
					$scope.loader($scope.pageno || 1);
				});
			}
		}
	}

	$scope.filter_new(false);
	$scope.loader(1);
	allowSelect2TypingInsideModal();
	$('#designationModal').on('shown.bs.modal', function () {
		initDesignationSelect2();
	});
	initDesignationSelect2();
}]);
//blank line is required
app.controller('pay_setting',['$scope','$rootScope','$http',function($scope,$rootScope,$http){
	module='hr_pay_setting/';
	rootUrl=$rootScope.site_url;
	$http.get(rootUrl+module+"index").success(function(data) {
		if(data==0){window.location.assign('login.html');}
		else if(data==2){messages("success", "Privilege not assigned.", 1000);window.location.assign('index.html');}
	});

	function initPaySettingSelect2() {
		if (typeof $ === 'undefined' || !$.fn || !$.fn.select2) {
			return;
		}

		setTimeout(function () {
			$('.paysetting-master-root select').each(function () {
				var $el = $(this);

				if ($el.data('select2')) {
					$el.select2('destroy');
				}

				if ($el.prop('disabled')) {
					return;
				}

				$el.select2({
					width: '100%',
					allowClear: false,
					minimumResultsForSearch: 0
				});
			});
		}, 0);
	}

	function bindPaySettingSelect2Events() {
		if (typeof $ === 'undefined') {
			return;
		}

		$(document)
			.off('shown.bs.modal.paySettingSelect2', '#paySettingModal')
			.on('shown.bs.modal.paySettingSelect2', '#paySettingModal', function () {
				initPaySettingSelect2();
			});
	}

	function resetPaySettingModalDefaults() {
		$scope.x = {
			grade: '',
			pt_id: '',
			min_amt: '',
			max_amt: '',
			per: ''
		};

		$scope.$applyAsync();

		$('#paySettingModalGrade').val('').trigger('change.select2');
		$('#paySettingModalPayType').val('').trigger('change.select2');
		initPaySettingSelect2();
	}

	$scope.pageno = 1;
	$scope.total_count = 0;
	$scope.itemsPerPage = '15';
	$scope.search_text = '';
	$scope.qx = { grade: '', pt_id: '', st: '' };
	$scope.x = {};
	$scope.datadb = [];
	$scope.grades = [];
	$scope.paytypes_data = [];
	$scope.paysetting_modal_title = "Add Pay Setting";

	$scope.ftype = function(t) {
		return t=='1' ? "-" : "+";
	};

	$scope.load_filters = function()
	{
		$http.get(rootUrl+"hr_paytype/view?st=1").success(function(data)
		{
			$scope.paytypes_data = data || [];
			initPaySettingSelect2();
		});
		$http.get(rootUrl+"hr_grades/view?st=1").success(function(data)
		{
			$scope.grades = data || [];
			initPaySettingSelect2();
		});
	};

	$scope.loader = function(pageno)
	{
		if(!pageno)
			pageno = 1;
		$scope.pageno = pageno;

		var params = ["join=1", "per_page=" + encodeURIComponent($scope.itemsPerPage), "page=" + encodeURIComponent(pageno)];
		if($scope.search_text)
			params.push("search=" + encodeURIComponent($scope.search_text));
		if($scope.qx.grade)
			params.push("grade=" + encodeURIComponent($scope.qx.grade));
		if($scope.qx.pt_id)
			params.push("pt_id=" + encodeURIComponent($scope.qx.pt_id));
		if($scope.qx.st !== undefined && $scope.qx.st !== "")
			params.push("st=" + encodeURIComponent($scope.qx.st));

		$http.get(rootUrl + module + "view?" + params.join("&")).success(function(response){
			if(response && response.data !== undefined)
			{
				$scope.datadb = response.data || [];
				$scope.total_count = response.total_count || 0;
			}
			else
			{
				$scope.datadb = response || [];
				$scope.total_count = ($scope.datadb || []).length;
			}
			initPaySettingSelect2();
		});
	};

	$scope.apply_filters = function()
	{
		$scope.loader(1);
	};

	$scope.clear_filters = function()
	{
		$scope.search_text = '';
		$scope.qx = { grade: '', pt_id: '', st: '' };
		$scope.itemsPerPage = '15';
		$scope.$applyAsync();
		$('#paySettingSearchText').val('');
		$('#paySettingGrade').val('').trigger('change.select2');
		$('#paySettingPayType').val('').trigger('change.select2');
		$('#paySettingStatus').val('').trigger('change.select2');
		$('#paySettingPerPage').val('15').trigger('change.select2');
		initPaySettingSelect2();
		$scope.loader(1);
	};

	$scope.on_items_per_page_change = function()
	{
		$scope.loader(1);
	};

	$scope.update_call = function(y)
	{
		$scope.paysetting_modal_title = "Edit Pay Setting";
		$scope.x = angular.copy(y);
		$scope.x.status = String($scope.x.status);
		initPaySettingSelect2();
	};

	$scope.open_paysetting_modal = function(mode, y)
	{
		if(mode=="edit" && y)
		{
			$scope.update_call(y);
		}
		else
		{
			$scope.paysetting_modal_title = "Add Pay Setting";
			$scope.filter_new(false);
		}
		$('#paySettingModal').modal('show');
		initPaySettingSelect2();
		bindPaySettingSelect2Events();
	};

	$scope.filter_new = function(refreshList)
	{
		resetPaySettingModalDefaults();
		$scope.paysetting_modal_title = "Add Pay Setting";
		if(refreshList!==false)
			$scope.loader($scope.pageno || 1);
	};

	$scope.save_data = function()
	{
		$('#submitbtn').attr('disabled',true);
		$.ajax({
			type: "POST",
			url: rootUrl + module + "save",
			data: $("#desform").serialize(),
			beforeSend: function()
			{
				$('#loader').css('display','inline');
			},
			success: function(data)
			{
				data = (data || '').trim();
				if(data=="1")
				{
					messages("success", "Success!","Pay Setting Saved Successfully", 3000);
					$scope.loader($scope.pageno || 1);
					resetPaySettingModalDefaults();
					$scope.paysetting_modal_title = "Add Pay Setting";
					$('#paySettingModal').modal('hide');
				}
				else if(data=="0")
				{
					messages("warning", "Info!","No Data Affected", 3000);
				}
				else
				{
					messages("danger", "Warning!",data, 6000);
				}
				$('#loader').css('display','none');
				$('#submitbtn').attr('disabled',false);
			}
		});
	};

	$scope.delete_data = function(id)
	{
		if(confirm("Deleting Pay Setting may hamper your data associated with it."))
		{
			if(confirm("Are you Sure to DELETE ??"))
			{
				$http.get(rootUrl+module+"delete?ps_id="+id).success(function(data){
					if(data=="1")
					{
						messages("success", "Success!","Pay Setting Deleted Successfully", 3000);
					}
					else
					{
						messages("danger", "Warning!","Pay Setting not Deleted", 4000);
					}
					$scope.loader($scope.pageno || 1);
				});
			}
		}
	};

	$scope.pageChangeHandler = function(newPageNumber)
	{
		$scope.loader(newPageNumber);
	};

	$scope.load_filters();
	$scope.filter_new(false);
	$scope.loader(1);
	bindPaySettingSelect2Events();
	initPaySettingSelect2();
}]);
app.controller('hr_leaves',['$scope','$rootScope','$http',function($scope,$rootScope,$http){
	lmodule='hr_leaves/';
	rootUrl=$rootScope.site_url;
	$http.get(rootUrl+lmodule+"/index").success (function(data) {if(data==0){window.location.assign('login.html');} else if(data==2){messages("success", "Privilege not assigned.", 1000);window.location.assign('index.html');}});

	$scope.login_check=localStorage.getItem('type');
	$scope.emp_id=localStorage.getItem('emp_id');
	$scope.pageno = 1;
	$scope.total_count = 0;
	$scope.itemsPerPage = '12';
	$scope.qx = {};
	$scope.datadb = [];
	$scope.session_list = [];
	$scope.x = {sess:1};
	$scope.leave_modal_title = "Add Leave";

	$scope.init_datepicker = function()
	{
		setTimeout(function(){
			$("#DOB2").datepicker();
			$("#DOB3").datepicker();
		},100);
	};

	function initHrLeavesSelect2() {
		if (typeof $ === 'undefined' || !$.fn || !$.fn.select2) {
			return;
		}

		setTimeout(function () {
			var $modal = $('#hrLeavesModal');
			var isSelect2V4 = !!($.fn.select2 && $.fn.select2.amd);

			if (!$modal.length) {
				return;
			}

			$modal.find('select').each(function () {
				var $el = $(this);
				if (!$el.is('select')) {
					return;
				}

				if ($el.data('select2')) {
					$el.select2('destroy');
				}

				var options = {
					width: '100%',
					allowClear: false,
					minimumResultsForSearch: 0
				};

				if (isSelect2V4) {
					options.dropdownParent = $modal;
				}

				$el.select2(options);
			});
		}, 0);
	}

	function initFilterSelect2() {
		if (typeof $ === 'undefined' || !$.fn || !$.fn.select2) {
			return;
		}

		setTimeout(function () {
			var $filterBar = $('.hr-leaves-filterbar');

			if (!$filterBar.length) {
				return;
			}

			$filterBar.find('select').each(function () {
				var $el = $(this);
				if (!$el.is('select')) {
					return;
				}

				if ($el.data('select2')) {
					$el.select2('destroy');
				}

				var options = {
					width: '100%',
					allowClear: true,
					minimumResultsForSearch: 0
				};

				$el.select2(options);
			});
		}, 0);
	}

	function allowSelect2TypingInsideHrLeavesModal() {
		if (typeof $ === 'undefined' || !$.fn || !$.fn.modal || !$.fn.modal.Constructor) {
			return;
		}

		var ModalConstructor = $.fn.modal.Constructor;
		if (ModalConstructor.prototype._hrLeavesSelect2FocusPatched) {
			return;
		}

		ModalConstructor.prototype.enforceFocus = function () {
			var modalThis = this;
			$(document)
				.off('focusin.bs.modal')
				.on('focusin.bs.modal', function (e) {
					var $target = $(e.target);
					var isInsideModal = modalThis.$element[0] === e.target || modalThis.$element.has(e.target).length;
					var isSelect2Input =
						$target.closest('.select2-container, .select2-dropdown, .select2-drop, .select2-search').length > 0 ||
						$target.is('.select2-input, .select2-search__field');

					if (!isInsideModal && !isSelect2Input) {
						modalThis.$element.trigger('focus');
					}
				});
		};

		ModalConstructor.prototype._hrLeavesSelect2FocusPatched = true;

		$(document).off('select2:open.hrLeaves select2-open.hrLeaves');
		$(document).on('select2:open.hrLeaves select2-open.hrLeaves', function () {
			setTimeout(function () {
				var $search = $('.select2-container-active .select2-input, .select2-drop-active .select2-input, .select2-container--open .select2-search__field');
				if ($search.length) {
					$search.focus();
				}
			}, 0);
		});
	}

	$scope.loader = function(pageno)
	{
		if(!pageno)
			pageno = 1;
		$scope.pageno = pageno;

		var params = ["join=1"];
		if($scope.qx.emp_id)
			params.push("emp_id=" + encodeURIComponent($scope.qx.emp_id));
		if($scope.qx.sess_id)
			params.push("sess_id=" + encodeURIComponent($scope.qx.sess_id));
		if($scope.qx.status!==undefined && $scope.qx.status!=="")
			params.push("status=" + encodeURIComponent($scope.qx.status));

		var url = rootUrl + lmodule + "view/" + $scope.itemsPerPage + "/" + pageno;
		if(params.length)
			url += "?" + params.join("&");

		$http.get(url).success(function(response)
		{
			if(response && response.data !== undefined)
			{
				$scope.datadb = response.data || [];
				$scope.total_count = response.total_count || 0;
			}
			else
			{
				$scope.datadb = response || [];
				$scope.total_count = ($scope.datadb || []).length;
			}
		});
	};

	$scope.apply_filters = function()
	{
		$scope.loader(1);
	};

	$scope.clear_filters = function()
	{
		$scope.qx = {};
		$scope.itemsPerPage = '12';
		$scope.loader(1);
	};

	$scope.on_items_per_page_change = function()
	{
		$scope.loader(1);
	};

	$scope.initalValue=function()
	{
		$scope.x.cl=0;
		$scope.x.sl=0;
		$scope.x.pl=0;
		$scope.x.ml=0;
		$scope.x.lwp=0;
		$scope.x.days=0;
	};

	$scope.get_days=function()
	{
		if(!$scope.x.to || !$scope.x.from)
			return;

		$scope.initalValue();
		$scope.disable=0;
		$scope.x.reason="";

		$scope.color2 = {
		        "background-color" : "white",
		   };

		$scope.dateError=0;
		$scope.compare = $scope.x.to.split("/");
		$scope.compare =parseInt($scope.compare[1]);

		$scope.x.fDate = $scope.x.from.split("/");
		$scope.x.fDate =parseInt($scope.x.fDate[1]);

		if($scope.compare==$scope.x.fDate)
		{
			if($scope.x.from && $scope.x.to)
			{
				$http.get(rootUrl+"hr_leaves/number_of_working_days?sdate="+$scope.x.from+"&edate="+$scope.x.to).success(function(data){
		    		$scope.x.days=parseInt(data);
		    	});
			}
		}
		else
		{
			$scope.disable=1;
			$scope.dateError=1;
			$scope.color2 = {
			        "background-color" : "#f17d7d6b",
			   };
			$scope.initalValue();
			$scope.x.reason="";

			$scope.x.casual="";
			$scope.x.sick="";
			$scope.x.paid="";
			$scope.x.maternity="";

			$scope.cl=0;
			$scope.sl=0;
			$scope.pl=0;
			$scope.ml=0;
			$scope.lwp=0;
		}

		$http.get(rootUrl+"hr_leaves/leaveRecord?from="+$scope.x.from+"&to="+$scope.x.to+"&emp_id="+$scope.x.emp_id).success(function(data)
		{
    		$scope.leaveshow=0;
    		if(data.show=='1')
    		{
    			$scope.leaveshow=1;
    			$scope.from=data.from;
    			$scope.to=data.to;
    		}
    	});
	};


	$scope.clear_leave_to=function()
	{
		$("#DOB3").val("");
		$scope.leaveshow=0;
	};

	$scope.fetch_employee=function(y)
	{
		$http.get(rootUrl+"hr_staff_details/view?data=emp_id,staff_name&status=1").success(function(data)
		{
			$scope.employees=data;
			initHrLeavesSelect2();
			initFilterSelect2();
		});
	};
	$scope.fetch_employee();

	$scope.load_session_filter = function()
	{
		$http.get(rootUrl+"hr_leaves/employee_session").success(function(data)
		{
			$scope.session_list = data || [];
			initFilterSelect2();
		});
	};

	$scope.fetch_total_leave=function(ses_id,id)
	{
		$scope.initalValue();
		$scope.cl=0;
		$scope.sl=0;
		$scope.pl=0;
		$scope.ml=0;

		$http.get(rootUrl+"hr_leave_setting/view?grade="+$scope.grade).success(function(data)
		{
			if(data.length>0)
			{
				$scope.x.casual=data[0].cl;
				$scope.x.sick=data[0].sl;
				$scope.x.paid=data[0].pl;
				$scope.x.maternity=data[0].ml;
			}

			$http.get(rootUrl+"hr_leaves/employe_session_leave?join=1&emp_id="+id+"&sess_id="+ses_id).success(function(data)
			{
				angular.forEach(data, function(val, key)
				{
					if(val.cl)
						$scope.cl=$scope.cl+parseFloat(val.cl);
					if(val.sl)
						$scope.sl=$scope.sl+parseFloat(val.sl);
					if(val.pl)
						$scope.pl=$scope.pl+parseFloat(val.pl);
					if(val.ml)
						$scope.ml=$scope.ml+parseFloat(val.ml);
				});
			});
    	});
	};


	$scope.fetch_leave=function(id,skipListRefresh,preserveFormData)
	{
		if(preserveFormData!==true)
		{
			$scope.cl=0;
			$scope.sl=0;
			$scope.pl=0;
			$scope.ml=0;
			$scope.initalValue();
			$scope.x.reason="";
			$scope.x.from="";
			$scope.x.to="";

			$scope.x.casual="";
			$scope.x.sick="";
			$scope.x.paid="";
			$scope.x.maternity="";

			$scope.x.sess=1;
		}
		if(!id)
			return;

		if(skipListRefresh!==true)
		{
			$scope.qx.emp_id = id;
			$scope.loader(1);
		}

		$http.get(rootUrl+"hr_staff_details/view?emp_id="+id).success(function(data)
		{
			$scope.grade=data[0].grade;
			$http.get(rootUrl+"hr_leaves/employe_session_leave?emp_id="+id).success(function(data)
			{
				if(data.length>1)
				{
					$scope.x.sess=2;
					 $scope.required = true;
					 $scope.session=data;
					 if(preserveFormData!==true || !$scope.x.sess_id)
					 	$scope.x.sess_id=data[0].sess_id;
					 $scope.fetch_total_leave($scope.x.sess_id,id);
				}
				else
				{
					$http.get(rootUrl+"hr_leave_setting/view?grade="+$scope.grade).success(function(data)
					{
						if(data.length>0)
						{
							$scope.x.casual=data[0].cl;
							$scope.x.sick=data[0].sl;
							$scope.x.paid=data[0].pl;
							$scope.x.maternity=data[0].ml;
						}

		    			});
					$http.get(rootUrl+"hr_leaves/view?emp_id="+id).success(function(data)
					{
						angular.forEach(data, function(val, key)
						{
							if(val.cl)
								$scope.cl=$scope.cl+parseFloat(val.cl);
							if(val.sl)
								$scope.sl=$scope.sl+parseFloat(val.sl);
							if(val.pl)
								$scope.pl=$scope.pl+parseFloat(val.pl);
							if(val.ml)
								$scope.ml=$scope.ml+parseFloat(val.ml);
						});
		    			});
				}
	    	});

			initHrLeavesSelect2();
		});
	};

	$scope.calc_lwp=function()
	{
		$scope.disable=0;
		$scope.x.lwp=parseFloat($scope.x.days)-(parseFloat($scope.x.cl)+parseFloat($scope.x.sl)+parseFloat($scope.x.ml)+parseFloat($scope.x.pl));
		if($scope.x.lwp<0)
		{
			$scope.disable=1;
			$scope.color = {
			        "color" : "white",
			        "background-color" : "red",
			   };
		}else{
			$scope.color = {
			        "background-color" : "white",
			   };
		}
	};
	$scope.update_call=function(y)
	{
		$scope.cl=0;
		$scope.sl=0;
		$scope.pl=0;
		$scope.ml=0;
		$scope.x=angular.copy(y);
		if(y.sess_id&&y.sess_id>0)
		{
			$http.get(rootUrl+"hr_leaves/employee_session?sess_id="+y.sess_id).success(function(data)
			{
				$scope.x.sess=2;
				 $scope.required = true;
				 $scope.session=data;
				 $scope.x.sess_id=data[0].sess_id;
			});
		}
		$scope.fetch_leave($scope.x.emp_id,true,true);
	};

	$scope.open_leave_modal=function(mode,y)
	{
		if(mode=="edit" && y)
		{
			$scope.leave_modal_title = "Edit Leave";
			$scope.update_call(y);
		}
		else
		{
			$scope.leave_modal_title = "Apply For Leave";
			$scope.filter_new(false);
		}
		$('#hrLeavesModal').modal('show');
		allowSelect2TypingInsideHrLeavesModal();
		initHrLeavesSelect2();
		$scope.init_datepicker();
	};

	$scope.filter_new=function(refreshList)
	{
		$scope.x={sess:1};
		$scope.salary="";
		$scope.required = false;
		$scope.session = [];
		$scope.disable = 0;
		$scope.leaveshow = 0;
		$scope.dateError = 0;
		$scope.color = {"background-color" : "white"};
		$scope.color2 = {"background-color" : "white"};
		$scope.cl=0;
		$scope.sl=0;
		$scope.pl=0;
		$scope.ml=0;
		$scope.lwp=0;
		if(refreshList!==false)
			$scope.loader($scope.pageno || 1);
	};

	$scope.save_data=function(x)
	{
		$('#submitbtn11').attr('disabled',true);
		$.ajax({
			type: "POST",
			url: rootUrl+lmodule+"/save",
			data: $("#hr_leaves_form2").serialize(),
			beforeSend: function()
			{
				$('#loader121').css('display','inline');
			},
			success: function(data)
			{
				console.log(data);
				if(data=="1")
				{
					messages("success", "Success!","Leave Saved Successfully", 3000);
					$scope.loader($scope.pageno || 1);
					$scope.filter_new(false);
					$scope.cl=0;
					$scope.sl=0;
					$scope.pl=0;
					$scope.ml=0;
					$scope.lwp=0;
					$('#hrLeavesModal').modal('hide');
				}
				else if(data=="0")
				{
					messages("warning", "Info!","No Data Affected", 3000);
				}
				else
				{
					messages("danger", "Warning!",data, 6000);
				}
				$('#loader121').css('display','none');
				$('#submitbtn11').attr('disabled',false);
			}
		});
	};

	$scope.delete_data=function(id)
	{
		if(confirm("Deleting Data may hamper your data associated with it."))
		{
			if(confirm("Are you Sure to DELETE ??"))
			{
				$http.get(rootUrl+lmodule+"/delete?el_id="+id).success(function(data){
					if(data=="1")
					{
						messages("success", "Success!","Data Deleted Successfully", 3000);
					}
					else
					{
						messages("danger", "Warning!","Data not Deleted", 4000);
					}
					$scope.loader($scope.pageno || 1);
				});
			}
		}
	};

	$scope.load_session_filter();
	$scope.filter_new(false);
	$scope.loader(1);
	$scope.init_datepicker();
	initFilterSelect2();
	allowSelect2TypingInsideHrLeavesModal();
	$(document).off('shown.bs.modal.hrLeavesSelect2', '#hrLeavesModal').on('shown.bs.modal.hrLeavesSelect2', '#hrLeavesModal', function () {
		initHrLeavesSelect2();
	});
}]);
app.controller('paytype',['$scope','$rootScope','$http',function($scope,$rootScope,$http){
	module='hr_paytype';
	rootUrl=$rootScope.site_url;
	$http.get(rootUrl+module+"/index").success (function(data) {if(data==0){window.location.assign('login.html');} else if(data==2){messages("success", "Privilege not assigned.", 1000);window.location.assign('index.html');}});

	function initPaytypeSelect2() {
		if (typeof $ === 'undefined' || !$.fn || !$.fn.select2) {
			return;
		}

		setTimeout(function () {
			$('.paytype-master-root select').each(function () {
				var $el = $(this);

				if ($el.data('select2')) {
					$el.select2('destroy');
				}

				$el.select2({
					width: '100%',
					allowClear: false,
					minimumResultsForSearch: 0
				});
			});
		}, 0);
	}

	function bindPaytypeSelect2Events() {
		if (typeof $ === 'undefined') {
			return;
		}

		$(document)
			.off('shown.bs.modal.paytypeSelect2', '#paytypeModal')
			.on('shown.bs.modal.paytypeSelect2', '#paytypeModal', function () {
				initPaytypeSelect2();
			});
	}

	$scope.pageno = 1;
	$scope.itemsPerPage = '12';
	$scope.total_count = 0;
	$scope.search_text = '';
	$scope.datadb = [];
	$scope.x = {};
	$scope.paytype_modal_title = "Add Pay Type";
	
	$scope.init=function()
	{
		$http.get(rootUrl+module+"/view").success(function(data){
			console.log('data', data);
			$scope.datadb=data;
			$scope.total_count = (data || []).length;
			initPaytypeSelect2();
			bindPaytypeSelect2Events();
		})
	}
	$scope.init();

	$scope.apply_filters = function()
	{
		$scope.pageno = 1;
	};

	$scope.clear_filters = function()
	{
		$scope.search_text = '';
		$scope.itemsPerPage = '12';
		$scope.pageno = 1;
		$scope.$applyAsync();
		$('#paytypeSearchText').val('');
		initPaytypeSelect2();
	};

	$scope.on_items_per_page_change = function()
	{
		$scope.pageno = 1;
	};

	$scope.update_call=function(y){
		$scope.paytype_modal_title = "Edit Pay Type";
		$scope.x=angular.copy(y);
		$scope.x.status = String($scope.x.status);
	}

	$scope.open_paytype_modal=function(mode,y)
	{
		if(mode=="edit" && y)
		{
			$scope.update_call(y);
		}
		else
		{
			$scope.paytype_modal_title = "Add Pay Type";
			$scope.filter_new(false);
		}
		$('#paytypeModal').modal('show');
		initPaytypeSelect2();
		bindPaytypeSelect2Events();
	}

	$scope.filter_new=function(refreshList){
		$scope.x={type:'1'};
		$scope.paytype_modal_title = "Add Pay Type";
		$('#paytypeType').val('1').trigger('change.select2');
		if(refreshList!==false)
			$scope.init();
		initPaytypeSelect2();
	}
	
	$scope.save_data=function(){
		$('#submitbtn').attr('disabled',true);
		$.ajax({
			type: "POST",
			url: rootUrl+module+"/save",
			data: $("#ptform").serialize(),
			beforeSend: function()
			{
				$('#loader').css('display','inline');
			},
			success: function(data){
				data = (data || '').trim();
				if(data=="1")
				{
					messages("success", "Success!","Saved Successfully", 3000);
					$scope.init();
					$scope.filter_new();
					$('#paytypeModal').modal('hide');
				}
				else if(data=="0")
				{
					messages("warning", "Info!","No Data Affected", 3000);
				}
				else
				{
					messages("danger", "Warning!",data, 6000);
				}
				$('#loader').css('display','none');
				$('#submitbtn').attr('disabled',false);
			}
		});
	}
	
	$scope.delete_data=function(id)
	{
		if(confirm("Deleting Paytype may hamper your data associated with it."))
		{
			if(confirm("Are you Sure to DELETE ??"))
			{
				$http.get(rootUrl+module+"/delete?pt_id="+id).success(function(data){
					if(data=="1")
					{
						messages("success", "Success!","Paytype Deleted Successfully", 3000);
					}
					else
					{
						messages("danger", "Warning!","Paytype not Deleted", 4000);
					}
					$scope.init();
				})
			}
		}
	}

	$scope.pageChangeHandler = function(newPageNumber)
	{
		$scope.pageno = newPageNumber;
	};

	bindPaytypeSelect2Events();
	initPaytypeSelect2();
}]);
app.controller('medical',['$scope','$rootScope','$http',function($scope,$rootScope,$http)
{
	mmodule='hr_medical_details';
	rootUrl=$rootScope.site_url;
	//login auth not required here..
	
	$scope.save_data1=function(x)
	{
		$('#submitbtn3').attr('disabled',true);
		$.ajax({
			type: "POST",
			url: rootUrl+mmodule+"/save",
			data: $("#medicalform").serialize(),
			beforeSend: function()
			{
				$('#loader3').css('display','inline');
			},
			success: function(data)
			{
				if(data=="1")
				{
					messages("success", "Success!","Saved Successfully", 3000);
					$("#dc_id").trigger('click');
				}
				else if(data=="0")
				{
					messages("warning", "Info!","No Data Affected", 3000);
				}
				else
				{
					messages("danger", "Warning!",data, 6000);
				}
				$('#loader3').css('display','none');
				$('#submitbtn3').attr('disabled',false);
			}
		});
	}
}]);app.controller('staffs',['$scope','$rootScope','$http',function($scope,$rootScope,$http)
{
	rootUrl=$rootScope.site_url;
	$http.get(rootUrl+"hr_staff_details/index").success (function(data) {if(data==0){window.location.assign('login.html');} else if(data==2){window.location.assign('index.html');}});

	$scope.login_check=localStorage.getItem('login');
	$scope.type_check=localStorage.getItem('type');
	// console.log('login_check:', $scope.login_check, 'type_check:', $scope.type_check);
	$scope.default_status="1";
	$scope.qs={status:$scope.default_status};
	$scope.staff_form_tab="staff_details1";
	$scope.select_staff_form_tab=function(tab_key)
	{
		var tab_map={
			"staff_details1":"#cus_tab",
			"medical_details":"#m_id",
			"documents_details":"#dc_id",
			"work_details":"#we_id",
			"guard_details":"#gd_id",
			"academic_details":"#acd_id",
			"payroll_monthly":"#ps_id"
		};
		var safe_tab=tab_key || "staff_details1";
		$scope.staff_form_tab=safe_tab;
		if(tab_map[safe_tab]){
			$(tab_map[safe_tab]).trigger('click');
		}
	}
	$http.get(rootUrl+"hr_grades/view?data=grade").success(function(data)
	{
		$scope.grades=data;
	})
	$scope.fetch_destinations=function(grade)
	{
		if(grade)
		{
			$http.get(rootUrl+"hr_designation/view?join=1&data=hr_departments.d_id,hr_departments.name&grade="+grade).success(function(data)
			{
				$scope.designations=data;
				$scope.syncStaffSelect2Values();
			})
			 
			$scope.salaryss="loading...";
			$http.get(rootUrl+"hr_grades/view?grade="+grade).success(function(data){
				if(data.length>0){
					$scope.min_sal=data[0].min_sal;
					$scope.max_sal=data[0].max_sal;
				}
			})
		}
		else
		{
			$scope.min_sal=0;
			$scope.max_sal=0;
		}
	}
	$scope.init=function()
	{
		var safe_status=$scope.qs.status || $scope.default_status;
		$http.get(rootUrl+"hr_staff_details/view?status="+encodeURIComponent(safe_status)+"&st="+encodeURIComponent(safe_status)).success(function(data)
		{
			$scope.datadb=data;
			$scope.qs.status=safe_status;
		})
	}
	$scope.init();
	$scope.filter_new=function()
	{
		$scope.qs={status:$scope.default_status};
		$scope.init();
	}

	$scope.open_add_staff=function()
	{
		$scope.filter_new1();
		$scope.staff_modal_title="Add Staff";
		$("#staffFormModal").modal("show");
		$scope.select_staff_form_tab("staff_details1");
		$scope.initializeSelect2();
	}

	$scope.open_view_staff=function()
	{
		$("#staffFormModal").modal("hide");
	}

	$scope.webcam=function(id)
	{
		window.open("./app/components/staffs/camera.html?emp_id="+id);
	}
	
	$scope.filter_data=function(name,grade,d_id,status)
	{
		var safe_name=name || "";
		var safe_grade=grade || "";
		var safe_d_id=d_id || "";
		var safe_status=(status===undefined || status===null || status==="") ? $scope.default_status : status;
		$http.get(rootUrl+"hr_staff_details/view?name="+encodeURIComponent(safe_name)+"&grade="+encodeURIComponent(safe_grade)+"&d_id="+encodeURIComponent(safe_d_id)+"&status="+encodeURIComponent(safe_status)+"&st="+encodeURIComponent(safe_status)).success(function(data){
			$scope.datadb=data;
			$scope.qs.status=safe_status;
		})
	}
	$scope.x={};
	$scope.docdb="";
	$scope.workdb="";
	$scope.m="";
	$scope.d="";
	$scope.w="";
	$scope.g="";
	$scope.eye_check="";
	$scope.staff_image_url="";

	$scope.build_staff_image_url=function(image_name)
	{
		// console.log('image name:', image_name);
		if(!image_name)
		{
			return "";
		}
		var clean_name=String(image_name).trim();
		if(!clean_name)
		{
			return "";
		}
		if(/^https?:\/\//i.test(clean_name))
		{
			return clean_name;
		}
		var base_img_url=$rootScope.img_url || "";
		if(!base_img_url && rootUrl)
		{
			base_img_url=rootUrl.replace(/index\.php\/?$/i,"public/");
		}
		if(/payroll\.groveus\.com/i.test(base_img_url))
		{
			base_img_url=base_img_url.replace(/api_crypt\/public\/?$/i,"api_crypt_new/public/");
			base_img_url=base_img_url.replace(/api_crypt\/public\//i,"api_crypt_new/public/");
		}
		if(base_img_url && base_img_url.charAt(base_img_url.length-1)!=="/")
		{
			base_img_url+="/";
		}
		return base_img_url ? base_img_url+"employee/"+clean_name : "";
	};

	$scope.update_staff_image_preview=function()
	{
		// console.log('x, x.image:', $scope.x, $scope.x.image);
		$scope.staff_image_url=$scope.build_staff_image_url($scope.x && $scope.x.image);
		// console.log('staff_image_url:', $scope.staff_image_url);
	};

	$scope.reset_staff_upload_label=function()
	{
		$("#staffUploadFileName").text("No file chosen");
		var $fileInput=$("#staffform input[type='file'][name='file']");
		if($fileInput.length)
		{
			$fileInput.val("");
		}
	};
	
	$scope.initializeSelect2=function()
	{
		setTimeout(function(){
			if (!$.fn.select2) {
				return;
			}
			var $modalSelects = $('#staffFormModal select');
			$modalSelects.each(function () {
				var $select = $(this);
				if ($select.data('select2')) {
					$select.select2('destroy');
				}
			});
			$modalSelects.select2({
				width: '100%'
			});
		}, 100);
	}

	$scope.syncStaffSelect2Values = function () {
		setTimeout(function () {
			if (!$.fn.select2) {
				return;
			}
			var gradeValue = ($scope.x && $scope.x.grade !== undefined && $scope.x.grade !== null) ? String($scope.x.grade) : '';
			var designationValue = ($scope.x && $scope.x.d_id !== undefined && $scope.x.d_id !== null) ? String($scope.x.d_id) : '';

			var $grade = $('#staffFormModal select[name="grade"]');
			var $designation = $('#staffFormModal select[name="d_id"]');

			if ($grade.length) {
				$grade.val(gradeValue).trigger('change');
			}
			if ($designation.length) {
				$designation.val(designationValue).trigger('change');
			}
		}, 150);
	};
	
	$scope.supdate_call=function(id,gr)
	{
		$scope.fetch_destinations(gr);
		$scope.emp_id=id;
		$scope.staff_image_url="";
		$scope.reset_staff_upload_label();
		$http.get(rootUrl+"hr_staff_details/view?emp_id="+id).success(function(data)
		{
			$scope.x=data[0];
			$scope.x.grade = ($scope.x.grade !== undefined && $scope.x.grade !== null) ? String($scope.x.grade) : '';
			$scope.x.d_id = ($scope.x.d_id !== undefined && $scope.x.d_id !== null) ? String($scope.x.d_id) : '';
			$scope.emp_staff_name=data[0].staff_name;
//			$scope.type=data[0].type;
			$scope.login=data[0].login;
			$scope.update_staff_image_preview();
			// console.log('staff data as:', $scope.x);
			$scope.initializeSelect2();
			$scope.syncStaffSelect2Values();
		})
		$http.get(rootUrl+"hr_medical_details/view?emp_id="+id).success(function(data)
		{
			$scope.m=data[0];
			if(data.length>0)
			{
				if(data[0].eye_problem)
				{
					$scope.eye_check=1;
					$("#eye_check").prop('checked',true);
				}
				if(data[0].disability)
				{
					$scope.disab_check=1;
					$("#disab_check").prop('checked',true);
				}
			}
		})
		$http.get(rootUrl+"hr_document_details/view?emp_id="+id).success(function(data)
		{
			$scope.docdb=data;
		})
		
		$http.get(rootUrl+"hr_work_experience/view?emp_id="+id).success(function(data)
		{
			$scope.workdb=data;
		})
		
		$http.get(rootUrl+"guardian_details/view?emp_id="+id).success(function(data)
		{
			$scope.gaurdiandb=data;
		})
		$http.get(rootUrl+"hr_academic/view?emp_id="+id).success(function(data)
		{
			$scope.accdata=data;
		})
		$scope.staff_modal_title="Edit Staff";
		$("#staffFormModal").modal("show");
		$scope.select_staff_form_tab("staff_details1");
		$scope.initializeSelect2();
		$scope.syncStaffSelect2Values();
	}
	
	$scope.filter_new1=function()
	{
		$scope.x={};
		$scope.emp_id="";
		$scope.docdb="";
		$scope.workdb="";
		$scope.m="";
		$scope.d="";
		$scope.w="";
		$scope.emp_staff_name="";
		$scope.staff_image_url="";
		$scope.reset_staff_upload_label();
	}
	$scope.save_data1=function(x)
	{
		$('#staffform').ajaxForm({
			type: "POST",
			url: rootUrl+"hr_staff_details/save",
			beforeSend: function()
			{
				$('#loader1').css('display','inline');
			},
			success: function(data)
			{
				console.log(data);
				if(data.error=="0")
				{
					if(data.emp_id)
					{
						$scope.x.emp_id=data.emp_id;
						$scope.emp_id=data.emp_id;
						$http.get(rootUrl+"hr_staff_details/view?emp_id="+$scope.emp_id).success(function(data)
						{
							$scope.x=data[0];
							$scope.emp_staff_name=data[0].staff_name;
							$scope.update_staff_image_preview();
						})
					}
					messages("success", "Success!","Saved Successfully", 3000);
					$scope.init();
					$scope.select_staff_form_tab("medical_details");
				}
				else if (data.error == '1')
				{
					messages("danger", "Warning!",data.msg, 6000);
				} else {
					messages("danger", "Warning!",data, 6000);
				}
				$('#loader1').css('display','none');
				$('#submitbtn1').attr('disabled',false);
			}
		});
	}
	
	$scope.delete_data=function(id)
	{
		if(confirm("Deleting Staff Details may hamper your data associated with it."))
		{
			if(confirm("Are you Sure to DELETE ??"))
			{
				$http.get(rootUrl+"hr_staff_details/delete?emp_id="+id).success(function(data){
					if(data=="1")
					{
						messages("success", "Success!","Staff Details Deleted Successfully", 3000);
					}
					else
					{
						messages("danger", "Warning!","Staff Details not Deleted. "+data, 4000);
					}
					$scope.init();
				})
			}
		}
	}
}]);
app.controller('documents',['$scope','$rootScope','$http',function($scope,$rootScope,$http){
	dmodule='hr_document_details';
	rootUrl=$rootScope.site_url;
	$scope.imgUrl=$rootScope.img_url;
	$scope.d={};
	$scope.initd=function()
	{
		$http.get(rootUrl+"hr_document_details/view?emp_id="+$scope.emp_id).success(function(data)
		{
			$scope.docdb=data;
		})
	}
	
//	$scope.init();
	
	$scope.update_call=function(y)
	{
		$scope.d=y;
	}
	$scope.filter_new_doc=function()
	{
		$scope.d={};
	}
	
	$scope.save_data=function()
	{
		$('#documents').ajaxForm({
			type: "POST",
			url: rootUrl+dmodule+"/save",
			beforeSend: function()
			{
				$('#submitbtn2').attr('disabled',true);
				$('#loader2').css('display','inline');
			},
			success: function(data)
			{
				if(data=="1")
				{
					messages("success", "Success!","Saved Successfully", 3000);
					$scope.initd();
					$scope.filter_new_doc();
					$("#we_id").trigger('click');
				}
				else if(data=="0")
				{
					messages("warning", "Info!","No Data Affected", 3000);
				}
				else
				{
					messages("danger", "Warning!",data, 6000);
				}
				$('#loader2').css('display','none');
				$('#submitbtn2').attr('disabled',false);
			}
		});
	}
	
	$scope.delete_data=function(id)
	{
		if(confirm("Deleting documents may hamper your data associated with it."))
		{
			if(confirm("Are you Sure to DELETE ??"))
			{
				$http.get(rootUrl+dmodule+"/delete?dc_id="+id).success(function(data){
					if(data=="1")
					{
						messages("success", "Success!","documents Deleted Successfully", 3000);
					}
					else
					{
						messages("danger", "Warning!","documents not Deleted", 4000);
					}
					$scope.initd();
				})
			}
		}
	}
}]);app.controller('work',['$scope','$rootScope','$http',function($scope,$rootScope,$http){
	wmodule='hr_work_experience';
	rootUrl=$rootScope.site_url;
//	$http.get(rootUrl+module+"/index").success (function(data) {if(data==0){window.location.assign('login.html');} else if(data==2){messages("success", "Privilege not assigned.", 3000);window.location.assign('index.html');}}); NOT Required in Child Module	
	
	$scope.w={};
	$scope.init=function()
	{
		$http.get(rootUrl+wmodule+"/view?emp_id="+$scope.emp_id).success(function(data)
		{
			$scope.workdb=data;
		})
	}
	
	$scope.update_call=function(y)
	{
		$scope.w=y;
	}
	$scope.filter_new=function()
	{
		$scope.w={};
	}
	
	$scope.save_data=function(x)
	{
		$('#submitbtn').attr('disabled',true);
		$.ajax({
			type: "POST",
			url: rootUrl+wmodule+"/save",
			data: $("#workform").serialize(),
			beforeSend: function()
			{
				$('#loader').css('display','inline');
			},
			success: function(data)
			{
				if(data=="1")
				{
					messages("success", "Success!","Saved Successfully", 3000);
					$scope.init();
					$scope.filter_new();
					$("#gd_id").trigger("click");
				}
				else if(data=="0")
				{
					messages("warning", "Info!","No Data Affected", 3000);
				}
				else
				{
					messages("danger", "Warning!",data, 6000);
				}
				$('#loader').css('display','none');
				$('#submitbtn').attr('disabled',false);
			}
		});
	}

	$scope.delete_data=function(id)
	{
		if(confirm("Are you Sure to DELETE ??"))
		{
			$http.get(rootUrl+wmodule+"/delete?we_id="+id).success(function(data){
				if(data=="1")
				{
					messages("success", "Success!","Work Experience Deleted Successfully", 3000);
				}
				else
				{
					messages("danger", "Warning!","Work Experience not Deleted", 4000);
				}
				$scope.init();
			})
		}
	}
}]);app.controller('guardian_details',['$scope','$rootScope','$http',function($scope,$rootScope,$http)
{
	gua_module='guardian_details';
	rootUrl=$rootScope.site_url;
	
	$scope.g={};
	$scope.initg=function()
	{
		$http.get(rootUrl+"guardian_details/view?emp_id="+$scope.emp_id).success(function(data)
		{
			$scope.gaurdiandb=data;
		})
	}
//	$scope.initg();
	$scope.change=true;
	$scope.update_call=function(y)
	{
		if(y.stch==1)
		{
			$scope.change=true;
		}	
		else
		{
			$scope.change=false;
			$scope.change2=true;
		}
		
		$scope.g=y;
	}
	$scope.filter_new_guardian=function()
	{
		$scope.change=true;
		$scope.change2=false;
		$scope.g={};
	}
	
	$scope.save_data=function()
	{
		$('#guardianform').ajaxForm({
			type: "POST",
			url: rootUrl+gua_module+"/save",
			beforeSend: function()
			{
				$('#submitbtn5').attr('disabled',true);
				$('#loader5').css('display','inline');
			},
			success: function(data)
			{
				if(data=="1")
				{
					messages("success", "Success!","Saved Successfully", 3000);
					$scope.initg();
					$scope.filter_new_guardian();
					$scope.change=true;
					$scope.change2=false;
				}
				else if(data=="0")
				{
					messages("warning", "Info!","No Data Affected", 3000);
				}
				else
				{
					messages("danger", "Warning!",data, 6000);
				}
				$('#loader5').css('display','none');
				$('#submitbtn5').attr('disabled',false);
			}
		});
	}
	
	$scope.delete_data=function(id)
	{
		if(confirm("Deleting documents may hamper your data associated with it."))
		{
			if(confirm("Are you Sure to DELETE ??"))
			{
				$http.get(rootUrl+gua_module+"/delete?id="+id).success(function(data){
					if(data=="1")
					{
						messages("success", "Success!","Guardian Details Deleted Successfully", 3000);
						$scope.initg();
					}
					else
					{
						messages("danger", "Warning!","Guardian Details not Deleted", 4000);
					}
					$scope.initg();
				})
			}
		}
	}
}]);app.controller('academic',['$scope','$rootScope','$http',function($scope,$rootScope,$http){
	accmodule='hr_academic';
	rootUrl=$rootScope.site_url;
//	$http.get(rootUrl+module+"/index").success (function(data) {if(data==0){window.location.assign('login.html');} else if(data==2){messages("success", "Privilege not assigned.", 3000);window.location.assign('index.html');}}); NOT Required in Child Module	
	
	$scope.acc={};
	$scope.init=function()
	{
		$http.get(rootUrl+accmodule+"/view?emp_id="+$scope.emp_id).success(function(data)
		{
			$scope.accdata=data;
		})
	}
	
	$scope.update_call=function(y)
	{
		$scope.acc=y;
	}
	$scope.filter_new=function()
	{
		$scope.acc={};
	}
	
	$scope.save_data=function(x)
	{
		$('#submitbtnacc').attr('disabled',true);
		$.ajax({
			type: "POST",
			url: rootUrl+accmodule+"/save",
			data: $("#academicform").serialize(),
			beforeSend: function()
			{
				$('#loaderacc').css('display','inline');
			},
			success: function(data)
			{
				if(data=="1")
				{
					messages("success", "Success!","Saved Successfully", 3000);
					$scope.init();
					$scope.filter_new();
					$("#gd_id").trigger("click");
				}
				else if(data=="0")
				{
					messages("warning", "Info!","No Data Affected", 3000);
				}
				else
				{
					messages("danger", "Warning!",data, 6000);
				}
				$('#loaderacc').css('display','none');
				$('#submitbtnacc').attr('disabled',false);
			}
		});
	}

	$scope.delete_data=function(id)
	{
		if(confirm("Are you Sure to DELETE ??"))
		{
			$http.get(rootUrl+accmodule+"/delete?acc_id="+id).success(function(data){
				if(data=="1")
				{
					messages("success", "Success!","Academic Details Deleted Successfully", 3000);
				}
				else
				{
					messages("danger", "Warning!","Academic Details not Deleted", 4000);
				}
				$scope.init();
			})
		}
	}
}]);//blank line is required
app.controller('hr_id_card',['$scope','$rootScope','$http',function($scope,$rootScope,$http)
{
	module='hr_id_card/';
	rootUrl=$rootScope.site_url;
	$http.get(rootUrl+module+"/index").success (function(data) {if(data==0){window.location.assign('login.html');} else if(data==2){messages("success", "Privilege not assigned.", 1000);window.location.assign('index.html');}});
	
	$http.get(rootUrl+"hr_grades/view?st=1").success(function(data)
	{
		$scope.grades=data;
	})
	$scope.x={};
	$scope.fetch_desig=function(grade)
	{
		$http.get(rootUrl+"hr_designation/view?grade="+grade+"&join="+1).success(function(data)
		{
			$scope.designation=data;
		})
	}
	$scope.print=function()
	{
		url=rootUrl+"hr_id_card/view?grade="+$scope.x.grade+"&d_id="+$scope.x.d_id+"&emp_id="+$scope.x.emp_id;
		window.open(url,"_blank", "toolbar=yes,scrollbars=yes,resizable=yes,top=10,left=10,width=1000;");
	}
	$scope.filter_new_id=function()
	{
		$scope.x={};
		$('#result').html("");
	}
	
	$scope.view_data = function(x)
	{
		$scope.myVar = true;
		$('#loader').css('display', 'inline');
		
		$http.get(rootUrl+"hr_id_card/view?grade="+$scope.x.grade+"&d_id="+$scope.x.d_id+"&emp_id="+$scope.x.emp_id).success(function(data)
		{
			$('#loader').css('display', 'none');
			$('#result').html(data);
		})
	}
	
}]);//blank line is required
app.controller('leave_setting',['$scope','$rootScope','$http',function($scope,$rootScope,$http){
	module='hr_leave_setting/';
	rootUrl=$rootScope.site_url;
	$http.get(rootUrl+module+"index").success(function(data) {
		if(data==0){window.location.assign('login.html');}
		else if(data==2){messages("success", "Privilege not assigned.", 1000);window.location.assign('index.html');}
	});

	function initLeaveSettingSelect2() {
		if (typeof $ === 'undefined' || !$.fn || !$.fn.select2) {
			return;
		}

		setTimeout(function () {
			$('.leave-setting-master-root select').each(function () {
				var $el = $(this);

				if ($el.data('select2')) {
					$el.select2('destroy');
				}

				if ($el.prop('disabled')) {
					return;
				}

				$el.select2({
					width: '100%',
					allowClear: false,
					minimumResultsForSearch: 0
				});
			});
		}, 0);
	}

	function bindLeaveSettingSelect2Events() {
		if (typeof $ === 'undefined') {
			return;
		}

		$(document)
			.off('shown.bs.modal.leaveSettingSelect2', '#leaveSettingModal')
			.on('shown.bs.modal.leaveSettingSelect2', '#leaveSettingModal', function () {
				initLeaveSettingSelect2();
			});
	}

	function resetLeaveSettingModalDefaults() {
		$scope.x = {
			grade: '',
			cl: '',
			sl: '',
			pl: '',
			ml: '',
			apply: '',
			prior: '',
			wp_min: '',
			wp_max: '',
			wp_per: '',
			cf_limit: ''
		};

		$scope.$applyAsync();

		$('#leaveSettingModalGrade').val('').trigger('change.select2');
		initLeaveSettingSelect2();
	}

	$scope.pageno = 1;
	$scope.total_count = 0;
	$scope.itemsPerPage = '15';
	$scope.search_text = '';
	$scope.qx = { grade: '', st: '' };
	$scope.x = {};
	$scope.datadb = [];
	$scope.grades = [];
	$scope.leave_modal_title = "Add Leave Setting";

	$scope.loader = function(pageno)
	{
		if(!pageno)
			pageno = 1;
		$scope.pageno = pageno;

		var params = ["per_page=" + encodeURIComponent($scope.itemsPerPage), "page=" + encodeURIComponent(pageno)];
		if($scope.search_text)
			params.push("search=" + encodeURIComponent($scope.search_text));
		if($scope.qx.grade)
			params.push("grade=" + encodeURIComponent($scope.qx.grade));
		if($scope.qx.st !== undefined && $scope.qx.st !== "")
			params.push("st=" + encodeURIComponent($scope.qx.st));

		$http.get(rootUrl + module + "view?" + params.join("&")).success(function(response){
			if(response && response.data !== undefined)
			{
				$scope.datadb = response.data || [];
				$scope.total_count = response.total_count || 0;
			}
			else
			{
				$scope.datadb = response || [];
				$scope.total_count = ($scope.datadb || []).length;
			}
			initLeaveSettingSelect2();
		});
	};

	$scope.load_grades = function()
	{
		$http.get(rootUrl+"hr_grades/view?st=1").success(function(data)
		{
			$scope.grades = data || [];
			initLeaveSettingSelect2();
		});
	};

	$scope.apply_filters = function()
	{
		$scope.loader(1);
	};

	$scope.clear_filters = function()
	{
		$scope.search_text = '';
		$scope.qx = { grade: '', st: '' };
		$scope.itemsPerPage = '15';
		$scope.$applyAsync();
		$('#leaveSettingSearchText').val('');
		$('#leaveSettingGrade').val('').trigger('change.select2');
		$('#leaveSettingStatus').val('').trigger('change.select2');
		$('#leaveSettingPerPage').val('15').trigger('change.select2');
		initLeaveSettingSelect2();
		$scope.loader(1);
	};

	$scope.on_items_per_page_change = function()
	{
		$scope.loader(1);
	};

	$scope.update_call = function(y)
	{
		$scope.leave_modal_title = "Edit Leave Setting";
		$scope.x = angular.copy(y);
		$scope.x.status = String($scope.x.status);
		initLeaveSettingSelect2();
	};

	$scope.open_leave_modal = function(mode, y)
	{
		if(mode=="edit" && y)
		{
			$scope.update_call(y);
		}
		else
		{
			$scope.leave_modal_title = "Add Leave Setting";
			$scope.filter_new(false);
		}
		$('#leaveSettingModal').modal('show');
		initLeaveSettingSelect2();
		bindLeaveSettingSelect2Events();
	};

	$scope.filter_new = function(refreshList)
	{
		resetLeaveSettingModalDefaults();
		$scope.leave_modal_title = "Add Leave Setting";
		if(refreshList!==false)
			$scope.loader($scope.pageno || 1);
	};

	$scope.save_data = function()
	{
		$('#submitbtn').attr('disabled',true);
		$.ajax({
			type: "POST",
			url: rootUrl + module + "save",
			data: $("#leaveSettingForm").serialize(),
			beforeSend: function()
			{
				$('#loader').css('display','inline');
			},
			success: function(data)
			{
				data = (data || '').trim();
				if(data=="1")
				{
					messages("success", "Success!","Leave Setting Saved Successfully", 3000);
					$scope.loader($scope.pageno || 1);
					resetLeaveSettingModalDefaults();
					$scope.leave_modal_title = "Add Leave Setting";
					$('#leaveSettingModal').modal('hide');
				}
				else if(data=="0")
				{
					messages("warning", "Info!","No Data Affected", 3000);
				}
				else
				{
					messages("danger", "Warning!",data, 6000);
				}
				$('#loader').css('display','none');
				$('#submitbtn').attr('disabled',false);
			}
		});
	};

	$scope.delete_data = function(id)
	{
		if(confirm("Deleting Leave Setting may hamper your data associated with it."))
		{
			if(confirm("Are you Sure to DELETE ??"))
			{
				$http.get(rootUrl+module+"delete?id="+id).success(function(data)
				{
					if(data=="1")
					{
						messages("success", "Success!","Leave Setting Deleted Successfully", 3000);
						$scope.loader($scope.pageno || 1);
					}
					else
					{
						messages("danger", "Warning!","Leave Setting not Deleted", 4000);
					}
				});
			}
		}
	};

	$scope.pageChangeHandler = function(newPageNumber)
	{
		$scope.loader(newPageNumber);
	};

	$scope.load_grades();
	$scope.filter_new(false);
	$scope.loader(1);
	bindLeaveSettingSelect2Events();
	initLeaveSettingSelect2();
}]);
//blank line is required
app.controller('hr_category',['$scope','$rootScope','$http',function($scope,$rootScope,$http)
{
	module='category/';
	rootUrl=$rootScope.site_url;
	$http.get(rootUrl+module+"/index").success (function(data) {if(data==0){window.location.assign('login.html');} else if(data==2){messages("success", "Privilege not assigned.", 1000);window.location.assign('index.html');}});
	$scope.x={};
	$scope.datadb=[];
	$scope.pageno=1;
	$scope.itemsPerPage=10;
	$scope.filters={
		search_text:"",
		status:""
	};
	$scope.appliedFilters=angular.copy($scope.filters);
	$scope.hr_category_modal_title="Add Category";
	
	$scope.init=function()
	{
		$http.get(rootUrl+module+"view_data").success(function(data)
		{
			$scope.datadb=data || [];
			$scope.initializeCategorySelect2();
			$scope.syncCategorySelect2Values();
		})
	}
	$scope.init();

	$scope.initializeCategorySelect2=function()
	{
		setTimeout(function(){
			if (!$.fn.select2) {
				return;
			}
			var isSelect2V4 = !!($.fn.select2 && $.fn.select2.amd);
			var $pageSelects = $('.hr-category-page select.hr-category-select2');

			$pageSelects.each(function () {
				var $select = $(this);
				if (!$select.is('select')) {
					return;
				}
				if ($select.data('select2')) {
					$select.select2('destroy');
				}

				var options = {
					width: '100%',
					minimumResultsForSearch: 0
				};

				if (isSelect2V4) {
					options.dropdownParent = $(document.body);
				}

				$select.select2(options);
			});
		}, 100);
	}

	$scope.syncCategorySelect2Values=function()
	{
		setTimeout(function () {
			if (!$.fn.select2) {
				return;
			}
			var statusValue = ($scope.filters && $scope.filters.status !== undefined && $scope.filters.status !== null) ? String($scope.filters.status) : '';
			var perPageValue = ($scope.itemsPerPage !== undefined && $scope.itemsPerPage !== null) ? String($scope.itemsPerPage) : '10';

			var $status = $('.hr-category-page select[ng-model="filters.status"]');
			var $perPage = $('.hr-category-page select[ng-model="itemsPerPage"]');

			if ($status.length) {
				$status.val(statusValue).trigger('change');
			}
			if ($perPage.length) {
				$perPage.val(perPageValue).trigger('change');
			}
		}, 150);
	}
	
	$scope.filter_new_cat=function()
	{
		$scope.x={};
	}

	$scope.openCategoryModal=function(mode,y)
	{
		$scope.hr_category_modal_title=(mode==="edit") ? "Update Category" : "Add Category";
		$scope.x=y ? angular.copy(y) : {};
		$("#hrCategoryFormModal").modal("show");
	}

	$scope.closeCategoryModal=function()
	{
		$("#hrCategoryFormModal").modal("hide");
	}

	$scope.update_call=function(y)
	{
		$scope.openCategoryModal("edit", y);
	}

	$scope.clear_filters=function()
	{
		$scope.filters={
			search_text:"",
			status:""
		};
		$scope.appliedFilters=angular.copy($scope.filters);
		$scope.pageno=1;
		$scope.initializeCategorySelect2();
		$scope.syncCategorySelect2Values();
	}

	$scope.apply_filters=function()
	{
		$scope.appliedFilters=angular.copy($scope.filters);
		$scope.pageno=1;
	}

	$scope.categorySearchFilter=function(item)
	{
		var query=($scope.appliedFilters.search_text || "").toLowerCase();
		if(!query)
		{
			return true;
		}
		var haystack=[
			item && item.name
		].join(" ").toLowerCase();
		return haystack.indexOf(query)!==-1;
	}

	$scope.categoryStatusFilter=function(item)
	{
		if($scope.appliedFilters.status==="" || $scope.appliedFilters.status===null || $scope.appliedFilters.status===undefined)
		{
			return true;
		}
		return String(item && item.status || "")===String($scope.appliedFilters.status);
	}

	$scope.countEnabledModules=function(item)
	{
		var keys=['journal','contact','follow_up','template','marketing'];
		var count=0;
		angular.forEach(keys,function(key){
			if(String(item && item[key] || "")==='1')
			{
				count++;
			}
		});
		return count;
	}

	$scope.save_data=function()
	{
		$('#submitbtn').attr('disabled',true);
		$.ajax({
			type: "POST",
			url: rootUrl+module+"save_data",
			data: $("#catform").serialize(),
			beforeSend: function()
			{
				$('#loader').css('display','inline');
			},
			success: function(data)
			{
				if(data=="1")
				{
					messages("success", "Success!","Saved Successfully", 3000);
					$scope.filter_new_cat();
					$scope.init();
					$scope.closeCategoryModal();
				}
				else if(data=="0")
				{
					messages("warning", "Info!","No Data Affected", 3000);
				}
				else
				{
					messages("danger", "Warning!",data, 6000);
				}
				$('#loader').css('display','none');
				$('#submitbtn').attr('disabled',false);
				if(!$scope.$$phase)
				{
					$scope.$applyAsync();
				}
			}
		});
	}
	
	$scope.delete_data=function(id)
	{
		if(confirm("Deleting Category may hamper your data associated with it."))
		{
			if(confirm("Are you Sure to DELETE ??"))
			{
				$http.get(rootUrl+module+"delete_data?id="+id).success(function(data){
					if(data=="1")
					{
						messages("success", "Success!","Category Details Deleted Successfully", 3000);
					}
					else
					{
						messages("danger", "Warning!","Category Details not Deleted", 4000);
					}
					$scope.init();
				})
			}
		}
	}

	$(function () {
		$scope.initializeCategorySelect2();
		$scope.syncCategorySelect2Values();
	});
	
}]);
app.controller('client_login', ['$scope', '$rootScope', '$http', '$timeout', function ($scope, $rootScope, $http, $timeout) {
	rootUrl = $rootScope.site_url;
	$scope.page = {
		loading: false,
		saving: false,
		message: '',
		messageType: 'info'
	};
	$scope.customers = [];
	$scope.selectedCustomerId = '';

	function refreshSelect2() {
		$timeout(function () {
			if (typeof $ === 'undefined' || !$.fn || !$.fn.select2) {
				return;
			}

			var $select = $('#client-login-customer');
			if (!$select.length) {
				return;
			}

			try {
				if ($select.data('select2')) {
					$select.select2('destroy');
				}
			} catch (e) {}

			$select.select2({
				width: '100%',
				placeholder: 'Select an active customer'
			});
		}, 0, false);
	}

	function setMessage(type, message) {
		$scope.page.messageType = type || 'info';
		$scope.page.message = message || '';
	}

	$scope.init = function () {
		$scope.page.loading = true;
		setMessage('info', 'Loading active customers...');

		$http.get(rootUrl + 'client_login/index').success(function (data) {
			if (data == 0) {
				window.location.assign('login.html');
				return;
			}
			if (data == 2) {
				window.location.assign('index.html');
				return;
			}
		});

		$http.get(rootUrl + 'client_login/active_customers').success(function (data) {
			$scope.customers = angular.isArray(data) ? data : [];
			$scope.page.loading = false;
			if (!$scope.customers.length) {
				setMessage('warning', 'No active customers were found.');
			} else {
				setMessage('success', 'Select a customer and click Login.');
			}
			refreshSelect2();
		}).error(function () {
			$scope.page.loading = false;
			setMessage('danger', 'Unable to load active customers.');
		});
	};

	$scope.login = function () {
		if (!$scope.selectedCustomerId) {
			setMessage('warning', 'Please select a customer first.');
			return;
		}

		var clientWindow = window.open('', '_blank');
		if (clientWindow) {
			clientWindow.document.write('<title>Client Login</title><p style="font-family:Arial,sans-serif;padding:16px;">Opening client portal...</p>');
			clientWindow.document.close();
		}

		$scope.page.saving = true;
		setMessage('info', 'Creating client session...');

		$http({
			method: 'POST',
			url: rootUrl + 'client_login/create_session',
			data: $.param({
				c_id: $scope.selectedCustomerId
			}),
			headers: {
				'Content-Type': 'application/x-www-form-urlencoded; charset=UTF-8'
			}
		}).then(function (response) {
			var res = (response && response.data) ? response.data : {};
			if (String(res.error) === '0' && res.redirect_url) {
				if (clientWindow && !clientWindow.closed) {
					clientWindow.location.href = res.redirect_url;
					clientWindow.focus();
				} else {
					window.open(res.redirect_url, '_blank');
				}
				$scope.page.saving = false;
				return;
			}

			if (clientWindow && !clientWindow.closed) {
				clientWindow.close();
			}
			$scope.page.saving = false;
			setMessage('danger', res.msg || 'Unable to start client login.');
		}, function () {
			if (clientWindow && !clientWindow.closed) {
				clientWindow.close();
			}
			$scope.page.saving = false;
			setMessage('danger', 'Unable to start client login.');
		});
	};

	$scope.$watch('customers.length', function () {
		refreshSelect2();
	});

	$scope.init();
}]);
app.controller('user_privileges',['$scope','$rootScope','$http',function($scope,$rootScope,$http){
	rootUrl=$rootScope.site_url;
	module="user_privileges/";
	$http.get(rootUrl+module+"/index").success (function(data) {if(data==0){window.location.assign('login.html');} else if(data==2){messages("success", "Privilege not assigned.", 1000);window.location.assign('index.html');}});
	
	$('#progress').hide();
	$http.get(rootUrl+'hr_staff_details/view?data=username as name,emp_id&status=1').success (function(data) {
			$scope.users=data;
	});
	$http.get(rootUrl+'user_privileges/view_privileges_json').success (function(data) {
			$scope.modules=data;
	});

	$scope.reset_all=function()
	{
		$scope.user="";
		$scope.user_name="";
		$scope.o="";
	};
	$scope.fetch_data=function(id)
	{
		$scope.reset_all();
		$('#progress').toggle();
		$http.get(rootUrl+'user_privileges/view_data?id='+ id).success (function(data)
		{
			if(data.length<1)
			{
				$http.get(rootUrl+'user_privileges/user_default_privileges/'+ id).success (function(data)//insert default privileges
				{
					$scope.fetch_data(id);
				});
			}else{
				$scope.user=data;
				$('#progress').hide();	
			}
			$http.get(rootUrl+'user_privileges/otherPrivlg_view_data?id='+ id).success (function(data)//insert default privileges
			{
				if(data.length>0)
				{
					$scope.o=data;
					if(data[0].hr_staff_details=='1')
						$('#employee').prop('checked',true); 
					else
						$('#employee').prop('checked',false);
					if(data[0].hr_id_card=='1')
						$('#id_card').prop('checked',true);
					else
						$('#id_card').prop('checked',false);
					if(data[0].hr_report=='1')
						$('#report').prop('checked',true); 
					else
						$('#report').prop('checked',false);
					if(data[0].sms=='1')
						$('#sms').prop('checked',true); 
					else
						$('#sms').prop('checked',false);
					if(data[0].attendance=='1')
						$('#attendance').prop('checked',true); 
					else
						$('#attendance').prop('checked',false);
					if(data[0].task=='1')
						$('#taskss').prop('checked',true); 
					else
						$('#taskss').prop('checked',false);
				}
				else
				{
					$('#employee').prop('checked',false);
					$('#id_card').prop('checked',false);
					$('#report').prop('checked',false);
					$('#sms').prop('checked',false);
					$('#attendance').prop('checked',false);
					$('#taskss').prop('checked',false);
//					$('#user_privilege').prop('checked',false);
//					$('#payslip').prop('checked',false);
				}
			});
		});
		$("#btnsubmit").prop('disabled',false);
	};
	
	$scope.update_data=function(x)
	{
		$("#btnsubmit").text('Please Wait...');
		$("#btnsubmit").prop('disabled',true);
		$("#uprogress").css( 'display' , 'inline');
		$.ajax({
			type: "POST",
			url: rootUrl+"user_privileges/update_data",
			data: $("#form2").serialize(),
			beforeSend: function()
			{
				$('#progress').toggle();
			},
			success: function(data)
			{
				$("#btnsubmit").text('Save');
				$("#btnsubmit").prop('disabled',false);
				$('#progress').toggle();
				var data = $.parseJSON(data);
				if (data.type == "1") {
					messages("danger", "Warning!",data.error, 8000);
				} else {
					setTimeout($scope.unlockwindow, 2000);
					messages("success", "Success!",data.error, 4000);
				}
			}
		});
	};
	$scope.other_save=function(x)
	{
		$("#btnsubmito").text('Please Wait...');
		$("#btnsubmito").prop('disabled',true);
		$.ajax({
			type: "POST",
			url: rootUrl+"user_privileges/other_privilege",
			data: $("#form3").serialize(),
			beforeSend: function()
			{
				$('#progress').toggle();
			},
			success: function(data)
			{
				$("#btnsubmito").text('Save');
				$("#btnsubmito").prop('disabled',false);
				$('#progress').toggle();
				var data = $.parseJSON(data);
				if (data.type == "1") {
					messages("danger", "Warning!",data.error, 8000);
				} else {
					setTimeout($scope.unlockwindow, 2000);
					messages("success", "Success!",data.error, 4000);
				}
			}
		});
	};
	$scope.unlockwindow=function(){
		window.location.reload();
	}
	
	$scope.update_data_other=function(x){
		$.ajax({
			type: "POST",
			url: "other_privileges/update_data",
			data: $("#form3").serialize(),
			beforeSend: function()
			{
				$('#webprogress').css('display','inline');
			},
			success: function(data)
			{
				var arr = $.parseJSON(data);
				if(arr.type=="1")
				{
					$('#error_msg').html(arr.error);
					$('#error_modal').trigger("click");
				}
				else
				{
					setTimeout($scope.unlockwindow, 3000);
					$('#success_msg').html(arr.error);
					$('#alert_modal').trigger("click");
				}
				$('#webprogress').css('display','none');
			}
		});
	};
	
}]);app.controller('pay_head',['$scope','$rootScope','$http',function($scope,$rootScope,$http)
{
	module='hr_payslip';
	rootUrl=$rootScope.site_url;
	$http.get(rootUrl+module+"/index").success (function(data) {if(data==0){window.location.assign('login.html');} else if(data==2){messages("success", "Privilege not assigned.", 1000);window.location.assign('index.html');}});
	
	$scope.type_check=localStorage.getItem('type');
	$scope.e={};
	$scope.init=function()
	{
		$http.get(rootUrl+module+"/view").success(function(data){
			$scope.datadb=data;
		})
	}
	
	$scope.update_call=function(y)
	{
		$scope.x=y;
		$scope.fetch_details(y.emp_id);
		$scope.calc_total();
		$("#pay_slip_form1").trigger('click');
	}
	
	$scope.filter_new=function()
	{
		$scope.x={};
		$scope.e={};
		$scope.qs={};
		$scope.x.value1=0;
		$scope.x.value2=0;
		$scope.x.net_deduction=0;
		$scope.init();
		$scope.show=0;
		
	}
	$scope.filter_new();
	
	$http.get(rootUrl+"hr_staff_details/view?st=1&data=staff_name,emp_id").success(function(data)
	{
		$scope.employees=data;
	})
	$http.get(rootUrl+"hr_grades/view?data=grade&st=1").success(function(data)
	{
		$scope.grades=data;
	})
	$scope.fetch_designations=function(grade)
	{
		$http.get(rootUrl+"hr_designation/view?join=1&data=d_id,name&grade="+grade).success(function(data)
		{
			$scope.designations=data;
		}) 
	}
	
	$scope.filter_data=function($name)
	{
		$http.get(rootUrl+"hr_payslip/view?name="+$name).success(function(data)
		{
				$scope.datadb=data;
		})
	}
	
	
	//testing
	$scope.print=function(y)
	{
		url=rootUrl+"printer/generate_payslip/"+y.pay_id;
		$.ajax(url).done(function(data)
		{ 
			var w = window.open();
			$(w.document.body).html(data);
		});
	}
	
	$scope.fetch_pay=function(pay_id)
	{
		if(pay_id)
		{
			$("#pay_id").val(pay_id);
			$http.get(rootUrl+"printer/generate_payslip/"+pay_id).success(function(data)
			{
				$("#modalBody").html(data);
			})
		}
	}
	
	$scope.initalize=function()
	{
		$scope.x={};
		$scope.check=0;
		$scope.show=0;
		
		$scope.halfMinTime=0;
		$scope.halfAmtPcnt=0;
		$scope.halfAbsnt=0;
		$scope.work_hour="";
		
		$scope.pf_comPcent="";
		$scope.pf_empPcent="";
		$scope.pf_max="";
		$scope.pf_min="";
		
		$scope.gminustotal=0;
		$scope.gplustotal=0;
		$scope.gtotal=0;
		$scope.total=0;
		$scope.x.value1=0;
		$scope.x.value2=0;
		
		$scope.minustotal=0;
		$scope.plustotal=0;
	}
	
	$scope.fetch_details=function(empid)
	{
		$scope.loading=1;
		$scope.initalize();
		
		$http.get(rootUrl+"hr_staff_details/view?emp_id="+empid).success(function(data)
		{
			$scope.emp_details=data;
			$scope.b_sal=parseFloat(data[0].b_salary);
			$scope.e.grade=data[0].grade;
			$http.get(rootUrl+"hr_grades/view?data=gpay&grade="+data[0].grade).success(function(data){
				if(data.length>0)
					$scope.x.gpay=data[0].gpay;
				else
					$scope.x.gpay=data[0].gpay;
				
				$scope.get_pay_names();
				$scope.loading=0;
				$scope.calc_pf($scope.b_sal);
			})
		})
	}
	
	$scope.GetHalfDaySettings=function(mnth,yr)
	{
		year=20+yr; // changing year into format of 20XX
		var date= new Date(year, mnth, 0).getDate();
		
		$http.get(rootUrl+"hr_master_settings/view?data=work_hour,wrk_days").success(function(data)
		{
			if(data.length>0)
			{
				$scope.work_hour=data[0].work_hour;
				if(data[0].wrk_days!=0)
					$scope.x.work_days=data[0].wrk_days;
				else
					$scope.x.work_days=date;
			}
			$http.get(rootUrl+"half_day_settings/view?data=m_time,pay_amt,absent").success(function(data)
			{
				if(data.length>0)
				{
					$scope.halfMinTime=data[0].m_time;
					$scope.halfAmtPcnt=parseFloat(data[0].pay_amt).toFixed(2);
					$scope.halfAbsnt=parseInt(data[0].absent);
				}
				else
				{
					$scope.halfMinTime=0;
					$scope.halfAmtPcnt=0;
					$scope.halfAbsnt=0;
				}
			})
		})
	}
	
	$scope.calc_pf=function(b_sal)
	{
		$http.get(rootUrl+"pf_settings/view").success(function(data)
		{
			if(data.length<1){
				$scope.x.pf_amt=0;
			}
			else
			{
				$scope.pf_empPcent=data[0].emp_pcnt;
				$scope.pf_comPcent=data[0].com_pcnt;
				$scope.pf_max=parseFloat(data[0].max_amt);
				$scope.pf_min=parseFloat(data[0].min_amt);
				
				
				$scope.x.pf_amt=parseFloat(parseFloat(b_sal)*(parseFloat($scope.pf_empPcent)/100)).toFixed(2);
				$scope.x.compf_amt=parseFloat(parseFloat(b_sal)*(parseFloat($scope.pf_comPcent)/100)).toFixed(2);
				if($scope.x.pf_amt>$scope.pf_max)
				{
					$scope.x.pf_amt=$scope.pf_max;
				}
				if($scope.x.pf_amt<$scope.pf_min)
				{
					$scope.x.pf_amt=$scope.pf_min;
				}
				if($scope.x.compf_amt>$scope.pf_max)
				{
					$scope.x.compf_amt=$scope.pf_max;
				}
				if($scope.x.compf_amt<$scope.pf_min)
				{
					$scope.x.compf_amt=$scope.pf_min;
				}
				$scope.show_value();
			}
		});
	}
	
	$scope.halfshow=0;
	$scope.calc_days=function()
	{
		$scope.GetHalfDaySettings($scope.x.month,$scope.x.year);
		
		$scope.x.cl=0;
		$scope.x.sl=0;
		$scope.x.pl=0;
		$scope.x.ml=0;
		$scope.x.lwp=0;
		$scope.check=0;
		$scope.leave_total=0;
		
		if($scope.x.month && $scope.x.year)
		{
			$http.get(rootUrl+"hr_attendance/get_half_day?emp_id="+$scope.e.emp_id+"&month="+$scope.x.month+"&year=20"+$scope.x.year+"&minTime="+$scope.halfMinTime+"&type=P"+"&wrking_hr="+$scope.work_hour).success(function(data)
			{
				$scope.x.AbsentHalfDay=0;
				$scope.x.AbsentHalfDayAmt=0;
				$scope.x.totalHalfDeduct=0;
				if(data.err=="0")
				{
					$scope.halfshow=1;
					$scope.x.HalfDay=parseFloat(data.half);
					$scope.x.AbsentHalfDay=parseFloat(data.absent);
					$scope.x.TotalHalfDay=parseFloat(data.half)+parseFloat(data.absent);
					
					$scope.get_attendance_data();
				}
				else
				{
					$scope.x.totalHalfDeduct=0;
					$scope.x.HalfDay=0;
					
					$scope.get_attendance_data();
				}
			})
		}
	}
	
	$scope.get_attendance_data=function()
	{
		$http.get(rootUrl+"hr_attendance/get_day/"+$scope.e.emp_id+"/"+$scope.x.month+"/20"+$scope.x.year+"/P").success(function(dat)
		{
			$scope.x.present=dat;
			$http.get(rootUrl+"hr_attendance/get_day/"+$scope.e.emp_id+"/"+$scope.x.month+"/20"+$scope.x.year+"/A").success(function(datas)
			{
				$scope.x.absent=parseFloat(datas);
//				$scope.x.absent+=parseFloat($scope.x.AbsentHalfDay);
				$http.get(rootUrl+"hr_payslip/get_session?month="+$scope.x.month+"&year=20"+$scope.x.year+"&emp_id="+$scope.e.emp_id).success(function(data)
				{
					$scope.check=1;
					if(data.length>0)
					{ 
						angular.forEach(data, function(val, key)
						{
							if(val.cl)
								$scope.x.cl+=parseFloat(val.cl);
							if(val.sl)
								$scope.x.sl+=parseFloat(val.sl);
							if(val.pl)
								$scope.x.pl+=parseFloat(val.pl);
							if(val.ml)
								$scope.x.ml+=parseFloat(val.ml);
							if(val.lwp)
								$scope.x.lwp+=parseFloat(val.lwp);
						});
						$scope.leave_total=$scope.x.cl+$scope.x.sl+$scope.x.pl+$scope.x.ml;
					}
					$scope.calc_total();
				})
			})
		})
	}
	
	$scope.show_value=function()
	{
		$scope.show=1;
	}
	
	$scope.get_pay_names=function()
	{
		$http.get(rootUrl+"hr_pay_setting/view?join=1&grade="+$scope.e.grade).success(function(data2)
		{
			$scope.pay_names=[];
			$scope.paysettings=data2;
			setTimeout(function()
			{
				$(data2).each(function(k,v)
				{
					var per=parseFloat(v.per);
					var val=$scope.b_sal*per/100;
					$scope.pay_names.push(v);
					if(val<v.min_amt)
					{
						$("#pay"+v.pt_id).val(v.min_amt);
					}	
					else if(val>v.max_amt)
					{
						$("#pay"+v.pt_id).val(v.max_amt);
					}
					else
					{
						$("#pay"+v.pt_id).val(val);
					}
					if(k==data2.length-1)
					{
						$scope.loading=0;
					}
				});
			}, 1000);
	    });
	}
	
	$scope.calc_total=function()
	{
		if($scope.x.value1=='') $scope.x.value1=0;
		if($scope.x.value2=='') $scope.x.value2=0;
		if($scope.x.net_deduction=='') $scope.x.net_deduction=0;
		if($scope.x.absent_deduction=='') $scope.x.absent_deduction=0;
		$scope.minustotal=0;
		$scope.plustotal=0;
		$scope.x.totalAbsentHalfDeduct=0;
		
		var perday_sal=(parseFloat($scope.b_sal)/parseFloat($scope.x.work_days)).toFixed(2);
		
		if($scope.halfAbsnt==1)
		{
			$scope.x.absent+=parseFloat($scope.x.AbsentHalfDay);
			$scope.x.AbsentHalfDayAmt=parseFloat(parseFloat($scope.x.AbsentHalfDay)*parseFloat(perday_sal)).toFixed(2);
		}
		else
		{
			$scope.x.totalAbsentHalfDeduct=parseFloat((parseFloat(perday_sal)*(parseFloat($scope.halfAmtPcnt)/100))*parseFloat($scope.x.AbsentHalfDay)).toFixed(2);
			$scope.x.AbsentHalfDayAmt=parseFloat((parseFloat(perday_sal)*(parseFloat($scope.halfAmtPcnt)/100))*parseFloat($scope.x.AbsentHalfDay)).toFixed(2);
		}
		$scope.x.totalHalfDeduct=parseFloat((parseFloat(perday_sal)*(parseFloat($scope.halfAmtPcnt)/100))*parseFloat($scope.x.HalfDay)).toFixed(2);
		
		$scope.totalAbsent=parseFloat($scope.x.absent)-parseFloat($scope.leave_total);
		
		$scope.x.absent_deduction=parseFloat((parseFloat(perday_sal)*parseFloat($scope.totalAbsent))+parseFloat($scope.x.totalAbsentHalfDeduct)).toFixed(2);

		if($scope.pay_names!='' && $scope.x.absent_deduction)
		{
			$($scope.pay_names).each(function(k,v)
			{
				if($("#pay"+v.pt_id).val()==' ') $("#pay"+v.pt_id).val(0);
				if(v.type==1) //minus
				{
					$scope.minustotal=$scope.minustotal+parseFloat($("#pay"+v.pt_id).val());
				}
				else  //plus
				{
					$scope.plustotal=$scope.plustotal+parseFloat($("#pay"+v.pt_id).val());
				}
				
				if(k==$scope.pay_names.length-1)//last row of loop
				{
					$scope.gminustotal=parseFloat(parseFloat($scope.minustotal)+parseFloat($scope.x.absent_deduction)+parseFloat($scope.x.value2)+parseFloat($scope.x.totalHalfDeduct)+parseFloat($scope.x.pf_amt)).toFixed(2);
					$scope.gplustotal=parseFloat(parseFloat($scope.plustotal)+parseFloat($scope.x.value1)).toFixed(2);
					$scope.gtotal=parseFloat(parseFloat($scope.plustotal)+parseFloat($scope.x.value1)+parseFloat($scope.b_sal)+parseFloat($scope.x.gpay)).toFixed(2);
					$scope.total=parseFloat(parseFloat($scope.gtotal)-parseFloat($scope.gminustotal)).toFixed(2);
					
					//loading checking error
					$scope.loading=0;
				}
			});
		}
		else
		{
			
			$scope.gminustotal=parseFloat(parseFloat($scope.minustotal)+parseFloat($scope.x.absent_deduction)+parseFloat($scope.x.value2)+parseFloat($scope.x.totalHalfDeduct)+parseFloat($scope.x.pf_amt)).toFixed(2);
			$scope.gplustotal=parseFloat(parseFloat($scope.plustotal)+parseFloat($scope.x.value1)).toFixed(2);
			$scope.gtotal=parseFloat(parseFloat($scope.plustotal)+parseFloat($scope.x.value1)+parseFloat($scope.b_sal)+parseFloat($scope.x.gpay)).toFixed(2);
			$scope.total=parseFloat(parseFloat($scope.gtotal)-parseFloat($scope.gminustotal)).toFixed(2);
		}
	}
	
	$scope.save_data=function()
	{
		$('#submitbtn').attr('disabled',true);
		$.ajax({
			type: "POST",
			url: rootUrl+module+"/save",
			data: $("#pay_slip").serialize(),
			beforeSend: function()
			{
				$('#loader').css('display','inline');
			},
			success: function(data)
			{
				console.log(data);
				if(data=="1")
				{
					messages("success", "Success!","Saved Successfully", 3000);
					$scope.init();
					$scope.filter_new();
					$scope.total=0;
					$("#pay_slip_view1").trigger('click');
				}
				else if(data=="0")
				{
					messages("warning", "Info!","No Data Affected", 3000);
				}
				else
				{
					messages("danger", "Warning!",data, 6000);
				}
				$('#loader').css('display','none');
				$('#submitbtn').attr('disabled',false);
			}
		});
	}
	$scope.PayNowSave=function()
	{
		$('#submitbtn312').attr('disabled',true);
		$.ajax({
			type: "POST",
			url: rootUrl+module+"/PayNowSave",
			data: $("#pay_slip").serialize(),
			beforeSend: function()
			{
				$('#loader').css('display','inline');
			},
			success: function(data)
			{
				console.log(data);
				if(data=="1")
				{
					messages("success", "Success!","Saved Successfully", 3000);
					$scope.init();
					$scope.filter_new();
					$scope.total=0;
					$("#pay_slip_view1").trigger('click');
				}
				else if(data=="0")
				{
					messages("warning", "Info!","No Data Affected", 3000);
				}
				else
				{
					messages("danger", "Warning!",data, 6000);
				}
				$('#loader').css('display','none');
				$('#submitbtn312').attr('disabled',false);
			}
		});
	}
	
	$scope.delete_data=function(id)
	{
		if(confirm("Deleting Pay Slip Data may hamper your data associated with it."))
		{
			if(confirm("Are you Sure to DELETE ??"))
			{
				$http.get(rootUrl+module+"/delete?id="+id).success(function(data)
				{
					console.log(data)
					if(data=="0")
					{
						messages("danger", "Warning!","Pay Slip not Deleted", 4000);
					}
					else
					{
						messages("success", "Success!","Pay Slip Deleted Successfully", 3000);
						$scope.init();
						$scope.filter_new();
					}
				})
			}
		}
	}
	
	
	
}]);app.controller('attendance',['$scope','$rootScope','$http',function($scope,$rootScope,$http){
	module='hr_attendance';
	rootUrl=$rootScope.site_url;
	//rootUrl="http://localhost/HrMaster/index.php/";
	$http.get(rootUrl+module+"/index").success (function(data) {if(data==0){window.location.assign('login.html');} else if(data==2){messages("success", "Privilege not assigned.", 1000);window.location.assign('index.html');}});
	$scope.x={};
	$("#DOB1").datepicker();

	function initAttendanceSelect2() {
		if (typeof $ === 'undefined' || !$.fn || !$.fn.select2) {
			return;
		}

		var runInit = function () {
			$('.attendance-root select').each(function () {
				var $el = $(this);
				if (!$el.is('select')) {
					return;
				}
				if ($el.hasClass('no-select2') || $el.is('[data-no-select2]')) {
					return;
				}

				if ($el.data('select2')) {
					$el.select2('destroy');
				}

				$el.select2({
					width: '100%',
					minimumResultsForSearch: 0
				});
			});
		};

		setTimeout(runInit, 0);
		setTimeout(runInit, 180);
	}

	$scope.filter_attend=function()
	{
		$scope.x.date="";
		$scope.x.grade="";
		$scope.employees="";
		$scope.show=0;
		$scope.shw=0;
		$scope.time="";
		$scope.name="";
		initAttendanceSelect2();
	}
	
//	$scope.filter_attend();
	
	$http.get(rootUrl+"hr_grades/view?st=1").success(function(data)
	{
		$scope.grades=data;
		initAttendanceSelect2();
	})
	
	$scope.filter_data=function(g)
	{
		$scope.shw=0;
		$scope.show=0;
		$scope.time="";
		$scope.name="";
		
		if(g)
			grade="&grade="+g;
		else
			grade="";
		
		$http.get(rootUrl+"hr_staff_details/view?data=emp_id,staff_name,grade&grade="+grade+"&st=1").success(function(data)
		{
			$scope.employees=data;
			$http.get(rootUrl+module+"/get_attendance?date="+$scope.x.date+grade).success(function(data)
			{
				if(data.type==1)
				{
					messages("warning", "Warning!",data.error, 3000);
					$scope.employees="";
				}
				if(data.list!="")
				{
					$.each(data.list, function(i, val) 
					{
						if(val.status==1){
							$("#ch"+val.emp_id).prop('checked',true);
						}
						if(val.status==3){
							$("#ch"+val.emp_id).prop('disabled',true);
							$("#time"+val.emp_id).prop('disabled',true);
							$("#time_out"+val.emp_id).prop('disabled',true);
							$("#row"+val.emp_id).addClass('warning');
						}
						$scope.show=1;
						$("#time"+val.emp_id).val(val.time_in);
						$("#id"+val.emp_id).val(val.id);
						$("#comment"+val.emp_id).val(val.comment);
						$("#time_out"+val.emp_id).val(val.time_out);
						$("#duration"+val.emp_id).val(val.duration);
				    });
					$http.get(rootUrl+module+"/GetLastUpdate?date="+$scope.x.date).success(function(data)
					{
						if(data.show=='1')
						{
							$scope.shw=1;
							$scope.time=data.time;
							$scope.name=data.name;
						}
					})
				}
				
			})
		})
	}
	
	
	$scope.save_data=function()
	{
		$.ajax({
			type: "POST",
			url: rootUrl+module+"/save",
			data: $("#attendanceform1").serialize(),
			beforeSend: function()
			{
				$('#submitbtn1').attr('disabled',true);
				$('#loader1').css('display','inline');
			},
			success: function(data)
			{
				console.log(data)
				if(data=="1")
				{
					$("#clearbtn").trigger('click');
					messages("success", "Success!","Attendance Saved Successfully", 3000);
				}
				else if(data=="0")
				{
					messages("warning", "Info!","Failed to save the attendance", 3000);
				}
				else
				{
					messages("danger", "Warning!",data, 6000);
				}
				$('#loader1').css('display','none');
				$('#submitbtn1').attr('disabled',false);
				$scope.filter_attend();
			}
		});
	}

	initAttendanceSelect2();
	
}]);
//blank line is required
app.controller('follow_up',['$scope','$rootScope','$http', 'sharedService',function($scope,$rootScope,$http, sharedService)
{
	module='hr_follow_up/';
	rootUrl=$rootScope.site_url;
	$http.get(rootUrl+module+"/index").success (function(data) {if(data==0){window.location.assign('login.html');} else if(data==2){window.location.assign('index.html');}});
	$scope.follow_up_modal_title = "Add Follow Up";

	function initFollowUpSelect2() {
		if (typeof $ === 'undefined' || !$.fn || !$.fn.select2) {
			return;
		}

		setTimeout(function () {
			var $modal = $('#followUpModal');
			var isSelect2V4 = !!($.fn.select2 && $.fn.select2.amd);

			$('#followUpModal .follow-up-select2').each(function () {
				var $el = $(this);
				if (!$el.is('select')) {
					return;
				}

				if ($el.data('select2')) {
					$el.select2('destroy');
				}

				var options = {
					width: '100%',
					minimumResultsForSearch: 0
				};

				if (isSelect2V4) {
					options.dropdownParent = $modal;
				}

				$el.select2(options);
			});
		}, 0);
	}

	function allowSelect2TypingInsideFollowUpModal() {
		if (typeof $ === 'undefined' || !$.fn || !$.fn.modal || !$.fn.modal.Constructor) {
			return;
		}

		var ModalConstructor = $.fn.modal.Constructor;
		if (ModalConstructor.prototype._followUpSelect2FocusPatched) {
			return;
		}

		ModalConstructor.prototype.enforceFocus = function () {
			var modalThis = this;
			$(document)
				.off('focusin.bs.modal')
				.on('focusin.bs.modal', function (e) {
					var $target = $(e.target);
					var isInsideModal = modalThis.$element[0] === e.target || modalThis.$element.has(e.target).length;
					var isSelect2Input =
						$target.closest('.select2-container, .select2-dropdown, .select2-drop, .select2-search').length > 0 ||
						$target.is('.select2-input, .select2-search__field');

					if (!isInsideModal && !isSelect2Input) {
						modalThis.$element.trigger('focus');
					}
				});
		};

		ModalConstructor.prototype._followUpSelect2FocusPatched = true;

		$(document).off('select2:open.followUp select2-open.followUp');
		$(document).on('select2:open.followUp select2-open.followUp', function () {
			setTimeout(function () {
				var $search = $('.select2-container-active .select2-input, .select2-drop-active .select2-input, .select2-container--open .select2-search__field');
				if ($search.length) {
					$search.focus();
				}
			}, 0);
		});
	}

	$scope.init=function()
	{
		var data_select = encodeURIComponent("f_id,c_id,hr_follow_up.cat_id,date,title,notes,rem_date,rem_time,hr_staff_details.staff_name as staff_name,category.name as cname,hr_follow_up.timestamp");
		$http.get(rootUrl+module+"view_data?data="+data_select).success(function(data){
			$scope.datadb=data;
		})
	}
	$scope.init();
	$scope.x={};
	
	$http.get(rootUrl+"category/view_data?data=cat_id,name&status=1&follow_up=1").success(function(data)
	{
		$scope.category=data;
		initFollowUpSelect2();
	})
	
	// Listen for update event
	$scope.$on('follow_up', function () {

		var sharedData = sharedService.getData();

		if (sharedData && sharedData.c_id) {
			$scope.c_id = sharedData.c_id;
			$http.get(rootUrl + "customer/view?c_id=" + $scope.c_id + "data=c_id,name").success(function (data) {
				$scope.customers = data;
				$scope.x.c_id = data[0].c_id;
				initFollowUpSelect2();
			});
			// 🔥 Call your API here
			// $scope.loadFollowUpData($scope.c_id);
		}
	});

	$http.get(rootUrl + "customer/view?data=c_id,name").success(function (data) {
		$scope.customers = data;
		initFollowUpSelect2();
	});
	
	$scope.typehead_load=function()
	{
		$('.typeahead').typeahead('destroy');
		$http.get(rootUrl+module+"view_typehead").success(function(data)
		{
			$(".typeahead").typeahead({
			  source: data,
			  autoSelect: true
			});
		})
	}
	$scope.typehead_load();
	$scope.filter_new=function(refreshList)
	{
		$scope.x={};
		if(refreshList!==false)
			$scope.init();
		setTimeout(function(){
			if($('#date').length)
				$('#date').datepicker('setDate','now');
		},120);
	}

	$scope.update_call=function(y)
	{
		$scope.x = angular.copy(y || {});
		if($scope.x.rem_time==':')
			$scope.x.rem_time="";
	}

	$scope.open_follow_up_modal=function(mode,y)
	{
		if(mode=="edit" && y)
		{
			$scope.follow_up_modal_title = "Edit Follow Up";
			$scope.update_call(y);
		}
		else
		{
			$scope.follow_up_modal_title = "Add Follow Up";
			$scope.filter_new(false);
		}
		$('#followUpModal').modal('show');
		setTimeout(function(){ $scope.typehead_load(); },120);
		allowSelect2TypingInsideFollowUpModal();
		initFollowUpSelect2();
	}
	
	$scope.save_data=function()
	{
		if(confirm("Save the followup"))
		{
			$('#submitbtn').attr('disabled',true);
			$.ajax({
				type: "POST",
				url: rootUrl+module+"save_data",
				data: $("#folform1").serialize(),
				beforeSend: function()
				{
					$('#loader').css('display','inline');
				},
				success: function(data)
				{
					console.log(data)
					if(data=="1")
					{
						messages("success", "Success!","Saved Successfully", 3000);
						$scope.filter_new(false);
						$scope.typehead_load();
						$scope.init();
						$('#followUpModal').modal('hide');
					}
					else if(data=="0")
					{
						messages("warning", "Info!","No Data Affected", 3000);
					}
					else
					{
						messages("danger", "Warning!",data, 6000);
					}
					$('#loader').css('display','none');
					$('#submitbtn').attr('disabled',false);
				}
			});
		}
	}
	
	$scope.delete_data=function(id)
	{
		if(confirm("Deleting Staff Details may hamper your data associated with it."))
		{
			if(confirm("Are you Sure to DELETE ??"))
			{
				$http.get(rootUrl+module+"delete_data?id="+id).success(function(data){
					if(data=="1")
					{
						messages("success", "Success!","Category Details Deleted Successfully", 3000);
					}
					else
					{
						messages("danger", "Warning!","Category Details not Deleted", 4000);
					}
					$scope.init();
				})
			}
		}
	}

	allowSelect2TypingInsideFollowUpModal();
	$(document).off('shown.bs.modal.followUpSelect2', '#followUpModal').on('shown.bs.modal.followUpSelect2', '#followUpModal', function () {
		initFollowUpSelect2();
	});
	
}]);
//blank line is required
app.controller('task_assigner',['$scope','$rootScope','$http',function($scope,$rootScope,$http){
	
	rootUrl=$rootScope.site_url;
	module="task_assigner/";
	$http.get(rootUrl+module+"/index").success (function(data) {if(data==0){window.location.assign('login.html');} else if(data==2){messages("success", "Privilege not assigned.", 1000);window.location.assign('index.html');}});

	function initTaskAssignerSelect2() {
		if (typeof $ === 'undefined' || !$.fn || !$.fn.select2) {
			return;
		}

		setTimeout(function () {
			var isSelect2V4 = !!($.fn.select2 && $.fn.select2.amd);

			$('.task-assigner-select2').each(function () {
				var $el = $(this);

				if (!$el.is('select')) {
					return;
				}

				if ($el.data('select2')) {
					$el.select2('destroy');
				}

				var options = {
					width: '100%',
					allowClear: false,
					minimumResultsForSearch: 0
				};

				if (isSelect2V4) {
					var $ownModal = $el.closest('.modal');
					options.dropdownParent = $ownModal.length ? $ownModal : $(document.body);
				}

				$el.select2(options);
			});
		}, 0);
	}

	function allowSelect2TypingInsideModal() {
		if (typeof $ === 'undefined' || !$.fn || !$.fn.modal || !$.fn.modal.Constructor) {
			return;
		}

		var ModalConstructor = $.fn.modal.Constructor;
		if (ModalConstructor.prototype._taskAssignerSelect2FocusPatched) {
			return;
		}

		ModalConstructor.prototype.enforceFocus = function () {
			var modalThis = this;
			$(document)
				.off('focusin.bs.modal')
				.on('focusin.bs.modal', function (e) {
					var $target = $(e.target);
					var isInsideModal = modalThis.$element[0] === e.target || modalThis.$element.has(e.target).length;
					var isSelect2Input =
						$target.closest('.select2-container, .select2-dropdown, .select2-drop, .select2-search').length > 0 ||
						$target.is('.select2-input, .select2-search__field');

					if (!isInsideModal && !isSelect2Input) {
						modalThis.$element.trigger('focus');
					}
				});
		};

		ModalConstructor.prototype._taskAssignerSelect2FocusPatched = true;

		$(document).off('select2:open.taskAssigner select2-open.taskAssigner');
		$(document).on('select2:open.taskAssigner select2-open.taskAssigner', function () {
			setTimeout(function () {
				var $search = $('.select2-container-active .select2-input, .select2-drop-active .select2-input, .select2-container--open .select2-search__field');
				if ($search.length) {
					$search.focus();
				}
			}, 0);
		});
	}
	
	$scope.pageno = 1;
	$scope.total_count = 0;
	$scope.itemsPerPage = '15';
	$scope.qx = {};
	$scope.x = {type:'employ', task_type:'regular'};
	$scope.datadb = [];
	$scope.task_title_filter = [];
	$scope.task_assigner_modal_title = "Add Task Assigner";
	$scope.designationList = [];
	$scope.workload = {d_id: '', loaded: false, employees: []};

	$scope.open_designation_workload_modal = function()
	{
		$scope.workload = {d_id: '', loaded: false, employees: []};
		$http.get(rootUrl+"task_assigner/designation_list").success(function(data)
		{
			$scope.designationList = data || [];
			initTaskAssignerSelect2();
		});
		$('#designationWorkloadModal').modal('show');
		initTaskAssignerSelect2();
	}

	$scope.load_designation_workload = function()
	{
		if(!$scope.workload.d_id)
			return;

		$scope.workload.loaded = false;
		$('#workloadLoader').css('display','inline');

		$http.get(rootUrl+"task_assigner/designation_workload?d_id="+$scope.workload.d_id).success(function(data)
		{
			$scope.workload.employees = data || [];
			$scope.workload.loaded = true;
			$('#workloadLoader').css('display','none');
		}).error(function()
		{
			$('#workloadLoader').css('display','none');
			messages("danger", "Warning!", "Unable to load workload for this designation.", 6000);
		});
	}

	$scope.typehead_load=function()
	{
		$('.typeahead').typeahead('destroy');
		$http.get(rootUrl+module+"/view_typehead").success(function(data)
		{
			$(".typeahead").typeahead({
			  source: data,
			  autoSelect: true
			});
		})
	}

	$scope.load_task_title_filter = function()
	{
		$http.get(rootUrl+"task_assigner/view_data?distinct=title").success(function(data)
		{
			$scope.task_title_filter = data || [];
		});
	};

	$scope.loader = function(pageno)
	{
		if(!pageno)
			pageno = 1;
		$scope.pageno = pageno;

		var params = [];

		if($scope.qx.emp_id)
			params.push("emp_id=" + encodeURIComponent($scope.qx.emp_id));
		if($scope.qx.title)
			params.push("title=" + encodeURIComponent($scope.qx.title));
		if($scope.qx.status!==undefined && $scope.qx.status!=="")
			params.push("status=" + encodeURIComponent($scope.qx.status));

		var url = rootUrl + "task_assigner/view/" + $scope.itemsPerPage + "/" + pageno;
		if(params.length)
			url += "?" + params.join("&");

		$http.get(url).success(function(response){
			if(response && response.data !== undefined)
			{
				$scope.datadb = response.data;
				$scope.total_count = response.total_count || 0;
			}
			else
			{
				$scope.datadb = response || [];
				$scope.total_count = ($scope.datadb || []).length;
			}
			initTaskAssignerSelect2();
		});
	};

	$scope.apply_filters = function()
	{
		$scope.loader(1);
	};

	$scope.clear_filters = function()
	{
		$scope.qx = {};
		$scope.itemsPerPage = '15';
		$scope.loader(1);
	};

	$scope.on_items_per_page_change = function()
	{
		$scope.loader(1);
	};
	
	$http.get(rootUrl+"hr_staff_details/view_staff?data=emp_id,staff_name&st=1").success(function(data)
	{
		$scope.employees=data;
		initTaskAssignerSelect2();
	});

	$http.get(rootUrl+"hr_departments/view_active").success(function(data)
	{
		$scope.designations=data;
		initTaskAssignerSelect2();
	});

	$scope.update_call=function(y)
	{
		$scope.x = angular.copy(y);
		$scope.x.type = 'employ';
	}

	$scope.open_task_assigner_modal=function(mode,y)
	{
		if(mode=="edit" && y)
		{
			$scope.task_assigner_modal_title = "Edit Task Assigner";
			$scope.update_call(y);
		}
		else
		{
			$scope.task_assigner_modal_title = "Add Task Assigner";
			$scope.filter_new(false);
		}
		$('#taskAssignerModal').modal('show');
		initTaskAssignerSelect2();
		setTimeout(function(){ $scope.typehead_load(); },200);
	}
	
	$scope.filter_new=function(refreshList)
	{
		$scope.x={type:'employ', task_type:'regular'};
		$scope.typehead_load();
		initTaskAssignerSelect2();
		if(refreshList!==false)
			$scope.loader($scope.pageno || 1);
	}

	$scope.$watch('x.type', function () {
		initTaskAssignerSelect2();
	});

	$scope.save_data=function(x)
	{
		$('#submitbtn').attr('disabled',true);
		$.ajax({
			type: "POST",
			url: rootUrl+"task_assigner/save_data",
			data: $("#form1").serialize(),
			beforeSend: function()
			{
				$('#webprogress').css('display','inline');
			},
			success: function(data)
			{
				if(data=="1")
				{
					messages("success", "Success!","Task Assiged Successfully", 4000);
					$scope.loader($scope.pageno || 1);
					$scope.load_task_title_filter();
					$scope.filter_new(false);
					$('#taskAssignerModal').modal('hide');
				}
				else if(data=="0")
				{
					messages("warning", "Info!","No Data Affected", 10000);
				} else if(data=="4")
				{
					messages("warning", "Info!","Employee is already assigned with this task title.", 10000);
				}
				else
				{
					messages("warning", "Warning!",data, 10000);
				}
				$('#webprogress').css('display','none');
			}
		});
		$('#submitbtn').attr('disabled',false);
	}
	
	$scope.delete_data=function(id)
	{
		if(confirm("Deleting task_assigners may hamper your data associated with it. You will loose the data related with this task_assigner."))
		{
			if(confirm("Are you Sure to DELETE ??"))
			{
				$http.get(rootUrl+"task_assigner/delete_data?id="+id).success(function(data){
					if(data=="1")
					{
						messages("success", "Success!","Assigned task Deleted Successfully", 4000);
						$scope.loader($scope.pageno || 1);
						$scope.load_task_title_filter();
					}
					else
					{
						messages("danger", "Warning!","Assigned task not Deleted", 10000);
					}
				})
			}
		}
	}

	$scope.typehead_load();
	$scope.load_task_title_filter();
	$scope.loader(1);
	allowSelect2TypingInsideModal();

	$('#taskAssignerModal, #designationWorkloadModal').on('shown.bs.modal', function () {
		// Select2 v3 appends search input outside modal; disable Bootstrap focus trap for this modal.
		$(document).off('focusin.bs.modal');
		initTaskAssignerSelect2();
	});
	initTaskAssignerSelect2();
	
}]);
app.controller('task', ['$scope', '$rootScope', '$http', '$timeout', '$interval', "$sce", function ($scope, $rootScope, $http, $timeout, $interval, $sce) {
	rootUrl = $rootScope.site_url;
	tmodule = "task/";
	$http.get(rootUrl + tmodule + "/index").success(function (data) { if (data == 0) { window.location.assign('login.html'); } else if (data == 2) { messages("success", "Privilege not assigned.", 1000); window.location.assign('index.html'); } });

	$scope.x = {};
	$scope.showLocationPopup = true;
	$scope.locationModal = {
		title: "Task Location",
		lat: "",
		lng: "",
		url: null
	};

	$scope.getMapUrl = function (lat, lng) {
		var url = 'https://maps.google.com/maps?q=' + lat + ',' + lng + '&z=15&output=embed';
		return $sce.trustAsResourceUrl(url);
	};

	function parseCoordinate(value) {
		var parsed = parseFloat(value);
		return isNaN(parsed) ? null : parsed;
	}

	function isValidCoordinatePair(lat, lng) {
		return lat !== null && lng !== null && lat >= -90 && lat <= 90 && lng >= -180 && lng <= 180;
	}

	function formatFilterDate(value) {
		if (!value) return "";
		if (angular.isDate(value)) {
			var yyyy = value.getFullYear();
			var mm = ('0' + (value.getMonth() + 1)).slice(-2);
			var dd = ('0' + value.getDate()).slice(-2);
			return yyyy + '-' + mm + '-' + dd;
		}
		return value;
	}

	function parseDurationToSeconds(duration) {
		if (!duration || typeof duration !== 'string') {
			return 0;
		}

		var parts = duration.split(':');
		if (parts.length < 3) {
			return 0;
		}

		var hours = parseInt(parts[0], 10) || 0;
		var minutes = parseInt(parts[1], 10) || 0;
		var seconds = parseInt(parts[2], 10) || 0;

		return (hours * 3600) + (minutes * 60) + seconds;
	}

	function formatSecondsToDuration(totalSeconds) {
		totalSeconds = Math.max(0, parseInt(totalSeconds, 10) || 0);

		var hours = Math.floor(totalSeconds / 3600);
		var minutes = Math.floor((totalSeconds % 3600) / 60);
		var seconds = totalSeconds % 60;

		return (hours < 10 ? '0' : '') + hours + ':' +
			(minutes < 10 ? '0' : '') + minutes + ':' +
			(seconds < 10 ? '0' : '') + seconds;
	}

	function getTaskRowDurationSeconds(item) {
		if (!item) return 0;

		if (!item.end_time && item.start_date && item.start_time) {
			var parts = item.start_date.split('/');
			var t = item.start_time.split(':');
			if (parts.length === 3 && t.length >= 2) {
				var start = new Date(
					parts[2],
					parts[1] - 1,
					parts[0],
					t[0], t[1], t[2] || 0
				);
				if (!isNaN(start.getTime())) {
					return Math.max(0, Math.floor((Date.now() - start.getTime()) / 1000));
				}
			}
		}

		if (item.duration) {
			return parseDurationToSeconds(item.duration);
		}

		if (item.total_duration) {
			return parseDurationToSeconds(item.total_duration);
		}

		return 0;
	}

	function buildDailyBreakdownFromRows(rows, filterParams) {
		rows = angular.isArray(rows) ? rows : [];

		var dailyMap = {};
		var dailyRows = [];
		var bounds = getPeriodBounds(filterParams);
		var totalSeconds = 0;

		rows.forEach(function (item) {
			ensureTaskRowTiming(item);

			var dateKey = item.start_date || item.task_date || "";
			var dateObj = parseTaskDateKey(dateKey);
			var key = dateObj ? dateObj.getTime() : dateKey;
			if (!dailyMap[key]) {
				dailyMap[key] = {
					sort_key: dateObj ? dateObj.getTime() : 0,
					date_label: dateObj ? formatTaskDateLabel(dateObj) : (dateKey || 'Unknown'),
					total_seconds: 0,
					task_count: 0
				};
			}

			var rowSeconds = getTaskRowDurationSeconds(item);
			dailyMap[key].total_seconds += rowSeconds;
			dailyMap[key].task_count += 1;
			totalSeconds += rowSeconds;
		});

		fillMissingDays(dailyMap, bounds);

		angular.forEach(dailyMap, function (entry) {
			entry.total_duration = formatSecondsToDuration(entry.total_seconds);
			dailyRows.push(entry);
		});

		dailyRows.sort(function (a, b) {
			return (a.sort_key || 0) - (b.sort_key || 0);
		});

		$scope.dailySummary.daily_breakdown = dailyRows;
		$scope.dailySummary.range_label = getPeriodLabel(filterParams);
		$scope.dailySummary.overall_duration = formatSecondsToDuration(totalSeconds);
	}

	function buildSelectedEmployeeSummaryFromRows(rows) {
		rows = angular.isArray(rows) ? rows : [];

		var totalSeconds = 0;
		var taskCount = 0;
		var staffName = "";
		var empId = "";

		rows.forEach(function (item) {
			if (!staffName && item && item.staff_name) {
				staffName = item.staff_name;
			}
			if (!empId && item && item.emp_id) {
				empId = item.emp_id;
			}
			totalSeconds += getTaskRowDurationSeconds(item);
			taskCount += 1;
		});

		return {
			emp_id: empId,
			staff_name: staffName,
			total_seconds: totalSeconds,
			total_duration: formatSecondsToDuration(totalSeconds),
			task_count: taskCount
		};
	}

	function ensureTaskRowTiming(item) {
		if (!item || item.end_time || item.startTimestamp || !item.start_date || !item.start_time) {
			return;
		}

		var parts = item.start_date.split('/');
		var t = item.start_time.split(':');
		if (parts.length !== 3 || t.length < 2) {
			return;
		}

		var start = new Date(
			parts[2],
			parts[1] - 1,
			parts[0],
			t[0], t[1], t[2] || 0
		);

		item.startTimestamp = start.getTime();
		if (!item.duration) {
			item.duration = '00:00:00';
		}
	}

	function parseTaskDateKey(dateValue) {
		if (!dateValue || typeof dateValue !== 'string') return null;

		var parts = dateValue.split('/');
		if (parts.length !== 3) return null;

		var day = parseInt(parts[0], 10);
		var month = parseInt(parts[1], 10);
		var year = parseInt(parts[2], 10);

		if (isNaN(day) || isNaN(month) || isNaN(year)) {
			return null;
		}

		return new Date(year, month - 1, day);
	}

	function formatTaskDateLabel(dateObj) {
		if (!(dateObj instanceof Date) || isNaN(dateObj.getTime())) {
			return '';
		}

		var day = ('0' + dateObj.getDate()).slice(-2);
		var month = ('0' + (dateObj.getMonth() + 1)).slice(-2);
		var year = dateObj.getFullYear();
		return day + '/' + month + '/' + year;
	}

	function parseIsoDate(value) {
		if (!value || typeof value !== 'string') return null;

		var parts = value.split('-');
		if (parts.length !== 3) return null;

		var year = parseInt(parts[0], 10);
		var month = parseInt(parts[1], 10);
		var day = parseInt(parts[2], 10);

		if (isNaN(year) || isNaN(month) || isNaN(day)) {
			return null;
		}

		return new Date(year, month - 1, day);
	}

	function startOfWeek(dateObj) {
		var date = new Date(dateObj.getFullYear(), dateObj.getMonth(), dateObj.getDate());
		var day = date.getDay();
		var diff = day === 0 ? -6 : 1 - day;
		date.setDate(date.getDate() + diff);
		return date;
	}

	function getMonthYearLabel(dateObj) {
		if (!(dateObj instanceof Date) || isNaN(dateObj.getTime())) {
			return "";
		}

		return dateObj.toLocaleString('en-US', { month: 'long', year: 'numeric' });
	}

	function getPeriodBounds(filterParams) {
		var dateType = getFilterParamValue(filterParams, "date_type");
		var fromDateValue = getFilterParamValue(filterParams, "date_from");
		var toDateValue = getFilterParamValue(filterParams, "date_to");
		var today = new Date();
		var start = null;
		var end = null;

		if (dateType === 'range') {
			start = parseIsoDate(fromDateValue);
			end = parseIsoDate(toDateValue);
		} else if (dateType === 'month') {
			start = new Date(today.getFullYear(), today.getMonth(), today.getDate() - 29);
			end = new Date(today.getFullYear(), today.getMonth(), today.getDate());
		} else if (dateType === 'week') {
			start = startOfWeek(today);
			end = new Date(today.getFullYear(), today.getMonth(), today.getDate());
		}

		if (start && end && start.getTime() > end.getTime()) {
			var swap = start;
			start = end;
			end = swap;
		}

		return {
			date_type: dateType,
			start: start,
			end: end,
			target_emp_id: getFilterParamValue(filterParams, "emp_id")
		};
	}

	function fillMissingDays(dailyMap, bounds) {
		if (!bounds || !bounds.start || !bounds.end) return;
		if (bounds.start.getTime() > bounds.end.getTime()) return;

		var cursor = new Date(bounds.start.getTime());
		while (cursor.getTime() <= bounds.end.getTime()) {
			var key = cursor.getTime();
			if (!dailyMap[key]) {
				dailyMap[key] = {
					sort_key: key,
					date_label: formatTaskDateLabel(cursor),
					total_seconds: 0,
					task_count: 0
				};
			}
			cursor.setDate(cursor.getDate() + 1);
		}
	}

	function getPeriodLabel(filterParams) {
		var bounds = getPeriodBounds(filterParams);
		if (bounds.start && bounds.end) {
			return formatTaskDateLabel(bounds.start) + " to " + formatTaskDateLabel(bounds.end);
		}
		if (bounds.date_type === 'month') {
			return getMonthYearLabel(new Date());
		}
		if (bounds.date_type === 'week') {
			return "This Week";
		}
		return "";
	}

	function taskMatchesSummaryFilters(item, filterParams) {
		if (!item) return false;

		var empId = getFilterParamValue(filterParams, "emp_id");
		var projectId = getFilterParamValue(filterParams, "project_id");
		var status = getFilterParamValue(filterParams, "status");
		var bounds = getPeriodBounds(filterParams);

		if (empId && String(item.emp_id) !== String(empId)) {
			return false;
		}

		if (projectId && String(item.project_id || "") !== String(projectId)) {
			return false;
		}

		if (status !== "" && status !== null && status !== undefined && String(item.status) !== String(status)) {
			return false;
		}

		if (bounds.date_type === 'range' || bounds.date_type === 'week' || bounds.date_type === 'month') {
			var taskDate = parseTaskDateKey(item.start_date || item.task_date || "");
			if (!taskDate || !bounds.start || !bounds.end) {
				return false;
			}
			if (taskDate.getTime() < bounds.start.getTime() || taskDate.getTime() > bounds.end.getTime()) {
				return false;
			}
		}

		return true;
	}

	function getFilterParamValue(filterParams, key) {
		if (!angular.isArray(filterParams)) {
			return "";
		}

		var prefix = key + "=";
		for (var i = 0; i < filterParams.length; i++) {
			if (filterParams[i] && filterParams[i].indexOf(prefix) === 0) {
				return decodeURIComponent(filterParams[i].substring(prefix.length));
			}
		}

		return "";
	}

	// function initTaskSelect2() {
	// 	if (typeof $ === 'undefined' || !$.fn || !$.fn.select2) {
	// 		return;
	// 	}

	// 	$timeout(function () {
	// 		var $modal = $('#taskFormModal');
	// 		$('.task-select2').each(function () {
	// 			var $el = $(this);
	// 			var inModal = $el.closest('#taskFormModal').length > 0;

	// 			if ($el.hasClass('select2-hidden-accessible')) {
	// 				$el.select2('destroy');
	// 			}

	// 			$el.select2({
	// 				width: '100%',
	// 				dropdownParent: inModal ? $modal : $(document.body)
	// 			});
	// 		});
	// 	}, 0);
	// }
	function initTaskSelect2() {
		if (typeof $ === 'undefined' || !$.fn || !$.fn.select2) {
			return;
		}

		$timeout(function () {
			var $modal = $('#taskFormModal');
			var isSelect2V4 = !!($.fn.select2 && $.fn.select2.amd);
			$('.task-select2').each(function () {
				var $el = $(this);
				var inModal = $el.closest('#taskFormModal').length > 0;

				// Guard against accidental initialization on non-select nodes (legacy Select2 v3 throws query errors).
				if (!$el.is('select')) {
					return;
				}

				// Works for both Select2 v3 and v4.
				if ($el.data('select2')) {
					$el.select2('destroy');
				}

				var options = {
					width: '100%',
					allowClear: false,
					minimumResultsForSearch: 0
				};

				// v4-only option; do not pass to v3.
				if (isSelect2V4) {
					options.dropdownParent = inModal ? $modal : $(document.body);
				}

				$el.select2(options);
			});
		}, 0);
	}

	function allowSelect2TypingInsideModal() {
		if (typeof $ === 'undefined' || !$.fn || !$.fn.modal || !$.fn.modal.Constructor) {
			return;
		}

		var ModalConstructor = $.fn.modal.Constructor;
		if (ModalConstructor.prototype._taskSelect2FocusPatched) {
			return;
		}

		ModalConstructor.prototype.enforceFocus = function () {
			var modalThis = this;
			$(document)
				.off('focusin.bs.modal')
				.on('focusin.bs.modal', function (e) {
					var $target = $(e.target);
					var isInsideModal = modalThis.$element[0] === e.target || modalThis.$element.has(e.target).length;
					var isSelect2Input =
						$target.closest('.select2-container, .select2-dropdown, .select2-drop, .select2-search').length > 0 ||
						$target.is('.select2-input, .select2-search__field');

					if (!isInsideModal && !isSelect2Input) {
						modalThis.$element.trigger('focus');
					}
				});
		};

		ModalConstructor.prototype._taskSelect2FocusPatched = true;

		$(document).off('select2:open.task select2-open.task');
		$(document).on('select2:open.task select2-open.task', function () {
			$timeout(function () {
				var $search = $('.select2-container--open .select2-search__field, .select2-drop-active .select2-input');
				if ($search.length) {
					$search.trigger('focus');
				}
			}, 0);
		});
	}

	function refreshTaskSelect2Values() {
		if (typeof $ === 'undefined' || !$.fn || !$.fn.select2) {
			return;
		}

		$timeout(function () {
			// Refresh select2 values for modal form inputs
			if ($scope.x.emp_id) {
				$('select[name="emp_id"]').val($scope.x.emp_id).trigger('change');
			}
			if ($scope.x.project_id) {
				$('select[name="project_id"]').val($scope.x.project_id).trigger('change');
			}
			if ($scope.x.assign_id) {
				$('select[name="assign_id"]').val($scope.x.assign_id).trigger('change');
			}
		}, 100);
	}

	$scope.openLocationMap = function (type, row) {
		var isStart = type === 'start';
		var lat = parseCoordinate(isStart ? row.start_latitude : row.end_latitude);
		var lng = parseCoordinate(isStart ? row.start_longitude : row.end_longitude);
		var locationLabel = isStart ? "Start Location" : "End Location";

		if (!isValidCoordinatePair(lat, lng)) {
			messages("warning", "Location Missing", locationLabel + " is not available for this task.", 3000);
			return;
		}

		$scope.locationModal.title = locationLabel + (row.staff_name ? " - " + row.staff_name : "");
		$scope.locationModal.lat = lat.toFixed(6);
		$scope.locationModal.lng = lng.toFixed(6);
		$scope.locationModal.url = $scope.getMapUrl(lat, lng);
		$('#taskLocationModal').modal('show');
	};

	$scope.hasCoordinates = function (type, row) {
		if (!row) return false;
		var isStart = type === 'start';
		var lat = parseCoordinate(isStart ? row.start_latitude : row.end_latitude);
		var lng = parseCoordinate(isStart ? row.start_longitude : row.end_longitude);
		return isValidCoordinatePair(lat, lng);
	};

	$scope.compactCoordinate = function (value) {
		var parsed = parseCoordinate(value);
		if (parsed === null) return "";
		return parsed.toFixed(3) + "...";
	};

	var TASK_LOCATION_MAX_AGE_MS = 60 * 1000;
	var TASK_LOCATION_REQUIRED_ACCURACY_M = 200;
	var TASK_LOCATION_WAIT_TIMEOUT_MS = 20000;
	var PREFETCH_LOCATION_MAX_AGE_MS = 5 * 60 * 1000;
	var BEST_EFFORT_LOCATION_MAX_AGE_MS = 10 * 60 * 1000;
	// Fallback fixes may miss the strict 200m gate, but a Wi-Fi/IP-based
	// browser fix can be off by kilometers - never worth saving as-is.
	var TASK_LOCATION_MAX_FALLBACK_ACCURACY_M = 500;

	function isAcceptableFallbackAccuracy(location) {
		if (!location) return false;
		if (typeof location.accuracy !== "number") return true;
		return location.accuracy <= TASK_LOCATION_MAX_FALLBACK_ACCURACY_M;
	}

	function normalizeListenerLocation(coords) {
		if (!coords) return null;

		var lat = parseCoordinate(coords.latitude);
		var lng = parseCoordinate(coords.longitude);
		var accuracy = parseCoordinate(coords.accuracy);
		var capturedAt = parseInt(coords.capturedAt || coords.timestamp || coords.time || 0, 10);

		if (!isValidCoordinatePair(lat, lng)) return null;
		if (isNaN(capturedAt) || capturedAt <= 0) capturedAt = 0;
		if (accuracy !== null && accuracy < 0) accuracy = null;

		return {
			latitude: lat,
			longitude: lng,
			accuracy: accuracy,
			capturedAt: capturedAt
		};
	}

	function isListenerLocationFresh(location, maxAgeMs) {
		if (!location || !location.capturedAt || maxAgeMs <= 0) return false;
		return (Date.now() - location.capturedAt) <= maxAgeMs;
	}

	function hasRequiredListenerAccuracy(location, requiredAccuracyM) {
		if (!requiredAccuracyM || requiredAccuracyM <= 0) return true;
		return typeof location.accuracy === "number" && location.accuracy <= requiredAccuracyM;
	}

	function isListenerLocationUsable(location, maxAgeMs, requiredAccuracyM) {
		return (
			!!location &&
			isListenerLocationFresh(location, maxAgeMs) &&
			hasRequiredListenerAccuracy(location, requiredAccuracyM)
		);
	}

	function getCachedListenerLocation() {
		if (typeof window.getLatestAppLocation === "function") {
			var fromGetter = normalizeListenerLocation(window.getLatestAppLocation());
			if (fromGetter) return fromGetter;
		}

		var direct = normalizeListenerLocation(window.latestAppLocation);
		if (direct) return direct;

		try {
			var raw = localStorage.getItem("latestAppLocation");
			if (!raw) return null;
			return normalizeListenerLocation(JSON.parse(raw));
		} catch (e) {
			return null;
		}
	}

	// Last resort when nothing meets the strict freshness/accuracy bar in
	// time: a slightly stale or lower-accuracy fix beats leaving the task's
	// location fields empty.
	function getBestEffortLocation() {
		var location = getCachedListenerLocation();
		if (location && isListenerLocationFresh(location, BEST_EFFORT_LOCATION_MAX_AGE_MS) && isAcceptableFallbackAccuracy(location)) {
			return location;
		}
		return null;
	}

	function setTaskLocationForTime(time, coords) {
		if (!coords) return;

		if (time === 'start') {
			$scope.x.start_latitude = coords.latitude;
			$scope.x.start_longitude = coords.longitude;
		} else if (time === 'stop') {
			$scope.x.end_latitude = coords.latitude;
			$scope.x.end_longitude = coords.longitude;
		}
	}

	function requestLocationFromParentApp(reason) {
		if (window.parent && window.parent !== window && window.parent.postMessage) {
			try {
				window.parent.postMessage({ type: "REQUEST_LOCATION", reason: reason || "general" }, "*");
			} catch (e) {
				// Parent app bridge unavailable, fallback handles this.
			}
		}
	}

	function requestFreshBrowserLocation(timeoutMs) {
		return new Promise(function (resolve, reject) {
			if (!navigator.geolocation) {
				reject(new Error("Geolocation API unavailable"));
				return;
			}

			navigator.geolocation.getCurrentPosition(
				function (position) {
					resolve(normalizeListenerLocation({
						latitude: position.coords.latitude,
						longitude: position.coords.longitude,
						accuracy: position.coords.accuracy,
						capturedAt: Date.now()
					}));
				},
				function (error) {
					reject(error || new Error("Unable to fetch browser location"));
				},
				{
					enableHighAccuracy: true,
					timeout: timeoutMs,
					maximumAge: 0
				}
			);
		});
	}

	$scope.requestLocation = function () {
		var cached = getCachedListenerLocation();
		if (isListenerLocationUsable(cached, PREFETCH_LOCATION_MAX_AGE_MS, null)) {
			return;
		}

		if (typeof window.ensureAppLocation === "function") {
			window.ensureAppLocation({
				reason: "prefetch",
				maxAgeMs: PREFETCH_LOCATION_MAX_AGE_MS,
				requiredAccuracyM: null,
				timeoutMs: TASK_LOCATION_WAIT_TIMEOUT_MS
			}).catch(function () {
				// Silent background prefetch; Start/Stop does strict hard-gate.
			});
			return;
		}

		requestLocationFromParentApp("prefetch");
	};
	$scope.isStartDisabled = false;

	$scope.requestLocation();
	allowSelect2TypingInsideModal();
	$('#taskFormModal').on('shown.bs.modal', function () {
		// Legacy Select2 (v3) often appends dropdown/search outside modal;
		// removing Bootstrap's focus trap here prevents blocked typing.
		$(document).off('focusin.bs.modal');
		initTaskSelect2();
	});
	// Get location automatically
	$scope.getLocation = function (time, onResolved, options) {
		var opts = options || {};
		var strict = !!opts.strict;
		var requestReason = opts.reason || time || "task";
		var maxAgeMs = typeof opts.maxAgeMs === "number"
			? opts.maxAgeMs
			: (strict ? TASK_LOCATION_MAX_AGE_MS : PREFETCH_LOCATION_MAX_AGE_MS);
		var requiredAccuracyM = strict
			? TASK_LOCATION_REQUIRED_ACCURACY_M
			: (typeof opts.requiredAccuracyM === "number" ? opts.requiredAccuracyM : null);
		var waitTimeoutMs = typeof opts.timeoutMs === "number"
			? opts.timeoutMs
			: TASK_LOCATION_WAIT_TIMEOUT_MS;
		var finished = false;
		var locationEventTimer = null;

		function done(success) {
			if (finished) return;
			finished = true;
			if (typeof onResolved === "function") {
				onResolved(success);
			}
		}

		// A location that just missed the strict freshness/accuracy bar (or
		// nothing at all) still beats leaving start/stop with no coordinates.
		function finishWithFallback(rawLocation) {
			var rawUsable = rawLocation
				&& rawLocation.latitude != null
				&& rawLocation.longitude != null
				&& isAcceptableFallbackAccuracy(rawLocation);
			var fallback = rawUsable ? rawLocation : getBestEffortLocation();

			if (!fallback) {
				done(false);
				return;
			}

			clearLocationListener();
			$scope.$applyAsync(function () {
				setTaskLocationForTime(time, fallback);
			});
			done(true);
		}

		var cachedLocation = getCachedListenerLocation();
		if (isListenerLocationUsable(cachedLocation, maxAgeMs, requiredAccuracyM)) {
			$scope.$applyAsync(function () {
				setTaskLocationForTime(time, cachedLocation);
			});
			done(true);
			return;
		}

		function clearLocationListener() {
			window.removeEventListener("app-location-updated", onAppLocationUpdated);
			if (locationEventTimer) {
				clearTimeout(locationEventTimer);
				locationEventTimer = null;
			}
		}

		function onAppLocationUpdated(event) {
			var eventLocation = normalizeListenerLocation(event && event.detail);
			if (!isListenerLocationUsable(eventLocation, maxAgeMs, requiredAccuracyM)) return;

			clearLocationListener();
			$scope.$applyAsync(function () {
				setTaskLocationForTime(time, eventLocation);
			});
			done(true);
		}

		window.addEventListener("app-location-updated", onAppLocationUpdated);
		requestLocationFromParentApp(requestReason);

		if (typeof window.ensureAppLocation === "function") {
			window.ensureAppLocation({
				reason: requestReason,
				maxAgeMs: maxAgeMs,
				requiredAccuracyM: requiredAccuracyM,
				timeoutMs: waitTimeoutMs
			}).then(function (location) {
				var normalized = normalizeListenerLocation(location);
				if (!isListenerLocationUsable(normalized, maxAgeMs, requiredAccuracyM)) {
					finishWithFallback(normalized);
					return;
				}
				clearLocationListener();
				$scope.$applyAsync(function () {
					setTaskLocationForTime(time, normalized);
				});
				done(true);
			}).catch(function () {
				requestFreshBrowserLocation(waitTimeoutMs).then(function (browserLocation) {
					if (!isListenerLocationUsable(browserLocation, maxAgeMs, requiredAccuracyM)) {
						finishWithFallback(browserLocation);
						return;
					}
					clearLocationListener();
					$scope.$applyAsync(function () {
						setTaskLocationForTime(time, browserLocation);
					});
					done(true);
				}).catch(function () {
					finishWithFallback(null);
				});
			});
		}

		locationEventTimer = setTimeout(function () {
			clearLocationListener();
			requestFreshBrowserLocation(waitTimeoutMs).then(function (browserLocation) {
				if (!isListenerLocationUsable(browserLocation, maxAgeMs, requiredAccuracyM)) {
					finishWithFallback(browserLocation);
					return;
				}
				$scope.$applyAsync(function () {
					setTaskLocationForTime(time, browserLocation);
				});
				done(true);
			}).catch(function () {
				finishWithFallback(null);
			});
		}, waitTimeoutMs);
	};
	
	$scope.on_employee_change = function (emp_id){
		$http.get(rootUrl + 'project_assign/get_cust?emp_id=' + emp_id).success(function (data) {	
			$scope.project_filter_list = data || [];
			initTaskSelect2();
		});
	}

	$scope.getDurationClass = function (totalSeconds) {
		if (!totalSeconds) totalSeconds = 0;
		
		var fourHours = 4 * 3600;      // 14400 seconds
		var eightHours = 8 * 3600;     // 28800 seconds
		
		if (totalSeconds < fourHours) {
			return 'red';
		} else if (totalSeconds < eightHours) {
			return 'orange';
		} else {
			return 'green';
		}
	};

	$scope.getProgressPercentage = function (totalSeconds) {
		if (!totalSeconds) totalSeconds = 0;
		
		var eightHours = 8 * 3600;     // 28800 seconds
		var percentage = (totalSeconds / eightHours) * 100;
		
		// Cap at 100%
		return Math.min(percentage, 100);
	};

	$scope.getTaskSerial = function (index) {
		var page = parseInt($scope.pageno, 10) || 1;
		var perPage = parseInt($scope.itemsPerPage, 10) || 15;
		var rowIndex = parseInt(index, 10) || 0;
		return ((page - 1) * perPage) + rowIndex + 1;
	};


	$('#DOB1').datepicker('now');
	$('#DOB2').datepicker('now');

	$scope.local = localStorage.getItem('type');
	$scope.emp_id = localStorage.getItem('emp_id');
	$scope.grade = localStorage.getItem('grade');
	$scope.dailySummary = {
		date: "",
		range_label: "",
		can_view_all: false,
		overall_duration: "00:00:00",
		totals: [],
		self: null,
		primary: null,
		daily_breakdown: [],
		raw_rows: []
	};
	$scope.lastSummaryParams = [];
	$scope.isDailySummaryRefreshing = false;
	$scope.isDailyBreakdownRefreshing = false;

	$scope.load_daily_breakdown = function (filterParams) {
		var qs = [];
		if (angular.isArray(filterParams) && filterParams.length) {
			qs = filterParams.slice(0);
		}

		$scope.isDailyBreakdownRefreshing = true;
		var url = rootUrl + "task/view_data";
		$http.get(url).success(function (response) {
			var rows = angular.isArray(response) ? response : ((response && response.data) ? response.data : []);
			var filteredRows = rows.filter(function (item) {
				return taskMatchesSummaryFilters(item, qs);
			});
			$scope.dailySummary.raw_rows = filteredRows;
			var targetEmpId = getFilterParamValue(qs, "emp_id");
			if (targetEmpId) {
				var employeeSummary = buildSelectedEmployeeSummaryFromRows(filteredRows);
				if (employeeSummary.task_count) {
					$scope.dailySummary.primary = employeeSummary;
					$scope.dailySummary.self = employeeSummary;
					$scope.dailySummary.overall_duration = employeeSummary.total_duration;
				}
			}
			buildDailyBreakdownFromRows(filteredRows, qs);
		}).finally(function () {
			$scope.isDailyBreakdownRefreshing = false;
		});
	};

	$scope.load_daily_summary = function (filterParams) {
		var qs = [];
		if (angular.isArray(filterParams) && filterParams.length) {
			qs = filterParams.slice(0);
		}
		$scope.lastSummaryParams = qs.slice(0);
		var url = rootUrl + "task/daily_duration_summary" + (qs.length ? ("?" + qs.join("&")) : "");
		$scope.isDailySummaryRefreshing = true;
		$http.get(url).success(function (data) {
			data = data || {};
			$scope.dailySummary.date = data.date || "";
			$scope.dailySummary.range_label = data.range_label || getPeriodLabel(qs);
			if (!$scope.dailySummary.range_label) {
				var fromDate = getFilterParamValue(qs, "date_from");
				var toDate = getFilterParamValue(qs, "date_to");
				var displayFrom = formatTaskDateLabel(parseIsoDate(fromDate));
				var displayTo = formatTaskDateLabel(parseIsoDate(toDate));
				if (displayFrom && displayTo) {
					$scope.dailySummary.range_label = displayFrom + " to " + displayTo;
				}
			}
			$scope.dailySummary.can_view_all = String(data.can_view_all) === '1' || data.can_view_all === 1 || data.can_view_all === true;
			$scope.dailySummary.overall_duration = data.overall_duration || "00:00:00";
			$scope.dailySummary.totals = angular.isArray(data.totals) ? data.totals : [];
			var targetEmpId = getFilterParamValue(qs, "emp_id");
			var targetSummary = null;
			if (targetEmpId && $scope.dailySummary.totals.length) {
				angular.forEach($scope.dailySummary.totals, function (row) {
					if (String(row.emp_id) === String(targetEmpId)) {
						targetSummary = row;
					}
				});
			}
			$scope.dailySummary.primary = targetSummary || ((!$scope.dailySummary.can_view_all && $scope.dailySummary.totals.length) ? $scope.dailySummary.totals[0] : null);
			$scope.dailySummary.self = $scope.dailySummary.primary;
			if (targetEmpId && $scope.dailySummary.primary) {
				$scope.dailySummary.overall_duration = $scope.dailySummary.primary.total_duration || $scope.dailySummary.overall_duration;
			} else if ($scope.dailySummary.totals.length) {
				var totalsSeconds = 0;
				angular.forEach($scope.dailySummary.totals, function (row) {
					totalsSeconds += parseDurationToSeconds(row.total_duration || "00:00:00");
				});
				$scope.dailySummary.overall_duration = formatSecondsToDuration(totalsSeconds);
			}
			var dateType = getFilterParamValue(qs, "date_type");
			if (dateType === 'range' || dateType === 'week' || dateType === 'month') {
				$scope.load_daily_breakdown(qs);
			} else {
				$scope.dailySummary.daily_breakdown = [];
			}
		}).finally(function () {
			$scope.isDailySummaryRefreshing = false;
		});
	};

	$scope.refresh_daily_summary = function () {
		$scope.load_daily_summary($scope.lastSummaryParams || []);
	};

	$http.get(rootUrl + "hr_staff_details/view_staff?data=emp_id,staff_name&st=1").success(function (data) {
		$scope.employees = data;
		initTaskSelect2();
	});
	$http.get(rootUrl + "task_assigner/view_data?emp_id=" + $scope.emp_id + "&status=1&exclude_completed_one_time=1").success(function (data) {
		$scope.tasks = data;
		initTaskSelect2();
	});
	$http.get(rootUrl + "task_assigner/view_data?data=assign_id,title&distinct=assign_id,title&status=1").success(function (data) {
		$scope.task_filter_list = data || [];
		initTaskSelect2();
	});
	$http.get(rootUrl + "customer/view?data=c_id,company_name&status=1").success(function (data) {
		$scope.project_filter_list = data || [];
		initTaskSelect2();
	});
	//	$http.get(rootUrl + "projects_assign/view_data").success(function (data) {
	//		$scope.projects = data;
	//	});

	$scope.pageno = 1; // initialize page no to 1
	$scope.total_count = 0;
	$scope.itemsPerPage = '15';
	$scope.qx = {};
	$scope.task_filter_list = [];
	$scope.project_filter_list = [];
	$scope.x = {};
	$scope.isLoading = false;
	$scope.loader = function (pageno = 1) {
		if (!pageno)
			pageno = 1;
		$scope.pageno = pageno;
		$scope.isLoading = true;
		var params = ['join=1'];
		var summaryParams = [];
		if ($scope.qx.emp_id) {
			var empParam = "emp_id=" + encodeURIComponent($scope.qx.emp_id);
			params.push(empParam);
			summaryParams.push(empParam);
		}
		if ($scope.qx.project_id) {
			var projectParam = "project_id=" + encodeURIComponent($scope.qx.project_id);
			params.push(projectParam);
			summaryParams.push(projectParam);
		}
		if ($scope.qx.status !== undefined && $scope.qx.status !== "") {
			var statusParam = "status=" + encodeURIComponent($scope.qx.status);
			params.push(statusParam);
			summaryParams.push(statusParam);
		}
		if ($scope.qx.date_type) {
			var dateTypeParam = "date_type=" + encodeURIComponent($scope.qx.date_type);
			params.push(dateTypeParam);
			summaryParams.push(dateTypeParam);
		}
		if ($scope.qx.date_type === 'range') {
			var fromDate = formatFilterDate($scope.qx.date_from);
			var toDate = formatFilterDate($scope.qx.date_to);
			if (fromDate) {
				var dateFromParam = "date_from=" + encodeURIComponent(fromDate);
				params.push(dateFromParam);
				summaryParams.push(dateFromParam);
			}
			if (toDate) {
				var dateToParam = "date_to=" + encodeURIComponent(toDate);
				params.push(dateToParam);
				summaryParams.push(dateToParam);
			}
		}

		var url = rootUrl + "task/view/" + $scope.itemsPerPage + "/" + pageno + "?" + params.join("&");
		$http.get(url).success(function (response) {
			$scope.datadb = (response && response.data) ? response.data : [];  // data to be displayed on current page.
			$scope.total_count = (response && response.total_count) ? response.total_count : 0; // total data count.
			$scope.load_daily_summary(summaryParams);
			//        	console.log(data);
			$scope.isLoading = false;
			$scope.datadb.forEach(function (item) {
				if (!item.end_time) {
					// convert start date+time to timestamp
					var parts = item.start_date.split('/');
					var t = item.start_time.split(':');

					// d/m/Y H:i:s → JS Date
					var start = new Date(
						parts[2],               // year
						parts[1] - 1,           // month
						parts[0],               // day
						t[0], t[1], t[2] || 0
					);

					item.startTimestamp = start.getTime();
					item.duration = '00:00:00';
				}
			});
			if ($scope.dailySummary.raw_rows && $scope.dailySummary.raw_rows.length) {
				buildDailyBreakdownFromRows($scope.dailySummary.raw_rows, $scope.lastSummaryParams || []);
			}
			startGlobalTimer();
			if ($scope.employees.length == "1") {
				$scope.x.emp_id = $scope.employees[0]['emp_id'];
			}
			initTaskSelect2();
		});
		/*
		$http.get(rootUrl + "task/view_data").success(function (data) {
			$scope.datadb = data;
			$scope.datadb.forEach(function (item) {
				if (!item.end_time) {
					// convert start date+time to timestamp
					var parts = item.start_date.split('/');
					var t = item.start_time.split(':');

					// d/m/Y H:i:s → JS Date
					var start = new Date(
						parts[2],               // year
						parts[1] - 1,           // month
						parts[0],               // day
						t[0], t[1], t[2] || 0
					);

					item.startTimestamp = start.getTime();
					item.duration = '00:00:00';
				}
			});
			startGlobalTimer();
		});
		*/
		$http.get(rootUrl + "project_assign/get_cust").success(function (data) {
			$scope.companies = data;
			if (data.length == 1) {
				$scope.x.project_id = data[0].c_id;
			}
			initTaskSelect2();
		});

		$http.get(rootUrl + tmodule + 'get_sess').success(function (data) {
			$scope.sess = data['type'];
			// console.log('sess:',$scope.sess);
		});
	}

	$scope.apply_filters = function () {
		if ($scope.qx.date_type === 'range') {
			var fromDate = formatFilterDate($scope.qx.date_from);
			var toDate = formatFilterDate($scope.qx.date_to);
			if (!fromDate || !toDate) {
				messages("warning", "Info!", "Please select both From Date and To Date for Date Range.", 3500);
				return;
			}
			if (fromDate > toDate) {
				messages("warning", "Info!", "From Date should be less than or equal to To Date.", 3500);
				return;
			}
		}
		$scope.loader(1);
	};

	$scope.clear_filters = function () {
		$scope.qx = {};
		$scope.itemsPerPage = '15';
		$scope.lastSummaryParams = [];
		$scope.dailySummary.date = "";
		$scope.dailySummary.range_label = "";
		$scope.dailySummary.overall_duration = "00:00:00";
		$scope.dailySummary.totals = [];
		$scope.dailySummary.self = null;
		$scope.dailySummary.primary = null;
		$scope.dailySummary.daily_breakdown = [];
		$scope.dailySummary.raw_rows = [];
		$scope.loader(1);
	};

	$scope.on_date_type_change = function () {
		if ($scope.qx.date_type !== 'range') {
			$scope.qx.date_from = "";
			$scope.qx.date_to = "";
		}
	};

	$scope.on_items_per_page_change = function () {
		$scope.loader(1);
	};
	$scope.loader();

	// $scope.page_refresh = function ()//to get value after page refresshing the stop watch
	// {
	// 	$http.get(rootUrl + "task/view_data?status=0").success(function (data) {
	// 		if (data.length > 0) {
	// 			if (data[0].status == '0') {
	// 				$scope.x.tstatus = data[0].status;
	// 				$scope.x.task_id = data[0].task_id;
	// 				$scope.x.start_date = data[0].start_date;
	// 				$scope.x.start_time = data[0].start_time;
	// 				$scope.x.emp_id = data[0].emp_id;
	// 				$scope.x.assign_id = data[0].assign_id;
	// 				$scope.x.comment = data[0].comment;
	// 				$scope.timer(data[0].start_date, data[0].start_time, "0");
	// 			}
	// 		}
	// 	})
	// }
	// if ($scope.local != "Administrator")
	// 	$scope.page_refresh();

	$scope.isOneTimeTask = function () {
		if (!$scope.x || !$scope.x.assign_id || !$scope.tasks) return false;
		for (var i = 0; i < $scope.tasks.length; i++) {
			if (String($scope.tasks[i].assign_id) === String($scope.x.assign_id)) {
				return $scope.tasks[i].task_type === 'one_time';
			}
		}
		return false;
	};

	$scope.fetch_task = function (emp_id) {
		$http.get(rootUrl + "task_assigner/view_data?emp_id=" + emp_id + "&status=1&exclude_completed_one_time=1").success(function (data) {
			$scope.tasks = data;
			initTaskSelect2();
		});
		$http.get(rootUrl + 'project_assign/get_cust?emp_id=' + emp_id).success(function (data) {
			$scope.companies = data;
			initTaskSelect2();
		});
	}
	$http.get(rootUrl + "task/check_timer_on").success(function (data) {
		if (data != '0') {
			$scope.x = data[0];
		}
	})

	$scope.update_call = function (y) {
		$http.get(rootUrl + "task_assigner/view_data?emp_id=" + y.emp_id+"&status=1").success(function (data) {
			$scope.tasks = data;//to fill empdata in select box
			// Find the task in datadb by task_id to ensure we have the live reference
			var liveTask = $scope.datadb.find(function (item) {
				return item.task_id === y.task_id;
			});
			// Use the live reference if found, otherwise use the passed task
			$scope.x = liveTask || y;
			//			console.log($scope.x);
			$scope.value = "on";
			// The global timer is already updating x.duration via ng-bind
			// No need to call $scope.timer() which conflicts with ng-bind
			$timeout(function () {
				$('#taskFormModal').modal('show');
				initTaskSelect2();
				refreshTaskSelect2Values();
			}, 80);
		});
		$timeout(function () {
			$scope.getLocation('stop', null, { reason: 'prefetch' });
		}, 500);
		//		$timeout(function () {
		//			$('#addform').tab('show');
		//		}, 1000);
	}

	$scope.filter_new = function () {
		$scope.x = {};
		$scope.value = "";
		$("#txt").html("");
	}

	$scope.openTaskModal = function (mode, row) {
		if (mode === 'edit' && row) {
			$scope.update_call(row);
			return;
		}

		$scope.filter_new();

		$scope.reminderOptions = [
			{label: '5 min',  value: 5},
			{label: '10 min', value: 10},
			{label: '20 min', value: 20},
			{label: '40 min', value: 40},
			{label: '1 hr',   value: 60},
			{label: '2 hr',   value: 120},
			{label: '4 hr',   value: 240},
			{label: '8 hr',   value: 480}
		];
		$scope.x.reminder_minutes = 240;

		if ($scope.local != "Administrator" && $scope.emp_id) {
			$scope.x.emp_id = $scope.emp_id;
			$scope.fetch_task($scope.emp_id);
		}

		if ($scope.employees && $scope.employees.length == 1) {
			$scope.x.emp_id = $scope.employees[0]['emp_id'];
			$scope.fetch_task($scope.x.emp_id);
		}

		$timeout(function () {
			$('#taskFormModal').modal('show');
			initTaskSelect2();
		}, 80);

		// Capture start location as soon as the Start Task modal opens,
		// so start_latitude/start_longitude are ready before submit click.
		$timeout(function () {
			$scope.getLocation('start', function () {}, { strict: true, reason: 'prefetch' });
		}, 120);
	}

		$scope.save_data = function (y, time) {
		$('#submitbtn').attr('disabled', true);

		function submitTaskForm() {
			$.ajax({
				type: "POST",
				url: rootUrl + "task/save_data",
				data: $("#form1").serialize(),
				beforeSend: function () {
					$('#loader').css('display', 'inline');
				},
				success: function (data1) {
					$('#loader').css('display', 'none');
					$('#submitbtn').attr('disabled', false);
					if (data1.error == "0") {
						if (data1.task_id) {
							messages("success", "Success!", "Task Saved Successfully", 3000);
							$('#taskFormModal').modal('hide');
							scheduleReminder(data1.task_id, $scope.x.reminder_minutes || 240, Date.now());
							/*
							$http.get(rootUrl + "task/view_data").success(function (data) {
								$scope.datadb = data;
								$scope.datadb.forEach(function (item) {
									if (!item.end_time) {
										// convert start date+time to timestamp
										var parts = item.start_date.split('/');
										var t = item.start_time.split(':');

										// d/m/Y H:i:s ? JS Date
										var start = new Date(
											parts[2],               // year
											parts[1] - 1,           // month
											parts[0],               // day
											t[0], t[1], t[2] || 0
										);

										item.startTimestamp = start.getTime();
										item.duration = '00:00:00';
									}
								});

								startGlobalTimer();

							});
							if (data1.multiple != 'true') {
								// disable button
								$scope.isStartDisabled = true;
							}
							$scope.x = {}; */

						}
						else {
							messages("success", "Success!", "Task Updated Successfully", 3000);
							$('#taskFormModal').modal('hide');
						}

						$scope.$applyAsync(function () {
							$scope.filter_new();
							$scope.loader($scope.pageno || 1);
							checkStartButton();
						});
					}
					else {
						messages("danger", "Warning!", data1.msg, 6000);
						$('#submitbtn').attr('disabled', false);
					}
				}
			});
		}

		if (time === 'start' || time === 'stop') {
			$scope.getLocation(time, function (success) {
				if (!success) {
					$('#submitbtn').attr('disabled', false);
					messages("warning", "Location Required", "Unable to capture a fresh and accurate location. Please enable GPS/location and try again.", 4000);
					return;
				}
				submitTaskForm();
			}, { strict: true, reason: time === 'start' ? 'start-task' : 'stop-task' });
			return;
		}

		submitTaskForm();
	}

	$scope.stop_data = function (y, time) {
		$('#stopbtn').attr('disabled', true);
		if (time === 'stop') {
			$scope.fetch_task(y.emp_id);
			$scope.x = y;
			$timeout(function () {
				$scope.getLocation('stop', null, { reason: 'prefetch' });
			}, 500);
			console.log($scope.x);
		}
	}

	// $scope.save_stop_data = function () {
	// 	$timeout(function () {
	// 		$http.post(rootUrl + tmodule + 'stop_data?task_id=' + y.task_id + '&duration=' + y.duration + '&end_longitude=' + $scope.end_longitude + '&end_latitude=' + $scope.end_latitude).success(function (data) {
	// 			console.log(data);
	// 			if (data.status === 'success') {
	// 				messages('success', 'Success!', data.msg, 3000);
	// 				$http.get(rootUrl + "hr_staff_details/view?data=emp_id,staff_name&st=1").success(function (data) {
	// 					if (data.length === 1) {
	// 						$scope.x.emp_id = data[0]['emp_id'];
	// 					}
	// 					$scope.employees = data;

	// 				});
	// 				$scope.isStartDisabled = false;
	// 				$http.get(rootUrl + "task/view_data").success(function (data) {
	// 					$scope.datadb = data;
	// 					$scope.datadb.forEach(function (item) {
	// 						if (!item.end_time) {
	// 							// convert start date+time to timestamp
	// 							var parts = item.start_date.split('/');
	// 							var t = item.start_time.split(':');

	// 							// d/m/Y H:i:s → JS Date
	// 							var start = new Date(
	// 								parts[2],               // year
	// 								parts[1] - 1,           // month
	// 								parts[0],               // day
	// 								t[0], t[1], t[2] || 0
	// 							);

	// 							item.startTimestamp = start.getTime();
	// 							item.duration = '00:00:00';
	// 						}
	// 					});

	// 					startGlobalTimer();

	// 				});
	// 			} else {
	// 				messages('danger', 'Warning!', data.msg, 3000);
	// 			}
	// 		});
	// 	}, 5000);
	// }

	$scope.delete_data = function (id) {
		if (confirm("Deleting Task may hamper your data associated with it.")) {
			if (confirm("Are you Sure to DELETE ??")) {
				$http.get(rootUrl + "task/delete_data?id=" + id).success(function (data) {
					if (data == "1") {
						messages("success", "Success!", "Task Deleted Successfully", 3000);
					}
					else {
						messages("danger", "Warning!", "Task not Deleted You cannot delete the task. Please contact admin", 4000);
					}
					$scope.loader();
					initTaskSelect2();
				})
			}
		}
	}

	$scope.checkDuration = function (duration) {
		if (!duration) return false;

		var parts = duration.split(':');   // ["26", "2", "11"]

		var totalSeconds = (+parts[0]) * 3600 + (+parts[1]) * 60 + (+parts[2]);

		var eightHours = 8 * 3600;  // 8 hours in seconds

		return totalSeconds < eightHours;
	};

	app.directive('bsTooltip', function () {
		return {
			restrict: 'A',
			link: function (scope, element, attrs) {
				$(element).tooltip();
			}
		};
	});

	$timeout(function () {
		$('[data-toggle="tooltip"]').tooltip();
	}, 100);


	var listTimerPromise;
	var activeTaskTimerPromise;
	var reminderTimeout = null;

	function startGlobalTimer() {
		if (listTimerPromise) return;

		listTimerPromise = $interval(function () {
			var now = Date.now();

			$scope.datadb.forEach(function (item) {

				// only for running tasks
				if (!item.end_time && item.startTimestamp) {

					var diff = now - item.startTimestamp;

					var h = Math.floor(diff / 3600000);
					var m = Math.floor((diff % 3600000) / 60000);
					var s = Math.floor((diff % 60000) / 1000);

					item.duration =
						(h < 10 ? '0' : '') + h + ':' +
						(m < 10 ? '0' : '') + m + ':' +
						(s < 10 ? '0' : '') + s;
				}
			});
			if ($scope.dailySummary.raw_rows && $scope.dailySummary.raw_rows.length) {
				buildDailyBreakdownFromRows($scope.dailySummary.raw_rows, $scope.lastSummaryParams || []);
			}

		}, 1000);
	}


	function clearReminder() {
		if (reminderTimeout) {
			clearTimeout(reminderTimeout);
			reminderTimeout = null;
		}
	}

	function scheduleReminder(taskId, minutes, startTimestamp) {
		clearReminder();
		var ms = (startTimestamp + minutes * 60000) - Date.now();
		if (ms <= 0) return;
		reminderTimeout = setTimeout(function () {
			reminderTimeout = null;
			// Only fire if the task is still active
			if (!$scope.x || $scope.x.task_id != taskId || $scope.x.end_time) return;
			var label = $scope.formatReminderLabel(minutes);
			messages("warning", "Task Reminder", "You have been on this task for " + label + ". Please review or stop.", 8000);
		}, ms);
	}

	$scope.formatReminderLabel = function (minutes) {
		minutes = parseInt(minutes, 10);
		if (minutes < 60) return minutes + ' min';
		var hrs = minutes / 60;
		return hrs + ' hr' + (hrs > 1 ? 's' : '');
	};

	function checkStartButton() {
		var isGradeAUser = String($scope.grade || '').trim().toUpperCase() === 'A';

		$http.get(rootUrl + tmodule + 'get_ongoing').success(function (data) {
			if (activeTaskTimerPromise) {
				$interval.cancel(activeTaskTimerPromise);
				activeTaskTimerPromise = null;
			}

			if (data.length > 0 && data[0]['end_time'] == null) {
				$timeout(function () {
					$scope.getLocation('stop', null, { reason: 'prefetch' });
				}, 500);
				$scope.isStartDisabled = !isGradeAUser;
				$scope.x = data[0];

				// Restore reminder timer from server data on page load/refresh
				var rParts = data[0].start_date.split('/');
				var rT = data[0].start_time.split(':');
				var rStart = new Date(rParts[2], rParts[1] - 1, rParts[0], rT[0], rT[1], rT[2] || 0);
				scheduleReminder(data[0].task_id, data[0].reminder_minutes || 240, rStart.getTime());

				activeTaskTimerPromise = $interval(function () {
					var now = Date.now();
					if (!data[0].end_time) {
						// convert start date+time to timestamp
						var parts = data[0].start_date.split('/');
						var t = data[0].start_time.split(':');

						// d/m/Y H:i:s → JS Date
						var start = new Date(
							parts[2],               // year
							parts[1] - 1,           // month
							parts[0],               // day
							t[0], t[1], t[2] || 0
						);

						data[0].startTimestamp = start.getTime();
						data[0].duration = '00:00:00';
					}
					if (!data[0].end_time && data[0].startTimestamp) {
						var diff = now - data[0].startTimestamp;
						var h = Math.floor(diff / 3600000);
						var m = Math.floor((diff % 3600000) / 60000);
						var s = Math.floor((diff % 60000) / 1000);
						$scope.x.duration =
							(h < 10 ? '0' : '') + h + ':' +
							(m < 10 ? '0' : '') + m + ':' +
							(s < 10 ? '0' : '') + s;
					}
				}, 1000);
				//				}
			} else {
				$scope.isStartDisabled = false;
				clearReminder();
				$timeout(function () {
					$scope.getLocation('start', null, { reason: 'prefetch' });
				}, 500);
			}
		});
	}

	checkStartButton();

}]);





//blank line is required
app.controller('task_details',['$scope','$rootScope','$http',function($scope,$rootScope,$http){
	
	rootUrl=$rootScope.site_url;
	module="task_details/";
	$http.get(rootUrl+module+"/index").success (function(data) {if(data==0){window.location.assign('login.html');} else if(data==2){messages("success", "Privilege not assigned.", 1000);window.location.assign('index.html');}});
	
	$scope.pageno = 1;
	$scope.total_count = 0;
	$scope.itemsPerPage = '15';
	$scope.qx = {};
	$scope.x = {};
	$scope.tasks = [];
	$scope.task_filter_list = [];
	$scope.task_details_modal_title="Add Task Details";

	function initTaskDetailsSelect2() {
		if (typeof $ === 'undefined' || !$.fn || !$.fn.select2) {
			return;
		}

		setTimeout(function () {
			var $modal = $('#taskDetailsModal');
			var isSelect2V4 = !!($.fn.select2 && $.fn.select2.amd);

			$('.task-details-filterbar select, #taskDetailsModal select').each(function () {
				var $el = $(this);
				if (!$el.is('select')) {
					return;
				}
				if ($el.hasClass('no-select2') || $el.is('[data-no-select2]')) {
					return;
				}

				if ($el.data('select2')) {
					$el.select2('destroy');
				}

				var options = {
					width: '100%',
					minimumResultsForSearch: 0
				};

				if (isSelect2V4 && $el.closest('#taskDetailsModal').length) {
					options.dropdownParent = $modal;
				}

				$el.select2(options);
			});
		}, 0);
	}

	function allowSelect2TypingInsideTaskDetailsModal() {
		if (typeof $ === 'undefined' || !$.fn || !$.fn.modal || !$.fn.modal.Constructor) {
			return;
		}

		var ModalConstructor = $.fn.modal.Constructor;
		if (ModalConstructor.prototype._taskDetailsSelect2FocusPatched) {
			return;
		}

		ModalConstructor.prototype.enforceFocus = function () {
			var modalThis = this;
			$(document)
				.off('focusin.bs.modal')
				.on('focusin.bs.modal', function (e) {
					var $target = $(e.target);
					var isInsideModal = modalThis.$element[0] === e.target || modalThis.$element.has(e.target).length;
					var isSelect2Input =
						$target.closest('.select2-container, .select2-dropdown, .select2-drop, .select2-search').length > 0 ||
						$target.is('.select2-input, .select2-search__field');

					if (!isInsideModal && !isSelect2Input) {
						modalThis.$element.trigger('focus');
					}
				});
		};

		ModalConstructor.prototype._taskDetailsSelect2FocusPatched = true;

		$(document).off('select2:open.taskDetails select2-open.taskDetails');
		$(document).on('select2:open.taskDetails select2-open.taskDetails', function () {
			setTimeout(function () {
				var $search = $('.select2-container-active .select2-input, .select2-drop-active .select2-input, .select2-container--open .select2-search__field');
				if ($search.length) {
					$search.focus();
				}
			}, 0);
		});
	}

	$http.get(rootUrl+"hr_staff_details/view_staff?data=emp_id as eid,staff_name as snam&st=1").success(function(data)
	{
		$scope.employees=data;
		initTaskDetailsSelect2();
	});

	$http.get(rootUrl+"task_assigner/view_distinct?data=title&distinct=1").success(function(data)
	{
		$scope.task_filter_list = data;
		initTaskDetailsSelect2();
	});

	$scope.loader = function(pageno)
	{
		if(!pageno)
			pageno = 1;
		$scope.pageno = pageno;

		var url = "task_details/view/" + $scope.itemsPerPage + "/" + pageno + "?join=1";
		var params = "";

		if($scope.qx.emp_id)
			params += "&emp_id=" + encodeURIComponent($scope.qx.emp_id);
		if($scope.qx.assign_id)
			params += "&assign_id=" + encodeURIComponent($scope.qx.assign_id);
		if($scope.qx.status!==undefined && $scope.qx.status!=="")
			params += "&status=" + encodeURIComponent($scope.qx.status);
		if($scope.qx.edit!==undefined && $scope.qx.edit!=="")
			params += "&edit=" + encodeURIComponent($scope.qx.edit);

		$http.get(rootUrl + url + params).success(function(response){
			if(response && response.data !== undefined)
			{
				$scope.datadb = response.data;
				$scope.total_count = response.total_count || 0;
			}
			else
			{
				$scope.datadb = response || [];
				$scope.total_count = ($scope.datadb || []).length;
			}
			initTaskDetailsSelect2();
		});
	};

	$scope.apply_filters = function()
	{
		$scope.loader(1);
	};

	$scope.clear_filters = function()
	{
		$scope.qx = {};
		$scope.itemsPerPage = '15';
		$scope.loader(1);
	};

	$scope.on_items_per_page_change = function()
	{
		$scope.loader(1);
	};

	$scope.filter_new=function(refreshList)
	{
		$scope.x={};
		if(refreshList!==false)
			$scope.loader($scope.pageno || 1);
	};

	$scope.loader(1);

	$scope.filter_employee=function(eid)
	{
		$http.get(rootUrl+"task_assigner/view_data?emp_id="+eid).success(function(data)
		{
			$scope.tasks=data;
			initTaskDetailsSelect2();
		})
	}
	$scope.update_call=function(y)
	{
		$scope.x=y;
		$scope.filter_employee(y.emp_id);
	}
	$scope.open_task_details_modal=function(mode,y)
	{
		if(mode=="edit" && y)
		{
			$scope.task_details_modal_title="Edit Task Details";
			$scope.update_call(y);
		}
		else
		{
			$scope.task_details_modal_title="Add Task Details";
			$scope.x={};
			$scope.tasks=[];
		}
		$('#taskDetailsModal').modal('show');
		allowSelect2TypingInsideTaskDetailsModal();
		initTaskDetailsSelect2();
	}
	
	$scope.options = { height: 150 };
	$scope.save_data=function(x)
	{
		$('#submitbtn').attr('disabled',true);
		$.ajax({
			type: "POST",
			url: rootUrl+"task_details/save_data",
			data: $("#form1").serialize(),
			beforeSend: function()
			{
				$('#webprogress').css('display','inline');
			},
			success: function(data)
			{
//				console.log(data);
				if(data=="1")
				{
					messages("success", "Success!","Task Assiged Successfully", 4000);
					$scope.filter_new();
					$('#taskDetailsModal').modal('hide');
				}
				else if(data=="0")
				{
					messages("warning", "Info!","No Data Affected", 10000);
				}
				else
				{
					messages("warning", "Warning!",data, 10000);
				}
				$('#webprogress').css('display','none');
			}
		});
		$('#submitbtn').attr('disabled',false);
	}
	
	$scope.delete_data=function(id)
	{
		if(confirm("Deleting task_assigners may hamper your data associated with it. You will loose the data related with this task_assigner."))
		{
			if(confirm("Are you Sure to DELETE ??"))
			{
				$http.get(rootUrl+"task_details/delete_data?id="+id).success(function(data){
//					console.log(data);
					if(data=="1")
					{
						messages("success", "Success!","Assigned task Deleted Successfully", 4000);
						$scope.filter_new(true);
					}
					else
					{
						messages("danger", "Warning!","Assigned task not Deleted", 10000);
					}
//					$http.get("task_assigner/view_data").success(function(data){
//						$scope.datadb=data;
//					})
				})
			}
		}
	}

	allowSelect2TypingInsideTaskDetailsModal();
	$(document).off('shown.bs.modal.taskDetailsSelect2', '#taskDetailsModal').on('shown.bs.modal.taskDetailsSelect2', '#taskDetailsModal', function () {
		initTaskDetailsSelect2();
	});
	initTaskDetailsSelect2();
	
}]);
app.controller('email_admin',['$scope','$rootScope','$http',function($scope,$rootScope,$http)
{
	module='email_admin/';
	rootUrl=$rootScope.site_url;
	$http.get(rootUrl+module+"/index").success (function(data) {if(data==0){window.location.assign('login.html');} else if(data==2){messages("success", "Privilege not assigned.", 1000);window.location.assign('index.html');}});
	
	$scope.init=function()
	{
		$http.get(rootUrl+"email_admin/view").success(function(data)
		{
			$scope.datadb=data || [];
			$scope.initializeAdminSelect2();
			$scope.syncAdminSelect2Values();
		});
	}
	$scope.init();
	$scope.x={default:'0',status:'1'};
	$scope.datadb=[];
	$scope.pageno=1;
	$scope.itemsPerPage=10;
	$scope.filters={
		search_text:"",
		status:"",
		default_flag:""
	};
	$scope.appliedFilters=angular.copy($scope.filters);
	$scope.email_admin_modal_title="Add Admin Email";

	$scope.initializeAdminSelect2=function()
	{
		setTimeout(function(){
			if (!$.fn.select2) {
				return;
			}
			var isSelect2V4 = !!($.fn.select2 && $.fn.select2.amd);
			var $pageSelects = $('.email-admin-page select.email-admin-select2');

			$pageSelects.each(function () {
				var $select = $(this);
				if (!$select.is('select')) {
					return;
				}
				if ($select.data('select2')) {
					$select.select2('destroy');
				}

				var options = {
					width: '100%',
					minimumResultsForSearch: 0
				};

				if (isSelect2V4) {
					options.dropdownParent = $(document.body);
				}

				$select.select2(options);
			});
		}, 100);
	};

	$scope.syncAdminSelect2Values=function()
	{
		setTimeout(function () {
			if (!$.fn.select2) {
				return;
			}
			var statusValue = ($scope.filters && $scope.filters.status !== undefined && $scope.filters.status !== null) ? String($scope.filters.status) : '';
			var defaultValue = ($scope.filters && $scope.filters.default_flag !== undefined && $scope.filters.default_flag !== null) ? String($scope.filters.default_flag) : '';
			var perPageValue = ($scope.itemsPerPage !== undefined && $scope.itemsPerPage !== null) ? String($scope.itemsPerPage) : '10';

			var $status = $('.email-admin-page select[ng-model="filters.status"]');
			var $default = $('.email-admin-page select[ng-model="filters.default_flag"]');
			var $perPage = $('.email-admin-page select[ng-model="itemsPerPage"]');

			if ($status.length) {
				$status.val(statusValue).trigger('change');
			}
			if ($default.length) {
				$default.val(defaultValue).trigger('change');
			}
			if ($perPage.length) {
				$perPage.val(perPageValue).trigger('change');
			}
		}, 150);
	};

	$scope.openAdminEmailModal=function(mode,y)
	{
		$scope.email_admin_modal_title=(mode==="edit") ? "Update Admin Email" : "Add Admin Email";
		$scope.x=y ? angular.copy(y) : {default:'0',status:'1'};
		$("#emailAdminFormModal").modal("show");
	}

	$scope.closeAdminEmailModal=function()
	{
		$("#emailAdminFormModal").modal("hide");
	}

	$scope.update_call=function(y)
	{
		$scope.openAdminEmailModal("edit", y);
	}

	$scope.filter_new=function()
	{
		$scope.x={default:'0',status:'1'};
	}

	$scope.clear_filters=function()
	{
		$scope.filters={
			search_text:"",
			status:"",
			default_flag:""
		};
		$scope.appliedFilters=angular.copy($scope.filters);
		$scope.pageno=1;
		$scope.initializeAdminSelect2();
		$scope.syncAdminSelect2Values();
	};

	$scope.apply_filters=function()
	{
		$scope.appliedFilters=angular.copy($scope.filters);
		$scope.pageno=1;
	};

	$scope.adminSearchFilter=function(item)
	{
		var query=($scope.appliedFilters.search_text || "").toLowerCase();
		if(!query)
		{
			return true;
		}
		var haystack=[
			item && item.name,
			item && item.email
		].join(" ").toLowerCase();
		return haystack.indexOf(query)!==-1;
	};

	$scope.adminStatusFilter=function(item)
	{
		if($scope.appliedFilters.status==="" || $scope.appliedFilters.status===null || $scope.appliedFilters.status===undefined)
		{
			return true;
		}
		return String(item && item.status || "")===String($scope.appliedFilters.status);
	};

	$scope.adminDefaultFilter=function(item)
	{
		if($scope.appliedFilters.default_flag==="" || $scope.appliedFilters.default_flag===null || $scope.appliedFilters.default_flag===undefined)
		{
			return true;
		}
		return String(item && item.default || "")===String($scope.appliedFilters.default_flag);
	};
	
	$scope.save_data=function()
	{
		$('#submitbtn').attr('disabled',true);
		$.ajax({
			type: "POST",
			url: rootUrl+module+"save",
			data: $("#admin_email_form").serialize(),
			beforeSend: function()
			{
				$('#loader').css('display','inline');
			},
			success: function(data)
			{
				if(data=="1")
				{
					messages("success", "Success!","Saved Successfully", 3000);
					$scope.filter_new();
					$scope.init();
					$scope.closeAdminEmailModal();
				}
				else if(data=="0")
				{
					messages("warning", "Info!","No Data Affected", 3000);
				}
				else
				{
					messages("danger", "Warning!",data, 6000);
				}
				$('#loader').css('display','none');
				$('#submitbtn').attr('disabled',false);
				if(!$scope.$$phase)
				{
					$scope.$applyAsync();
				}
			}
		});
	}
	$scope.delete_data=function(id)
	{
		if(confirm("Deleting Data may hamper your data associated with it."))
		{
			if(confirm("Are you Sure to DELETE ??"))
			{
				$http.get(rootUrl+module+"delete?id="+id).success(function(data)
				{
					if(data=="1")
					{
						messages("success", "Success!","Details Deleted Successfully", 3000);
						$scope.init();
					}
					else
					{
						messages("danger", "Warning!","Details not Deleted", 4000);
					}
				})
			}
		}
	}

	$(function () {
		$scope.initializeAdminSelect2();
		$scope.syncAdminSelect2Values();
	});
	
}]);
app.controller('email_contact',['$scope','$rootScope','$http',function($scope,$rootScope,$http)
{
	module='email_contact/';
	rootUrl=$rootScope.site_url;
	$http.get(rootUrl+module+"/index").success (function(data) {if(data==0){window.location.assign('login.html');} else if(data==2){messages("success", "Privilege not assigned.", 1000);window.location.assign('index.html');}});
	
	$scope.init=function()
	{
		$http.get(rootUrl+"email_contact/view").success(function(data)
		{
			$scope.datadb=data || [];
			$scope.initializeContactSelect2();
			$scope.syncContactSelect2Values();
		});
	}
	$scope.init();
	$scope.x={status:'1'};
	$scope.datadb=[];
	$scope.pageno=1;
	$scope.email_contact_modal_title="Add Contact";
	
	$http.get(rootUrl+"category/view_data").success(function(data)
	{
		$scope.categories=data || [];
		$scope.initializeContactSelect2();
		$scope.syncContactSelect2Values();
	});

	$scope.initializeContactSelect2=function()
	{
		setTimeout(function(){
			if (!$.fn.select2) {
				return;
			}
			var isSelect2V4 = !!($.fn.select2 && $.fn.select2.amd);
			var $modalSelects = $('#emailContactFormModal select.email-contact-select2');

			$modalSelects.each(function () {
				var $select = $(this);
				if (!$select.is('select')) {
					return;
				}
				if ($select.data('select2')) {
					$select.select2('destroy');
				}

				var options = {
					width: '100%',
					minimumResultsForSearch: 0
				};

				if (isSelect2V4) {
					options.dropdownParent = $('#emailContactFormModal');
				}

				$select.select2(options);
			});
		}, 100);
	};

	$scope.allowSelect2TypingInsideContactModal=function()
	{
		if (typeof $ === 'undefined' || !$.fn || !$.fn.modal || !$.fn.modal.Constructor) {
			return;
		}

		var ModalConstructor = $.fn.modal.Constructor;
		if (ModalConstructor.prototype._emailContactSelect2FocusPatched) {
			return;
		}

		ModalConstructor.prototype.enforceFocus = function () {
			var modalThis = this;
			$(document)
				.off('focusin.bs.modal')
				.on('focusin.bs.modal', function (e) {
					var $target = $(e.target);
					var isInsideModal = modalThis.$element[0] === e.target || modalThis.$element.has(e.target).length;
					var isSelect2Input =
						$target.closest('.select2-container, .select2-dropdown, .select2-drop, .select2-search').length > 0 ||
						$target.is('.select2-input, .select2-search__field');

					if (!isInsideModal && !isSelect2Input) {
						modalThis.$element.trigger('focus');
					}
				});
		};

		ModalConstructor.prototype._emailContactSelect2FocusPatched = true;

		$(document).off('select2:open.emailContact select2-open.emailContact');
		$(document).on('select2:open.emailContact select2-open.emailContact', function () {
			setTimeout(function () {
				var $search = $('.select2-container-active .select2-input, .select2-drop-active .select2-input, .select2-container--open .select2-search__field');
				if ($search.length) {
					$search.focus();
				}
			}, 0);
		});
	};

	$scope.syncContactSelect2Values=function()
	{
		setTimeout(function () {
			if (!$.fn.select2) {
				return;
			}
			var categoryValue = ($scope.x && $scope.x.cat_id !== undefined && $scope.x.cat_id !== null) ? String($scope.x.cat_id) : '';
			var $category = $('#emailContactFormModal select[name="cat_id"]');

			if ($category.length) {
				$category.val(categoryValue).trigger('change');
			}
		}, 150);
	};
	
	$scope.openContactModal=function(mode,y)
	{
		$scope.email_contact_modal_title=(mode==="edit") ? "Update Contact" : "Add Contact";
		$scope.x=y ? angular.copy(y) : {status:'1'};
		$scope.allowSelect2TypingInsideContactModal();
		$("#emailContactFormModal").modal("show");
		$scope.initializeContactSelect2();
		$scope.syncContactSelect2Values();
	}

	$scope.closeContactModal=function()
	{
		$("#emailContactFormModal").modal("hide");
	}

	$scope.update_call=function(y)
	{
		$scope.openContactModal("edit", y);
	}

	$scope.filter_new=function()
	{
		$scope.x={status:'1'};
		$scope.initializeContactSelect2();
		$scope.syncContactSelect2Values();
	}
	
	$scope.save_data=function()
	{
		$('#submitbtn').attr('disabled',true);
		$.ajax({
			type: "POST",
			url: rootUrl+module+"save",
			data: $("#mailform").serialize(),
			beforeSend: function()
			{
				$('#loader').css('display','inline');
			},
			success: function(data)
			{
				if(data=="1")
				{
					messages("success", "Success!","Saved Successfully", 3000);
					$scope.filter_new();
					$scope.init();
					$scope.closeContactModal();
				}
				else if(data=="0")
				{
					messages("warning", "Info!","No Data Affected", 3000);
				}
				else
				{
					messages("danger", "Warning!",data, 6000);
				}
				$('#loader').css('display','none');
				$('#submitbtn').attr('disabled',false);
				if(!$scope.$$phase)
				{
					$scope.$applyAsync();
				}
			}
		});
	}
	$scope.delete_data=function(id)
	{
		if(confirm("Deleting Staff Details may hamper your data associated with it."))
		{
			if(confirm("Are you Sure to DELETE ??"))
			{
				$http.get(rootUrl+module+"delete?id="+id).success(function(data)
				{
					if(data=="1")
					{
						messages("success", "Success!","Details Deleted Successfully", 3000);
						$scope.init();
					}
					else
					{
						messages("danger", "Warning!","Details not Deleted", 4000);
					}
				})
			}
		}
	}

	$(function () {
		$scope.allowSelect2TypingInsideContactModal();
		$('#emailContactFormModal').on('shown.bs.modal', function () {
			setTimeout(function () {
				$scope.initializeContactSelect2();
				$scope.syncContactSelect2Values();
			}, 200);
		});
	});
	
}]);
app.controller('email_headerFooter',['$scope','$rootScope','$http',function($scope,$rootScope,$http)
{
	module='email_headerFooter/';
	rootUrl=$rootScope.site_url;
	$http.get(rootUrl+module+"/index").success (function(data) {if(data==0){window.location.assign('login.html');} else if(data==2){messages("success", "Privilege not assigned.", 1000);window.location.assign('index.html');}});
	
	$scope.init=function()
	{
		$http.get(rootUrl+"email_headerFooter/view").success(function(data)
		{
			$scope.datadb=data || [];
			$scope.initializeHeaderFooterSelect2();
			$scope.syncHeaderFooterSelect2Values();
		});
	}
	$scope.init();
	$scope.x={status:'1'};
	$scope.datadb=[];
	$scope.pageno=1;
	$scope.itemsPerPage=10;
	$scope.filters={
		search_text:"",
		type:"",
		status:""
	};
	$scope.appliedFilters=angular.copy($scope.filters);
	$scope.email_hf_modal_title="Add Header/Footer";
	
	$scope.initializeHeaderFooterSelect2=function()
	{
		setTimeout(function(){
			if (!$.fn.select2) {
				return;
			}
			var isSelect2V4 = !!($.fn.select2 && $.fn.select2.amd);
			var $pageSelects = $('.email-hf-page select.email-hf-select2');
			var $modalSelects = $('#emailHeaderFooterFormModal select.email-hf-select2');
			var $allSelects = $pageSelects.add($modalSelects);

			$allSelects.each(function () {
				var $select = $(this);
				if (!$select.is('select')) {
					return;
				}
				if ($select.data('select2')) {
					$select.select2('destroy');
				}

				var options = {
					width: '100%',
					minimumResultsForSearch: 0
				};

				if (isSelect2V4 && $select.closest('#emailHeaderFooterFormModal').length) {
					options.dropdownParent = $('#emailHeaderFooterFormModal');
				} else if (isSelect2V4) {
					options.dropdownParent = $(document.body);
				}

				$select.select2(options);
			});
		}, 100);
	};

	$scope.allowSelect2TypingInsideHeaderFooterModal=function()
	{
		if (typeof $ === 'undefined' || !$.fn || !$.fn.modal || !$.fn.modal.Constructor) {
			return;
		}

		var ModalConstructor = $.fn.modal.Constructor;
		if (ModalConstructor.prototype._emailHeaderFooterSelect2FocusPatched) {
			return;
		}

		ModalConstructor.prototype.enforceFocus = function () {
			var modalThis = this;
			$(document)
				.off('focusin.bs.modal')
				.on('focusin.bs.modal', function (e) {
					var $target = $(e.target);
					var isInsideModal = modalThis.$element[0] === e.target || modalThis.$element.has(e.target).length;
					var isSelect2Input =
						$target.closest('.select2-container, .select2-dropdown, .select2-drop, .select2-search').length > 0 ||
						$target.is('.select2-input, .select2-search__field');

					if (!isInsideModal && !isSelect2Input) {
						modalThis.$element.trigger('focus');
					}
				});
		};

		ModalConstructor.prototype._emailHeaderFooterSelect2FocusPatched = true;

		$(document).off('select2:open.emailHeaderFooter select2-open.emailHeaderFooter');
		$(document).on('select2:open.emailHeaderFooter select2-open.emailHeaderFooter', function () {
			setTimeout(function () {
				var $search = $('.select2-container-active .select2-input, .select2-drop-active .select2-input, .select2-container--open .select2-search__field');
				if ($search.length) {
					$search.focus();
				}
			}, 0);
		});
	};

	$scope.syncHeaderFooterSelect2Values=function()
	{
		setTimeout(function () {
			if (!$.fn.select2) {
				return;
			}
			var typeFilterValue = ($scope.filters && $scope.filters.type !== undefined && $scope.filters.type !== null) ? String($scope.filters.type) : '';
			var statusFilterValue = ($scope.filters && $scope.filters.status !== undefined && $scope.filters.status !== null) ? String($scope.filters.status) : '';
			var perPageValue = ($scope.itemsPerPage !== undefined && $scope.itemsPerPage !== null) ? String($scope.itemsPerPage) : '10';
			var typeValue = ($scope.x && $scope.x.type !== undefined && $scope.x.type !== null) ? String($scope.x.type) : '';

			var $typeFilter = $('.email-hf-page select[ng-model="filters.type"]');
			var $statusFilter = $('.email-hf-page select[ng-model="filters.status"]');
			var $perPage = $('.email-hf-page select[ng-model="itemsPerPage"]');
			var $type = $('#emailHeaderFooterFormModal select[name="type"]');

			if ($typeFilter.length) {
				$typeFilter.val(typeFilterValue).trigger('change');
			}
			if ($statusFilter.length) {
				$statusFilter.val(statusFilterValue).trigger('change');
			}
			if ($perPage.length) {
				$perPage.val(perPageValue).trigger('change');
			}
			if ($type.length) {
				$type.val(typeValue).trigger('change');
			}
		}, 150);
	};

	$scope.openHeaderFooterModal=function(mode,y)
	{
		$scope.email_hf_modal_title=(mode==="edit") ? "Update Header/Footer" : "Add Header/Footer";
		$scope.x=y ? angular.copy(y) : {status:'1'};
		$scope.allowSelect2TypingInsideHeaderFooterModal();
		$("#emailHeaderFooterFormModal").modal("show");
		$scope.initializeHeaderFooterSelect2();
		$scope.syncHeaderFooterSelect2Values();
	}

	$scope.closeHeaderFooterModal=function()
	{
		$("#emailHeaderFooterFormModal").modal("hide");
	}

	$scope.update_call=function(y)
	{
		$scope.openHeaderFooterModal("edit", y);
	}
	
	$scope.options = {
		    height: 150,
		    toolbar: [
		               ['style', ["undo","redo",'style','bold', 'italic', 'underline']],
      		           ['fontname', ['fontname']],
      		           ['fontsize', ['fontsize']],
      		           ['color', ['color']],
      		           [ 'para', [ 'ol', 'ul', 'paragraph', 'height' ] ],
      		           ['font', ['strikethrough', 'superscript', 'subscript']],
      		           ['table',['table']],
      		           [ 'insert', [ 'link','video','hr','picture'] ],
      	               ["view", ["fullscreen", "codeview", "help",]]
		        ]
		  };
	
	$scope.filter_new=function()
	{
		$scope.x={status:'1'};
		$scope.initializeHeaderFooterSelect2();
		$scope.syncHeaderFooterSelect2Values();
	}

	$scope.clear_filters=function()
	{
		$scope.filters={
			search_text:"",
			type:"",
			status:""
		};
		$scope.appliedFilters=angular.copy($scope.filters);
		$scope.pageno=1;
		$scope.initializeHeaderFooterSelect2();
		$scope.syncHeaderFooterSelect2Values();
	};

	$scope.apply_filters=function()
	{
		$scope.appliedFilters=angular.copy($scope.filters);
		$scope.pageno=1;
	};

	$scope.headerFooterSearchFilter=function(item)
	{
		var query=($scope.appliedFilters.search_text || "").toLowerCase();
		if(!query)
		{
			return true;
		}
		var haystack=[
			item && item.name,
			item && item.type
		].join(" ").toLowerCase();
		return haystack.indexOf(query)!==-1;
	};

	$scope.headerFooterTypeFilter=function(item)
	{
		if(!$scope.appliedFilters.type)
		{
			return true;
		}
		return String(item && item.type || "")===$scope.appliedFilters.type;
	};

	$scope.headerFooterStatusFilter=function(item)
	{
		if($scope.appliedFilters.status==="" || $scope.appliedFilters.status===null || $scope.appliedFilters.status===undefined)
		{
			return true;
		}
		return String(item && item.status || "")===String($scope.appliedFilters.status);
	}
	
	$scope.save_data=function()
	{
		$('#submitbtn').attr('disabled',true);
		$.ajax({
			type: "POST",
			url: rootUrl+module+"save",
			data: $("#head_foot_form").serialize(),
			beforeSend: function()
			{
				$('#loader').css('display','inline');
			},
			success: function(data)
			{
				if(data=="1")
				{
					messages("success", "Success!","Saved Successfully", 3000);
					$scope.filter_new();
					$scope.init();
					$scope.closeHeaderFooterModal();
				}
				else if(data=="0")
				{
					messages("warning", "Info!","No Data Affected", 3000);
				}
				else
				{
					messages("danger", "Warning!",data, 6000);
				}
				$('#loader').css('display','none');
				$('#submitbtn').attr('disabled',false);
				if(!$scope.$$phase)
				{
					$scope.$applyAsync();
				}
			}
		});
	}
	$scope.delete_data=function(id)
	{
		if(confirm("Deleting Data may hamper your data associated with it."))
		{
			if(confirm("Are you Sure to DELETE ??"))
			{
				$http.get(rootUrl+module+"delete?id="+id).success(function(data)
				{
					if(data=="1")
					{
						messages("success", "Success!","Details Deleted Successfully", 3000);
						$scope.init();
					}
					else
					{
						messages("danger", "Warning!","Details not Deleted", 4000);
					}
				})
			}
		}
	}

	$(function () {
		$scope.allowSelect2TypingInsideHeaderFooterModal();
		$scope.initializeHeaderFooterSelect2();
		$scope.syncHeaderFooterSelect2Values();
		$('#emailHeaderFooterFormModal').on('shown.bs.modal', function () {
			setTimeout(function () {
				$scope.initializeHeaderFooterSelect2();
				$scope.syncHeaderFooterSelect2Values();
			}, 200);
		});
	});
	
}]);
app.controller('email_main',['$scope','$rootScope','$http',function($scope,$rootScope,$http)
{
	module='email_main/';
	rootUrl=$rootScope.site_url;
	$http.get(rootUrl+module+"/index").success (function(data) {if(data==0){window.location.assign('login.html');} else if(data==2){messages("success", "Privilege not assigned.", 1000);window.location.assign('index.html');}});
	
	$scope.init=function()
	{
		$http.get(rootUrl+"email_main/view").success(function(data)
		{
			$scope.datadb=data || [];
			$scope.refreshEmailTypeOptions();
			$scope.initializeEmailSelect2();
			$scope.syncEmailSelect2Values();
		});
	}
	$scope.init();
	$scope.x={};
	$scope.datadb=[];
	$scope.pageno=1;
	$scope.itemsPerPage=10;
	$scope.filters={
		search_text:"",
		type:"",
		status:""
	};
	$scope.appliedFilters=angular.copy($scope.filters);
	$scope.email_type_options=[];
	$scope.email_main_modal_title="Compose Email";
	
	$http.get(rootUrl+"email_contact/view").success(function(data)
	{
		$scope.contacts=data || [];
	});
	$http.get(rootUrl+"email_admin/view").success(function(data)
	{
		$scope.emails=data || [];
		$scope.initializeEmailSelect2();
		$scope.syncEmailSelect2Values();
	});
	$http.get(rootUrl+"email_template/view?data=et_id,name").success(function(data)
	{
		$scope.templates=data || [];
		$scope.initializeEmailSelect2();
		$scope.syncEmailSelect2Values();
	});
	
	$scope.refreshEmailTypeOptions=function()
	{
		var unique={};
		var options=[];
		angular.forEach($scope.datadb,function(item){
			var type=(item && item.type) ? String(item.type).trim() : "";
			if(type && !unique[type])
			{
				unique[type]=true;
				options.push(type);
			}
		});
		$scope.email_type_options=options;
	};

	$scope.initializeEmailSelect2=function()
	{
		setTimeout(function(){
			if (!$.fn.select2) {
				return;
			}
			var $pageSelects = $('.email-main-page select.email-main-select2');
			var $modalSelects = $('#emailMainFormModal select.email-main-select2');
			var $allSelects = $pageSelects.add($modalSelects);

			$allSelects.each(function () {
				var $select = $(this);
				if ($select.data('select2')) {
					$select.select2('destroy');
				}
			});

			$pageSelects.select2({
				width: '100%'
			});

			$modalSelects.select2({
				width: '100%',
				dropdownParent: $('#emailMainFormModal')
			});
		}, 100);
	};

	$scope.syncEmailSelect2Values=function()
	{
		setTimeout(function () {
			if (!$.fn.select2) {
				return;
			}
			var templateValue = ($scope.x && $scope.x.template_id !== undefined && $scope.x.template_id !== null) ? String($scope.x.template_id) : '';
			var senderValue = ($scope.x && $scope.x.e_id !== undefined && $scope.x.e_id !== null) ? String($scope.x.e_id) : '';
			var typeValue = ($scope.filters && $scope.filters.type !== undefined && $scope.filters.type !== null) ? String($scope.filters.type) : '';
			var statusValue = ($scope.filters && $scope.filters.status !== undefined && $scope.filters.status !== null) ? String($scope.filters.status) : '';
			var perPageValue = ($scope.itemsPerPage !== undefined && $scope.itemsPerPage !== null) ? String($scope.itemsPerPage) : '10';

			var $template = $('#emailMainFormModal select[name="template_id"]');
			var $sender = $('#emailMainFormModal select[name="e_id"]');
			var $type = $('.email-main-page select[ng-model="filters.type"]');
			var $status = $('.email-main-page select[ng-model="filters.status"]');
			var $perPage = $('.email-main-page select[ng-model="itemsPerPage"]');

			if ($template.length) {
				$template.val(templateValue).trigger('change');
			}
			if ($sender.length) {
				$sender.val(senderValue).trigger('change');
			}
			if ($type.length) {
				$type.val(typeValue).trigger('change');
			}
			if ($status.length) {
				$status.val(statusValue).trigger('change');
			}
			if ($perPage.length) {
				$perPage.val(perPageValue).trigger('change');
			}
		}, 150);
	};
	
	$scope.openEmailModal=function(mode,y)
	{
		$scope.email_main_modal_title=(mode==="edit") ? "Update Email" : "Compose Email";
		$scope.x=y ? angular.copy(y) : {};
		$scope.all=false;
		$("#emailMainFormModal").modal("show");
		$scope.initializeEmailSelect2();
		$scope.syncEmailSelect2Values();
	};

	$scope.closeEmailModal=function()
	{
		$("#emailMainFormModal").modal("hide");
	};

	$scope.update_call=function(y)
	{
		$scope.openEmailModal("edit", y);
	};

	$scope.clear_filters=function()
	{
		$scope.filters={
			search_text:"",
			type:"",
			status:""
		};
		$scope.appliedFilters=angular.copy($scope.filters);
		$scope.pageno=1;
		$scope.syncEmailSelect2Values();
	};

	$scope.apply_filters=function()
	{
		$scope.appliedFilters=angular.copy($scope.filters);
		$scope.pageno=1;
	};

	$scope.emailSearchFilter=function(item)
	{
		var query=($scope.appliedFilters.search_text || "").toLowerCase();
		if(!query)
		{
			return true;
		}
		var haystack=[
			item && item.name,
			item && item.subject,
			item && item.type,
			item && item.email,
			item && item.from_email,
			item && item.template_name
		].join(" ").toLowerCase();
		return haystack.indexOf(query)!==-1;
	};

	$scope.emailTypeFilter=function(item)
	{
		if(!$scope.appliedFilters.type)
		{
			return true;
		}
		return String(item && item.type || "")===$scope.appliedFilters.type;
	};

	$scope.emailStatusFilter=function(item)
	{
		if($scope.appliedFilters.status==="" || $scope.appliedFilters.status===null || $scope.appliedFilters.status===undefined)
		{
			return true;
		}
		return String(item && item.status || "")===String($scope.appliedFilters.status);
	};

	$scope.fetch_template=function(template_id)
	{
		if(!template_id)
		{
			return;
		}
		$http.get(rootUrl+"email_template/view?et_id="+template_id).success(function(data)
		{
			if(data && data.length)
			{
				$scope.x.subject=data[0].subject || $scope.x.subject;
				$scope.x.content=data[0].content || $scope.x.content;
				$scope.syncEmailSelect2Values();
			}
		});
	};
	
	$scope.options = 
	{
	    height: 150,
	    toolbar: [
	               ['style', ["undo","redo",'style','bold', 'italic', 'underline']],
  		           ['fontname', ['fontname']],
  		           ['fontsize', ['fontsize']],
  		           ['color', ['color']],
  		           [ 'para', [ 'ol', 'ul', 'paragraph', 'height' ] ],
  		           ['font', ['strikethrough', 'superscript', 'subscript']],
  		           ['table',['table']],
  		           [ 'insert', [ 'link','video','hr','picture'] ],
  	               ["view", ["fullscreen", "codeview", "help",]]
	        ]
	  };
	
	$scope.filter_new=function()
	{
		$scope.x={};
		$scope.all=false;
		$scope.initializeEmailSelect2();
		$scope.syncEmailSelect2Values();
	}
	
	$scope.save_data=function()
	{
		$('#submitbtn').attr('disabled',true);
		$.ajax({
			type: "POST",
			url: rootUrl+module+"save",
			data: $("#head_foot_form").serialize(),
			beforeSend: function()
			{
				$('#loader').css('display','inline');
			},
			success: function(data)
			{
				if(data=="1")
				{
					messages("success", "Success!","Saved Successfully", 3000);
					$scope.filter_new();
					$scope.init();
					$scope.closeEmailModal();
				}
				else if(data=="0")
				{
					messages("warning", "Info!","No Data Affected", 3000);
				}
				else
				{
					messages("danger", "Warning!",data, 6000);
				}
				$('#loader').css('display','none');
				$('#submitbtn').attr('disabled',false);
				if(!$scope.$$phase)
				{
					$scope.$applyAsync();
				}
			}
		});
	}
	$scope.delete_data=function(id)
	{
		if(confirm("Deleting Data may hamper your data associated with it."))
		{
			if(confirm("Are you Sure to DELETE ??"))
			{
				$http.get(rootUrl+module+"delete?id="+id).success(function(data)
				{
					if(data=="1")
					{
						messages("success", "Success!","Email Details Deleted Successfully", 3000);
						$scope.init();
					}
					else
					{
						messages("danger", "Warning!","Email Details not Deleted", 4000);
					}
				})
			}
		}
	}

	$(function () {
		$('#emailMainFormModal').on('shown.bs.modal', function () {
			setTimeout(function () {
				$scope.initializeEmailSelect2();
				$scope.syncEmailSelect2Values();
			}, 200);
		});
	});
	
}]);
app.controller('email_template',['$scope','$rootScope','$http',function($scope,$rootScope,$http)
{
	module='email_template/';
	rootUrl=$rootScope.site_url;
	$http.get(rootUrl+module+"/index").success (function(data) {if(data==0){window.location.assign('login.html');} else if(data==2){messages("success", "Privilege not assigned.", 1000);window.location.assign('index.html');}});
	
	$scope.init=function()
	{
		$http.get(rootUrl+"email_template/view").success(function(data)
		{
			$scope.datadb=data || [];
			$scope.initializeTemplateSelect2();
			$scope.syncTemplateSelect2Values();
		});
	}
	$scope.init();
	$scope.x={status:'1'};
	$scope.datadb=[];
	$scope.pageno=1;
	$scope.itemsPerPage=10;
	$scope.filters={
		search_text:"",
		category:"",
		status:""
	};
	$scope.appliedFilters=angular.copy($scope.filters);
	$scope.email_template_modal_title="Add Template";
	
	$http.get(rootUrl+"category/view_data").success(function(data)
	{
		$scope.categories=data || [];
		$scope.initializeTemplateSelect2();
		$scope.syncTemplateSelect2Values();
	});
	
	$http.get(rootUrl+"email_headerFooter/view?type=H").success(function(data)
	{
		$scope.headers=data || [];
		$scope.initializeTemplateSelect2();
		$scope.syncTemplateSelect2Values();
	});
		
	$http.get(rootUrl+"email_headerFooter/view?type=F").success(function(data)
	{
		$scope.footers=data || [];
		$scope.initializeTemplateSelect2();
		$scope.syncTemplateSelect2Values();
	});

	$scope.initializeTemplateSelect2=function()
	{
		setTimeout(function(){
			if (!$.fn.select2) {
				return;
			}
			var isSelect2V4 = !!($.fn.select2 && $.fn.select2.amd);
			var $pageSelects = $('.email-template-page select.email-template-select2');
			var $modalSelects = $('#emailTemplateFormModal select.email-template-select2');
			var $allSelects = $pageSelects.add($modalSelects);

			$allSelects.each(function () {
				var $select = $(this);
				if (!$select.is('select')) {
					return;
				}
				if ($select.data('select2')) {
					$select.select2('destroy');
				}

				var options = {
					width: '100%',
					minimumResultsForSearch: 0
				};

				if (isSelect2V4 && $select.closest('#emailTemplateFormModal').length) {
					options.dropdownParent = $('#emailTemplateFormModal');
				} else if (isSelect2V4) {
					options.dropdownParent = $(document.body);
				}

				$select.select2(options);
			});
		}, 100);
	};

	$scope.allowSelect2TypingInsideTemplateModal=function()
	{
		if (typeof $ === 'undefined' || !$.fn || !$.fn.modal || !$.fn.modal.Constructor) {
			return;
		}

		var ModalConstructor = $.fn.modal.Constructor;
		if (ModalConstructor.prototype._emailTemplateSelect2FocusPatched) {
			return;
		}

		ModalConstructor.prototype.enforceFocus = function () {
			var modalThis = this;
			$(document)
				.off('focusin.bs.modal')
				.on('focusin.bs.modal', function (e) {
					var $target = $(e.target);
					var isInsideModal = modalThis.$element[0] === e.target || modalThis.$element.has(e.target).length;
					var isSelect2Input =
						$target.closest('.select2-container, .select2-dropdown, .select2-drop, .select2-search').length > 0 ||
						$target.is('.select2-input, .select2-search__field');

					if (!isInsideModal && !isSelect2Input) {
						modalThis.$element.trigger('focus');
					}
				});
		};

		ModalConstructor.prototype._emailTemplateSelect2FocusPatched = true;

		$(document).off('select2:open.emailTemplate select2-open.emailTemplate');
		$(document).on('select2:open.emailTemplate select2-open.emailTemplate', function () {
			setTimeout(function () {
				var $search = $('.select2-container-active .select2-input, .select2-drop-active .select2-input, .select2-container--open .select2-search__field');
				if ($search.length) {
					$search.focus();
				}
			}, 0);
		});
	};

	$scope.syncTemplateSelect2Values=function()
	{
		setTimeout(function () {
			if (!$.fn.select2) {
				return;
			}
			var categoryFilterValue = ($scope.filters && $scope.filters.category !== undefined && $scope.filters.category !== null) ? String($scope.filters.category) : '';
			var statusFilterValue = ($scope.filters && $scope.filters.status !== undefined && $scope.filters.status !== null) ? String($scope.filters.status) : '';
			var perPageValue = ($scope.itemsPerPage !== undefined && $scope.itemsPerPage !== null) ? String($scope.itemsPerPage) : '10';
			var categoryValue = ($scope.x && $scope.x.cat_id !== undefined && $scope.x.cat_id !== null) ? String($scope.x.cat_id) : '';
			var headerValue = ($scope.x && $scope.x.h_name !== undefined && $scope.x.h_name !== null) ? String($scope.x.h_name) : '';
			var footerValue = ($scope.x && $scope.x.f_name !== undefined && $scope.x.f_name !== null) ? String($scope.x.f_name) : '';

			var $categoryFilter = $('.email-template-page select[ng-model="filters.category"]');
			var $statusFilter = $('.email-template-page select[ng-model="filters.status"]');
			var $perPage = $('.email-template-page select[ng-model="itemsPerPage"]');
			var $category = $('#emailTemplateFormModal select[name="cat_id"]');
			var $header = $('#emailTemplateFormModal select[name="h_name"]');
			var $footer = $('#emailTemplateFormModal select[name="f_name"]');

			if ($categoryFilter.length) {
				$categoryFilter.val(categoryFilterValue).trigger('change');
			}
			if ($statusFilter.length) {
				$statusFilter.val(statusFilterValue).trigger('change');
			}
			if ($perPage.length) {
				$perPage.val(perPageValue).trigger('change');
			}
			if ($category.length) {
				$category.val(categoryValue).trigger('change');
			}
			if ($header.length) {
				$header.val(headerValue).trigger('change');
			}
			if ($footer.length) {
				$footer.val(footerValue).trigger('change');
			}
		}, 150);
	};
	
	$scope.openTemplateModal=function(mode,y)
	{
		$scope.email_template_modal_title=(mode==="edit") ? "Update Template" : "Add Template";
		$scope.x=y ? angular.copy(y) : {status:'1'};
		$scope.allowSelect2TypingInsideTemplateModal();
		$("#emailTemplateFormModal").modal("show");
		$scope.initializeTemplateSelect2();
		$scope.syncTemplateSelect2Values();
	}

	$scope.closeTemplateModal=function()
	{
		$("#emailTemplateFormModal").modal("hide");
	}

	$scope.update_call=function(y)
	{
		$scope.openTemplateModal("edit", y);
	}
	$scope.filter_new=function()
	{
		$scope.x={status:'1'};
		$scope.initializeTemplateSelect2();
		$scope.syncTemplateSelect2Values();
	}

	$scope.clear_filters=function()
	{
		$scope.filters={
			search_text:"",
			category:"",
			status:""
		};
		$scope.appliedFilters=angular.copy($scope.filters);
		$scope.pageno=1;
		$scope.initializeTemplateSelect2();
		$scope.syncTemplateSelect2Values();
	};

	$scope.apply_filters=function()
	{
		$scope.appliedFilters=angular.copy($scope.filters);
		$scope.pageno=1;
	};

	$scope.templateSearchFilter=function(item)
	{
		var query=($scope.appliedFilters.search_text || "").toLowerCase();
		if(!query)
		{
			return true;
		}
		var haystack=[
			item && item.name,
			item && item.subject,
			item && item.h_name,
			item && item.f_name,
			item && item.category_name,
			item && item.category
		].join(" ").toLowerCase();
		return haystack.indexOf(query)!==-1;
	};

	$scope.templateCategoryFilter=function(item)
	{
		if(!$scope.appliedFilters.category)
		{
			return true;
		}
		var categoryName=String(item && (item.category_name || item.category) || "");
		return categoryName===$scope.appliedFilters.category;
	};

	$scope.templateStatusFilter=function(item)
	{
		if($scope.appliedFilters.status==="" || $scope.appliedFilters.status===null || $scope.appliedFilters.status===undefined)
		{
			return true;
		}
		return String(item && item.status || "")===String($scope.appliedFilters.status);
	}
	
	$scope.save_data=function()
	{
		$('#submitbtn').attr('disabled',true);
		$.ajax({
			type: "POST",
			url: rootUrl+module+"save",
			data: $("#et_form").serialize(),
			beforeSend: function()
			{
				$('#loader').css('display','inline');
			},
			success: function(data)
			{
				if(data=="1")
				{
					messages("success", "Success!","Saved Successfully", 3000);
					$scope.filter_new();
					$scope.init();
					$scope.closeTemplateModal();
				}
				else if(data=="0")
				{
					messages("warning", "Info!","No Data Affected", 3000);
				}
				else
				{
					messages("danger", "Warning!",data, 6000);
				}
				$('#loader').css('display','none');
				$('#submitbtn').attr('disabled',false);
				if(!$scope.$$phase)
				{
					$scope.$applyAsync();
				}
			}
		});
	}
	$scope.delete_data=function(id)
	{
		if(confirm("Deleting Data may hamper your data associated with it."))
		{
			if(confirm("Are you Sure to DELETE ??"))
			{
				$http.get(rootUrl+module+"delete?id="+id).success(function(data)
				{
					if(data=="1")
					{
						messages("success", "Success!","Details Deleted Successfully", 3000);
						$scope.init();
					}
					else
					{
						messages("danger", "Warning!","Details not Deleted", 4000);
					}
				})
			}
		}
	}

	$scope.options = 
	{
	    height: 180,
	    toolbar: [
	               ['style', ["undo","redo",'style','bold', 'italic', 'underline']],
  		           ['fontname', ['fontname']],
  		           ['fontsize', ['fontsize']],
  		           ['color', ['color']],
  		           [ 'para', [ 'ol', 'ul', 'paragraph', 'height' ] ],
  		           ['font', ['strikethrough', 'superscript', 'subscript']],
  		           ['table',['table']],
  		           [ 'insert', [ 'link','video','hr','picture'] ],
  	               ["view", ["fullscreen", "codeview", "help",]]
	        ]
	  };

	$(function () {
		$scope.allowSelect2TypingInsideTemplateModal();
		$scope.initializeTemplateSelect2();
		$scope.syncTemplateSelect2Values();
		$('#emailTemplateFormModal').on('shown.bs.modal', function () {
			setTimeout(function () {
				$scope.initializeTemplateSelect2();
				$scope.syncTemplateSelect2Values();
			}, 200);
		});
	});
	
}]);
//blank line is required
app.controller('journals', ['$scope', '$rootScope', '$http', '$timeout', function ($scope, $rootScope, $http, $timeout) {
	var jmodule = 'hr_journals/';
	var rootUrl = $rootScope.site_url;
	var modeLabels = {
		'1': 'Cash',
		'2': 'UPI',
		'3': 'Cheque',
		'4': 'Bank Transfer'
	};

	$http.get(rootUrl + jmodule + "/index").success(function (data) {
		if (data == 0) {
			window.location.assign('login.html');
		} else if (data == 2) {
			messages("success", "Privilege not assigned.", 1000);
			window.location.assign('index.html');
		}
	});

	$scope.datadb = [];
	$scope.category = [];
	$scope.filtered_journal_data = [];
	$scope.itemsPerPage = '10';
	$scope.pageno = 1;
	$scope.filters = {
		search_text: '',
		category: '',
		mode: ''
	};
	$scope.x = {
		emailsend: '0'
	};
	$scope.journals_modal_title = 'Add Journal';

	function getTodayDisplay() {
		var today = new Date();
		var dd = String(today.getDate()).padStart(2, '0');
		var mm = String(today.getMonth() + 1).padStart(2, '0');
		var yyyy = today.getFullYear();
		return dd + '/' + mm + '/' + yyyy;
	}

	function normalizeAmountValue(value) {
		return value === 0 || value === '0' || value === '0.00' ? '' : value;
	}

	function buildEmptyJournal() {
		return {
			j_id: '',
			date: getTodayDisplay(),
			cat_id: '',
			title: '',
			mode: '',
			debit: '',
			credit: '',
			desc: '',
			name: '',
			time: ''
		};
	}

	function initDatepicker() {
		$timeout(function () {
			var $dateField = $('#DOB1');
			if (!$dateField.length || !$.fn.datepicker) {
				return;
			}

			$dateField.datepicker('destroy');
			$dateField.datepicker({
				format: 'dd/mm/yyyy',
				autoclose: true
			});

			if ($scope.d && $scope.d.date) {
				$dateField.datepicker('setDate', $scope.d.date);
			} else {
				$dateField.datepicker('setDate', getTodayDisplay());
			}

			$dateField.off('changeDate.journals change.journals').on('changeDate.journals change.journals', function () {
				var value = $(this).val();
				$scope.$applyAsync(function () {
					$scope.d.date = value;
				});
			});
		}, 0);
	}

	function initSelect2() {
		$timeout(function () {
			if (!$.fn.select2) {
				return;
			}

			$('.journals-select2').each(function () {
				var $select = $(this);
				if ($select.data('select2')) {
					$select.select2('destroy');
				}
				$select.select2({
					width: '100%',
					minimumResultsForSearch: 0
				});
			});
		}, 0);
	}

	$scope.getModeLabel = function (mode) {
		var key = String(mode || '').trim();
		return modeLabels[key] || '-';
	};

	$scope.init = function () {
		$http.get(rootUrl + jmodule + "view_data").success(function (data) {
			$scope.datadb = angular.isArray(data) ? data : [];
		});
	};

	$http.get(rootUrl + "category/view_data?data=cat_id,name&status=1&journal=1").success(function (data) {
		$scope.category = angular.isArray(data) ? data : [];
		initSelect2();
	});

	$scope.filter_new_jour = function () {
		$scope.d = buildEmptyJournal();
		$scope.x = {
			emailsend: '0'
		};
		initDatepicker();
		initSelect2();
	};

	$scope.openJournalModal = function (mode, row) {
		if (mode === 'edit' && row) {
			$scope.d = angular.copy(row);
			$scope.d.debit = normalizeAmountValue($scope.d.debit);
			$scope.d.credit = normalizeAmountValue($scope.d.credit);
			$scope.journals_modal_title = 'Update Journal';
		} else {
			$scope.filter_new_jour();
			$scope.journals_modal_title = 'Add Journal';
		}

		$timeout(function () {
			initDatepicker();
			initSelect2();
			$('#journalsFormModal').modal('show');
		}, 0);
	};

	$scope.update_call = function (row) {
		$scope.openJournalModal('edit', row);
	};

	$scope.apply_filters = function () {
		$scope.pageno = 1;
	};

	$scope.clear_filters = function () {
		$scope.filters = {
			search_text: '',
			category: '',
			mode: ''
		};
		$scope.pageno = 1;
		initSelect2();
	};

	$scope.journalSearchFilter = function (item) {
		var query = String(($scope.filters && $scope.filters.search_text) || '').trim().toLowerCase();
		if (!query) {
			return true;
		}

		var haystack = [
			item && item.title,
			item && item.cname,
			item && item.desc,
			item && item.name,
			item && item.date
		].join(' ').toLowerCase();

		return haystack.indexOf(query) !== -1;
	};

	$scope.journalCategoryFilter = function (item) {
		var category = String(($scope.filters && $scope.filters.category) || '').trim().toLowerCase();
		if (!category) {
			return true;
		}

		return String(item && item.cname || '').trim().toLowerCase() === category;
	};

	$scope.journalModeFilter = function (item) {
		var mode = String(($scope.filters && $scope.filters.mode) || '').trim();
		if (!mode) {
			return true;
		}

		return String(item && item.mode || '').trim() === mode;
	};

	$scope.save_data = function () {
		$('#submitbtn').attr('disabled', true);
		$.ajax({
			type: "POST",
			url: rootUrl + jmodule + "save",
			data: $("#form1").serialize(),
			beforeSend: function () {
				$('#loader').css('display', 'inline');
			},
			success: function (data) {
				if (data == "1") {
					messages("success", "Success!", "Saved Successfully", 3000);
					$scope.$applyAsync(function () {
						$scope.init();
						$scope.filter_new_jour();
						$scope.pageno = 1;
					});
					$('#journalsFormModal').modal('hide');
				} else if (data == "0") {
					messages("warning", "Info!", "No Data Affected", 3000);
				} else {
					messages("danger", "Warning!", data, 6000);
				}
				$('#loader').css('display', 'none');
				$('#submitbtn').attr('disabled', false);
			},
			error: function () {
				messages("danger", "Warning!", "Unable to save journal entry.", 6000);
				$('#loader').css('display', 'none');
				$('#submitbtn').attr('disabled', false);
			}
		});
	};

	$scope.delete_data = function (id) {
		if (confirm("Deleting Journals Records may hamper your data associated with it.")) {
			if (confirm("Are you Sure to DELETE ??")) {
				$http.get(rootUrl + jmodule + "delete_data?id=" + id).success(function (data) {
					if (data == "1") {
						messages("success", "Success!", "Journals Records Deleted Successfully", 3000);
					} else {
						messages("danger", "Warning!", "Journals Records not Deleted", 4000);
					}
					$scope.init();
				});
			}
		}
	};

	$scope.$watch('itemsPerPage', function () {
		$scope.pageno = 1;
	});

	$scope.$watchCollection('category', function () {
		initSelect2();
	});

	$scope.$watchGroup(['filters.category', 'filters.mode'], function () {
		initSelect2();
	});

	$scope.$on('$destroy', function () {
		if ($.fn.select2) {
			$('.journals-select2').each(function () {
				var $select = $(this);
				if ($select.data('select2')) {
					$select.select2('destroy');
				}
			});
		}

		var $dateField = $('#DOB1');
		if ($dateField.length && $.fn.datepicker) {
			$dateField.datepicker('destroy');
		}
	});

	$scope.filter_new_jour();
	$scope.init();
}]);
//blank line is required
app.controller('company_master', ['$scope', '$rootScope', '$http', function ($scope, $rootScope, $http) {
	com_module = 'company_master/';
	rootUrl = $rootScope.site_url;
	$http.get(rootUrl + com_module + "/index").success(function (data) { if (data == 0) { window.location.assign('login.html'); } else if (data == 2) { messages("success", "Privilege not assigned.", 1000); window.location.assign('index.html'); } });
	$scope.y = {};

	$scope.init_com = function () {
		$http.get(rootUrl + com_module + "view").success(function (data) {
			$scope.datadb = data;
		});
		$http.get(rootUrl + com_module + "view?data=com_name,com_id").success(function (data) {
			$scope.parent = data;
		});
		$http.get(rootUrl + com_module + "view_state?data=iso2,name").success(function (data) {
			$scope.state_list = data;
		});
	}
	$scope.init_com();

	$scope.filter_new_com = function () {
		$scope.y = {};
	}

	$scope.update_call = function (y) {
		$scope.y = y;
		$("#addform").trigger('click');
	}

	$scope.save_data_com = function () {
		$('#comform1').ajaxForm({
			type: "POST",
			url: rootUrl + com_module + "/save",
			beforeSend: function () {
				$('#submitbtn1com').attr('disabled', true);
				$('#loader1com').css('display', 'inline');
			},
			success: function (data) {
				if (data.error == "0") {
					messages("success", "Success!", data.msg, 3000);
					$scope.filter_new_com();
					$scope.init_com();
				}
				else if (data.error == '1') {
					messages("danger", "Warning!", data.msg, 6000);
				} else {
					messages("danger", "Warning!", data, 6000);
				}
				$('#loader1com').css('display', 'none');
				$('#submitbtn1com').attr('disabled', false);
			}
		});
	}

	$scope.delete_data = function (id) {
		if (confirm("Deleting Company Details may hamper your data associated with it.")) {
			if (confirm("Are you Sure to DELETE ??")) {
				$http.get(rootUrl + com_module + "delete?id=" + id).success(function (data) {
					if (data == "1") {
						messages("success", "Success!", "Company Details Deleted Successfully", 3000);
					}
					else {
						messages("danger", "Warning!", "Company Details not Deleted", 4000);
					}
					$scope.init_com();
				})
			}
		}
	}

	$scope.add_company_serials = function (y) {
		$scope.company = y;
		console.log($scope.company);
		$('#addModal').modal('show');
	};

	$scope.save_serials = function(company, $event) {
		if ($event) $event.preventDefault(); // stop reload
	  
		console.log('test: ', company)
		$('#serialForm').ajaxForm({
		  type: "POST",
		  url: rootUrl + com_module + "/save_serial",
		  beforeSend: function() {
			$('#serialbtn').attr('disabled', true);
		  },
		  success: function(data) {
			console.log(data);
			if(data.status == 'success'){
				messages("success", "Success!", 'Data Updated Successfully.', 3000);
				$('#serialbtn').attr('disabled', false);
				$('#addModal').modal('hide');
			} else if (data.status == 'error'){
				messages("danger", "Warning!", 'Error in updating data.', 3000);
			} else {
				messages("danger", "Warning!", data, 3000);
			}
			
		  },
		  error: function() {
			$('#serialbtn').attr('disabled', false);
		  }
		}).submit();
	  };
	  


}]);//blank line is required
app.controller('hr_session',['$scope','$rootScope','$http',function($scope,$rootScope,$http)
{
	rootUrl=$rootScope.site_url;
	sess_module="hr_session/";
	$http.get(rootUrl+sess_module+"/index").success (function(data) {if(data==0){window.location.assign('login.html');} else if(data==2){messages("success", "Privilege not assigned.", 1000);window.location.assign('index.html');}});
	
	$scope.s={};
	$scope.com_id=[];
	$scope.branch_id=[];
	
	$("#DOB12").datepicker();
	$("#DOB13").datepicker();
	
	$scope.init_sess=function()
	{
		$http.get(rootUrl+sess_module+"view").success(function(data)
		{
			$scope.datadb=data;
		})
	}
	$scope.init_sess();
	
	$http.get(rootUrl+"company_master/view?data=com_id,com_name,branch_id").success(function(data)
	{
		$scope.company=data;
	})
	
	$scope.filter_new_sess=function()
	{
		$scope.s={};
	}
	$scope.update_call_sess=function(y)
	{
		$scope.s=y;
		$('#addformsess').trigger('click');
	}
	$scope.save_data_sess=function(x)
	{
		$.ajax({
			type: "POST",
			url: rootUrl+sess_module+"save",
			data: $("#sessionform").serialize(),
			beforeSend: function()
			{
				$('#loader1ses').css('display','inline');
				$('#submitbtn1ses').attr('disabled',true);
			},
			success: function(data)
			{
				console.log(data);
				if(data=="1")
				{
					messages("success", "Success!","Session Set Successfully", 3000);
					$scope.filter_new_sess();
					$scope.init_sess();
				}
				else if(data=="0")
				{
					messages("warning", "Info!","No Data Affected", 3000);
					$scope.filter_new_sess();
				}
				else
				{
					messages("danger", "Warning!",data, 6000);
				}
				$('#loader1ses').css('display','none');
				$('#submitbtn1ses').attr('disabled',false);
			}
		});
	}
	
	$scope.delete_data_sess=function(id)
	{
		if(confirm("Deleting Session Details may hamper your data associated with it."))
		{
			if(confirm("Are you Sure to DELETE ??"))
			{
				$http.get(rootUrl+sess_module+"delete?id="+id).success(function(data){
					if(data=="1")
					{
						messages("success", "Success!","Sesssion Details Deleted Successfully", 3000);
					}
					else
					{
						messages("danger", "Warning!","Session Details not Deleted", 4000);
					}
					$scope.init_sess();
				})
			}
		}
	}
	
	
	
}]);//blank line is required
app.controller('help',['$scope','$rootScope','$http',function($scope,$rootScope,$http)
{
	module='help/';
	rootUrl=$rootScope.site_url;
	//$http.get(rootUrl+module+"/index").success (function(data) {if(data==0){window.location.assign('login.html');} else if(data==2){messages("success", "Privilege not assigned.", 1000);window.location.assign('index.html');}});
	
	
}]);app.controller('logs',['$scope','$rootScope','$http',function($scope,$rootScope,$http)
{
	rootUrl=$rootScope.site_url;
	$http.get(rootUrl+'login/auth').success (function(data) 
	{
		if(data!=1){window.location.assign('login.html');}
	});
	
	$http.get(rootUrl+"logs/view_data").success(function(data)
	{
//		console.log(data);
		$scope.datadb=data;
	})
	$scope.fetch_log=function(id)
	{
	 	$(".modal-body").html('');
	 	$("#modalbtn").trigger('click');
	 	$("#myModalLabel").html("Log Details");
	 	$.get(rootUrl+"logs/get_object?id="+id, function(data, status)
	 	{
	 		$(".modal-body").html(data);
	     });
	 }
}]);//blank line is required
app.controller('marketing_management',['$scope','$rootScope','$http',function($scope,$rootScope,$http)
{
	marketing='marketing_management/';
	rootUrl=$rootScope.site_url;
	$scope.x={};
	$scope.y={};
	
	$http.get(rootUrl+"hr_staff_details/view?data=emp_id,staff_name&st=1").success(function(data)
	{
		$scope.employees=data;
	})
	$http.get(rootUrl+"category/view_data?data=name,cat_id&marketing=1&status=1").success(function(data)
	{
		$scope.activities=data;
	})
	
	$scope.x.arv_cst=0;
	$scope.x.dept_cst=0;
	$scope.x.other_cst=0;
	$scope.x.total_exp=0;
	$scope.x.food=0;
	$scope.x.lodging=0;
	$scope.get_followUp=function()
	{
		$http.get(rootUrl+marketing+"follow_temp_view").success(function(data)
		{
			$scope.mar_follows=data;
		})
	}
	$scope.get_followUp();
	
	$scope.get_marketingfollowUp=function()
	{
		$http.get(rootUrl+marketing+"view_data").success(function(data)
		{
			$scope.datadb=data;
		})
	}
	$scope.get_marketingfollowUp();
	
	$scope.total_km=function()
	{
		$scope.x.tot_km=parseFloat($scope.x.km_strt)+parseFloat($scope.x.km_end);
	}
	$scope.total_arr_dep=function()
	{
		$scope.x.total_exp=parseFloat($scope.x.arv_cst)+parseFloat($scope.x.dept_cst)+parseFloat($scope.x.other_cst);
	}
	
	$scope.main_clear=function()
	{
		$http.get(rootUrl+marketing+"ClearPreviousMarketingTemp").success(function(data)
		{
			$scope.x={};
			$scope.mar_follows="";
		})
		
	}
	$scope.follow_clear=function()
	{
		$scope.y={};
	}
	
	$scope.follow_update_call=function(y)
	{
		$scope.y=y;
	}
	
	$scope.update_call=function(y)
	{
		$scope.x=y;
		$http.get(rootUrl+marketing+"fetch_marketing_list?mar_head_id="+y.mar_head_id).success(function(data)
		{
			if(data==1)
			{
				$scope.get_followUp();
				$scope.mar_follows=data;
			}
		})
		$("#addform").trigger('click');
	}
	
	
	$scope.follow_temp=function(y)
	{
		if(y.mar_temp_id)
		{
			$('#loader1add').css('display','inline');
			$('#submitbtn1add').attr('disabled',true);
			$http.get(rootUrl+marketing+"follow_temp?cat_id="+y.cat_id+"&place="+y.place+"&rem_date="+y.rem_date+"&visit_place="+y.visit_place+"&remarks="+y.remarks+"&id="+y.mar_temp_id+"&mar_list_id="+y.mar_list_id).success(function(data)
			{
				$('#submitbtn1add').attr('disabled',false);
				if(data=="1")
				{
					$scope.y={};
					messages("success", "Success!","Data Assigned Successfully", 3000);
					$scope.get_followUp();
				}
				else
				{
					messages("danger", "Warning!",data, 4000);
				}
				$('#loader1add').css('display','none');
			})
		}
		else
		{
			$('#loader1add').css('display','inline');
			$('#submitbtn1add').attr('disabled',true);
			$http.get(rootUrl+marketing+"follow_temp?cat_id="+y.cat_id+"&place="+y.place+"&rem_date="+y.rem_date+"&visit_place="+y.visit_place+"&remarks="+y.remarks).success(function(data)
			{
				if(data=="1")
				{
					$scope.y={};
					messages("success", "Success!","Data Assigned Successfully", 3000);
					$scope.get_followUp();
				}
				else
				{
					messages("danger", "Warning!",data, 4000);
				}
				$('#submitbtn1add').attr('disabled',false);
				$('#loader1add').css('display','none');
			})
		}
	}
	
	
	$scope.save_data=function()
	{
		$('#markform1').ajaxForm({
			type: "POST",
			url: rootUrl+marketing+"/save",
			beforeSend: function()
			{
				$('#submitbtn1mar').attr('disabled',true);
				$('#loader1mar').css('display','inline');
			},
			success: function(data)
			{
				if(data=="1")
				{
					$scope.main_clear();
					$scope.follow_clear();
					messages("success", "Success!","Data Saved Successfully", 3000);
					$scope.get_marketingfollowUp();
					$scope.get_followUp();
				}
				else
				{
					messages("danger", "Warning!",data, 6000);
				}
				$('#loader1mar').css('display','none');
				$('#submitbtn1mar').attr('disabled',false);
			}
		});
	}
	
	
	$scope.delete_data=function(id)
	{
		if(confirm("Deleting Staff Details may hamper your data associated with it."))
		{
			if(confirm("Are you Sure to DELETE ??"))
			{
				$http.get(rootUrl+marketing+"delete?id="+id).success(function(data){
					if(data=="1")
					{
						messages("success", "Success!","Category Details Deleted Successfully", 3000);
					}
					else
					{
						messages("danger", "Warning!","Category Details not Deleted", 4000);
					}
					$scope.get_marketingfollowUp();
				})
			}
		}
	}
	
	$scope.folow_temp_delete_data=function(id)
	{
		if(confirm("Deleting Data may hamper your data associated with it."))
		{
			if(confirm("Are you Sure to DELETE ??"))
			{
				$http.get(rootUrl+marketing+"folow_temp_delete?id="+id).success(function(data){
					if(data=="1")
					{
						messages("success", "Success!","Data Deleted Successfully", 3000);
					}
					else
					{
						messages("danger", "Warning!","Data not Deleted", 4000);
					}
					$scope.get_followUp();
				})
			}
		}
	}
	
}]);//blank line is required
app.controller('hr_master_settings',['$scope','$rootScope','$http',function($scope,$rootScope,$http){
	
	rootUrl=$rootScope.site_url;
	module="hr_master_settings/";
	$http.get(rootUrl+module+"/index").success (function(data) {if(data==0){window.location.assign('login.html');} else if(data==2){messages("success", "Privilege not assigned.", 1000);window.location.assign('index.html');}});
	
	$scope.show=0;
	$scope.x={};
	$scope.y={};
	$scope.s={};
	$scope.s1={};
	$scope.exit=function(){
		$scope.show=0;
	}
	$scope.exit();
	
	//Getting Modules View Data
	$scope.loader=function()
	{
		$http.get(rootUrl+"shift_settings/view").success(function(data)
		{
			$scope.s=data[0];
			if(data[1])
			{
				$scope.s1=data[1];
				if($scope.s1.name)
					$scope.show=1;
			}
		})
	}
	$scope.loader();
	$scope.loader_half=function()
	{
		$http.get(rootUrl+"half_day_settings/view").success(function(data){
			console.log("Half day");
			console.log(data);
			if(data.length>0){
				$scope.y=data[0];
				var halftim=data[0].m_time.split(':');
				$('#HalfTimeHour').val(halftim[0]);
				$('#HalfTimeMin').val(halftim[1]);
			}
		})
	}
	$scope.loader_half();
	$scope.loader_com=function()
	{
		$http.get(rootUrl+"com_config/view").success(function(data){
			console.log(data);
			$scope.c=data[0];
		})
	}
	$scope.loader_com();
	$scope.loader2=function()
	{
		$http.get(rootUrl+"hr_master_settings/view_data").success(function(data)
		{
			console.log(data);
			$scope.m=data[0];
			var tim=data[0].work_hour.split(':');
			$('#hour').val(tim[0]);
			$('#min').val(tim[1]);
		})
	}
	$scope.loader2();
	
	$scope.loader3=function()
	{
		$http.get(rootUrl+"pf_settings/view").success(function(data){
			console.log(data);
			$scope.p=data[0];
		})
	}
	$scope.loader3();
	//Getting Modules View Data END
	
	
	// Each Module Data
	$scope.shift_new=function()
	{
		$("#form1").trigger('reset');
		$scope.s={};
	}
	$scope.changeForm=function()
	{
		console.log("asdfhj");
		$scope.show=1;
	}
	
	$scope.master_new=function()
	{
		$("#master_form").trigger('reset');
		$scope.x={};
	}
	$scope.half_new=function()
	{
		$("#half_form").trigger('reset');
		$scope.h={};
	}
	//end here//
	
	
	
	// Save 
	$scope.master_save_data=function(x)
	{
		$('#master_submitbtn').attr('disabled',true);
		$.ajax({
			type: "POST",
			url: rootUrl+"hr_master_settings/master_save_data",
			data: $("#master_form").serialize(),
			beforeSend: function()
			{
				$('#webprogress').css('display','inline');
			},
			success: function(data)
			{
				console.log(data)
				if(data=="1")
				{
					$scope.loader2();
					$scope.loader3();
					$scope.loader_half();
					$scope.loader();
					messages("success", "Success!","Master Settings Set Successfully", 4000);
					
				}
				else if(data=="0")
				{
					messages("warning", "Info!","No Data Affected", 10000);
				}
				else
				{
					messages("danger", "Warning!",data, 10000);
				}
				$('#webprogress').css('display','none');
			}
		});
		$('#master_submitbtn').attr('disabled',false);
	}
}]);app.controller('billing_window_settings', ['$scope', '$rootScope', '$http', function ($scope, $rootScope, $http) {
	let rootUrl = $rootScope.site_url;
	let module = 'billing_window_settings';

	function initBillingWindowSettingsSelect2() {
		if (typeof $ === 'undefined' || !$.fn || !$.fn.select2) {
			return;
		}

		setTimeout(function () {
			$('.billing-window-settings-root .billing-window-settings-select2').each(function () {
				var $el = $(this);

				if ($el.data('select2')) {
					$el.select2('destroy');
				}

				if ($el.prop('disabled')) {
					return;
				}

				$el.select2({
					width: '100%',
					allowClear: false,
					minimumResultsForSearch: 0
				});
			});
		}, 0);
	}

	$http.get(rootUrl + module + '/index').success(function (data) {
		if (data == 0) {
			window.location.assign('login.html');
		} else if (data == 2) {
			messages('success', 'Privilege not assigned.', 1000);
			window.location.assign('index.html');
		}
	});

	$scope.billingTypeOptions = [
		{ value: 'prepaid', label: 'Prepaid' },
		{ value: 'postpaid', label: 'Postpaid' }
	];

	$scope.rows = [];

	$scope.format_billing_type = function (value) {
		value = (value || '').toString().replace(/_/g, ' ').trim();
		if (!value) {
			return '';
		}
		return value.charAt(0).toUpperCase() + value.slice(1);
	};

	$scope.reset_form = function () {
		$scope.x = {
			id: '',
			billing_type: '',
			days_limit: 0
		};
		$scope.$applyAsync();
		setTimeout(function () {
			$('.billing-window-settings-root select[name="billing_type"]').val(String($scope.x.billing_type || '')).trigger('change');
			initBillingWindowSettingsSelect2();
		}, 0);
	};

	$scope.loader = function () {
		$http.get(rootUrl + module + '/view').success(function (data) {
			$scope.rows = angular.isArray(data) ? data : [];
			initBillingWindowSettingsSelect2();
		});
	};

	$scope.edit_row = function (row) {
		if (!row) {
			return;
		}

		$scope.x = {
			id: row.id || '',
			billing_type: row.billing_type || 'prepaid',
			days_limit: row.days_limit || 0
		};
		$scope.$applyAsync();
		setTimeout(function () {
			$('.billing-window-settings-root select[name="billing_type"]').val(String($scope.x.billing_type || '')).trigger('change');
			initBillingWindowSettingsSelect2();
		}, 0);
	};

	$scope.save_data = function () {
		$('#billingwindowsettingsbtn').attr('disabled', true);
		$.ajax({
			type: 'POST',
			url: rootUrl + module + '/save',
			data: $('#billing_window_settings_form').serialize(),
			beforeSend: function () {
				$('#billingwindowloader').css('display', 'inline');
			},
			success: function (data) {
				data = (data || '').toString().trim();
				if (data == '1' || data == '2') {
					messages('success', 'Success!', 'Billing window settings saved successfully.', 3000);
					$scope.loader();
					$scope.reset_form();
					initBillingWindowSettingsSelect2();
				} else if (data == '0') {
					messages('warning', 'Info!', 'No Data Affected', 3000);
				} else {
					messages('danger', 'Warning!', data || 'Unable to save billing window settings.', 5000);
				}
				$('#billingwindowloader').css('display', 'none');
				$('#billingwindowsettingsbtn').attr('disabled', false);
			},
			error: function () {
				$('#billingwindowloader').css('display', 'none');
				$('#billingwindowsettingsbtn').attr('disabled', false);
				messages('danger', 'Warning!', 'Unable to save billing window settings.', 5000);
			}
		});
	};

	$scope.reset_form();
	$scope.loader();
	initBillingWindowSettingsSelect2();
}]);
//blank line is required
app.controller('hr_docs_design',['$scope','$rootScope','$http',function($scope,$rootScope,$http){
	
	rootUrl=$rootScope.site_url;
	module="hr_docs_design/";
	$http.get(rootUrl+module+"/index").success (function(data) {if(data==0){window.location.assign('login.html');} else if(data==2){messages("success", "Privilege not assigned.", 1000);window.location.assign('index.html');}});
	
	
	$scope.loader=function()
	{
		$http.get(rootUrl+"hr_docs_design/view").success(function(data)
		{
			$scope.datadb=data;
		})
	}
	$scope.loader();
	$scope.filter_new=function()
	{
		$scope.x={};
	}
	$scope.update_call=function(y)
	{
		$scope.x=y;
		$http.get(rootUrl+"hr_docs_design/view?data=content&id="+y.dd_id).success(function(data)
		{
			$scope.x.content=data[0].content;
		})
	}
	$scope.save_data=function(x)
	{
		$('#submitbtn').attr('disabled',true);
		$.ajax({
			type: "POST",
			url: rootUrl+"hr_docs_design/save_data",
			data: $("#dd_form").serialize(),
			beforeSend: function()
			{
				$('#webprogress').css('display','inline');
			},
			success: function(data)
			{
				if(data=="1")
				{
					$scope.loader();
					$scope.filter_new();
					messages("success", "Success!","Master Settings Set Successfully", 4000);
					
				}
				else if(data=="0")
				{
					messages("warning", "Info!","No Data Affected", 10000);
				}
				else
				{
					messages("warning", "Warning!",data, 10000);
				}
				$('#webprogress').css('display','none');
			}
		});
		$('#submitbtn').attr('disabled',false);
	}
	
	
	$scope.delete_data=function(id)
	{
		if(confirm("Deleting data may hamper your data associated with it. You will loose the data related with this."))
		{
			if(confirm("Are you Sure to DELETE ??"))
			{
				$http.get(rootUrl+"hr_docs_design/delete_data?id="+id).success(function(data){
					if(data=="1")
					{
						messages("success", "Success!","Document Deleted Successfully", 4000);
					}
					else
					{
						messages("danger", "Warning!","Document not Deleted", 10000);
					}
					$scope.loader();
				})
			}
		}
	}
	
}]);//blank line is required
app.controller('hr_issue_certificate',['$scope','$rootScope','$http',function($scope,$rootScope,$http){
	
	rootUrl=$rootScope.site_url;
	module="hr_issue_certificate/";
	$http.get(rootUrl+module+"/index").success (function(data) {if(data==0){window.location.assign('login.html');} else if(data==2){messages("success", "Privilege not assigned.", 1000);window.location.assign('index.html');}});

	$scope.x = {};
	$scope.show = 0;
	$scope.employee = [];
	$scope.datadb = [];
	$scope.pageno = 1;
	$scope.total_count = 0;
	$scope.pager = {itemsPerPage:'15'};
	$scope.qx = {};
	$scope.issue_certificate_modal_title = "Add Issue Certificate";

	function initIssueCertificateSelect2() {
		if (typeof $ === 'undefined' || !$.fn || !$.fn.select2) {
			return;
		}

		setTimeout(function () {
			var $modal = $('#issueCertificateModal');
			var isSelect2V4 = !!($.fn.select2 && $.fn.select2.amd);

			$('.issue-certificate-root select, #issueCertificateModal select').each(function () {
				var $el = $(this);
				if (!$el.is('select')) {
					return;
				}
				if ($el.hasClass('no-select2') || $el.is('[data-no-select2]')) {
					return;
				}

				if ($el.data('select2')) {
					$el.select2('destroy');
				}

				var options = {
					width: '100%',
					minimumResultsForSearch: 0
				};

				if (isSelect2V4 && $el.closest('#issueCertificateModal').length) {
					options.dropdownParent = $modal;
				}

				$el.select2(options);
			});
		}, 0);
	}

	function allowSelect2TypingInsideIssueCertificateModal() {
		if (typeof $ === 'undefined' || !$.fn || !$.fn.modal || !$.fn.modal.Constructor) {
			return;
		}

		var ModalConstructor = $.fn.modal.Constructor;
		if (ModalConstructor.prototype._issueCertificateSelect2FocusPatched) {
			return;
		}

		ModalConstructor.prototype.enforceFocus = function () {
			var modalThis = this;
			$(document)
				.off('focusin.bs.modal')
				.on('focusin.bs.modal', function (e) {
					var $target = $(e.target);
					var isInsideModal = modalThis.$element[0] === e.target || modalThis.$element.has(e.target).length;
					var isSelect2Input =
						$target.closest('.select2-container, .select2-dropdown, .select2-drop, .select2-search').length > 0 ||
						$target.is('.select2-input, .select2-search__field');

					if (!isInsideModal && !isSelect2Input) {
						modalThis.$element.trigger('focus');
					}
				});
		};

		ModalConstructor.prototype._issueCertificateSelect2FocusPatched = true;

		$(document).off('select2:open.issueCertificate select2-open.issueCertificate');
		$(document).on('select2:open.issueCertificate select2-open.issueCertificate', function () {
			setTimeout(function () {
				var $search = $('.select2-container-active .select2-input, .select2-drop-active .select2-input, .select2-container--open .select2-search__field');
				if ($search.length) {
					$search.focus();
				}
			}, 0);
		});
	}

	$scope.loader=function(pageno)
	{
		if(!pageno)
			pageno = 1;
		$scope.pageno = pageno;

		var params = [];
		if($scope.qx.emp_code)
			params.push("emp_code=" + encodeURIComponent($scope.qx.emp_code));
		if($scope.qx.dd_id)
			params.push("dd_id=" + encodeURIComponent($scope.qx.dd_id));
		if($scope.qx.issue_date)
			params.push("issue_date=" + encodeURIComponent($scope.qx.issue_date));
		if($scope.qx.q)
			params.push("q=" + encodeURIComponent($scope.qx.q));

		var url = rootUrl + "hr_issue_certificate/view/" + $scope.pager.itemsPerPage + "/" + pageno;
		if(params.length)
			url += "?" + params.join("&");

		$http.get(url).success(function(response)
		{
			if(response && response.data !== undefined)
			{
				$scope.datadb = response.data || [];
				$scope.total_count = response.total_count || 0;
				initIssueCertificateSelect2();
			}
			else
			{
				$scope.datadb = response || [];
				$scope.total_count = ($scope.datadb || []).length;
				initIssueCertificateSelect2();
			}
		});
	};
	$scope.loader(1);

	$scope.get_page_count = function()
	{
		var per = parseInt($scope.pager.itemsPerPage, 10) || 0;
		var total = parseInt($scope.total_count, 10) || 0;
		var page = parseInt($scope.pageno, 10) || 1;

		if(per <= 0 || total <= 0)
			return 0;

		var shown = total - ((page - 1) * per);
		if(shown <= 0)
			return 0;
		return shown > per ? per : shown;
	};

	$scope.apply_filters=function()
	{
		$scope.loader(1);
	};

	$scope.clear_filters=function()
	{
		$scope.qx = {};
		$scope.pager.itemsPerPage = '15';
		$scope.loader(1);
	};

	$scope.on_items_per_page_change=function()
	{
		$scope.loader(1);
	};

	$http.get(rootUrl+"hr_docs_design/view?data=dd_id,title").success(function(data){
		$scope.docs=data;
		initIssueCertificateSelect2();
	});
	
	$scope.filter_form=function(refreshList)
	{
		$scope.show=0;
		$scope.x={};
		$scope.employee=[];
		$("#response").html("");
		if(refreshList!==false)
			$scope.loader(1);
	};

	$scope.update_call=function(y)
	{
		$scope.x = angular.copy(y || {});
		$scope.x.date = y.issue_date;
	};

	$scope.open_issue_certificate_modal=function(mode,y)
	{
		if(mode=="edit" && y)
		{
			$scope.issue_certificate_modal_title = "Edit Issue Certificate";
			$scope.update_call(y);
		}
		else
		{
			$scope.issue_certificate_modal_title = "Add Issue Certificate";
			$scope.filter_form(false);
		}
		$('#issueCertificateModal').modal('show');
		allowSelect2TypingInsideIssueCertificateModal();
		initIssueCertificateSelect2();
		if(mode=="edit" && y && y.emp_code)
			$scope.fetchEmployee(y.emp_code);
	};
	
	$scope.fetchEmployee=function(emp_code)
	{
		$scope.show=0;
		$("#response").html("");
		$http.get(rootUrl+"hr_staff_details/view?data=emp_id,staff_name as ename,grade as gr,hr_departments.name as dname,gender,emp_type&emp_code="+emp_code).success(function(data)
		{
			if(data.length>0)
			{
				$scope.show=1;
				$scope.x.emp_id=data[0].emp_id;
				$scope.employee=data;
			}	
			else
			{
				$msg="<div class='alert alert-danger'><h4 style='text-align:center;color:red;'>Invalid Employee Code!! Check Employee Code</h4><div>";
				$("#response").html($msg);
			}
		});
	};
	
	$scope.print=function(isc_id)
	{
		$http.get(rootUrl+"hr_issue_certificate/view?data=layout&id="+isc_id).success(function(data)
		{
			if(data.length>0)
			{
				var wnd = window.open("about:blank", "", "_blank");
				wnd.document.write(data[0].layout);
			}
		});
	};
	
	$scope.save_data=function(x)
	{
		$('#submitbtn').attr('disabled',true);
		$.ajax({
			type: "POST",
			url: rootUrl+"hr_issue_certificate/save_data",
			data: $("#isc_form").serialize(),
			beforeSend: function()
			{
				$('#loader').css('display','inline');
			},
			success: function(data)
			{
				console.log(data);
				if(data=="1")
				{
					messages("success", "Success!","Certificate Issued Successfully", 4000);
					$scope.loader($scope.pageno || 1);
					$scope.filter_form(false);
					$('#issueCertificateModal').modal('hide');
				}
				else if(data=="0")
				{
					messages("warning", "Info!","No Data Affected", 10000);
				}
				else
				{
					messages("warning", "Warning!",data, 10000);
				}
				$('#loader').css('display','none');
			}
		});
		$('#submitbtn').attr('disabled',false);
	};
	
	$scope.delete_data=function(id)
	{
		if(confirm("Deleting data may hamper your data associated with it. You will loose the data related with this."))
		{
			if(confirm("Are you Sure to DELETE ??"))
			{
				$http.get(rootUrl+"hr_issue_certificate/delete_data?id="+id).success(function(data){
					if(data=="1")
					{
						messages("success", "Success!","Issued Certificate Deleted Successfully", 4000);
						$scope.loader($scope.pageno || 1);
					}
					else
					{
						messages("danger", "Warning!","Issued Certificate Not Deleted", 10000);
					}
				});
			}
		}
	};

	allowSelect2TypingInsideIssueCertificateModal();
	$(document).off('shown.bs.modal.issueCertificateSelect2', '#issueCertificateModal').on('shown.bs.modal.issueCertificateSelect2', '#issueCertificateModal', function () {
		initIssueCertificateSelect2();
	});
	initIssueCertificateSelect2();
	
}]);
//blank line is required
app.controller('master_follow_up',['$scope','$rootScope','$http',function($scope,$rootScope,$http)
{
	module='Hr_master_follow_up/';
	rootUrl=$rootScope.site_url;
	$http.get(rootUrl+module+"/index").success (function(data) {if(data==0){window.location.assign('login.html');} else if(data==2){window.location.assign('index.html');}});
	$scope.init=function()
	{
		$http.get(rootUrl+module+"view_data").success(function(data){
			$scope.datadb=data;
		})
	}
	$scope.init();
	$scope.x={};
	$scope.update_call=function(y)
	{
		$scope.x=y;
	}
	$scope.filter_new=function()
	{
		$scope.x={};
		$scope.init();
	}
	
	$scope.save_data=function()
	{
			$('#submitbtn').attr('disabled',true);
			$.ajax({
				type: "POST",
				url: rootUrl+module+"save_data",
				data: $("#master_folform1").serialize(),
				beforeSend: function()
				{
					$('#loader').css('display','inline');
				},
				success: function(data)
				{
					if(data=="1")
					{
						messages("success", "Success!","Master Follow Up Saved Successfully", 3000);
						$scope.filter_new();
						$scope.init();
					}
					else if(data=="0")
					{
						messages("warning", "Info!","No Data Affected", 3000);
					}
					else
					{
						messages("danger", "Warning!",data, 6000);
					}
					$('#loader').css('display','none');
					$('#submitbtn').attr('disabled',false);
				}
			});
	}
	
	$scope.delete_data=function(id)
	{
		if(confirm("Deleting Staff Details may hamper your data associated with it."))
		{
			if(confirm("Are you Sure to DELETE ??"))
			{
				$http.get(rootUrl+module+"delete_data?fm_id="+id).success(function(data){
					if(data=="1")
					{
						messages("success", "Success!","Master Follow Up Deleted Successfully", 3000);
					}
					else
					{
						messages("danger", "Warning!","Master Follow Up not Deleted", 4000);
					}
					$scope.init();
				})
			}
		}
	}
	
}]);//blank line is required
app.controller('payments',['$scope','$rootScope','$http',function($scope,$rootScope,$http)
{
	jmodule='payments/';
	rootUrl=$rootScope.site_url;
	$http.get(rootUrl+jmodule+"/index").success (function(data) {if(data==0){window.location.assign('login.html');} else if(data==2){messages("success", "Privilege not assigned.", 1000);window.location.assign('index.html');}});

	$scope.d = {};
	$scope.x = {};
	$scope.datadb = [];
	$scope.show = 0;
	$scope.pageno = 1;
	$scope.total_count = 0;
	$scope.pager = { itemsPerPage: '15' };
	$scope.qx = {};
	$scope.payments_modal_title = "Add Payment";

	function initPaymentsSelect2() {
		if (typeof $ === 'undefined' || !$.fn || !$.fn.select2) {
			return;
		}

		setTimeout(function () {
			var $modal = $('#paymentsModal');
			var isSelect2V4 = !!($.fn.select2 && $.fn.select2.amd);

			$('.payments-root .payments-select2, #paymentsModal .payments-select2').each(function () {
				var $el = $(this);
				if (!$el.is('select')) {
					return;
				}

				if ($el.data('select2')) {
					$el.select2('destroy');
				}

				var options = {
					width: '100%',
					minimumResultsForSearch: 0
				};

				if (isSelect2V4 && $el.closest('#paymentsModal').length) {
					options.dropdownParent = $modal;
				}

				$el.select2(options);
			});
		}, 0);
	}

	function allowSelect2TypingInsidePaymentsModal() {
		if (typeof $ === 'undefined' || !$.fn || !$.fn.modal || !$.fn.modal.Constructor) {
			return;
		}

		var ModalConstructor = $.fn.modal.Constructor;
		if (ModalConstructor.prototype._paymentsSelect2FocusPatched) {
			return;
		}

		ModalConstructor.prototype.enforceFocus = function () {
			var modalThis = this;
			$(document)
				.off('focusin.bs.modal')
				.on('focusin.bs.modal', function (e) {
					var $target = $(e.target);
					var isInsideModal = modalThis.$element[0] === e.target || modalThis.$element.has(e.target).length;
					var isSelect2Input =
						$target.closest('.select2-container, .select2-dropdown, .select2-drop, .select2-search').length > 0 ||
						$target.is('.select2-input, .select2-search__field');

					if (!isInsideModal && !isSelect2Input) {
						modalThis.$element.trigger('focus');
					}
				});
		};

		ModalConstructor.prototype._paymentsSelect2FocusPatched = true;

		$(document).off('select2:open.payments select2-open.payments');
		$(document).on('select2:open.payments select2-open.payments', function () {
			setTimeout(function () {
				var $search = $('.select2-container-active .select2-input, .select2-drop-active .select2-input, .select2-container--open .select2-search__field');
				if ($search.length) {
					$search.focus();
				}
			}, 0);
		});
	}

	$scope.loader = function(pageno)
	{
		if(!pageno)
			pageno = 1;
		$scope.pageno = pageno;

		var params = [];
		if($scope.qx.emp_id)
			params.push("emp_id=" + encodeURIComponent($scope.qx.emp_id));
		if($scope.qx.mode)
			params.push("mode=" + encodeURIComponent($scope.qx.mode));
		if($scope.qx.date)
			params.push("date=" + encodeURIComponent($scope.qx.date));
		if($scope.qx.q)
			params.push("q=" + encodeURIComponent($scope.qx.q));

		var url = rootUrl + jmodule + "view/" + $scope.pager.itemsPerPage + "/" + pageno;
		if(params.length)
			url += "?" + params.join("&");

		$http.get(url).success(function(response)
		{
			if(response && response.data !== undefined)
			{
				$scope.datadb = response.data || [];
				$scope.total_count = response.total_count || 0;
				initPaymentsSelect2();
			}
			else
			{
				// Backward-compatible fallback if paginated endpoint is unavailable.
				$http.get(rootUrl+jmodule+"view_data").success(function(data)
				{
					$scope.datadb = data || [];
					$scope.total_count = ($scope.datadb || []).length;
					initPaymentsSelect2();
				});
			}
		});
	};

	$scope.apply_filters = function()
	{
		$scope.loader(1);
	};

	$scope.clear_filters = function()
	{
		$scope.qx = {};
		$scope.pager.itemsPerPage = '15';
		$scope.loader(1);
	};

	$scope.on_items_per_page_change = function()
	{
		$scope.loader(1);
	};

	$scope.get_page_count = function()
	{
		var per = parseInt($scope.pager.itemsPerPage, 10) || 0;
		var total = parseInt($scope.total_count, 10) || 0;
		var page = parseInt($scope.pageno, 10) || 1;

		if(per <= 0 || total <= 0)
			return 0;

		var shown = total - ((page - 1) * per);
		if(shown <= 0)
			return 0;
		return shown > per ? per : shown;
	};

	$http.get(rootUrl+"hr_staff_details/view?data=staff_name as name,emp_id&st=1").success(function(data)
	{
		$scope.employees=data;
		initPaymentsSelect2();
	});

	$scope.filter_new_jour=function()
	{
		$scope.d={};
		$scope.show=0;
		setTimeout(function(){
			if($('#DOB1').length && !$('#DOB1').val())
				$('#DOB1').datepicker('setDate','now');
		},120);
	};

	$scope.update_call=function(y)
	{
		$scope.show=0;
		$scope.d=angular.copy(y);
		if($scope.d.dr==0)
			$scope.d.dr="";
		if($scope.d.cr==0)
			$scope.d.cr="";
		if($scope.d.emp_id)
			$scope.calcDue($scope.d.emp_id);
	};

	$scope.open_payment_modal = function(mode,y)
	{
		if(mode=="edit" && y)
		{
			$scope.payments_modal_title = "Edit Payment";
			$scope.update_call(y);
		}
		else
		{
			$scope.payments_modal_title = "Add Payment";
			$scope.filter_new_jour();
		}
		$('#paymentsModal').modal('show');
		allowSelect2TypingInsidePaymentsModal();
		initPaymentsSelect2();
	};

	$scope.calcDue=function(emp_id)
	{
		$scope.show=1;
		$scope.due=0;
		$scope.lastCrDate="";
		$scope.lastDrDate="";
		$http.get(rootUrl+jmodule+"calcDue?emp_id="+emp_id).success(function(data)
		{
			if(data)
			{
				$scope.due=data.due;
				$scope.lastCrDate=data.lastCrDate;
				$scope.lastDrDate=data.lastDrDate;
				$scope.lastCredit=data.lastCredit;
				$scope.lastDebit=data.lastDebit;
			}
		});
	};

	$scope.save_data=function()
	{
		$('#submitbtn').attr('disabled',true);
		$.ajax({
			type: "POST",
			url: rootUrl+jmodule+"save",
			data: $("#form1").serialize(),
			beforeSend: function()
			{
				$('#loader').css('display','inline');
			},
			success: function(data)
			{
				if(data=="1")
				{
					messages("success", "Success!","Saved Successfully", 3000);
					$scope.loader($scope.pageno || 1);
					$scope.filter_new_jour();
					$('#paymentsModal').modal('hide');
				}
				else if(data=="0")
				{
					messages("warning", "Info!","No Data Affected", 3000);
				}
				else
				{
					messages("danger", "Warning!",data, 6000);
				}
				$('#loader').css('display','none');
				$('#submitbtn').attr('disabled',false);
			}
		});
	};

	$scope.delete_data=function(id)
	{
		if(confirm("Deleting payments Records may hamper your data associated with it."))
		{
			if(confirm("Are you Sure to DELETE ??"))
			{
				$http.get(rootUrl+jmodule+"delete_data?id="+id).success(function(data){
					if(data=="1")
					{
						messages("success", "Success!","payments Records Deleted Successfully", 3000);
					}
					else
					{
						messages("danger", "Warning!","payments Records not Deleted", 4000);
					}
					$scope.loader($scope.pageno || 1);
				});
			}
		}
	};

	$scope.loader(1);
	allowSelect2TypingInsidePaymentsModal();
	$(document).off('shown.bs.modal.paymentsSelect2', '#paymentsModal').on('shown.bs.modal.paymentsSelect2', '#paymentsModal', function () {
		initPaymentsSelect2();
	});
	initPaymentsSelect2();
}]);
//blank line is required
app.controller('sms',['$scope','$rootScope','$http',function($scope,$rootScope,$http){
	rootUrl=$rootScope.site_url;
	module="sms/";
	$http.get(rootUrl+module+"/index").success (function(data) {if(data==0){window.location.assign('login.html');} else if(data==2){messages("success", "Privilege not assigned.", 1000);window.location.assign('index.html');}});

	function initSmsSelect2() {
		if (typeof $ === 'undefined' || !$.fn || !$.fn.select2) {
			return;
		}

		setTimeout(function () {
			$('.sms-page .select-two').each(function () {
				var $el = $(this);
				if (!$el.is('select')) {
					return;
				}

				if ($el.data('select2')) {
					$el.select2('destroy');
				}

				$el.select2({
					width: '100%',
					minimumResultsForSearch: 0
				});
			});
		}, 0);
	}

	$scope.x={};
	$scope.isLoading=true;
	$('#progress').hide();
	
	$scope.url=rootUrl+'hr_staff_details/view?data=staff_name as nm,hr_departments.name as desig,type as tp,grade as gd,contact_no as phn,emp_id as id&status=1';
    $http.get($scope.url)
		.success (function(data) {$scope.staff_data=data;
		$scope.rows=Object.keys(data).length;
		$scope.isLoading=false;
		initSmsSelect2();
	});
    
    $http.get(rootUrl+"hr_grades/view?data=grade").success(function(data){
		$scope.grades=data;
		initSmsSelect2();
	})
    
	$scope.fetch_design=function(x){
    	$scope.x.design='';
		$scope.isLoading=true;
		if(x && x.grade)
		{
			$http.get(rootUrl+"hr_designation/view?join=1&data=d_id,name&grade="+x.grade).success(function(data)
			{
				$scope.designations=data;
				initSmsSelect2();
			})
		}
		$http.get($scope.url+'&grade='+x.grade)
			.success (function(data) {$scope.staff_data=data;
			$scope.rows=Object.keys(data).length;
			$scope.isLoading=false;
			initSmsSelect2();
		});
		$("#selecctall").checked= false;
	};
	
	$scope.design_filter=function(x){
		$scope.isLoading=true;
		$http.get($scope.url+'&grade='+x.grade+"&d_id="+x.design)
			.success (function(data) {$scope.staff_data=data;
			$scope.rows=Object.keys(data).length;
			$scope.isLoading=false;
			initSmsSelect2();
		});
	};

	$scope.select_all=function(){
		 if(this.checked) { // check select status
	            $('.checkbox1').each(function() { //loop through each checkbox
	                this.checked = true;  //select all checkboxes with class "checkbox1"               
	            });
	        }else{
	            $('.checkbox1').each(function() { //loop through each checkbox
	                this.checked = false; //deselect all checkboxes with class "checkbox1"                       
	            });         
	        }
	}
 	$scope.send=function(){
 		$("#btnsubmit").text('Please Wait...');
		$("#btnsubmit").prop('disabled',true);
 		$.ajax({
 			type: "POST",
 			url: rootUrl+"psms/send_sms",
 			data: $("#form").serialize(),
 			beforeSend: function()
 			{
 				$('#progress').toggle();
 			},
 			success: function(data){
 				$('#progress').hide();
 				var arr = $.parseJSON(data);
				if(arr.type=="1")
 				{
					messages("danger", "Warning!",arr.error, 4000);
 				}
 				else
 				{
 					$("#form").trigger('reset');
 					messages("success", "Success!",arr.error, 5000);
 				}
				$("#btnsubmit").text('Send');
				$("#btnsubmit").prop('disabled',false);
 			}
 		});
 	};

	initSmsSelect2();
}]);
app.controller('payroll_monthly',['$scope','$rootScope','$http',function($scope,$rootScope,$http)
{
	mmodule='payroll_monthly';
	rootUrl=$rootScope.site_url;
	//login auth not required here..
	
	$http.get(rootUrl+"hr_paytype/view?st=1").success(function(data){
		$scope.paytypes_data=data;
	})
	$scope.ftype=function(t){
		if(t=='1') return "-"; else return "+";
	}
	
	$scope.save_data1=function(x)
	{
		$('#submitbtn3').attr('disabled',true);
		$.ajax({
			type: "POST",
			url: rootUrl+mmodule+"/save",
			data: $("#medicalform").serialize(),
			beforeSend: function()
			{
				$('#loader3').css('display','inline');
			},
			success: function(data)
			{
				if(data=="1")
				{
					messages("success", "Success!","Saved Successfully", 3000);
					$("#dc_id").trigger('click');
				}
				else if(data=="0")
				{
					messages("warning", "Info!","No Data Affected", 3000);
				}
				else
				{
					messages("danger", "Warning!",data, 6000);
				}
				$('#loader3').css('display','none');
				$('#submitbtn3').attr('disabled',false);
			}
		});
	}
}]);app.controller('plan_master',['$scope','$rootScope','$http',function($scope,$rootScope,$http){
	module='plan_master/';
	rootUrl=$rootScope.site_url;
	$http.get(rootUrl+module+"index").success(function(data) {
		if(data==0){window.location.assign('login.html');}
		else if(data==2){messages("success", "Privilege not assigned.", 1000);window.location.assign('index.html');}
	});

	function initPlanMasterSelect2() {
		if (typeof $ === 'undefined' || !$.fn || !$.fn.select2) {
			return;
		}

		setTimeout(function () {
			$('.plan-master-root select').each(function () {
				var $el = $(this);

				if ($el.data('select2')) {
					$el.select2('destroy');
				}

				if ($el.prop('disabled')) {
					return;
				}

				$el.select2({
					width: '100%',
					allowClear: false,
					minimumResultsForSearch: 0
				});
			});
		}, 0);
	}

	function bindPlanMasterSelect2Events() {
		if (typeof $ === 'undefined') {
			return;
		}

		$(document)
			.off('shown.bs.modal.planMasterSelect2', '#planMasterModal')
			.on('shown.bs.modal.planMasterSelect2', '#planMasterModal', function () {
				initPlanMasterSelect2();
			});
	}

	function resetPlanMasterModalDefaults() {
		$scope.x = {
			service_id: '',
			type: '',
			name: '',
			mrp: '',
			sp: '',
			subscription: '0',
			status: '1',
			description: '',
			terms: ''
		};

		$scope.$applyAsync();
		$('#planMasterService').val('').trigger('change.select2');
		$('#planMasterType').val('').trigger('change.select2');
		initPlanMasterSelect2();
	}

	$scope.pageno = 1;
	$scope.total_count = 0;
	$scope.itemsPerPage = '15';
	$scope.search_text = '';
	$scope.qx = { service_id: '', status: '' };
	$scope.x = {};
	$scope.datadb = [];
	$scope.service_list = [];
	$scope.plan_modal_title = "Add Plan";

	$scope.options = {
		height: 200,
		toolbar: [
			['font', ['bold', 'italic', 'underline']],
			['para', ['ol', 'paragraph']],
			['insert', ['link']],
			['view', ['codeview']]
		]
	};

	$scope.load_services = function()
	{
		$http.get(rootUrl + "service_master/view").success(function(data){
			$scope.service_list = data || [];
			initPlanMasterSelect2();
		});
	};

	$scope.loader = function(pageno)
	{
		if(!pageno)
			pageno = 1;
		$scope.pageno = pageno;

		var params = ["join=1", "per_page=" + encodeURIComponent($scope.itemsPerPage), "page=" + encodeURIComponent(pageno)];
		if($scope.search_text)
			params.push("search=" + encodeURIComponent($scope.search_text));
		if($scope.qx.service_id)
			params.push("service_id=" + encodeURIComponent($scope.qx.service_id));
		if($scope.qx.status !== undefined && $scope.qx.status !== "")
			params.push("status=" + encodeURIComponent($scope.qx.status));

		$http.get(rootUrl + module + "view?" + params.join("&")).success(function(response){
			if(response && response.data !== undefined)
			{
				$scope.datadb = response.data || [];
				$scope.total_count = response.total_count || 0;
			}
			else
			{
				$scope.datadb = response || [];
				$scope.total_count = ($scope.datadb || []).length;
			}
			initPlanMasterSelect2();
		});
	};

	$scope.apply_filters = function()
	{
		$scope.loader(1);
	};

	$scope.clear_filters = function()
	{
		$scope.search_text = '';
		$scope.qx = { service_id: '', status: '' };
		$scope.itemsPerPage = '15';
		$scope.$applyAsync();
		$('#planMasterSearchText').val('');
		$('#planMasterServiceFilter').val('').trigger('change.select2');
		$('#planMasterStatusFilter').val('').trigger('change.select2');
		$('#planMasterPerPage').val('15').trigger('change.select2');
		initPlanMasterSelect2();
		$scope.loader(1);
	};

	$scope.on_items_per_page_change = function()
	{
		$scope.loader(1);
	};

	$scope.update_call = function(y)
	{
		$scope.plan_modal_title = "Edit Plan";
		$scope.x = angular.copy(y);
		$scope.x.status = String($scope.x.status);
		$scope.x.subscription = String($scope.x.subscription || '0');
		initPlanMasterSelect2();
	};

	$scope.open_plan_modal = function(mode, y)
	{
		if(mode=="edit" && y)
		{
			$scope.update_call(y);
		}
		else
		{
			$scope.plan_modal_title = "Add Plan";
			$scope.filter_new(false);
		}
		$('#planMasterModal').modal('show');
		initPlanMasterSelect2();
		bindPlanMasterSelect2Events();
	};

	$scope.filter_new = function(refreshList)
	{
		resetPlanMasterModalDefaults();
		$scope.plan_modal_title = "Add Plan";
		if(refreshList!==false)
			$scope.loader($scope.pageno || 1);
	};

	$scope.save_data = function()
	{
		$('#planbtn').attr('disabled',true);
		$.ajax({
			type: "POST",
			url: rootUrl+module+"save",
			data: $("#planform").serialize(),
			beforeSend: function()
			{
				$('#webprogress').css('display','inline');
			},
			success: function(data)
			{
				data = (data || '').trim();
				if(data=="1")
				{
					messages("success", "Success!","Plan Saved Successfully", 3000);
					$scope.loader($scope.pageno || 1);
					resetPlanMasterModalDefaults();
					$scope.plan_modal_title = "Add Plan";
					$('#planMasterModal').modal('hide');
				}
				else if(data=="0")
				{
					messages("warning", "Info!","No Data Affected", 3000);
				}
				else
				{
					messages("danger", "Warning!",data, 6000);
				}
				$('#webprogress').css('display','none');
				$('#planbtn').attr('disabled',false);
			}
		});
	};

	$scope.delete_data = function(id)
	{
		if(confirm("Deleting Plan may hamper your data associated with it."))
		{
			if(confirm("Are you Sure to DELETE ??"))
			{
				$http.get(rootUrl+module+"delete?plan_id="+id).success(function(data){
					if(data=="1")
					{
						messages("success", "Success!","Plan Deleted Successfully", 3000);
					}
					else
					{
						messages("danger", "Warning!","Plan not Deleted", 4000);
					}
					$scope.loader($scope.pageno || 1);
				});
			}
		}
	};

	$scope.pageChangeHandler = function(newPageNumber)
	{
		$scope.loader(newPageNumber);
	};

	$scope.load_services();
	$scope.filter_new(false);
	$scope.loader(1);
	bindPlanMasterSelect2Events();
	initPlanMasterSelect2();
}]);
app.controller('customer', ['$scope', '$rootScope', '$http', '$timeout', '$q', 'sharedService', function ($scope, $rootScope, $http, $timeout, $q, sharedService) {
	rootUrl = $rootScope.site_url;
	$http.get(rootUrl + "customer/index").success(function (data) { if (data == 0) { window.location.assign('login.html'); } else if (data == 2) { window.location.assign('index.html'); } });

	$scope.login_check = localStorage.getItem('login');
	$scope.type_check = localStorage.getItem('type');
	$scope.pageno = 1;
	$scope.total_count = 0;
	$scope.itemsPerPage = '15';
	$scope.qx = {};
	$scope.x = {};
	$scope.qs = {};
	$scope.datadb = [];
	$scope.customer_modal_title = "Add Customer";
	$scope.activeCustomerTab = 'details';
	$scope.customerQuotationPlans = [];
	$scope.availableCustomerQuotationPlans = [];
	$scope.subscriptionServicePlans = [];
	$scope.subscriptionServiceName = '';
	$scope.subscriptionPlanPickerOptions = [];
	$scope.subscriptionPlanPickerLoading = false;
	$scope.subscriptionUsePlanMaster = false;
	$scope.planMasterSubscriptionPlans = [];
	$scope.planMasterSubscriptionPlansLoading = false;
	$scope.subscriptionDraftPlan = null;
	$scope.subscriptionDraftPlanId = '';
	$scope.subscriptionDraftMode = 'add';
	$scope.subscriptionChangeSourcePlan = null;
	$scope.invoiceCompanies = [];
	$scope.customerStaffOptions = [];
	$scope.numbers = Array.from({ length: 12 }, (_, i) => i + 1);
	$scope.reviewStatusUpdating = {};
	$scope.acSuggestions = {};
	$scope.acActive = {};
	var acTimers = {};
	$scope.subscriptionListView = 'active';
	$scope.visibleSubscriptionPlans = [];
	$scope.removedSubscriptionPlanIds = [];
	$scope.subscriptionAutoSavePending = false;
	var customerSubscriptionUiInitScheduled = false;
	var customerSubscriptionRowSequence = 0;
	var customerSubscriptionAutoSaveTimer = null;
	var customerSubscriptionSaveInProgress = false;

	$scope.options =
	{
		height: 150,
		toolbar: [
			['style', ["undo", "redo", 'style', 'bold', 'italic', 'underline']],
			['fontname', ['fontname']],
			['fontsize', ['fontsize']],
			['color', ['color']],
			['para', ['ol', 'ul', 'paragraph', 'height']],
			['font', ['strikethrough', 'superscript', 'subscript']],
			['table', ['table']],
			['insert', ['link', 'video', 'hr', 'picture']],
			["view", ["fullscreen", "codeview", "help",]]
		]
	};

	$scope.loader = function (pageno) {
		if (!pageno)
			pageno = 1;
		$scope.pageno = pageno;

		var params = [];
		if ($scope.qx.name)
			params.push("name=" + encodeURIComponent($scope.qx.name));
		if ($scope.qx.com_name)
			params.push("com_name=" + encodeURIComponent($scope.qx.com_name));
		if ($scope.qx.grade)
			params.push("grade=" + encodeURIComponent($scope.qx.grade));
		if ($scope.qx.phone)
			params.push("phone=" + encodeURIComponent($scope.qx.phone));

		var url = rootUrl + "customer/view_paginated/" + $scope.itemsPerPage + "/" + pageno;
		if (params.length)
			url += "?" + params.join("&");

		$http.get(url).success(function (response) {
			if (response && response.data !== undefined) {
				$scope.datadb = response.data || [];
				$scope.total_count = response.total_count || 0;
			} else {
				$scope.datadb = response || [];
				$scope.total_count = ($scope.datadb || []).length;
			}
		});
	};

	function loadCustomerStateList() {
		return $http.get(rootUrl + "company_master/view_state?data=iso2,name").then(function (response) {
			$scope.state_list = normalizeArrayResponse(response);
			normalizeCustomerDetailSelections();
			refreshCustomerDetailSelect2();
			return $scope.state_list;
		}).catch(function () {
			$scope.state_list = $scope.state_list || [];
			normalizeCustomerDetailSelections();
			refreshCustomerDetailSelect2();
			return $scope.state_list;
		});
	}

	function loadLoggedInCompanyContext() {
		if (String(localStorage.getItem('com_id') || '').trim()) {
			return $q.when(localStorage.getItem('com_id'));
		}

		return $http.get(rootUrl + 'dashboard/fetch_userdata').then(function (response) {
			var userData = response && response.data ? response.data : {};
			if (userData && userData.com_id !== undefined && userData.com_id !== null && String(userData.com_id).trim() !== '') {
				localStorage.setItem('com_id', String(userData.com_id));
			}
			return localStorage.getItem('com_id') || '';
		}).catch(function () {
			return localStorage.getItem('com_id') || '';
		});
	}

	function loadInvoiceCompanies() {
		var loggedInComId = String(localStorage.getItem('com_id') || '').trim();
		if (!loggedInComId) {
			$scope.invoiceCompanies = [];
			return $q.when([]);
		}

		return $http.get(rootUrl + "company_master/view?data=com_id,com_name,parent").then(function (response) {
			var allCompanies = normalizeArrayResponse(response);
			var childrenMap = {};
			var byId = {};
			var queue = [loggedInComId];
			var seen = {};
			var companies = [];

			angular.forEach(allCompanies || [], function (company) {
				if (!company || company.com_id === undefined || company.com_id === null) {
					return;
				}
				byId[String(company.com_id).trim()] = company;
				var parentId = String(company.parent || '').trim();
				if (!parentId) {
					return;
				}
				if (!childrenMap[parentId]) {
					childrenMap[parentId] = [];
				}
				childrenMap[parentId].push(company);
			});

			while (queue.length) {
				var currentId = queue.shift();
				if (!currentId || seen[currentId]) {
					continue;
				}
				seen[currentId] = true;

				if (byId[currentId]) {
					companies.push(byId[currentId]);
				}

				angular.forEach(childrenMap[currentId] || [], function (child) {
					if (child && child.com_id !== undefined && child.com_id !== null) {
						queue.push(String(child.com_id).trim());
					}
				});
			}

			$scope.invoiceCompanies = companies;
			angular.forEach($scope.invoiceCompanies, function (company) {
				if (company && company.com_id !== undefined && company.com_id !== null) {
					company.com_id = String(company.com_id);
				}
			});
			refreshCustomerDetailSelect2();
			return companies;
		}).catch(function () {
			$scope.invoiceCompanies = [];
			refreshCustomerDetailSelect2();
			return [];
		});
	}

	function loadCustomerStaffOptions() {
		return $http.get(rootUrl + "hr_staff_details/view_employee?status=1").then(function (response) {
			$scope.customerStaffOptions = normalizeArrayResponse(response);
			angular.forEach($scope.customerStaffOptions, function (staff) {
				if (staff && staff.eid !== undefined && staff.eid !== null) {
					staff.eid = String(staff.eid);
				}
			});
			refreshCustomerDetailSelect2();
			return $scope.customerStaffOptions;
		}).catch(function () {
			$scope.customerStaffOptions = [];
			refreshCustomerDetailSelect2();
			return [];
		});
	}

	function normalizeCustomerDetailSelections() {
		if (!$scope.x) {
			return;
		}

		var customerType = String($scope.x.c_type || '').trim().toUpperCase();
		if (customerType === 'INHOUSE') {
			customerType = 'IN HOUSE';
		}
		if (customerType === 'NEW' || customerType === 'IN HOUSE' || customerType === '') {
			$scope.x.c_type = customerType;
		}

		$scope.x.grade = String($scope.x.grade || '').trim().toUpperCase();
		['invoice_com', 'senior_crm_id', 'junior_crm_id', 'converted_by'].forEach(function (field) {
			if ($scope.x[field] !== undefined && $scope.x[field] !== null && $scope.x[field] !== '') {
				$scope.x[field] = String($scope.x[field]);
			}
		});

		var stateValue = String($scope.x.state || '').trim();
		if (stateValue) {
			var normalizedStateValue = stateValue.toUpperCase();
			var matchedState = null;

			angular.forEach($scope.state_list || [], function (state) {
				if (matchedState || !state) {
					return;
				}

				var iso2 = String(state.iso2 || '').trim().toUpperCase();
				var name = String(state.name || '').trim().toUpperCase();
				if (normalizedStateValue === iso2 || normalizedStateValue === name) {
					matchedState = state;
				}
			});

			if (matchedState && matchedState.iso2) {
				$scope.x.state = matchedState.iso2;
			}
		}
	}

	$scope.init = function () {
		$scope.loader(1);
		loadLoggedInCompanyContext().then(function () {
			loadCustomerStateList();
			loadInvoiceCompanies();
			loadCustomerStaffOptions();
		});
	};
	$scope.init();

	$scope.filter_data = function (com_name, grade, phone) {
		$scope.qx.com_name = com_name;
		$scope.qx.grade = grade;
		$scope.qx.phone = phone;
		$scope.loader(1);
	};

	$scope.apply_filters = function () {
		$scope.loader(1);
	};

	$scope.clear_filters = function () {
		$scope.qx = {};
		$scope.itemsPerPage = '15';
		$scope.acSuggestions = {};
		$scope.acActive = {};
		angular.forEach(acTimers, function (t, f) {
			if (t) { $timeout.cancel(t); acTimers[f] = null; }
		});
		$scope.loader(1);
	};

	$scope.on_items_per_page_change = function () {
		$scope.loader(1);
	};

	$scope.on_ac_input_change = function (qxField) {
		if (acTimers[qxField]) { $timeout.cancel(acTimers[qxField]); }
		var query = String(($scope.qx && $scope.qx[qxField]) || '').trim();
		if (query.length < 3) {
			$scope.acSuggestions[qxField] = [];
			$scope.acActive[qxField] = false;
			return;
		}
		var apiField = qxField === 'com_name' ? 'company_name' : qxField;
		acTimers[qxField] = $timeout(function () {
			acTimers[qxField] = null;
			$http.get(rootUrl + 'customer/autocomplete?field=' + apiField + '&query=' + encodeURIComponent(query))
				.success(function (response) {
					$scope.acSuggestions[qxField] = angular.isArray(response) ? response : [];
					$scope.acActive[qxField] = $scope.acSuggestions[qxField].length > 0;
				});
		}, 300);
	};

	$scope.select_ac_suggestion = function (qxField, value) {
		$scope.qx[qxField] = value;
		$scope.acSuggestions[qxField] = [];
		$scope.acActive[qxField] = false;
	};

	$scope.close_ac_delayed = function (qxField) {
		$timeout(function () {
			$scope.acActive[qxField] = false;
		}, 200);
	};

	$scope.filter_new = function () {
		$scope.clear_filters();
	};

	function normalizeArrayResponse(response) {
		if (!response) {
			return [];
		}

		if (angular.isArray(response)) {
			return response;
		}

		if (response.data && angular.isArray(response.data)) {
			return response.data;
		}

		if (response.data && angular.isArray(response.data.data)) {
			return response.data.data;
		}

		return [];
	}

	function initCustomerSubscriptionDatepickers() {
		$timeout(function () {
			if (!$.fn.datepicker) {
				return;
			}

			var $modal = $('#customerFormModal');
			if (!$modal.length) {
				return;
			}

			$modal.find('.customer-plan-start-date, .customer-installment-due-date').each(function () {
				var $el = $(this);
				if ($el.data('customer-datepicker-bound')) {
					return;
				}
				$el.data('customer-datepicker-bound', true);
				$el.datepicker({ format: 'yyyy-mm-dd', autoclose: true, todayHighlight: true });
			});
		}, 0);
	}

	function scheduleCustomerSubscriptionUiInit() {
		if (customerSubscriptionUiInitScheduled) {
			return;
		}

		customerSubscriptionUiInitScheduled = true;
		$timeout(function () {
			customerSubscriptionUiInitScheduled = false;
			if ($scope.activeCustomerTab === 'subscription') {
				initCustomerSubscriptionDatepickers();
			}
		}, 0, false);
	}

	function scrollCustomerSubscriptionFormIntoView() {
		$timeout(function () {
			var $modalBody = $('#customerFormModal .modal-body');
			var $draftCard = $('#customerSubscriptionDraftCard');

			if ($modalBody.length && $draftCard.length) {
				var targetTop = $draftCard.position().top + $modalBody.scrollTop() - 12;
				$modalBody.animate({ scrollTop: Math.max(targetTop, 0) }, 200);
				return;
			}

			if ($draftCard.length && $draftCard[0] && $draftCard[0].scrollIntoView) {
				$draftCard[0].scrollIntoView({ behavior: 'smooth', block: 'start' });
			}
		}, 0, false);
	}

	function scrollCustomerSubscriptionPickerIntoView() {
		$timeout(function () {
			var $modalBody = $('#customerFormModal .modal-body');
			var $pickerWrap = $('#customerSubscriptionPlanPickerWrap');
			var $picker = $('#customerSubscriptionPlanPicker');

			if ($picker.length && $picker[0] && $picker[0].focus) {
				$picker[0].focus();
			}

			if ($modalBody.length && $pickerWrap.length) {
				var targetTop = $pickerWrap.position().top + $modalBody.scrollTop() - 12;
				$modalBody.animate({ scrollTop: Math.max(targetTop, 0) }, 200);
				return;
			}

			if ($pickerWrap.length && $pickerWrap[0] && $pickerWrap[0].scrollIntoView) {
				$pickerWrap[0].scrollIntoView({ behavior: 'smooth', block: 'start' });
			}
		}, 0, false);
	}

	function getSelectNgModelValue($select) {
		var modelPath = $select.attr('ng-model');
		if (!modelPath || typeof angular === 'undefined') {
			return $select.val();
		}

		try {
			var selectScope = angular.element($select[0]).scope();
			if (selectScope && selectScope.$eval) {
				var modelValue = selectScope.$eval(modelPath);
				return modelValue === undefined || modelValue === null ? '' : String(modelValue);
			}
		} catch (err) {
			// Fall back to the DOM value if the Angular model cannot be read.
		}

		return $select.val();
	}

	function refreshCustomerDetailSelect2() {
		$timeout(function () {
			if (typeof $ === 'undefined' || !$.fn || !$.fn.select2) {
				return;
			}

			var $modal = $('#customerFormModal');
			if (!$modal.length) {
				return;
			}

			if ($.fn.modal && $.fn.modal.Constructor && $.fn.modal.Constructor.prototype && !$.fn.modal.Constructor.prototype._customerSelect2FocusPatched) {
				var originalEnforceFocus = $.fn.modal.Constructor.prototype.enforceFocus;
				$.fn.modal.Constructor.prototype.enforceFocus = function () {
					var modal = this;
					$(document)
						.off('focusin.bs.modal')
						.on('focusin.bs.modal', function (event) {
							if ($(event.target).closest('.select2-container, .select2-drop, .select2-dropdown').length) {
								return;
							}
							if (modal.$element[0] !== event.target && !modal.$element.has(event.target).length) {
								modal.$element.trigger('focus');
							}
						});
				};
				$.fn.modal.Constructor.prototype._customerSelect2FocusPatched = true;
				$.fn.modal.Constructor.prototype._customerOriginalEnforceFocus = originalEnforceFocus;
			}

			normalizeCustomerDetailSelections();
			$modal.find('select.state-select, select.invoice-company-select, select.customer-staff-select').each(function () {
				var $select = $(this);
				try {
					var selectedValue = getSelectNgModelValue($select);
					if ($select.data('select2')) {
						$select.select2('destroy');
					}
					if (selectedValue !== undefined && selectedValue !== null) {
						$select.val(selectedValue);
					}
					$select.select2({
						width: '100%',
						dropdownParent: $modal
					});
					$select.trigger('change.select2');
				} catch (err) {
					// Keep the modal usable if one select2 instance cannot be rebuilt.
				}
			});
		}, 0, false);
	}

	function showCustomerModalAndRefresh() {
		var $modal = $('#customerFormModal');
		if (!$modal.length) {
			return;
		}

		$modal.off('shown.bs.modal.customerDetailSelect2').one('shown.bs.modal.customerDetailSelect2', function () {
			refreshCustomerDetailSelect2();
		});
		$modal.modal('show');
		refreshCustomerDetailSelect2();
	}

	function normalizePlanType(type) {
		return String(type || '').toLowerCase().replace(/\s+/g, '_').trim();
	}

	function isOneTimePlanType(type) {
		var normalized = normalizePlanType(type);
		return normalized.indexOf('one_time') !== -1 || normalized.indexOf('one_ti') !== -1 || normalized.indexOf('onetime') !== -1;
	}

	function isRepeatingPlanType(type) {
		var normalized = normalizePlanType(type);
		return normalized.indexOf('repeat') !== -1 ||
			normalized.indexOf('monthly') !== -1 ||
			normalized.indexOf('month') !== -1 ||
			normalized.indexOf('yearly') !== -1 ||
			normalized.indexOf('annual') !== -1 ||
			normalized.indexOf('year') !== -1 ||
			normalized.indexOf('quarterly') !== -1 ||
			normalized.indexOf('quarter') !== -1 ||
			normalized.indexOf('recurr') !== -1;
	}

	function getPlanStatusOptions(plan) {
		if (plan && isOneTimePlanType(plan.type)) {
			return [
				{ value: 'active', label: 'Active' },
				{ value: 'completed', label: 'Completed' }
			];
		}

		return [
			{ value: 'active', label: 'Active' },
			{ value: 'inactive', label: 'Inactive' }
		];
	}

	function getPlanBillingStatusOptions() {
		return [
			{ value: 'prepaid', label: 'Prepaid' },
			{ value: 'postpaid', label: 'Postpaid' }
		];
	}

	function normalizeSubscriptionStatusForPlan(planType, status) {
		var normalizedStatus = String(status || '').trim().toLowerCase();
		if (!isOneTimePlanType(planType) && normalizedStatus === 'completed') {
			return 'inactive';
		}

		return normalizedStatus || 'active';
	}

	function attachPlanStatusOptions(plan) {
		if (!plan) {
			return plan;
		}

		plan.status_options = getPlanStatusOptions(plan);
		return plan;
	}

	function normalizePlanBillingStatus(status) {
		var normalizedStatus = String(status || '').trim().toLowerCase();
		if (normalizedStatus === 'postpaid') {
			return 'postpaid';
		}

		return 'prepaid';
	}

	function billingStatusFromPaymentModel(paymentModel) {
		var normalizedPaymentModel = String(paymentModel || '').trim().toLowerCase();
		return normalizedPaymentModel === 'postpaid' ? 'postpaid' : 'prepaid';
	}

	function paymentModelFromBillingStatus(plan) {
		if (!plan) {
			return '';
		}

		if (String(plan.payment_mode || '').toLowerCase() === 'installments' || (angular.isArray(plan.installments) && plan.installments.length && isOneTimePlanType(plan.type))) {
			return 'installment';
		}

		return normalizePlanBillingStatus(plan.billing_status) === 'postpaid' ? 'postpaid' : 'advance';
	}

	function syncPlanPaymentModelFromBillingStatus(plan) {
		if (!plan) {
			return plan;
		}

		plan.billing_status = normalizePlanBillingStatus(firstDefinedValue(plan.billing_status, billingStatusFromPaymentModel(plan.payment_model)));
		plan.payment_model = paymentModelFromBillingStatus(plan);
		return plan;
	}

	function attachPlanBillingStatusOptions(plan) {
		if (!plan) {
			return plan;
		}

		plan.billing_status_options = getPlanBillingStatusOptions();
		return syncPlanPaymentModelFromBillingStatus(plan);
	}

	function createInstallmentRow(installmentNo) {
		return {
			installment_no: installmentNo,
			title: '',
			percentage: '',
			amount: '',
			due_date: '',
			paid_status: '0'
		};
	}

	function todayIsoDate() {
		var now = new Date();
		var month = ('0' + (now.getMonth() + 1)).slice(-2);
		var day = ('0' + now.getDate()).slice(-2);
		return now.getFullYear() + '-' + month + '-' + day;
	}

	function createSubscriptionRowUid(plan, index) {
		customerSubscriptionRowSequence += 1;
		return [
			'customer-subscription',
			plan && plan.subscribed_plan_id ? plan.subscribed_plan_id : 'new',
			plan && plan.qd_id ? plan.qd_id : 'plan',
			plan && plan.version_no ? plan.version_no : (index || 0),
			customerSubscriptionRowSequence
		].join('-');
	}

	function parseInstallmentsFromRow(row) {
		if (!row) {
			return [];
		}

		if (angular.isArray(row.installments)) {
			return cloneInstallments(row.installments);
		}

		if (row.installments_json && typeof row.installments_json === 'string') {
			try {
				var parsedInstallments = JSON.parse(row.installments_json);
				if (angular.isArray(parsedInstallments)) {
					return cloneInstallments(parsedInstallments);
				}
			} catch (err) {
				return [];
			}
		}

		if (row.snapshot_json && typeof row.snapshot_json === 'string') {
			try {
				var parsedSnapshot = JSON.parse(row.snapshot_json);
				if (parsedSnapshot && angular.isArray(parsedSnapshot.installments)) {
					return cloneInstallments(parsedSnapshot.installments);
				}
			} catch (err2) {
				return [];
			}
		}

		return [];
	}

	function cloneInstallments(installments) {
		var rows = [];
		angular.forEach(installments || [], function (item, index) {
			rows.push({
				installment_no: item.installment_no || (index + 1),
				title: item.title || '',
				percentage: item.percentage || item.percent || '',
				amount: item.amount || '',
				due_date: item.due_date || '',
				paid_status: item.paid_status || '0'
			});
		});
		return rows;
	}

	function cloneSubscriptionHistory(historyRows) {
		var rows = [];
		angular.forEach(historyRows || [], function (item, index) {
			rows.push(angular.extend({}, item, {
				version_no: item.version_no || (index + 1)
			}));
		});
		return rows;
	}

	function inferSubscriptionPaymentMode(plan) {
		if (!plan) {
			return '';
		}

		if (plan.payment_mode) {
			return String(plan.payment_mode);
		}

		if (angular.isArray(plan.installments) && plan.installments.length) {
			return 'installments';
		}

		return '';
	}

	function inferSubscriptionPaymentModel(plan) {
		if (!plan) {
			return '';
		}

		if (plan.payment_model) {
			return String(plan.payment_model);
		}

		if (String(plan.payment_mode || '').toLowerCase() === 'installments' || (angular.isArray(plan.installments) && plan.installments.length)) {
			return 'installment';
		}

		var billingStatus = normalizePlanBillingStatus(plan.billing_status);
		if (billingStatus === 'prepaid') {
			return 'advance';
		}
		if (billingStatus === 'postpaid') {
			return 'postpaid';
		}

		if (isOneTimePlanType(plan.type)) {
			return 'advance';
		}

		if (String(plan.payment_mode || '').toLowerCase() === 'repeating') {
			return 'postpaid';
		}

		return '';
	}

	function parseAmount(value) {
		var parsed = parseFloat(value);
		return isFinite(parsed) ? parsed : null;
	}

	function firstDefinedValue() {
		for (var i = 0; i < arguments.length; i++) {
			if (arguments[i] !== undefined && arguments[i] !== null && arguments[i] !== '') {
				return arguments[i];
			}
		}

		return '';
	}

	function formatAmount(value) {
		var parsed = parseAmount(value);
		if (parsed === null) {
			return '';
		}

		return String(Math.round(parsed * 100) / 100);
	}

	function getSubscribedPlanSnapshot(plan) {
		return {
			subscribed_plan_id: plan && plan.subscribed_plan_id ? plan.subscribed_plan_id : '',
			parent_subscribed_plan_id: plan && plan.parent_subscribed_plan_id ? plan.parent_subscribed_plan_id : '',
			version_no: plan && plan.version_no ? plan.version_no : 1,
			is_current: plan && plan.is_current !== undefined ? plan.is_current : 1,
			qd_id: plan && plan.qd_id ? plan.qd_id : '',
			q_id: plan && plan.q_id ? plan.q_id : '',
			quotation_number: plan && plan.quotation_number ? plan.quotation_number : '',
			plan_id: plan && plan.plan_id ? plan.plan_id : (plan && plan.qd_id ? plan.qd_id : ''),
			name: plan && plan.name ? plan.name : '',
			display_name: plan && plan.display_name ? plan.display_name : '',
			type: plan && plan.type ? plan.type : '',
			unit: plan && plan.unit !== undefined ? plan.unit : '',
			mrp: plan && plan.mrp !== undefined ? plan.mrp : '',
			discount: plan && plan.discount !== undefined ? plan.discount : '',
			sp: plan && plan.sp !== undefined ? plan.sp : '',
			payment_model: plan && plan.payment_model ? plan.payment_model : '',
			start_date: plan && plan.start_date ? plan.start_date : '',
			end_date: plan && plan.end_date ? plan.end_date : '',
			payment_status: plan && plan.payment_status ? plan.payment_status : '0',
			payment_mode: plan && plan.payment_mode ? plan.payment_mode : '',
			subscription_status: plan && plan.subscription_status ? plan.subscription_status : 'active',
			billing_status: plan && plan.billing_status ? plan.billing_status : 'prepaid',
			plan_description: plan && plan.plan_description ? plan.plan_description : '',
			change_type: plan && plan.change_type ? plan.change_type : 'initial',
			change_reason: plan && plan.change_reason ? plan.change_reason : '',
			installment_count: angular.isArray(plan && plan.installments) ? plan.installments.length : 0,
			recorded_at: new Date().toISOString()
		};
	}

	function getPlanChangeType(plan) {
		if (!plan) {
			return 'initial';
		}

		if (plan.change_type === 'upgrade' || plan.change_type === 'downgrade' || plan.change_type === 'upgrade_superseded' || plan.change_type === 'downgrade_superseded' || plan.change_type === 'history') {
			return plan.change_type;
		}

		var original = plan._original_snapshot || {};
		var originalMrp = parseAmount(original.mrp);
		var currentMrp = parseAmount(plan.mrp);

		if (original.subscription_status && original.subscription_status !== plan.subscription_status) {
			return 'status_change';
		}

		if (original.payment_mode && original.payment_mode !== plan.payment_mode) {
			return 'payment_mode_change';
		}

		if (originalMrp !== null && currentMrp !== null) {
			if (currentMrp > originalMrp) {
				return 'upgrade';
			}
			if (currentMrp < originalMrp) {
				return 'downgrade';
			}
		}

		if (original.mrp !== plan.mrp || original.discount !== plan.discount || original.unit !== plan.unit || original.start_date !== plan.start_date || original.plan_description !== plan.plan_description || original.payment_status !== plan.payment_status || original.billing_status !== plan.billing_status || original.payment_model !== plan.payment_model) {
			return 'update';
		}

		return 'initial';
	}

	function getPlanRowAction(plan) {
		if (!plan) {
			return 'update';
		}

		if ((plan.change_type === 'upgrade' || plan.change_type === 'downgrade') && (!plan.subscribed_plan_id || plan.subscribed_plan_id === '')) {
			return 'insert';
		}

		if (plan.subscribed_plan_id !== undefined && plan.subscribed_plan_id !== null && plan.subscribed_plan_id !== '') {
			return 'update';
		}

		return 'insert';
	}

	function syncSubscribedPlansPayload() {
		syncSubscriptionDraftStartDateFromInput();

		var payload = [];
		var sourceMode = ($scope.subscriptionDraftMode === 'change' || $scope.subscriptionDraftMode === 'edit') && $scope.subscriptionChangeSourcePlan;
		var sourceRowUid = sourceMode && $scope.subscriptionChangeSourcePlan ? String($scope.subscriptionChangeSourcePlan._row_uid || '') : '';
		var sourceSubscribedPlanId = sourceMode && $scope.subscriptionChangeSourcePlan ? String($scope.subscriptionChangeSourcePlan.subscribed_plan_id || '') : '';
		var draftPlanId = $scope.subscriptionDraftPlan && ($scope.subscriptionDraftPlan.option_id || $scope.subscriptionDraftPlan.qd_id || $scope.subscriptionDraftPlan.plan_id || '');
		var draftPlan = ($scope.subscriptionDraftPlan && draftPlanId !== undefined && draftPlanId !== null && draftPlanId !== '') ? angular.copy($scope.subscriptionDraftPlan) : null;
		var sourceInsertIndex = -1;

		angular.forEach($scope.selectedPlans || [], function (plan) {
			if (sourceMode && plan) {
				if (sourceRowUid && plan._row_uid && String(plan._row_uid) === sourceRowUid) {
					sourceInsertIndex = payload.length;
					return;
				}
				if (sourceSubscribedPlanId && plan.subscribed_plan_id && String(plan.subscribed_plan_id) === sourceSubscribedPlanId) {
					sourceInsertIndex = payload.length;
					return;
				}
			}
			payload.push(angular.copy(plan));
		});
		if (draftPlan) {
			if (sourceMode && sourceInsertIndex >= 0) {
				payload.splice(sourceInsertIndex, 0, draftPlan);
			} else {
				payload.push(draftPlan);
			}
		}
		angular.forEach(payload, function (plan) {
			syncPlanPaymentModelFromBillingStatus(plan);
			plan.change_type = (plan.change_type === 'upgrade' || plan.change_type === 'downgrade' || plan.change_type === 'upgrade_superseded' || plan.change_type === 'downgrade_superseded' || plan.change_type === 'history') ? plan.change_type : getPlanChangeType(plan);
			plan.row_action = getPlanRowAction(plan);
			plan.persist_action = plan.row_action;
			plan.current_snapshot = getSubscribedPlanSnapshot(plan);
			plan.snapshot_json = JSON.stringify(plan.current_snapshot);
		});
		$scope.x.subscribed_plans_json = JSON.stringify(payload);
		$scope.x.removed_subscribed_plan_ids_json = JSON.stringify($scope.removedSubscriptionPlanIds || []);
	}

	function refreshSubscriptionPlanPickerOptions() {
		var baseOptions = [];
		var selectedIds = {};
		angular.forEach($scope.selectedPlans || [], function (plan) {
			var selectedId = plan && (plan.option_id || plan.qd_id || plan.plan_id);
			if (selectedId !== undefined && selectedId !== null && selectedId !== '') {
				selectedIds[String(selectedId)] = true;
			}
		});
		if ($scope.subscriptionDraftPlan) {
			var draftSelectedId = $scope.subscriptionDraftPlan.option_id || $scope.subscriptionDraftPlan.qd_id || $scope.subscriptionDraftPlan.plan_id;
			if (draftSelectedId !== undefined && draftSelectedId !== null && draftSelectedId !== '') {
				selectedIds[String(draftSelectedId)] = true;
			}
		}
		if ($scope.subscriptionDraftMode === 'change' && $scope.subscriptionChangeSourcePlan) {
			var sourceOptionId = String($scope.subscriptionChangeSourcePlan.option_id || $scope.subscriptionChangeSourcePlan.qd_id || $scope.subscriptionChangeSourcePlan.plan_id || '');
			baseOptions = ($scope.subscriptionServicePlans || []).filter(function (plan) {
				var optionId = String(plan.option_id || plan.qd_id || plan.plan_id || '');
				return plan && optionId !== sourceOptionId && !selectedIds[optionId];
			});
		} else {
			var sourceOptions = $scope.subscriptionUsePlanMaster
				? ($scope.planMasterSubscriptionPlans || [])
				: ($scope.availableCustomerQuotationPlans || []);
			baseOptions = sourceOptions.filter(function (plan) {
				var optionId = plan && (plan.option_id || plan.qd_id || plan.plan_id);
				return !!(plan && optionId !== undefined && optionId !== null && optionId !== '' && !selectedIds[String(optionId)]);
			});
		}

		$scope.subscriptionPlanPickerOptions = baseOptions;
	}

	function refreshVisibleSubscriptionPlans() {
		var rows = $scope.selectedPlans || [];
		if ($scope.subscriptionListView === 'active') {
			rows = rows.filter(function (plan) {
				var status = String(plan && plan.subscription_status ? plan.subscription_status : '').toLowerCase();
				var current = plan && (plan.is_current === 1 || plan.is_current === '1' || plan.is_current === true);
				return current && status === 'active';
			});
		} else if ($scope.subscriptionListView === 'history') {
			rows = rows.filter(function (plan) {
				var status = String(plan && plan.subscription_status ? plan.subscription_status : '').toLowerCase();
				var current = plan && (plan.is_current === 1 || plan.is_current === '1' || plan.is_current === true);
				return !current || status === 'inactive' || (isOneTimePlanType(plan && plan.type) && status === 'completed');
			});
		}

		$scope.visibleSubscriptionPlans = rows;
	}

	$scope.set_subscription_list_view = function (view) {
		$scope.subscriptionListView = view || 'all';
		refreshVisibleSubscriptionPlans();
	};

	$scope.reset_subscription_change_mode = function () {
		$scope.subscriptionDraftMode = 'add';
		$scope.subscriptionChangeSourcePlan = null;
		refreshSubscriptionPlanPickerOptions();
	};

	function queueSubscriptionAutoSave() {
		if (!$scope.x || !$scope.x.c_id || $scope.activeCustomerTab !== 'subscription') {
			return;
		}

		$scope.subscriptionAutoSavePending = true;
		if (customerSubscriptionAutoSaveTimer) {
			$timeout.cancel(customerSubscriptionAutoSaveTimer);
		}

		customerSubscriptionAutoSaveTimer = $timeout(function () {
			customerSubscriptionAutoSaveTimer = null;

			if (!$scope.subscriptionAutoSavePending) {
				return;
			}

			$scope.subscriptionAutoSavePending = false;
			$scope.save_customer_subscription($scope.x, { silent: true, autoSave: true });
		}, 600, false);
	}

	function syncPlanAmounts(plan, sourceField) {
		if (!plan) {
			return;
		}

		var mrp = parseAmount(plan.mrp);
		var discount = parseAmount(plan.discount);
		var sp = parseAmount(plan.sp);

		if (sourceField === 'mrp') {
			if (mrp === null) {
				plan.sp = '';
				return;
			}

			discount = discount === null ? 0 : discount;
			plan.sp = formatAmount(mrp - discount);
			return;
		}

		if (sourceField === 'discount') {
			if (mrp === null) {
				plan.sp = '';
				return;
			}

			discount = discount === null ? 0 : discount;
			plan.sp = formatAmount(mrp - discount);
			return;
		}

		if (sourceField === 'sp') {
			if (mrp === null) {
				plan.discount = '';
				return;
			}

			sp = sp === null ? 0 : sp;
			plan.discount = formatAmount(mrp - sp);
			return;
		}

		if (mrp !== null && discount !== null) {
			plan.sp = formatAmount(mrp - discount);
		}
	}

	function syncSubscriptionDraftAmounts(sourceField) {
		if (!$scope.subscriptionDraftPlan) {
			return;
		}

		if ($scope.subscriptionDraftMode === 'change') {
			$scope.subscriptionDraftPlan.unit = '1';
		}

		if (sourceField === 'mrp' || sourceField === 'discount' || sourceField === 'sp') {
			syncPlanAmounts($scope.subscriptionDraftPlan, sourceField);
			return;
		}

		var draftMrp = parseAmount($scope.subscriptionDraftPlan.mrp);
		var draftDiscount = parseAmount($scope.subscriptionDraftPlan.discount);
		var draftSp = parseAmount($scope.subscriptionDraftPlan.sp);

		if (draftMrp !== null && draftSp !== null) {
			$scope.subscriptionDraftPlan.discount = formatAmount(draftMrp - draftSp);
			return;
		}

		if (draftMrp !== null && draftDiscount !== null) {
			$scope.subscriptionDraftPlan.sp = formatAmount(draftMrp - draftDiscount);
		}
	}

	function syncSelectedPlansPayload() {
		angular.forEach($scope.selectedPlans || [], function (plan) {
			syncPlanPaymentModelFromBillingStatus(plan);
		});
		$scope.x.selected_plans_json = JSON.stringify($scope.selectedPlans || []);
	}

	function clearSubscriptionDraftIfMatches(plan) {
		if (!$scope.subscriptionDraftPlan || !plan) {
			return false;
		}

		var draftId = String($scope.subscriptionDraftPlan.option_id || $scope.subscriptionDraftPlan.qd_id || $scope.subscriptionDraftPlan.plan_id || '');
		var targetId = String(plan.option_id || plan.qd_id || plan.plan_id || '');
		if (draftId && targetId && draftId === targetId) {
			$scope.subscriptionDraftPlan = null;
			$scope.subscriptionDraftPlanId = '';
			$scope.x.plan = '';
			return true;
		}

		return false;
	}

	function refreshAvailableCustomerQuotationPlans() {
		var selectedIds = {};
		angular.forEach($scope.selectedPlans || [], function (plan) {
			var selectedId = plan && (plan.option_id || plan.qd_id || plan.plan_id);
			if (selectedId !== undefined && selectedId !== null && selectedId !== '') {
				selectedIds[String(selectedId)] = true;
			}
		});

		if ($scope.subscriptionDraftPlan) {
			var draftSelectedId = $scope.subscriptionDraftPlan.option_id || $scope.subscriptionDraftPlan.qd_id || $scope.subscriptionDraftPlan.plan_id;
			if (draftSelectedId !== undefined && draftSelectedId !== null && draftSelectedId !== '') {
				selectedIds[String(draftSelectedId)] = true;
			}
		}

		$scope.availableCustomerQuotationPlans = ($scope.customerQuotationPlans || []).filter(function (plan) {
			var optionId = plan && (plan.option_id || plan.qd_id || plan.plan_id);
			return !!(plan && optionId !== undefined && optionId !== null && optionId !== '' && !selectedIds[String(optionId)]);
		});
		refreshSubscriptionPlanPickerOptions();
	}

	function normalizeSelectedPlanIds(selectedPlanNames) {
		var selectedIds = angular.isArray(selectedPlanNames) ? selectedPlanNames : [selectedPlanNames];

		return selectedIds.map(function (item) {
			return String(item);
		}).filter(function (item) {
			return item !== '' && item !== 'undefined' && item !== 'null';
		});
	}

	function getCurrentSelectedPlanIds() {
		return ($scope.selectedPlans || []).map(function (plan) {
			return String(plan.option_id || plan.qd_id || plan.plan_id);
		}).filter(function (item) {
			return item !== '';
		});
	}

	function sameSelectedPlanIds(nextIds) {
		var currentIds = getCurrentSelectedPlanIds();
		if (currentIds.length !== nextIds.length) {
			return false;
		}

		for (var i = 0; i < currentIds.length; i++) {
			if (currentIds[i] !== String(nextIds[i])) {
				return false;
			}
		}

		return true;
	}

	function syncSelectedPlanRow(plan) {
		if (!plan) {
			return;
		}

		syncPlanPaymentModelFromBillingStatus(plan);
		plan.change_type = getPlanChangeType(plan);
		syncSelectedPlansPayload();
		syncSubscribedPlansPayload();
	}

	function getSubscriptionDraftStartDateValue() {
		if (!$scope.subscriptionDraftPlan) {
			return '';
		}

		var draftId = $scope.subscriptionDraftPlan._row_uid || $scope.subscriptionDraftPlan.qd_id;
		if (!draftId) {
			return String($scope.subscriptionDraftPlan.start_date || '').trim();
		}

		var $input = $('#customerPlanStart_' + draftId);
		if ($input.length) {
			return String($input.val() || '').trim();
		}

		return String($scope.subscriptionDraftPlan.start_date || '').trim();
	}

	function buildSubscriptionDraftFromQuotation(plan, existing, sourcePlan) {
		var draft = buildSubscribedPlanFromQuotation(plan, existing || null, sourcePlan || null);
		draft.is_draft = true;
		return draft;
	}

	function buildSubscriptionDraftFromExistingRow(plan) {
		var draft = angular.copy(plan || {});
		if (angular.isArray(draft.installments) && draft.installments.length && String(draft.payment_mode || '') !== 'installments') {
			draft.payment_mode = 'installments';
		}
		draft.is_draft = true;
		draft.is_version_draft = true;
		draft._original_snapshot = getSubscribedPlanSnapshot(plan);
		draft._row_uid = plan && plan._row_uid ? plan._row_uid : createSubscriptionRowUid(draft);
		draft.status_options = getPlanStatusOptions(draft);
		draft.billing_status_options = getPlanBillingStatusOptions();
		syncPlanPaymentModelFromBillingStatus(draft);
		return draft;
	}

	function normalizePlanMasterOption(plan, serviceName) {
		if (!plan) {
			return null;
		}

		var planId = plan.plan_id || plan.qd_id || plan.id;
		if (planId === undefined || planId === null || planId === '') {
			return null;
		}

		var planName = plan.name || plan.plan_name || plan.display_name || ('Plan ' + planId);
		var mrp = firstDefinedValue(plan.mrp, plan.plan_mrp, plan.price, plan.amount);
		var sp = firstDefinedValue(plan.sp, plan.sale_price, plan.selling_price);

		return {
			option_id: planId,
			qd_id: plan.qd_id || '',
			plan_id: planId,
			source_type: 'plan_master',
			service_id: plan.service_id || '',
			service_name: serviceName || plan.service_name || '',
			plan_name: planName,
			display_name: planName,
			picker_display_name: (serviceName ? serviceName + ' - ' : '') + planName,
			type: plan.type,
			unit: '1',
			mrp: mrp,
			discount: '',
			sp: sp,
			payment_model: firstDefinedValue(plan.payment_model, isOneTimePlanType(plan.type) ? 'advance' : 'postpaid'),
			billing_status: normalizePlanBillingStatus(firstDefinedValue(plan.billing_status, billingStatusFromPaymentModel(firstDefinedValue(plan.payment_model, isOneTimePlanType(plan.type) ? 'advance' : 'postpaid')))),
			subscription: plan.subscription,
			plan_description: ''
		};
	}

	function normalizeQuotationSubscriptionOption(plan, quotation) {
		if (!plan) {
			return null;
		}

		var qdId = plan.qd_id || plan.qd_id === 0 ? plan.qd_id : plan.plan_id;
		if (!qdId) {
			return null;
		}

		var planName = plan.plan_name || plan.name || plan.plan || ('Plan ' + qdId);

		return {
			qd_id: qdId,
			option_id: qdId,
			plan_id: plan.plan_id || qdId,
			q_id: quotation && quotation.q_id ? quotation.q_id : '',
			quotation_number: quotation && quotation.quotation_number ? quotation.quotation_number : '',
			plan_name: planName,
			display_name: planName,
			picker_display_name: (quotation && quotation.quotation_number ? quotation.quotation_number + ' - ' : '') + planName,
			mrp: plan.mrp,
			discount: plan.discount,
			sp: plan.sp,
			payment_model: plan.payment_model || '',
			subscription: plan.subscription,
			type: plan.type,
			unit: plan.unit,
			plan_description: plan.plan_description || plan.description || ''
		};
	}

	function clearSubscriptionPlanSourceSelection() {
		$scope.subscriptionDraftPlan = null;
		$scope.subscriptionDraftPlanId = '';
		$scope.x.plan = '';
		syncSubscribedPlansPayload();
		refreshSubscriptionPlanPickerOptions();
		scheduleCustomerSubscriptionUiInit();
	}

	function loadPlanMasterSubscriptionPlans() {
		$scope.planMasterSubscriptionPlansLoading = true;
		return $http.get(rootUrl + "plan_master/view?status=1").then(function (response) {
			var plans = normalizeArrayResponse(response);
			$scope.planMasterSubscriptionPlans = plans.map(function (plan) {
				return normalizePlanMasterOption(plan, plan && (plan.service_name || plan.service || ''));
			}).filter(function (plan) {
				return !!plan;
			});
			refreshSubscriptionPlanPickerOptions();
			return $scope.planMasterSubscriptionPlans;
		}).catch(function () {
			$scope.planMasterSubscriptionPlans = [];
			refreshSubscriptionPlanPickerOptions();
			return [];
		}).finally(function () {
			$scope.planMasterSubscriptionPlansLoading = false;
			$scope.subscriptionPlanPickerLoading = false;
			scheduleCustomerSubscriptionUiInit();
		});
	}

	function loadServicePlansForSubscriptionChange(sourcePlan) {
		var sourcePlanId = sourcePlan && (sourcePlan.plan_id || sourcePlan.qd_id || sourcePlan.id);
		if (!sourcePlanId) {
			$scope.subscriptionServicePlans = [];
			$scope.subscriptionServiceName = '';
			$scope.subscriptionPlanPickerLoading = false;
			refreshSubscriptionPlanPickerOptions();
			return $q.when([]);
		}

		$scope.subscriptionPlanPickerLoading = true;
		$scope.subscriptionServicePlans = [];
		$scope.subscriptionServiceName = '';
		refreshSubscriptionPlanPickerOptions();

		return $http.get(rootUrl + "customer/get_service_plans?plan_id=" + encodeURIComponent(sourcePlanId)).then(function (response) {
			var payload = response && response.data ? response.data : {};
			var serviceName = payload.service_name || '';
			var plans = payload.plans || [];
			$scope.subscriptionServiceName = serviceName;
			$scope.subscriptionServicePlans = plans.map(function (plan) {
				return normalizePlanMasterOption(plan, serviceName);
			}).filter(function (plan) {
				return !!plan;
			});
			refreshSubscriptionPlanPickerOptions();
			return $scope.subscriptionServicePlans;
		}).catch(function () {
			$scope.subscriptionServicePlans = [];
			$scope.subscriptionServiceName = '';
			refreshSubscriptionPlanPickerOptions();
			return [];
		}).finally(function () {
			$scope.subscriptionPlanPickerLoading = false;
			scheduleCustomerSubscriptionUiInit();
		});
	}

	function syncSubscriptionDraftStartDateFromInput() {
		if (!$scope.subscriptionDraftPlan) {
			return '';
		}

		var nextDate = getSubscriptionDraftStartDateValue();
		if (nextDate !== ($scope.subscriptionDraftPlan.start_date || '')) {
			$scope.subscriptionDraftPlan.start_date = nextDate;
		}

		return nextDate;
	}

	function buildSubscribedPlanFromQuotation(plan, existing, sourcePlan) {
		var snapshotSource = sourcePlan || existing || null;
		var sourceParentId = sourcePlan && sourcePlan.subscribed_plan_id ? sourcePlan.subscribed_plan_id : '';
		var sourceVersionNo = snapshotSource && snapshotSource.version_no ? (parseInt(snapshotSource.version_no, 10) || 1) : 1;
		var isPlanMasterSource = String(plan && plan.source_type ? plan.source_type : '') === 'plan_master';
		var unit = isPlanMasterSource ? '' : firstDefinedValue(plan.unit, plan.units, plan.no_of_unit, plan.quantity, plan.qty, plan.plan_unit, '');
		var discount = isPlanMasterSource ? '' : firstDefinedValue(plan.discount, plan.plan_discount, plan.discount_amount, plan.discount_value, '');
		var mrp = firstDefinedValue(plan.mrp, plan.plan_mrp, plan.price, plan.amount, '');
		var sp = firstDefinedValue(plan.sp, plan.sale_price, plan.selling_price, '');
		var planDescription = isPlanMasterSource ? '' : firstDefinedValue(plan.plan_description, plan.description, '');
		var fallbackPaymentModel = firstDefinedValue(
			plan.payment_model,
			existing && existing.payment_model,
			(String(plan.payment_mode || (existing && existing.payment_mode) || '').toLowerCase() === 'installments' || (angular.isArray(plan.installments) && plan.installments.length))
				? 'installment'
				: (isOneTimePlanType(plan.type) ? 'advance' : 'postpaid')
		);
		var billingStatus = normalizePlanBillingStatus(firstDefinedValue(plan.billing_status, existing && existing.billing_status, billingStatusFromPaymentModel(fallbackPaymentModel)));
		var planName = firstDefinedValue(plan.plan_name, plan.name, plan.plan, ('Plan ' + (plan.qd_id || plan.plan_id || '')));

		var mergedPlan = angular.extend({}, existing || {}, {
			qd_id: plan.qd_id || '',
			option_id: plan.option_id || plan.qd_id || plan.plan_id || '',
			plan_id: plan.plan_id || plan.qd_id,
			q_id: plan.q_id,
			quotation_number: plan.quotation_number,
			name: planName,
			display_name: planName,
			service_id: plan.service_id || '',
			service_name: plan.service_name || '',
			type: plan.type,
			unit: unit,
			mrp: mrp,
			discount: discount,
			sp: sp,
			payment_model: fallbackPaymentModel,
			start_date: '',
			payment_status: existing && existing.payment_status ? existing.payment_status : '0',
			payment_mode: plan.type ? (isOneTimePlanType(plan.type) ? 'all' : 'repeating') : (existing && existing.payment_mode ? existing.payment_mode : ''),
			subscription_status: normalizeSubscriptionStatusForPlan(plan.type, existing && existing.subscription_status ? existing.subscription_status : 'active'),
			billing_status: billingStatus,
			installments: sourcePlan ? [] : (existing && angular.isArray(existing.installments) ? cloneInstallments(existing.installments) : []),
			subscription_history: sourcePlan ? [] : (existing && angular.isArray(existing.subscription_history) ? cloneSubscriptionHistory(existing.subscription_history) : []),
			plan_description: planDescription,
			subscribed_plan_id: existing && existing.subscribed_plan_id ? existing.subscribed_plan_id : '',
			parent_subscribed_plan_id: sourceParentId || (existing && existing.parent_subscribed_plan_id ? existing.parent_subscribed_plan_id : ''),
			source_subscribed_plan_id: snapshotSource && snapshotSource.subscribed_plan_id ? snapshotSource.subscribed_plan_id : '',
			version_no: sourcePlan ? (sourceVersionNo + 1) : (existing && existing.version_no ? existing.version_no : 1),
			is_current: sourcePlan ? 1 : (existing && existing.is_current !== undefined ? existing.is_current : 1),
			end_date: existing && existing.end_date ? existing.end_date : '',
			change_reason: existing && existing.change_reason ? existing.change_reason : ''
		});

		mergedPlan.payment_mode = inferSubscriptionPaymentMode(mergedPlan);

		attachPlanStatusOptions(mergedPlan);
		attachPlanBillingStatusOptions(mergedPlan);
		mergedPlan._original_snapshot = getSubscribedPlanSnapshot(snapshotSource || mergedPlan);
		mergedPlan.change_type = 'initial';
		mergedPlan.show_history = false;
		mergedPlan._row_uid = createSubscriptionRowUid(mergedPlan);

		if (mergedPlan.payment_mode === 'installments' && !mergedPlan.installments.length) {
			mergedPlan.installments.push(createInstallmentRow(1));
		}

		return mergedPlan;
	}

	function decorateLoadedSubscriptionRow(row, index) {
		var plan = buildSubscribedPlanFromQuotation({
			qd_id: row.qd_id || '',
			option_id: row.option_id || row.qd_id || row.plan_id || '',
			q_id: row.q_id,
			quotation_number: row.quotation_number || '',
			plan_name: row.plan_name || row.display_name || '',
			display_name: row.plan_name || row.display_name || '',
			plan_id: row.plan_id || row.qd_id,
			service_id: row.service_id || '',
			service_name: row.service_name || '',
			type: row.type,
			unit: row.unit,
			mrp: row.mrp,
			discount: row.discount,
			sp: row.sp,
			plan_description: row.plan_description || '',
			payment_status: row.payment_status,
			payment_mode: row.payment_mode,
			payment_model: row.payment_model || '',
			subscription_status: row.subscription_status,
			billing_status: row.billing_status || ''
		}, row, row);

		plan.subscribed_plan_id = row.subscribed_plan_id || row.id || '';
		plan.parent_subscribed_plan_id = row.parent_subscribed_plan_id || '';
		plan.version_no = row.version_no || plan.version_no || 1;
		plan.is_current = row.is_current !== undefined ? row.is_current : 1;
		plan.change_type = row.change_type || plan.change_type || 'initial';
		plan.change_reason = row.change_reason || '';
		plan.start_date = row.start_date || plan.start_date || '';
		plan.end_date = row.end_date || '';
		plan.installments = parseInstallmentsFromRow(row);
		if (plan.installments.length && String(plan.payment_mode || '') !== 'installments') {
			plan.payment_mode = 'installments';
		}
		plan.subscription_status = normalizeSubscriptionStatusForPlan(plan.type, plan.subscription_status);
		plan.snapshot_json = row.snapshot_json || '';
		plan._original_snapshot = getSubscribedPlanSnapshot(plan);
		plan._row_uid = row._row_uid || createSubscriptionRowUid(plan, index);
		attachPlanStatusOptions(plan);
		return attachPlanBillingStatusOptions(plan);
	}

	function findSelectedPlanIndex(plan) {
		if (!plan) {
			return -1;
		}

		var rowUid = plan._row_uid || '';
		var planKey = String(plan.option_id || plan.qd_id || plan.plan_id || '');
		for (var i = 0; i < ($scope.selectedPlans || []).length; i++) {
			var item = $scope.selectedPlans[i];
			if (!item) {
				continue;
			}
			if (rowUid && item._row_uid === rowUid) {
				return i;
			}
			if (!rowUid && planKey && String(item.option_id || item.qd_id || item.plan_id || '') === planKey) {
				return i;
			}
		}

		return -1;
	}

	function findSelectedPlanByQuotationId(qdId) {
		var currentMatch = null;
		var fallbackMatch = null;

		angular.forEach($scope.selectedPlans || [], function (plan) {
			if (!plan || String(plan.qd_id) !== String(qdId)) {
				return;
			}

			if (plan.is_current === 1 || plan.is_current === '1' || plan.is_current === true) {
				currentMatch = plan;
				return;
			}

			if (!fallbackMatch) {
				fallbackMatch = plan;
			}
		});

		return currentMatch || fallbackMatch;
	}

	function findSubscriptionPlanByRowIdentifier(rowIdentifier) {
		var target = String(rowIdentifier || '');
		var plan = null;

		angular.forEach($scope.selectedPlans || [], function (item) {
			if (plan || !item) {
				return;
			}

			if (item._row_uid && String(item._row_uid) === target) {
				plan = item;
				return;
			}

			if (String(item.option_id || item.qd_id || item.plan_id || '') === target) {
				plan = item;
			}
		});

		if (!plan && $scope.subscriptionDraftPlan) {
			if (($scope.subscriptionDraftPlan._row_uid && String($scope.subscriptionDraftPlan._row_uid) === target) || String($scope.subscriptionDraftPlan.option_id || $scope.subscriptionDraftPlan.qd_id || $scope.subscriptionDraftPlan.plan_id || '') === target) {
				plan = $scope.subscriptionDraftPlan;
			}
		}

		return plan;
	}

	function markPlanAsHistorical(plan, changeType) {
		if (!plan) {
			return;
		}

		plan.is_current = 0;
		if (plan.subscription_status === 'active') {
			plan.subscription_status = 'inactive';
		}
		plan.end_date = plan.end_date || todayIsoDate();
		plan.change_type = changeType || 'history';
		plan.change_reason = plan.change_reason || 'Superseded by a newer version.';
	}

	function createVersionedPlanRow(plan, direction) {
		if (!plan) {
			return null;
		}

		var versionPlan = angular.copy(plan);
		versionPlan.subscribed_plan_id = '';
		versionPlan.parent_subscribed_plan_id = plan.subscribed_plan_id || plan.parent_subscribed_plan_id || '';
		versionPlan.version_no = (parseInt(plan.version_no, 10) || 1) + 1;
		versionPlan.is_current = 1;
		versionPlan.change_type = direction || 'upgrade';
		versionPlan.change_reason = direction === 'downgrade' ? 'Downgraded from previous plan.' : 'Upgraded from previous plan.';
		versionPlan.subscription_status = 'active';
		versionPlan.payment_status = plan.payment_status || '0';
		versionPlan.billing_status = normalizePlanBillingStatus(plan.billing_status);
		versionPlan.start_date = '';
		versionPlan.end_date = '';
		versionPlan.is_draft = true;
		versionPlan.is_version_draft = true;
		versionPlan._row_uid = createSubscriptionRowUid(versionPlan);
		versionPlan._original_snapshot = getSubscribedPlanSnapshot(plan);
		attachPlanStatusOptions(versionPlan);
		attachPlanBillingStatusOptions(versionPlan);
		return versionPlan;
	}

	function insertVersionedPlanRow(plan, direction) {
		var currentIndex = findSelectedPlanIndex(plan);
		if (currentIndex === -1) {
			return;
		}

		var currentPlan = $scope.selectedPlans[currentIndex];
		if (!currentPlan) {
			return;
		}

		markPlanAsHistorical(currentPlan, direction === 'downgrade' ? 'downgrade_superseded' : 'upgrade_superseded');
		var newPlan = createVersionedPlanRow(currentPlan, direction);
		if (!newPlan) {
			return;
		}

		$scope.selectedPlans.splice(currentIndex + 1, 0, newPlan);
		syncSelectedPlansPayload();
		syncSubscribedPlansPayload();
		refreshAvailableCustomerQuotationPlans();
		scheduleCustomerSubscriptionUiInit();
	}

	function initCustomerPlanDatepickers() {
		$timeout(function () {
			if (!$.fn.datepicker) {
				return;
			}

			$('.customer-plan-start-date').each(function () {
				var $el = $(this);
				if ($el.data('customer-datepicker-bound')) {
					return;
				}
				$el.data('customer-datepicker-bound', true);
				$el.datepicker({ format: 'yyyy-mm-dd', autoclose: true, todayHighlight: true }).off('changeDate').on('changeDate', function (e) {
					var match = (this.id || '').match(/^customerPlanStart_(.+)$/);
					if (!match) {
						return;
					}
					var rowId = match[1];
					var plan = findSubscriptionPlanByRowIdentifier(rowId);
					if (!plan) {
						return;
					}
					$scope.$apply(function () {
						var nextDate = e.format(0, 'yyyy-mm-dd');
						if (plan.start_date === nextDate) {
							return;
						}
						plan.start_date = nextDate;
						syncSelectedPlansPayload();
						syncSubscribedPlansPayload();
					});
				});
				$el.off('input.customerDraftStart blur.customerDraftStart keyup.customerDraftStart change.customerDraftStart').on('input.customerDraftStart blur.customerDraftStart keyup.customerDraftStart change.customerDraftStart', function () {
					var match = (this.id || '').match(/^customerPlanStart_(.+)$/);
					if (!match) {
						return;
					}
					var rowId = match[1];
					var plan = findSubscriptionPlanByRowIdentifier(rowId);
					if (!plan || plan !== $scope.subscriptionDraftPlan) {
						return;
					}
					$scope.$applyAsync(function () {
						syncSubscriptionDraftStartDateFromInput();
						$scope.subscriptionDraftPlan.change_type = getPlanChangeType($scope.subscriptionDraftPlan);
						syncSubscribedPlansPayload();
					});
				});
			});

			$('.customer-installment-due-date').each(function () {
				var $el = $(this);
				if ($el.data('customer-datepicker-bound')) {
					return;
				}
				$el.data('customer-datepicker-bound', true);
				$el.datepicker({ format: 'yyyy-mm-dd', autoclose: true, todayHighlight: true }).off('changeDate').on('changeDate', function (e) {
					var match = (this.id || '').match(/^customerInstallmentDue_(.+)_(\d+)$/);
					if (!match) {
						return;
					}
					var rowId = match[1];
					var index = parseInt(match[2], 10);
					var plan = null;
					if ($scope.subscriptionDraftPlan && String($scope.subscriptionDraftPlan._row_uid || '') === String(rowId)) {
						plan = $scope.subscriptionDraftPlan;
					} else {
						plan = findSubscriptionPlanByRowIdentifier(rowId);
					}
					if (!plan || !plan.installments || !plan.installments[index]) {
						return;
					}
					$scope.$apply(function () {
						var nextDate = e.format(0, 'yyyy-mm-dd');
						if (plan.installments[index].due_date === nextDate) {
							return;
						}
						plan.installments[index].due_date = nextDate;
						syncSelectedPlansPayload();
						syncSubscribedPlansPayload();
					});
				});
			});
		}, 0, false);
	}

	$scope.selectedPlans = [];

	$scope.on_customer_tab_change = function (tab) {
		if ((tab || 'details') === $scope.activeCustomerTab) {
			return;
		}

		$scope.activeCustomerTab = tab || 'details';
		$timeout(function () {
			var $modalBody = $('#customerFormModal .modal-body');
			if ($modalBody.length) {
				$modalBody.scrollTop(0);
			}
			if ($scope.activeCustomerTab === 'details') {
				refreshCustomerDetailSelect2();
			} else {
				scheduleCustomerSubscriptionUiInit();
			}
		}, 0, false);
	};

	$scope.$watchCollection('customerQuotationPlans', function () {
		refreshAvailableCustomerQuotationPlans();
		refreshSubscriptionPlanPickerOptions();
		scheduleCustomerSubscriptionUiInit();
	});

	$scope.$watchCollection('selectedPlans', function () {
		syncSelectedPlansPayload();
		syncSubscribedPlansPayload();
		refreshAvailableCustomerQuotationPlans();
		refreshSubscriptionPlanPickerOptions();
		refreshVisibleSubscriptionPlans();
		scheduleCustomerSubscriptionUiInit();
	});

	$scope.$watch('subscriptionDraftPlan.start_date', function (newValue, oldValue) {
		if (newValue === oldValue || !$scope.subscriptionDraftPlan) {
			return;
		}

		$scope.subscriptionDraftPlan.change_type = getPlanChangeType($scope.subscriptionDraftPlan);
		syncSubscribedPlansPayload();
	});

	$scope.$watch('subscriptionListView', function () {
		refreshVisibleSubscriptionPlans();
	});

	$scope.is_one_time_plan = function (plan) {
		return !!(plan && isOneTimePlanType(plan.type));
	};

	$scope.get_plan_status_options = function (plan) {
		return getPlanStatusOptions(plan);
	};

	$scope.get_subscription_row_badge_class = function (plan) {
		if (!plan) {
			return 'badge-default';
		}

		if (plan.change_type === 'upgrade' || plan.change_type === 'downgrade') {
			return 'badge-warning';
		}

		if (isOneTimePlanType(plan.type) && plan.subscription_status === 'completed') {
			return 'badge-success';
		}

		if (plan.is_current === 0 || plan.is_current === '0' || plan.is_current === false) {
			return 'badge-muted';
		}

		return 'badge-primary';
	};

	$scope.get_subscription_row_badge_label = function (plan) {
		if (!plan) {
			return '';
		}

		if (plan.change_type === 'upgrade') {
			return 'Upgraded';
		}

		if (plan.change_type === 'downgrade') {
			return 'Downgraded';
		}

		if (isOneTimePlanType(plan.type) && plan.subscription_status === 'completed') {
			return 'Completed';
		}

		if (plan.is_current === 0 || plan.is_current === '0' || plan.is_current === false) {
			return 'History';
		}

		return 'Current';
	};

	$scope.subscription_row_visible = function (plan) {
		var status = String(plan && plan.subscription_status ? plan.subscription_status : '').toLowerCase();
		var current = plan && (plan.is_current === 1 || plan.is_current === '1' || plan.is_current === true);

		if ($scope.subscriptionListView === 'active') {
			return !!(plan && current && status === 'active');
		}

		if ($scope.subscriptionListView === 'history') {
			return !!(plan && (!current || status === 'inactive' || (isOneTimePlanType(plan.type) && status === 'completed')));
		}

		return !!plan;
	};

	$scope.is_subscription_row_editable = function (plan) {
		return !!(plan && (plan.is_current === 1 || plan.is_current === '1' || plan.is_current === true || plan.is_version_draft));
	};

	$scope.add_installment = function (plan) {
		if (!plan) {
			return;
		}

		if (!angular.isArray(plan.installments)) {
			plan.installments = [];
		}

		plan.installments.push(createInstallmentRow(plan.installments.length + 1));
		syncSelectedPlansPayload();
		scheduleCustomerSubscriptionUiInit();
	};

	$scope.remove_installment = function (plan, index) {
		if (!plan || !angular.isArray(plan.installments)) {
			return;
		}

		plan.installments.splice(index, 1);
		angular.forEach(plan.installments, function (item, i) {
			item.installment_no = i + 1;
		});
		syncSelectedPlansPayload();
		scheduleCustomerSubscriptionUiInit();
	};

	$scope.on_payment_mode_change = function (plan) {
		if (!plan) {
			return;
		}

		if (plan.payment_mode === 'installments' && (!angular.isArray(plan.installments) || !plan.installments.length)) {
			plan.installments = [createInstallmentRow(1)];
		}

		syncPlanPaymentModelFromBillingStatus(plan);
		plan.change_type = getPlanChangeType(plan);
		syncSelectedPlansPayload();
		syncSubscribedPlansPayload();
		scheduleCustomerSubscriptionUiInit();
	};

	$scope.on_plan_mrp_change = function (plan) {
		syncPlanAmounts(plan, 'mrp');
		plan.change_type = getPlanChangeType(plan);
		syncSelectedPlansPayload();
		syncSubscribedPlansPayload();
	};

	$scope.on_plan_discount_change = function (plan) {
		syncPlanAmounts(plan, 'discount');
		plan.change_type = getPlanChangeType(plan);
		syncSelectedPlansPayload();
		syncSubscribedPlansPayload();
	};

	$scope.on_plan_sp_change = function (plan) {
		syncPlanAmounts(plan, 'sp');
		plan.change_type = getPlanChangeType(plan);
		syncSelectedPlansPayload();
		syncSubscribedPlansPayload();
	};

	$scope.on_plan_status_change = function (plan) {
		if (!plan) {
			return;
		}

		if (plan.subscription_status === 'inactive' || (isOneTimePlanType(plan.type) && plan.subscription_status === 'completed')) {
			$scope.subscriptionListView = 'history';
		}

		plan.change_type = getPlanChangeType(plan);
		syncSelectedPlansPayload();
		syncSubscribedPlansPayload();
		refreshVisibleSubscriptionPlans();

		if ($scope.x && $scope.x.c_id) {
			$scope.subscriptionAutoSavePending = false;
			$scope.save_customer_subscription($scope.x, { silent: true, autoSave: true });
		}
	};

	$scope.on_plan_payment_status_change = function (plan) {
		if (!plan) {
			return;
		}

		plan.change_type = getPlanChangeType(plan);
		syncSelectedPlansPayload();
		syncSubscribedPlansPayload();
		refreshVisibleSubscriptionPlans();
		queueSubscriptionAutoSave();
	};

	$scope.on_plan_mode_change = function (plan) {
		if (!plan) {
			return;
		}

		if (plan.payment_mode === 'installments' && (!angular.isArray(plan.installments) || !plan.installments.length)) {
			plan.installments = [createInstallmentRow(1)];
		}

		plan.change_type = getPlanChangeType(plan);
		syncSelectedPlansPayload();
		syncSubscribedPlansPayload();
		refreshVisibleSubscriptionPlans();
	};

	$scope.on_subscription_row_status_change = function (plan) {
		if (!plan) {
			return;
		}

		if (plan.subscription_status === 'inactive' || (isOneTimePlanType(plan.type) && plan.subscription_status === 'completed')) {
			$scope.subscriptionListView = 'history';
		}

		plan.change_type = getPlanChangeType(plan);
		syncSelectedPlansPayload();
		syncSubscribedPlansPayload();
		refreshVisibleSubscriptionPlans();

		if ($scope.x && $scope.x.c_id) {
			$scope.subscriptionAutoSavePending = false;
			$scope.save_customer_subscription($scope.x, { silent: true, autoSave: true });
		}
	};

	$scope.on_subscription_row_payment_change = function (plan) {
		if (!plan) {
			return;
		}

		plan.change_type = getPlanChangeType(plan);
		syncSelectedPlansPayload();
		syncSubscribedPlansPayload();
		refreshVisibleSubscriptionPlans();
		queueSubscriptionAutoSave();
	};

	$scope.on_subscription_row_field_change = function (plan) {
		if (!plan) {
			return;
		}

		syncPlanPaymentModelFromBillingStatus(plan);
		plan.change_type = getPlanChangeType(plan);
		if (plan.change_type === 'upgrade' || plan.change_type === 'downgrade') {
			plan.is_version_draft = true;
		}
		syncSelectedPlansPayload();
		syncSubscribedPlansPayload();
		refreshVisibleSubscriptionPlans();
		queueSubscriptionAutoSave();
	};

	$scope.change_subscription_plan = function (plan) {
		if (!plan) {
			return;
		}

		$scope.on_customer_tab_change('subscription');
		$scope.subscriptionDraftMode = 'change';
		$scope.subscriptionChangeSourcePlan = plan;
		$scope.subscriptionDraftPlan = null;
		$scope.subscriptionDraftPlanId = '';
		$scope.x.plan = '';
		$scope.subscriptionServicePlans = [];
		$scope.subscriptionServiceName = '';
		$scope.subscriptionPlanPickerLoading = true;
		refreshSubscriptionPlanPickerOptions();
		scrollCustomerSubscriptionPickerIntoView();
		loadServicePlansForSubscriptionChange(plan).then(function () {
			scrollCustomerSubscriptionPickerIntoView();
		});
		scheduleCustomerSubscriptionUiInit();
	};

	$scope.edit_subscription_plan = function (plan) {
		if (!plan) {
			return;
		}

		$scope.on_customer_tab_change('subscription');
		$scope.subscriptionDraftMode = 'edit';
		$scope.subscriptionChangeSourcePlan = plan;
		$scope.subscriptionDraftPlan = buildSubscriptionDraftFromExistingRow(plan);
		syncSubscriptionDraftStartDateFromInput();
		$scope.subscriptionDraftPlanId = String($scope.subscriptionDraftPlan.option_id || $scope.subscriptionDraftPlan.qd_id || $scope.subscriptionDraftPlan.plan_id || '');
		$scope.x.plan = '';
		refreshSubscriptionPlanPickerOptions();
		syncSubscribedPlansPayload();
		scheduleCustomerSubscriptionUiInit();
		$timeout(function () {
			var $modalBody = $('#customerFormModal .modal-body');
			if ($modalBody.length) {
				$modalBody.scrollTop(0);
			}
		}, 0, false);
	};

	$scope.cancel_subscription_plan_change = function () {
		$scope.reset_subscription_change_mode();
		$scope.subscriptionDraftPlan = null;
		$scope.subscriptionDraftPlanId = '';
		$scope.x.plan = '';
		$scope.subscriptionServicePlans = [];
		$scope.subscriptionServiceName = '';
		$scope.subscriptionPlanPickerLoading = false;
		syncSubscribedPlansPayload();
		refreshAvailableCustomerQuotationPlans();
		refreshVisibleSubscriptionPlans();
	};

	$scope.plan_change = function (selectedPlanNames) {
		var selectedIds = normalizeSelectedPlanIds(selectedPlanNames);
		if (!selectedIds.length) {
			$scope.subscriptionDraftPlan = null;
			$scope.subscriptionDraftPlanId = '';
			$scope.x.plan = '';
			syncSubscribedPlansPayload();
			refreshAvailableCustomerQuotationPlans();
			refreshSubscriptionPlanPickerOptions();
			return;
		}

		var planName = selectedIds[0];
		var optionSource = $scope.subscriptionPlanPickerOptions || [];
		var plan = optionSource.find(function (p) { return String(p.option_id || p.qd_id || p.plan_id) === String(planName); });
		if (!plan) {
			$scope.x.plan = '';
			return;
		}

		var existing = $scope.subscriptionDraftMode === 'change' && $scope.subscriptionChangeSourcePlan ? $scope.subscriptionChangeSourcePlan : findSelectedPlanByQuotationId(planName);

		$scope.subscriptionDraftPlan = buildSubscriptionDraftFromQuotation(plan, existing, $scope.subscriptionDraftMode === 'change' ? $scope.subscriptionChangeSourcePlan : null);
		if ($scope.subscriptionDraftPlan && $scope.subscriptionUsePlanMaster) {
			$scope.subscriptionDraftPlan.unit = '1';
			syncSubscriptionDraftAmounts();
			$scope.subscriptionDraftPlan.plan_description = '';
		}
		if ($scope.subscriptionDraftMode === 'change' && $scope.subscriptionDraftPlan) {
			$scope.subscriptionDraftPlan.unit = '1';
			syncSubscriptionDraftAmounts();
			$scope.subscriptionDraftPlan.plan_description = '';
		}
		$scope.subscriptionDraftPlanId = String($scope.subscriptionDraftPlan.option_id || $scope.subscriptionDraftPlan.qd_id || $scope.subscriptionDraftPlan.plan_id || '');
		$scope.x.plan = '';
		if ($scope.subscriptionDraftMode === 'change' && $scope.subscriptionChangeSourcePlan) {
			$scope.subscriptionDraftPlan.change_type = getPlanChangeType($scope.subscriptionDraftPlan);
		}
		syncSubscribedPlansPayload();
		refreshAvailableCustomerQuotationPlans();
		refreshSubscriptionPlanPickerOptions();
		scheduleCustomerSubscriptionUiInit();
	};

	$scope.on_subscription_plan_picker_change = function () {
		$scope.plan_change($scope.x.plan);
	};

	$scope.on_draft_plan_field_change = function (sourceField) {
		if (!$scope.subscriptionDraftPlan) {
			return;
		}

		if ($scope.subscriptionDraftMode === 'change') {
			$scope.subscriptionDraftPlan.unit = '1';
		}

		syncSubscriptionDraftStartDateFromInput();
		if (sourceField === 'mrp' || sourceField === 'discount' || sourceField === 'sp') {
			syncSubscriptionDraftAmounts(sourceField);
		} else {
			syncSubscriptionDraftAmounts();
		}
		syncPlanPaymentModelFromBillingStatus($scope.subscriptionDraftPlan);
		$scope.subscriptionDraftPlan.change_type = getPlanChangeType($scope.subscriptionDraftPlan);
		syncSubscribedPlansPayload();
	};

	$scope.remove_selected_plan = function (plan) {
		if (!plan || (plan.qd_id === undefined || plan.qd_id === null) && (plan.plan_id === undefined || plan.plan_id === null)) {
			return;
		}

		clearSubscriptionDraftIfMatches(plan);

		var targetId = plan._row_uid || String(plan.option_id || plan.qd_id || plan.plan_id);
		$scope.selectedPlans = ($scope.selectedPlans || []).filter(function (item) {
			if (item._row_uid) {
				return item._row_uid !== targetId;
			}
			return String(item.option_id || item.qd_id || item.plan_id) !== String(plan.option_id || plan.qd_id || plan.plan_id);
		});
		if (plan.subscribed_plan_id !== undefined && plan.subscribed_plan_id !== null && plan.subscribed_plan_id !== '') {
			$scope.removedSubscriptionPlanIds.push(String(plan.subscribed_plan_id));
		}
		syncSelectedPlansPayload();
		syncSubscribedPlansPayload();
		refreshAvailableCustomerQuotationPlans();
		scheduleCustomerSubscriptionUiInit();

		if ($scope.x && $scope.x.c_id) {
			$scope.subscriptionAutoSavePending = false;
			$scope.save_customer_subscription($scope.x, { silent: true, autoSave: true });
		}
	};

	$scope.sync_subscription_plans = function () {
		var selectedPlans = ($scope.x && $scope.x.plan) ? $scope.x.plan : [];

		if (typeof selectedPlans === 'string') {
			try {
				var parsedPlans = JSON.parse(selectedPlans);
				if (angular.isArray(parsedPlans)) {
					selectedPlans = parsedPlans;
				}
			} catch (err) {
				selectedPlans = selectedPlans.split(',').map(function (item) {
					return item.trim();
				}).filter(function (item) {
					return item;
				});
			}
		}

		if (angular.isArray(selectedPlans) && selectedPlans.length && angular.isObject(selectedPlans[0])) {
			selectedPlans = selectedPlans.map(function (item) {
				return item.option_id || item.qd_id || item.plan_id;
			}).filter(function (item) {
				return item !== undefined && item !== null && item !== '';
			});
		}

		if (!angular.isArray(selectedPlans) || !selectedPlans.length || !$scope.customerQuotationPlans || !$scope.customerQuotationPlans.length) {
			return;
		}

		$scope.x.plan = selectedPlans;
		$scope.plan_change(selectedPlans);
	};

	$scope.load_customer_quotation_plans = function (c_id) {
		$scope.customerQuotationPlans = [];

		if (!c_id) {
			$scope.x.plan = '';
			$scope.x.selected_plans_json = "";
			$scope.x.subscribed_plans_json = "";
			$scope.x.removed_subscribed_plan_ids_json = "[]";
			$scope.availableCustomerQuotationPlans = [];
			$scope.subscriptionServicePlans = [];
			$scope.subscriptionServiceName = '';
			$scope.subscriptionPlanPickerLoading = false;
			$scope.subscriptionDraftPlan = null;
			$scope.subscriptionDraftPlanId = '';
			$scope.removedSubscriptionPlanIds = [];
			$scope.reset_subscription_change_mode();
			syncSelectedPlansPayload();
			syncSubscribedPlansPayload();
			return;
		}

		return $http.get(rootUrl + "quotation/view?c_id=" + encodeURIComponent(c_id)).then(function (response) {
			var quotations = normalizeArrayResponse(response);
			if (!quotations.length) {
				$scope.reset_subscription_change_mode();
				scheduleCustomerSubscriptionUiInit();
				return [];
			}

			var planRequests = [];

			angular.forEach(quotations, function (quotation) {
				if (!quotation || !quotation.q_id) {
					return;
				}

				planRequests.push(
					$http.get(rootUrl + "quotation/get_quotation_details?q_id=" + encodeURIComponent(quotation.q_id) + "&data=qd_id,plan_name,plan_id,discount,mrp,sp,subscription,type,unit").then(function (detailResponse) {
						var plans = normalizeArrayResponse(detailResponse);

						angular.forEach(plans, function (plan) {
							var option = normalizeQuotationSubscriptionOption(plan, quotation);
							if (option) {
								$scope.customerQuotationPlans.push(option);
							}
						});
					})
				);
			});

			return $q.all(planRequests);
		}).then(function () {
			scheduleCustomerSubscriptionUiInit();
		}).catch(function () {
			$scope.customerQuotationPlans = [];
			$scope.selectedPlans = [];
			$scope.x.plan = '';
			$scope.x.selected_plans_json = "";
			$scope.x.subscribed_plans_json = "";
			$scope.activeCustomerTab = 'details';
			$scope.subscriptionServicePlans = [];
			$scope.subscriptionServiceName = '';
			$scope.subscriptionPlanPickerLoading = false;
			$scope.subscriptionDraftPlan = null;
			$scope.subscriptionDraftPlanId = '';
			$scope.reset_subscription_change_mode();
			syncSelectedPlansPayload();
			syncSubscribedPlansPayload();
			scheduleCustomerSubscriptionUiInit();
		});
	};

	$scope.load_customer_subscribed_plans = function (c_id) {
		if (!c_id) {
			$scope.selectedPlans = [];
			$scope.x.plan = '';
			$scope.x.subscribed_plans_json = "";
			$scope.subscriptionDraftPlan = null;
			$scope.subscriptionDraftPlanId = '';
			$scope.subscriptionServicePlans = [];
			$scope.subscriptionServiceName = '';
			$scope.subscriptionPlanPickerLoading = false;
			$scope.reset_subscription_change_mode();
			syncSubscribedPlansPayload();
			return;
		}

		return $http.get(rootUrl + "customer/get_subscribed_plans?c_id=" + encodeURIComponent(c_id)).then(function (response) {
			var rows = normalizeArrayResponse(response);
			$scope.selectedPlans = rows.map(function (row, index) {
				return decorateLoadedSubscriptionRow(row, index);
			});

			$scope.x.plan = '';
			$scope.subscriptionDraftPlan = null;
			$scope.subscriptionDraftPlanId = '';
			$scope.removedSubscriptionPlanIds = [];
			$scope.reset_subscription_change_mode();
			syncSelectedPlansPayload();
			syncSubscribedPlansPayload();
			refreshAvailableCustomerQuotationPlans();
			scheduleCustomerSubscriptionUiInit();
		}).catch(function () {
			$scope.selectedPlans = [];
			$scope.x.plan = '';
			$scope.x.subscribed_plans_json = "";
			$scope.x.removed_subscribed_plan_ids_json = "[]";
			$scope.availableCustomerQuotationPlans = [];
			$scope.subscriptionServicePlans = [];
			$scope.subscriptionServiceName = '';
			$scope.subscriptionPlanPickerLoading = false;
			$scope.subscriptionDraftPlan = null;
			$scope.subscriptionDraftPlanId = '';
			$scope.removedSubscriptionPlanIds = [];
			$scope.reset_subscription_change_mode();
			syncSubscribedPlansPayload();
		});
	};

	$scope.load_subscription_plan_picker_options = function (c_id, usePlanMaster) {
		$scope.subscriptionPlanPickerLoading = true;
		// console.log('Loading subscription plan picker options for customer ID:', c_id);
		if (!$scope.x) {
			$scope.x = {};
		}

		clearSubscriptionPlanSourceSelection();

		if (usePlanMaster === true || usePlanMaster === 1 || usePlanMaster === '1') {
			$scope.subscriptionUsePlanMaster = true;
			return loadPlanMasterSubscriptionPlans();
		}

		$scope.subscriptionUsePlanMaster = false;
		return $scope.load_customer_quotation_plans(c_id).finally(function () {
			$scope.subscriptionPlanPickerLoading = false;
		});
	};

	$scope.on_subscription_plan_source_change = function (usePlanMaster) {
		if ($scope.subscriptionDraftMode === 'change') {
			$scope.subscriptionUsePlanMaster = false;
			return;
		}

		if (!$scope.x || !$scope.x.c_id) {
			console.warn('Customer ID is not available. Cannot load subscription plans.');
			clearSubscriptionPlanSourceSelection();
			return;
		}
	// console.log('Subscription plan source changed. Loading options for customer ID:', $scope.x.c_id);
		$scope.load_subscription_plan_picker_options($scope.x.c_id, usePlanMaster);
	};

	$scope.supdate_call = function (c_id) {
		sharedService.setData({ c_id: c_id });
		$rootScope.$broadcast('follow_up');
		$http.get(rootUrl + "customer/view?c_id=" + c_id).success(function (data) {
			$scope.x = data[0] || {};
			normalizeCustomerDetailSelections();
			$scope.activeCustomerTab = 'details';
			$scope.subscriptionListView = 'active';
			$scope.selectedPlans = [];
			$scope.customerQuotationPlans = [];
			$scope.availableCustomerQuotationPlans = [];
			$scope.subscriptionDraftPlan = null;
			$scope.subscriptionDraftPlanId = '';
			$scope.removedSubscriptionPlanIds = [];
			$scope.reset_subscription_change_mode();
			$scope.subscriptionUsePlanMaster = false;
			$scope.planMasterSubscriptionPlans = [];
			$scope.planMasterSubscriptionPlansLoading = false;
			$scope.name = ($scope.x && $scope.x.name) ? $scope.x.name : '';
			$scope.login = ($scope.x && $scope.x.login) ? $scope.x.login : '';
			loadLoggedInCompanyContext().then(function () {
				return loadCustomerStateList();
			}).then(function () {
				return loadInvoiceCompanies();
			}).then(function () {
				return loadCustomerStaffOptions();
			}).then(function () {
				return $scope.load_subscription_plan_picker_options($scope.x.c_id, $scope.subscriptionUsePlanMaster);
			}).then(function () {
				return $scope.load_customer_subscribed_plans($scope.x.c_id);
			}).then(function () {
				$timeout(function () {
					$scope.activeCustomerTab = 'details';
					var $modalBody = $('#customerFormModal .modal-body');
					if ($modalBody.length) {
						$modalBody.scrollTop(0);
					}
					refreshCustomerDetailSelect2();
					scheduleCustomerSubscriptionUiInit();
					showCustomerModalAndRefresh();
				}, 0, false);
			});
		});
	};

	$scope.open_customer_modal = function (mode, y) {
		if (mode == 'edit' && y) {
			$scope.customer_modal_title = "Edit Customer";
			$scope.supdate_call(y.c_id);
			return;
		}

		$scope.customer_modal_title = "Add Customer";
		$scope.subscriptionListView = 'active';
		$scope.on_customer_tab_change('details');
		$scope.filter_new1();
		$scope.selectedPlans = [];
		$scope.customerQuotationPlans = [];
		$scope.availableCustomerQuotationPlans = [];
		$scope.subscriptionServicePlans = [];
		$scope.subscriptionServiceName = '';
		$scope.subscriptionPlanPickerLoading = false;
		$scope.subscriptionDraftPlan = null;
		$scope.subscriptionDraftPlanId = '';
		$scope.removedSubscriptionPlanIds = [];
		$scope.reset_subscription_change_mode();
		$scope.subscriptionUsePlanMaster = false;
		$scope.planMasterSubscriptionPlans = [];
		$scope.planMasterSubscriptionPlansLoading = false;
		loadLoggedInCompanyContext().then(function () {
			loadInvoiceCompanies();
			loadCustomerStaffOptions();
		});
		$scope.x.plan = '';
		$scope.x.selected_plans_json = "";
		$scope.x.subscribed_plans_json = "";
		$scope.x.removed_subscribed_plan_ids_json = "[]";
		$scope.x.status = '1';
		$scope.x.review_status = '0';
		$scope.on_customer_tab_change('details');
		scheduleCustomerSubscriptionUiInit();
		showCustomerModalAndRefresh();
	};

	function clearCustomerDetailsForm() {
		$scope.x = $scope.x || {};
		$scope.x.c_id = '';
		$scope.x.c_type = '';
		$scope.x.name = '';
		$scope.x.phone = '';
		$scope.x.email = '';
		$scope.x.address = '';
		$scope.x.grade = '';
		$scope.x.company_name = '';
		$scope.x.company_phone = '';
		$scope.x.company_whatsapp = '';
		$scope.x.company_gst = '';
		$scope.x.company_email = '';
		$scope.x.website = '';
		$scope.x.state = '';
		$scope.x.city = '';
		$scope.x.company_address = '';
		$scope.x.invoice_com = '';
		$scope.x.senior_crm_id = '';
		$scope.x.junior_crm_id = '';
		$scope.x.converted_by = '';
		$scope.x.hr_login = '0';
		$scope.x.status = '1';
		$scope.x.review_status = '0';
		$scope.samePhone = false;
		$scope.sameWhatsapp = false;
		$scope.sameAddress = false;
	}

	function clearSubscriptionDraftForm() {
		$scope.subscriptionDraftPlan = null;
		$scope.subscriptionDraftPlanId = '';
		$scope.x.plan = '';
		$scope.subscriptionServicePlans = [];
		$scope.subscriptionServiceName = '';
		$scope.subscriptionPlanPickerLoading = false;
		$scope.removedSubscriptionPlanIds = [];
		$scope.subscriptionListView = 'active';
		$scope.reset_subscription_change_mode();
		syncSubscribedPlansPayload();
		refreshAvailableCustomerQuotationPlans();
		refreshSubscriptionPlanPickerOptions();
		refreshVisibleSubscriptionPlans();
		$timeout(function () {
			var $picker = $('#customerSubscriptionPlanPicker');
			if ($picker.length) {
				$picker.val('');
				$picker.trigger('change');
			}
		}, 0, false);
	}

	$scope.clear_customer_details_form = function () {
		clearCustomerDetailsForm();
	};

	$scope.clear_subscription_form = function () {
		clearSubscriptionDraftForm();
	};

	$scope.filter_new1 = function () {
		clearCustomerDetailsForm();
	};

	function save_customer_details(x) {
		var customerPayload = $('#customerform').serialize();

		$('#loader1').css('display', 'inline');
		$('#submitbtn1').attr('disabled', true);

		$.ajax({
			type: "POST",
			url: rootUrl + "customer/save",
			data: customerPayload,
			dataType: "json"
		}).done(function (data) {
			if (!data || data.error != "0") {
				messages("danger", "Warning!", (data && data.msg) ? data.msg : "Unable to save customer.", 6000);
				return;
			}

			if (x && data.c_id) {
				x.c_id = data.c_id;
			}

			messages("success", data.msg);
			$scope.loader($scope.pageno || 1);
		}).fail(function () {
			messages("danger", "Warning!", "Unable to save customer.", 6000);
		}).always(function () {
			$('#loader1').css('display', 'none');
			$('#submitbtn1').attr('disabled', false);
		});
	}

	$scope.save_customer_details = function (x) {
		save_customer_details(x);
	};

	$scope.save_data1 = function (x) {
		save_customer_details(x);
	};

	$scope.save_customer_subscription = function (x) {
		var options = arguments[1] || {};
		syncSubscriptionDraftStartDateFromInput();
		syncSelectedPlansPayload();
		syncSubscribedPlansPayload();
		if ($scope.subscriptionDraftMode === 'change' && $scope.subscriptionChangeSourcePlan && (!$scope.subscriptionDraftPlan || (!$scope.subscriptionDraftPlan.plan_id && !$scope.subscriptionDraftPlan.option_id && !$scope.subscriptionDraftPlan.qd_id))) {
			if (!options.silent) {
				messages("warning", "Warning!", "Select a repeating plan before saving the plan change.", 5000);
			}
			return;
		}
		if (!x || !x.c_id) {
			if (!options.silent) {
				messages("warning", "Warning!", "Save customer details first before saving subscription plans.", 5000);
			}
			return;
		}

		if (customerSubscriptionSaveInProgress) {
			$scope.subscriptionAutoSavePending = true;
			queueSubscriptionAutoSave();
			return;
		}

		customerSubscriptionSaveInProgress = true;
		var savedDraftPlanId = $scope.subscriptionDraftPlan && ($scope.subscriptionDraftPlan.option_id || $scope.subscriptionDraftPlan.qd_id || $scope.subscriptionDraftPlan.plan_id || '');

		if (!options.silent) {
			$('#loader1').css('display', 'inline');
			$('#submitbtn1').attr('disabled', true);
		}

		$.ajax({
			type: "POST",
			url: rootUrl + "customer/save_subscribed_plans",
			dataType: "json",
			data: {
				c_id: x.c_id,
				subscribed_plans_json: x && x.subscribed_plans_json ? x.subscribed_plans_json : '[]',
				removed_subscribed_plan_ids_json: x && x.removed_subscribed_plan_ids_json ? x.removed_subscribed_plan_ids_json : '[]'
			}
		}).done(function (planData) {
			if (planData && planData.error == 0) {
				$scope.$applyAsync(function () {
					if (!options.silent) {
						messages("success", planData.msg || "Subscribed plans saved successfully.");
					}
					$scope.subscriptionDraftPlan = null;
					$scope.subscriptionDraftPlanId = '';
					$scope.x.plan = '';
					$scope.removedSubscriptionPlanIds = [];
					$scope.x.removed_subscribed_plan_ids_json = "[]";
					$scope.reset_subscription_change_mode();
					if (savedDraftPlanId !== undefined && savedDraftPlanId !== null && savedDraftPlanId !== '') {
						$scope.availableCustomerQuotationPlans = ($scope.availableCustomerQuotationPlans || []).filter(function (plan) {
							var optionId = plan && (plan.option_id || plan.qd_id || plan.plan_id);
							return String(optionId || '') !== String(savedDraftPlanId);
						});
						$scope.subscriptionPlanPickerOptions = ($scope.subscriptionPlanPickerOptions || []).filter(function (plan) {
							var optionId = plan && (plan.option_id || plan.qd_id || plan.plan_id);
							return String(optionId || '') !== String(savedDraftPlanId);
						});
					}
					refreshAvailableCustomerQuotationPlans();
					refreshSubscriptionPlanPickerOptions();
					refreshVisibleSubscriptionPlans();
					$scope.load_subscription_plan_picker_options(x.c_id, $scope.subscriptionUsePlanMaster);
					$scope.load_customer_subscribed_plans(x.c_id);
					$scope.on_customer_tab_change('subscription');
					$scope.loader($scope.pageno || 1);
				});
			} else {
				if (!options.silent) {
					messages("danger", "Warning!", (planData && planData.msg) ? planData.msg : "Subscription plans were not saved.", 6000);
				}
			}
		}).fail(function () {
			messages("danger", "Warning!", "Subscription plans were not saved.", 6000);
		}).always(function () {
			customerSubscriptionSaveInProgress = false;
			if (!options.silent) {
				$('#loader1').css('display', 'none');
				$('#submitbtn1').attr('disabled', false);
			}
			if ($scope.subscriptionAutoSavePending) {
				$scope.subscriptionAutoSavePending = false;
				queueSubscriptionAutoSave();
			}
		});
	};

	$scope.update_review_status = function (row) {
		if (!row || !row.c_id) {
			return;
		}

		var c_id = row.c_id;
		var nextStatus = (row.review_status == '1' || row.review_status === 1 || row.review_status === true) ? '1' : '0';
		var previous = nextStatus === '1' ? '0' : '1';

		if ($scope.reviewStatusUpdating[c_id]) {
			row.review_status = previous;
			return;
		}

		$scope.reviewStatusUpdating[c_id] = true;

		$http({
			method: 'POST',
			url: rootUrl + "customer/update_review_status",
			data: $.param({
				c_id: c_id,
				review_status: nextStatus
			}),
			headers: {
				'Content-Type': 'application/x-www-form-urlencoded; charset=UTF-8'
			}
		}).then(function (response) {
			var res = (response && response.data) ? response.data : {};
			if (String(res.error) === "0") {
				row.review_status = nextStatus;
				messages("success", "Success!", res.msg || "Review status updated successfully.", 2500);
			} else {
				row.review_status = previous;
				messages("warning", "Warning!", (res.msg || "Review status update failed."), 4000);
			}
		}, function () {
			row.review_status = previous;
			messages("danger", "Warning!", "Unable to update review status.", 4000);
		}).finally(function () {
			$scope.reviewStatusUpdating[c_id] = false;
		});
	};

	$scope.delete_data = function (id) {
		if (confirm("Deleting Customer Details may hamper your data associated with it.")) {
			if (confirm("Are you Sure to DELETE ??")) {
				$http.get(rootUrl + "customer/delete?c_id=" + id).success(function (data) {
					if (data == "1") {
						messages("success", "Success!", "Customer Details Deleted Successfully", 3000);
					}
					else {
						messages("danger", "Warning!", "Customer Details not Deleted. " + data, 4000);
					}
					$scope.loader($scope.pageno || 1);
				});
			}
		}
	};
}]);
﻿app.controller('quotation', ['$scope', '$rootScope', '$http', '$timeout', '$state', '$stateParams', function ($scope, $rootScope, $http, $timeout, $state, $stateParams) {
	var module = 'quotation';
	var rootUrl = $rootScope.site_url;

	$http.get(rootUrl + module + '/index').success(function (data) {
		if (data == 0) {
			window.location.assign('login.html');
		} else if (data == 2) {
			messages('success', 'Privilege not assigned.', 1000);
			window.location.assign('index.html');
		}
	});

	$scope.pageno = 1;
	$scope.total_count = 0;
	$scope.totalPages = 1;
	$scope.itemsPerPage = '15';
	$scope.pageLinks = [];
	$scope.qx = {
		search_text: '',
		project_id: '',
		c_id: '',
		quotation_number: ''
	};
	$scope.acSuggestions = {};
	$scope.acActive = {};
	var acTimers = {};
	$scope.datadb = [];
	$scope.loading = false;
	$scope.x = {};
	$scope.selected = {};
	$scope.q_id = '';
	$scope.loggedInComId = '';
	$scope.temp_q_id = '';
	$scope.temp_is_quotation_copy = false;
	$scope.helpLanguage = 'en';
	$scope.helpLanguages = [
		{ code: 'en', label: 'English' },
		{ code: 'hi', label: 'Hindi' },
		{ code: 'bn', label: 'Bengali' },
		{ code: 'ne', label: 'Nepali' }
	];
	$scope.helpDocs = {
		en: {
			title: 'Quotation Workflow Guide',
			intro: 'Use this guide to create quotations, update them, review plan details, and let the client choose plans from the quotation.',
			sections: [
				{
					title: 'Create a quotation',
					points: [
						'Click Add Quotation.',
						'Select Company first. The quotation number is generated automatically.',
						'Then select Customer, Generator, date, and an optional template.',
						'Add one or more plans by filling plan, unit, price, discount, description, and terms.',
						'Click Save Item for each plan.',
						'Review the plan items and save the quotation.'
					]
				},
				{
					title: 'Add multiple plans',
					points: [
						'You can add any number of plans to the same quotation.',
						'Monthly and one-time plans can be mixed together.',
						'Each saved plan appears in the Plan Items table.',
						'You may set Total Cost to 0 when the quotation is only for client selection.'
					]
				},
				{
					title: 'Update a quotation',
					points: [
						'Click the pencil icon in the quotation list to edit an existing quotation.',
						'The modal loads the saved quotation data and plan rows.',
						'You can change header details, add new plans, or delete unwanted plans.',
						'Click Save Quotation again to store the update.'
					]
				},
				{
					title: 'View quotation plans',
					points: [
						'Click the eye icon under View Plans.',
						'This opens the read-only plan details modal.',
						'Use it when you only want to inspect quotation plan items.'
					]
				},
				{
					title: 'Client plan selection',
					points: [
						'Click the Client Plans button for any saved quotation.',
						'A modal opens with all plans in that quotation.',
						'Select one or more plans using the checkboxes.',
						'Selected plans stay locked by default. Use the lock/unlock permission button before deselecting a plan.',
						'Click Save Selected Plans to store the selected plans in the separate client-selection table.'
					]
				},
				{
					title: 'Search and filter',
					points: [
						'Use the Company, Customer, Quotation No, and Per Page filters above the table.',
						'Click Filter to apply the search.',
						'Click Clear to reset the filters and reload the quotation list.'
					]
				},
				{
					title: 'Important notes',
					points: [
						'The quotation number is generated when the Company is selected.',
						'The page supports multiple plans inside one quotation.',
						'Client-selected plans are stored separately from the quotation header.',
						'The modal closes automatically after a successful save.'
					]
				}
			]
		},
		hi: {
			title: 'कोटेशन कार्यप्रवाह मार्गदर्शिका',
			intro: 'इस मार्गदर्शिका का उपयोग कोटेशन बनाने, अपडेट करने, प्लान विवरण देखने और ग्राहक को प्लान चुनने देने के लिए करें।',
			sections: [
				{ title: 'कोटेशन बनाएं', points: ['Add Quotation पर क्लिक करें।', 'सबसे पहले Company चुनें। कोटेशन नंबर अपने आप बन जाएगा।', 'फिर Customer, Generator, date और optional template चुनें।', 'Plan, unit, price, discount, description और terms भरकर plan add करें।', 'हर plan के लिए Save Item पर क्लिक करें।', 'Plan items देखकर Save Quotation करें।'] },
				{ title: 'एक से अधिक प्लान जोड़ें', points: ['एक ही कोटेशन में कई प्लान जोड़े जा सकते हैं।', 'Monthly और one-time plans को साथ में mix किया जा सकता है।', 'हर saved plan Plan Items table में दिखेगा।', 'अगर quotation सिर्फ selection के लिए है, तो Total Cost को 0 रख सकते हैं।'] },
				{ title: 'कोटेशन अपडेट करें', points: ['Quotation list में pencil icon पर क्लिक करें।', 'Modal में saved quotation data और plan rows load हो जाएंगी।', 'Header details बदलें, नया plan जोड़ें या unwanted plan delete करें।', 'Update save करने के लिए Save Quotation पर फिर से क्लिक करें।'] },
				{ title: 'कोटेशन प्लान देखें', points: ['View Plans के नीचे eye icon पर क्लिक करें।', 'इससे read-only plan details modal खुल जाएगा।', 'जब सिर्फ plan items inspect करने हों, तब इसका उपयोग करें।'] },
				{ title: 'ग्राहक द्वारा प्लान चयन', points: ['किसी saved quotation के लिए Client Plans button पर क्लिक करें।', 'Modal में उस quotation के सभी plans दिखेंगे।', 'Checkbox से एक या अधिक plans चुनें।', 'Selected plans locked रहते हैं। Deselect करने के लिए पहले lock/unlock permission button पर क्लिक करें।', 'Selected plans को अलग client-selection table में save करने के लिए Save Selected Plans पर क्लिक करें।'] },
				{ title: 'खोज और फ़िल्टर', points: ['Table के ऊपर Company, Customer, Quotation No और Per Page filters उपयोग करें।', 'Filter से search apply करें।', 'Clear से filters reset करके list reload करें।'] },
				{ title: 'महत्वपूर्ण बातें', points: ['Quotation number Company select करते ही generate होता है।', 'एक quotation में multiple plans support होते हैं।', 'Client-selected plans अलग table में save होते हैं।', 'Successful save के बाद modal अपने आप बंद हो जाता है।'] }
			]
		},
		bn: {
			title: 'কোটেশন ওয়ার্কফ্লো গাইড',
			intro: 'এই গাইড ব্যবহার করে কোটেশন তৈরি, আপডেট, প্ল্যানের বিস্তারিত দেখা এবং ক্লায়েন্টের জন্য প্ল্যান নির্বাচন করুন।',
			sections: [
				{ title: 'কোটেশন তৈরি করুন', points: ['Add Quotation-এ ক্লিক করুন।', 'প্রথমে Company নির্বাচন করুন। Quotation number স্বয়ংক্রিয়ভাবে তৈরি হবে।', 'তারপর Customer, Generator, date এবং optional template নির্বাচন করুন।', 'Plan, unit, price, discount, description এবং terms পূরণ করে plan add করুন।', 'প্রতিটি plan-এর জন্য Save Item-এ ক্লিক করুন।', 'Plan items দেখে Save Quotation করুন।'] },
				{ title: 'একাধিক প্ল্যান যোগ করুন', points: ['একটি quotation-এ একাধিক plan যোগ করা যাবে।', 'Monthly এবং one-time plan একসাথে mix করা যাবে।', 'প্রতিটি saved plan Plan Items table-এ দেখাবে।', 'Quotation যদি শুধুমাত্র selection-এর জন্য হয়, তাহলে Total Cost 0 রাখা যাবে।'] },
				{ title: 'কোটেশন আপডেট করুন', points: ['Quotation list-এর pencil icon-এ ক্লিক করুন।', 'Modal-এ saved quotation data এবং plan rows load হবে।', 'Header details পরিবর্তন করুন, নতুন plan add করুন অথবা unwanted plan delete করুন।', 'Update save করতে আবার Save Quotation-এ ক্লিক করুন।'] },
				{ title: 'কোটেশন প্ল্যান দেখুন', points: ['View Plans-এর নিচের eye icon-এ ক্লিক করুন।', 'এতে read-only plan details modal খুলবে।', 'শুধু plan items inspect করতে এটি ব্যবহার করুন।'] },
				{ title: 'গ্রাহক দ্বারা প্ল্যান নির্বাচন', points: ['কোনো saved quotation-এর জন্য Client Plans button-এ ক্লিক করুন।', 'Modal-এ ওই quotation-এর সব plans দেখাবে।', 'Checkbox দিয়ে এক বা একাধিক plan select করুন।', 'Selected plans locked থাকে। Deselect করার আগে lock/unlock permission button ব্যবহার করুন।', 'Selected plans আলাদা client-selection table-এ save করতে Save Selected Plans-এ ক্লিক করুন।'] },
				{ title: 'সার্চ এবং ফিল্টার', points: ['Table-এর উপরে Company, Customer, Quotation No এবং Per Page filters ব্যবহার করুন।', 'Filter দিয়ে search apply করুন।', 'Clear দিয়ে filters reset করে list reload করুন।'] },
				{ title: 'গুরুত্বপূর্ণ নোট', points: ['Company select করলেই quotation number generate হয়।', 'একটি quotation-এ multiple plans support করে।', 'Client-selected plans আলাদা table-এ save হয়।', 'Successful save-এর পরে modal স্বয়ংক্রিয়ভাবে বন্ধ হয়ে যায়।'] }
			]
		},
		ne: {
			title: 'कोटेशन कार्यप्रवाह मार्गदर्शिका',
			intro: 'यो मार्गदर्शिकाको प्रयोग कोटेशन बनाउन, अपडेट गर्न, प्लान विवरण हेर्न र ग्राहकलाई प्लान छान्न दिन गर्नुहोस्।',
			sections: [
				{ title: 'कोटेशन बनाउनुहोस्', points: ['Add Quotation मा click गर्नुहोस्।', 'पहिले Company select गर्नुहोस्। Quotation number automatic generate हुनेछ।', 'त्यसपछि Customer, Generator, date र optional template select गर्नुहोस्।', 'Plan, unit, price, discount, description र terms भरेर plan add गर्नुहोस्।', 'हरेक plan का लागि Save Item मा click गर्नुहोस्।', 'Plan items हेरेर Save Quotation गर्नुहोस्।'] },
				{ title: 'एकै पटक धेरै प्लान जोड्नुहोस्', points: ['एक quotation मा धेरै plans add गर्न सकिन्छ।', 'Monthly र one-time plans mix गर्न सकिन्छ।', 'हरेक saved plan Plan Items table मा देखिन्छ।', 'Quotation केवल selection का लागि हो भने Total Cost 0 राख्न सकिन्छ।'] },
				{ title: 'Quotation update गर्नुहोस्', points: ['Quotation list मा pencil icon मा click गर्नुहोस्।', 'Saved quotation data र plan rows modal मा load हुन्छन्।', 'Header details परिवर्तन गर्न, नयाँ plan add गर्न, वा unwanted plan delete गर्न सकिन्छ।', 'Update save गर्न फेरि Save Quotation मा click गर्नुहोस्।'] },
				{ title: 'Quotation plans हेर्नुहोस्', points: ['View Plans अन्तर्गत eye icon मा click गर्नुहोस्।', 'यसले read-only plan details modal खोल्छ।', 'Plan items मात्र inspect गर्न यो प्रयोग गर्नुहोस्।'] },
				{ title: 'ग्राहक द्वारा प्लान चयन', points: ['कुनै saved quotation का लागि Client Plans button मा click गर्नुहोस्।', 'Modal मा उक्त quotation का सबै plans देखिन्छन्।', 'Checkbox बाट एक वा धेरै plans select गर्नुहोस्।', 'Selected plans locked हुन्छन्। Deselect गर्नुभन्दा पहिले lock/unlock permission button प्रयोग गर्नुहोस्।', 'Selected plans अलग client-selection table मा save गर्न Save Selected Plans मा click गर्नुहोस्।'] },
				{ title: 'Search र filter', points: ['Table माथि Company, Customer, Quotation No र Per Page filters प्रयोग गर्नुहोस्।', 'Filter बाट search apply गर्नुहोस्।', 'Clear बाट filters reset गरेर list reload गर्नुहोस्।'] },
				{ title: 'महत्वपूर्ण कुराहरू', points: ['Company select गर्दा quotation number generate हुन्छ।', 'एक quotation मा multiple plans support हुन्छ।', 'Client-selected plans अलग table मा save हुन्छन्।', 'Successful save पछि modal स्वतः बन्द हुन्छ।'] }
			]
		}
	};
	var quotationSelect2Selectors = '.quotation-select2';
	var quotationTempCacheKey = 'quotation_temp_copy_cache';

	function readQuotationTempCache() {
		try {
			return JSON.parse(sessionStorage.getItem(quotationTempCacheKey) || '{}') || {};
		} catch (e) {
			return {};
		}
	}

	function writeQuotationTempCache(cache) {
		try {
			sessionStorage.setItem(quotationTempCacheKey, JSON.stringify(cache || {}));
		} catch (e) {
			// Ignore storage failures and rely on the in-memory state.
		}
	}

	function markQuotationTempCopied(q_id) {
		if (!q_id) {
			return;
		}

		var cache = readQuotationTempCache();
		cache[String(q_id)] = true;
		writeQuotationTempCache(cache);
	}

	function clearQuotationTempCopied(q_id) {
		if (!q_id) {
			try {
				sessionStorage.removeItem(quotationTempCacheKey);
			} catch (e) {
				// Ignore storage failures.
			}
			return;
		}

		var cache = readQuotationTempCache();
		delete cache[String(q_id)];
		writeQuotationTempCache(cache);
	}

	function hasQuotationTempCopied(q_id) {
		var cache = readQuotationTempCache();
		return !!cache[String(q_id || '')];
	}

	function initQuotationSelect2() {
		$timeout(function () {
			if (!$.fn.select2) {
				return;
			}

			var $modal = $('#quotationFormModal');
			var isSelect2V4 = !!($.fn.select2 && $.fn.select2.amd);

			$(quotationSelect2Selectors).each(function () {
				var $el = $(this);
				if (!$el.is('select')) {
					return;
				}

				if ($el.data('select2')) {
					$el.select2('destroy');
				}

				var options = {
					width: '100%'
				};

				if (isSelect2V4) {
					options.dropdownParent = $el.closest('#quotationFormModal').length ? $modal : $(document.body);
				}

				$el.select2(options);
			});
		}, 0);
	}

	function allowSelect2TypingInQuotationModal() {
		if (typeof $ === 'undefined' || !$.fn || !$.fn.modal || !$.fn.modal.Constructor) {
			return;
		}

		var ModalConstructor = $.fn.modal.Constructor;
		if (ModalConstructor.prototype._quotationSelect2FocusPatched) {
			return;
		}

		ModalConstructor.prototype.enforceFocus = function () {
			var modalThis = this;
			$(document)
				.off('focusin.bs.modal')
				.on('focusin.bs.modal', function (e) {
					var $target = $(e.target);
					var isInsideModal = modalThis.$element[0] === e.target || modalThis.$element.has(e.target).length;
					var isSelect2Input =
						$target.closest('.select2-container, .select2-dropdown, .select2-drop, .select2-search').length > 0 ||
						$target.is('.select2-input, .select2-search__field');

					if (!isInsideModal && !isSelect2Input) {
						modalThis.$element.trigger('focus');
					}
				});
		};

		ModalConstructor.prototype._quotationSelect2FocusPatched = true;

		$(document).off('select2:open.quotation select2-open.quotation');
		$(document).on('select2:open.quotation select2-open.quotation', function () {
			setTimeout(function () {
				var $search = $('.select2-container-active .select2-input, .select2-drop-active .select2-input, .select2-container--open .select2-search__field');
				if ($search.length) {
					$search.focus();
				}
			}, 0);
		});
	}

	function queueQuotationSelect2Init(delay) {
		allowSelect2TypingInQuotationModal();
		$timeout(function () {
			initQuotationSelect2();
		}, delay || 0);
	}

	function toNumber(value) {
		var parsed = parseFloat(value);
		return isNaN(parsed) ? 0 : parsed;
	}

	function buildPageLinks(currentPage, totalPages) {
		var pages = [];
		var start = 1;
		var end = totalPages;

		if (totalPages > 7) {
			start = currentPage - 2;
			end = currentPage + 2;

			if (start < 1) {
				end += (1 - start);
				start = 1;
			}
			if (end > totalPages) {
				start -= (end - totalPages);
				end = totalPages;
			}

			if (start < 1) {
				start = 1;
			}
		}

		for (var i = start; i <= end; i++) {
			pages.push(i);
		}

		return pages;
	}

	function refreshPagerMeta() {
		var perPage = parseInt($scope.itemsPerPage, 10) || 1;
		$scope.totalPages = Math.max(1, Math.ceil(($scope.total_count || 0) / perPage));
		$scope.pageLinks = buildPageLinks($scope.pageno || 1, $scope.totalPages);
	}

	function fetchQuotationNumber(projectId) {
		if (!projectId) {
			return;
		}

		if ($scope.x && $scope.x.q_id && String($scope.x.__original_project_id || '') === String(projectId)) {
			return;
		}

		$http.get(rootUrl + module + '/get_next_quotation_number?project_id=' + encodeURIComponent(projectId)).success(function (data) {
			if (data && data.status === 'success' && data.quotation_number) {
				$scope.x.quotation_number = data.quotation_number;
			} else if (data && data.quotation_number) {
				$scope.x.quotation_number = data.quotation_number;
			}
		});
	}

	$scope.loader = function (pageno) {
		if (!pageno) {
			pageno = 1;
		}
		pageno = parseInt(pageno, 10) || 1;
		if (pageno < 1) {
			pageno = 1;
		}

		$scope.pageno = pageno;
		$scope.loading = true;

		var params = [];
		if ($scope.qx.search_text) {
			params.push('search_text=' + encodeURIComponent($scope.qx.search_text));
		}
		if ($scope.qx.project_id) {
			params.push('project_id=' + encodeURIComponent($scope.qx.project_id));
		}
		if ($scope.qx.c_id) {
			params.push('c_id=' + encodeURIComponent($scope.qx.c_id));
		}
		if ($scope.qx.quotation_number) {
			params.push('quotation_number=' + encodeURIComponent($scope.qx.quotation_number));
		}

		var url = rootUrl + module + '/view_paginated/' + $scope.itemsPerPage + '/' + pageno;
		if (params.length) {
			url += '?' + params.join('&');
		}

		$http.get(url).then(function (response) {
			var data = response.data || {};
			if (angular.isArray(data)) {
				$scope.datadb = data || [];
				$scope.total_count = ($scope.datadb || []).length;
			} else {
				$scope.datadb = data.data || [];
				$scope.total_count = toNumber(data.total_count);
			}
			refreshPagerMeta();
		}, function () {
			$scope.datadb = [];
			$scope.total_count = 0;
			refreshPagerMeta();
		}).finally(function () {
			$scope.loading = false;
		});
	};

	$scope.apply_filters = function () {
		$scope.loader(1);
	};

	$scope.clear_filters = function () {
		$scope.qx = {
			search_text: '',
			project_id: '',
			c_id: '',
			quotation_number: ''
		};
		$scope.itemsPerPage = '15';
		$scope.acSuggestions = {};
		$scope.acActive = {};
		angular.forEach(acTimers, function (t, f) {
			if (t) { $timeout.cancel(t); acTimers[f] = null; }
		});
		$timeout(function () {
			$('#quotation_filter_company_select').val('').trigger('change');
			$('#quotation_filter_customer_select').val('').trigger('change');
			$('#quotation_filter_per_page_select').val($scope.itemsPerPage).trigger('change');
		}, 0);
		$scope.loader(1);
	};

	$scope.on_items_per_page_change = function () {
		$scope.loader(1);
	};

	$scope.on_ac_input_change = function (qxField) {
		if (acTimers[qxField]) { $timeout.cancel(acTimers[qxField]); }
		var query = String(($scope.qx && $scope.qx[qxField]) || '').trim();
		if (query.length < 3) {
			$scope.acSuggestions[qxField] = [];
			$scope.acActive[qxField] = false;
			return;
		}
		acTimers[qxField] = $timeout(function () {
			acTimers[qxField] = null;
			$http.get(rootUrl + 'quotation/autocomplete?field=' + qxField + '&query=' + encodeURIComponent(query))
				.success(function (response) {
					$scope.acSuggestions[qxField] = angular.isArray(response) ? response : [];
					$scope.acActive[qxField] = $scope.acSuggestions[qxField].length > 0;
				});
		}, 300);
	};

	$scope.select_ac_suggestion = function (qxField, value) {
		$scope.qx[qxField] = value;
		$scope.acSuggestions[qxField] = [];
		$scope.acActive[qxField] = false;
	};

	$scope.close_ac_delayed = function (qxField) {
		$timeout(function () {
			$scope.acActive[qxField] = false;
		}, 200);
	};

	$scope.identity = {
		name: localStorage.getItem('staff_name') || 'Team Member',
		type: localStorage.getItem('type') || 'User',
		grade: localStorage.getItem('grade') || '',
		emp_id: localStorage.getItem('emp_id') || '',
		com_id: localStorage.getItem('com_id') || '',
		branch_id: localStorage.getItem('branch_id') || ''
	};

	$scope.today_date = function () {
		$scope.x = $scope.x || {};
		var d = new Date();
		var day = ('0' + d.getDate()).slice(-2);
		var month = ('0' + (d.getMonth() + 1)).slice(-2);
		var year = d.getFullYear();
		$scope.x.date = day + '/' + month + '/' + year;
	};

	function buildCompanyChildrenMap(companies) {
		let childrenMap = {};

		angular.forEach(companies || [], function (company) {
			let parentId = (company && company.parent !== undefined && company.parent !== null)
				? String(company.parent).trim()
				: '';

			if (!parentId) {
				return;
			}

			if (!childrenMap[parentId]) {
				childrenMap[parentId] = [];
			}

			childrenMap[parentId].push(company);
		});

		return childrenMap;
	}

	function collectDescendantCompanies(rootComId, companies) {
		let rootId = String(rootComId || '').trim();
		if (!rootId) {
			return [];
		}

		let childrenMap = buildCompanyChildrenMap(companies);
		let byId = {};
		let queue = [rootId];
		let seen = {};
		let allowed = [];

		angular.forEach(companies || [], function (company) {
			if (!company || company.com_id === undefined || company.com_id === null) {
				return;
			}
			byId[String(company.com_id).trim()] = company;
		});

		while (queue.length) {
			let currentId = queue.shift();
			if (!currentId || seen[currentId]) {
				continue;
			}
			seen[currentId] = true;

			if (byId[currentId]) {
				allowed.push(byId[currentId]);
			}

			angular.forEach(childrenMap[currentId] || [], function (child) {
				if (child && child.com_id !== undefined && child.com_id !== null) {
					queue.push(String(child.com_id).trim());
				}
			});
		}

		return allowed;
	}

	function loadQuotationCompanies() {
		$http.get(rootUrl + 'company_master/view?data=name,com_id,parent').success(function (data) {
			let allCompanies = angular.isArray(data) ? data : [];
			let rootComId = String($scope.loggedInComId || '').trim();
			$scope.companies = rootComId ? collectDescendantCompanies(rootComId, allCompanies) : allCompanies;
			if ($scope.companies.length === 1 && !$scope.x.project_id) {
				$scope.x.project_id = $scope.companies[0].com_id;
			}
			if ($scope.x.project_id) {
				fetchQuotationNumber($scope.x.project_id);
			}
			queueQuotationSelect2Init();
		});
	}

	$scope.init = function () {
		allowSelect2TypingInQuotationModal();
		$scope.loader(1);

		$http.get(rootUrl + 'customer/view?data=name,c_id').success(function (data) {
			$scope.customers = data || [];
			if ($scope.customers.length === 1 && !$scope.x.c_id) {
				$scope.x.c_id = $scope.customers[0].c_id;
			}
			queueQuotationSelect2Init();
		});

		$http.get(rootUrl + 'dashboard/fetch_userdata').success(function (data) {
			$scope.loggedInComId = (data && data.com_id !== undefined && data.com_id !== null)
				? String(data.com_id).trim()
				: '';
			loadQuotationCompanies();
		});

		$http.get(rootUrl + module + '/get_staff_list?quotation_generator=1').success(function (data) {
			$scope.employies = data || [];
			if ($scope.employies.length === 1 && !$scope.x.emp_id) {
				$scope.x.emp_id = $scope.employies[0].emp_id;
			}
			queueQuotationSelect2Init();
		});

		$http.get(rootUrl + module + '/get_template_list').then(function (response) {
			$scope.template_list = response.data || [];
			queueQuotationSelect2Init();
		});

		$http.get(rootUrl + 'plan_master/view').success(function (data) {
			$scope.plan_data = data || [];
			queueQuotationSelect2Init();
		});

		queueQuotationSelect2Init();
	};

	$scope.q_id = '';

	$scope.update_call = function (y) {
		$scope.x = angular.copy(y);
		$scope.x.__isEditMode = true;
		$scope.x.__original_project_id = y.project_id;
		$scope.x.__manual_plan_cost = parseFloat($scope.x.plan_cost) === 0;
		$scope.q_id = y.q_id;
		if (hasQuotationTempCopied($scope.q_id) || ($scope.temp_is_quotation_copy && $scope.temp_data && $scope.temp_data.length && String($scope.temp_q_id || '') === String($scope.q_id || ''))) {
			$scope.get_plan_temp_data($scope.q_id);
		} else {
			$scope.copy_plans_to_temp($scope.q_id);
		}
		queueQuotationSelect2Init(50);
	};

	$scope.$watch('x.project_id', function (newVal, oldVal) {
		if (newVal && newVal !== oldVal) {
			fetchQuotationNumber(newVal);
		}
		queueQuotationSelect2Init();
	});

	$scope.$watch('x.t_save', function (newVal) {
		if (!$scope.x) {
			$scope.x = {};
		}
		if (newVal == '0') {
			$scope.x.template_name = '';
			$('#template_name').hide();
		} else if (newVal == 1) {
			$('#template_name').show();
		}
		queueQuotationSelect2Init();
	});

	$scope.$watch('x.template_id', function (newVal) {
		if (newVal) {
			$http.get(rootUrl + module + '/get_template_details?id=' + newVal).then(function (response) {
				if (response.data && response.data.status == '1') {
					$scope.temp_is_quotation_copy = false;
					$scope.get_plan_temp_data();
				}
			});
		}
		queueQuotationSelect2Init();
	});

	$scope.copy_plans_to_temp = function (q_id) {
		q_id = q_id || 0;
		$http.get(rootUrl + module + '/copy_plans_to_temp_table?q_id=' + q_id).success(function () {
			markQuotationTempCopied(q_id);
			$scope.get_plan_temp_data(q_id);
			queueQuotationSelect2Init(50);
		});
	};

	$scope.get_quotation_plans_details = function (q_id) {
		$scope.quotation_plans_data = [];
		$http.get(rootUrl + module + '/get_quotation_details?q_id=' + q_id).success(function (data) {
			$scope.quotation_plans_data = data || [];
		});
	};

	$scope.filter_new = function () {
		$scope.x = {};
		$scope.selected = {};
		$scope.q_id = '';
		$scope.temp_q_id = '';
		$scope.temp_is_quotation_copy = false;
		$scope.x.__manual_plan_cost = false;
		$scope.quotation_plans_data = [];
		clearQuotationTempCopied();
		$scope.today_date();
		queueQuotationSelect2Init();
		$http.get(rootUrl + module + '/delete_temp').success(function (data) {
			if (data === '1') {
				$scope.temp_data = {};
			}
		});
	};

	$scope.filter_plans = function () {
		$scope.selected = {};
		queueQuotationSelect2Init();
	};

	$scope.save_data = function () {
		$('#quotationbtn').attr('disabled', true);
		$.ajax({
			type: 'POST',
			url: rootUrl + module + '/save',
			data: $('#quotation_details').serialize(),
			beforeSend: function () {
				$('#loader').css('display', 'inline');
			},
			success: function (data) {
				data = (data || '').trim();
				if (data == '1') {
					messages('success', 'Success!', 'Quotation Saved Successfully', 3000);
					clearQuotationTempCopied($scope.q_id || ($scope.x && $scope.x.q_id));
					$('#quotationFormModal').modal('hide');
					$scope.init();
					$scope.filter_new();
					$scope.temp_data = {};
					$scope.today_date();
					$scope.loader($scope.pageno || 1);
				} else if (data == '0') {
					messages('warning', 'Info!', 'No Data Affected', 3000);
				} else {
					messages('danger', 'Warning!', data, 6000);
				}
				$('#loader').css('display', 'none');
				$('#quotationbtn').attr('disabled', false);
			}
		});
	};

	$scope.add_plans = function (y) {
		$.ajax({
			type: 'POST',
			url: rootUrl + module + '/add_plan',
			data: y,
			dataType: 'json',
			success: function (data) {
				if (data && data.status == 'success') {
					messages('success', 'Success', data.msg, 4000);
					$scope.filter_plans();
					$scope.get_plan_temp_data();
					queueQuotationSelect2Init(50);
				} else {
					messages('warning', 'Warning', (data && data.msg) ? data.msg : 'Plan Details Not Added.', 4000);
				}
			}
		});
	};

	$scope.edit_temp_plan = function (plan) {
		if (!plan) {
			return;
		}

		$scope.selected = {
			temp_id: plan.temp_id,
			qd_id: plan.qd_id,
			plan: plan.plan_id,
			plan_id: plan.plan_id,
			name: plan.plan_name,
			mrp: plan.mrp,
			unit: plan.unit,
			type: plan.type,
			discount: plan.discount,
			sp: plan.sp,
			description: plan.plan_description,
			term_condition: plan.term_condition,
			subscription: plan.subscription
		};
		queueQuotationSelect2Init(50);
	};

	$scope.get_plan_temp_data = function (source_q_id) {
		$http.get(rootUrl + module + '/get_temp_data').success(function (data) {
			if (!$scope.x) {
				$scope.x = {};
			}
			var total = 0;
			angular.forEach(data, function (plan) {
				total += parseFloat(plan.sp) || 0;
			});
			if (!$scope.x.__manual_plan_cost) {
				$scope.x.plan_cost = total;
			}
			$scope.temp_data = data || [];
			if (source_q_id !== undefined) {
				$scope.temp_q_id = source_q_id || '';
				$scope.temp_is_quotation_copy = true;
				if ($scope.temp_data && $scope.temp_data.length) {
					markQuotationTempCopied(source_q_id);
				}
			}
		});
	};

	$scope.generate_proforma_invoice = function (y) {
		$state.go('proforma_invoice', { q_id: y });
	};

	$scope.plan_selected = function (y) {
		$http.get(rootUrl + 'plan_master/view?plan_id=' + y).success(function (data) {
			if (!$scope.selected) {
				$scope.selected = {};
			}
			if (data && data.length) {
				angular.extend($scope.selected, data[0]);
				$scope.selected.unit = 1;
				$scope.calculateDiscount();
				$http.get(rootUrl + 'service_master/view?service_id=' + data[0].service_id + '&data=term_condition').success(function (response) {
					if (response && response.length) {
						$scope.selected.term_condition = response[0].term_condition;
					}
				});
			}
		});
		queueQuotationSelect2Init();
	};

	$scope.calculateDiscount = function (unitForm) {
		unitForm = unitForm || false;
		if ($scope.selected && $scope.selected.mrp && $scope.selected.unit && $scope.selected.sp) {
			var mrp = parseFloat($scope.selected.mrp);
			var unit = parseFloat($scope.selected.unit);
			var sp = parseFloat($scope.selected.sp);

			if (!isNaN(mrp) && !isNaN(sp) && !isNaN(unit)) {
				var price = mrp * unit;
				if (price > sp) {
					if (unitForm === true) {
						$scope.selected.sp = price;
						$scope.selected.discount = 0;
					} else {
						$scope.selected.discount = price - sp;
					}
				} else {
					$scope.selected.discount = 0;
				}
			} else {
				$scope.selected.discount = '';
			}
		} else {
			$scope.selected.discount = '';
		}
	};

	$scope.unit_change = function () {
		if ($scope.selected && $scope.selected.unit && $scope.selected.mrp) {
			$scope.calculateDiscount(true);
		}
	};

	$scope.delete_temp = function (y) {
		if (y.temp_id) {
			$http.get(rootUrl + module + '/delete_temp?temp_id=' + y.temp_id).success(function (data) {
				if (data.status == 'success') {
					$scope.x.plan_cost = 0;
					messages('success', 'Success', data.msg, 4000);
					if ($scope.selected && String($scope.selected.temp_id || '') === String(y.temp_id || '')) {
						$scope.filter_plans();
					}
					$scope.get_plan_temp_data();
					queueQuotationSelect2Init();
				} else {
					messages('warning', 'Warning', data.msg, 4000);
				}
			});
		}
	};

	$scope.generate_pdf = function (id) {
		$scope.ID = id;
		if ($scope.ID) {
			var url = rootUrl + module + '/generate_pdf?&id=' + $scope.ID;
			window.open(url, '_blank');
		}
	};

	$scope.options = {
		height: 200,
		toolbar: [
			['font', ['bold', 'italic', 'underline']],
			['font', ['fontsize']],
			['para', ['ol']],
			['insert', ['link']],
			['view', ['codeview']],
			['para', ['justifyLeft', 'justifyCenter', 'justifyRight', 'justifyFull']]
		]
	};

	$scope.cleanHTML = function (html) {
		return html ? html.replace(/<[^>]+>/g, '') : '';
	};

	$scope.init();
	$scope.today_date();

	$(document)
		.off('shown.bs.modal.quotation', '#quotationFormModal')
		.on('shown.bs.modal.quotation', '#quotationFormModal', function () {
			queueQuotationSelect2Init();
		});

	$scope.$watchCollection('customers', function () {
		queueQuotationSelect2Init();
	});

	$scope.$watchCollection('companies', function () {
		queueQuotationSelect2Init();
	});

	$scope.$watchCollection('employies', function () {
		queueQuotationSelect2Init();
	});

	$scope.$watchCollection('template_list', function () {
		queueQuotationSelect2Init();
	});

	$scope.$watchCollection('plan_data', function () {
		queueQuotationSelect2Init();
	});

	$scope.mark_plan_cost_manual = function () {
		$scope.x = $scope.x || {};
		$scope.x.__manual_plan_cost = true;
	};

	$scope.$watchGroup(['x.c_id', 'x.project_id', 'x.emp_id', 'x.template_id', 'selected.plan'], function () {
		queueQuotationSelect2Init();
	});

	$scope.$on('$destroy', function () {
		if ($.fn.select2) {
			$(quotationSelect2Selectors).each(function () {
				var $el = $(this);
				if ($el.data('select2')) {
					$el.select2('destroy');
				}
			});
		}
	});

	$('#DOB1').datepicker({
		format: 'dd/mm/yyyy',
		autoclose: true
	});
	$('.date').datepicker({
		format: 'dd/mm/yyyy',
		autoclose: true
	});
}]);


app.controller('invoice', ['$scope', '$rootScope', '$http', '$timeout', '$state', '$stateParams', function ($scope, $rootScope, $http, $timeout, $state, $stateParams) {
	let module = 'invoice';
	let rootUrl = $rootScope.site_url;
	let select2Selectors = '#invoice_filter_company_select, #invoice_filter_customer_select, #invoice_filter_per_page_select, #invoice_customer_select, #invoice_company_select, #invoice_plan_select';
	$http.get(rootUrl + module + "/index").success(function (data) { if (data == 0) { window.location.assign('login.html'); } else if (data == 2) { messages("success", "Privilege not assigned.", 1000); window.location.assign('index.html'); } });

	$scope.proforma_id = $stateParams.proforma_id;
	$scope.pageno = 1;
	$scope.total_count = 0;
	$scope.totalPages = 1;
	$scope.itemsPerPage = '15';
	$scope.pageLinks = [];
	$scope.qx = {
		project_id: '',
		c_id: '',
		invoice_number: '',
		inv_date: ''
	};
	$scope.acSuggestions = {};
	$scope.acActive = {};
	var acTimers = {};
	$scope.datadb = [];
	$scope.loading = false;
	$scope.x = {};
	$scope.selected = {
		plan: '',
		plan_id: '',
		payment_model: 'advance',
		subscription: 'advance'
	};
	$scope.subscriptionBuilder = {
		visible: false,
		loading: false,
		mode: '',
		oneTimePlans: [],
		repeatingPlans: [],
		plans: [],
		selectedPlanId: '',
		selectedPlan: null,
		selectedInstallments: {},
		error: ''
	};
	$scope.editingTempInvoicePlanId = '';
	let loadingProformaPlanId = '';
	let planSelectionRequestSeq = 0;
	let preserveProformaInvoiceCopy = false;
	let proformaInvoiceCopySnapshot = null;
	let syncingProformaInvoiceCopyTotals = false;
	$scope.paymentModelOptions = [
		{ value: 'advance', label: 'Advance' },
		{ value: 'partly', label: 'Partially' },
		{ value: 'final', label: 'Final' }
	];
	$scope.helpLanguage = 'en';
	$scope.helpLanguages = [
		{ code: 'en', label: 'English' },
		{ code: 'hi', label: 'Hindi' },
		{ code: 'bn', label: 'Bengali' },
		{ code: 'ne', label: 'Nepali' }
	];
	$scope.helpDocs = {
		en: {
			title: 'Invoice Workflow Guide',
			intro: 'Use this guide to create a final invoice from a saved proforma, from subscribed plans, or by adding plans manually. The invoice page is where invoice numbering, GST, billable totals, due amount, and repeating-plan billing are finalized before the record is saved.',
			sections: [
				{
					title: 'Start from the list',
					points: [
						'Use the filter bar to search invoices by company, customer, invoice number, or date.',
						'Click Add Invoice to open a new invoice draft.',
						'Use the PDF action from the list when you need a printable invoice copy.'
					]
				},
				{
					title: 'Fill general details',
					points: [
						'Select the Date, Customer, and Company first.',
						'When you choose a customer, the linked invoice company may be selected automatically.',
						'The company and customer state are used to determine GST type automatically.',
						'Invoice number is generated when the invoice is saved, not while filling the form.'
					]
				},
				{
					title: 'Load from proforma',
					points: [
						'If you open the invoice page from a proforma, the proforma header data is copied automatically.',
						'The saved proforma plan rows are loaded into the invoice draft rows.',
						'Review the copied plans, totals, GST, and due values before saving the invoice.',
						'This is the main proforma-to-invoice handoff used when the customer has approved the proforma.'
					]
				},
				{
					title: 'Build from subscription',
					points: [
						'Select the customer first, then click Build From Subscription.',
						'Choose One Time or Repeating to view the customer active subscribed plans.',
						'Select the plan you want to bill.',
						'For one-time plans with installments, select the installments to include now.',
						'Click the generate action to load the selected subscription into the Add Plan form.',
						'Review the loaded values and click Save Plan to add the row into the invoice draft.'
					]
				},
				{
					title: 'Add or edit plan rows',
					points: [
						'Choose a plan from the Plan dropdown to load default plan details.',
						'Review or edit Unit Price, Unit, Discount, Total (SP), date range, and description as needed.',
						'Click Save Plan to store the current row in the invoice draft table.',
						'Use Edit in the Plans table to load a saved draft row back into the form.',
						'Use Delete to remove an unwanted draft row before saving the invoice.'
					]
				},
				{
					title: 'Installments and repeating plans',
					points: [
						'One-time plans can include installment rows inside the draft plan form.',
						'Fill or review installment title, percentage, and amount before saving the plan.',
						'Repeating plans check previous invoice history before the next billing period is prepared.',
						'For repeating or carry-forward billing, Plan Cost can include the remaining due while Total (SP) keeps the original plan value.',
						'If the paid amount for a repeating plan becomes higher than the original plan total, adjust the extra amount in Total Cost, not in Plan Total.',
						'If an installment paid amount is lower or higher than the installment amount, update the paid amount in the installment amount itself, not in Total Cost.'
					]
				},
				{
					title: 'Review totals and GST',
					points: [
						'Total Cost is calculated from the saved invoice plan rows.',
						'GST is applied automatically based on company GST details and customer state.',
						'The form shows CGST and SGST or IGST when applicable.',
						'Payable Amount updates from the total and GST values.',
						'Use the summary section to verify Total Cost, GST, payable amount, and due or carry-forward before saving.'
					]
				},
				{
					title: 'Save, update, and print',
					points: [
						'Click Generate Invoice after checking the header, plans, totals, GST, and due values.',
						'The system saves the invoice header together with the draft plan rows.',
						'When saving a new invoice, the final invoice number is generated at that stage.',
						'Use Generate Pdf from the list to open the printable invoice document.'
					]
				}
			]
		},
		hi: {
			title: 'इनवॉइस कार्यप्रवाह मार्गदर्शिका',
			intro: 'इस मार्गदर्शिका की मदद से नया इनवॉइस बनाएं, प्रॉफॉर्मा से डेटा लाएं, प्लान जोड़ें, GST जांचें, और अंतिम इनवॉइस सेव या प्रिंट करें।',
			sections: [
				{ title: 'इनवॉइस बनाएं', points: ['Add Invoice पर क्लिक करें।', 'इनवॉइस नंबर अपने आप बन जाएगा।', 'सबसे पहले Date, Company और Customer चुनें।', 'Company और customer से GST नियम तय होते हैं।'] },
				{ title: 'इनवॉइस नंबर कैसे बनता है', points: ['नंबर company के आधार पर बनता है, हाथ से नहीं भरा जाता।', 'अगर company में prefix और unique number दोनों हैं, तो वही company setup उपयोग होता है।', 'अगर उसी company का कोई पिछला invoice है, तो latest invoice number से अगला नंबर बनता है।', 'अगर prefix खाली है लेकिन unique number मौजूद है, तो unique value या next numeric sequence उपयोग होती है।', 'अगर company numbering configured नहीं है, तो backend fallback value तय करता है।'] },
				{ title: 'प्रॉफॉर्मा से लाएं', points: ['यदि आप प्रॉफॉर्मा से Invoice खोलते हैं, तो header data अपने आप भर जाता है।', 'प्रॉफॉर्मा के plan rows invoice temp list में आ जाते हैं।', 'Save करने से पहले आप rows जांच सकते हैं।', 'इससे final invoice approved proforma के अनुसार रहता है।'] },
				{ title: 'प्लान जोड़ें और बदलें', points: ['Plan dropdown से plan चुनें।', 'Unit Price, Unit, Discount और Total (SP) जांचें या बदलें।', 'Description selected plan से लोड होती है।', 'Save Plan पर क्लिक करके current plan invoice में जोड़ें।', 'जरूरत हो तो कई plan rows जोड़ें।'] },
				{ title: 'Installment schedule', points: ['अगर plan one-time है, तो installment rows अपने आप दिखेंगी।', 'हर installment के लिए title, percentage, amount और due date भरें।', 'Schedule selected plan के साथ save होता है।', 'Saved installments Plans section में देखे जा सकते हैं।'] },
				{ title: 'Total और GST देखें', points: ['Total Cost saved plan rows से calculate होता है।', 'Company और customer state के अनुसार GST अपने आप लगता है।', 'प्रोजेक्ट के अनुसार CGST/SGST या IGST दिखता है।', 'Payable Amount total और GST के आधार पर update होता है।', 'Final values summary section में जांचें।'] },
				{ title: 'Save, update और print', points: ['Values जांचने के बाद Generate Invoice या Edit Invoice पर क्लिक करें।', 'नया record होने पर system invoice बनाता है और plans save करता है।', 'पुराने record में यही form header और rows update करता है।', 'Generate Pdf से printable invoice खुलता है।'] }
			]
		},
		bn: {
			title: 'ইনভয়েস ওয়ার্কফ্লো গাইড',
			intro: 'এই গাইড ব্যবহার করে নতুন ইনভয়েস তৈরি করুন, প্রোফর্মা থেকে ডেটা আনুন, প্ল্যান যোগ করুন, GST যাচাই করুন, এবং চূড়ান্ত ইনভয়েস save বা print করুন।',
			sections: [
				{ title: 'ইনভয়েস তৈরি করুন', points: ['Add Invoice-এ ক্লিক করুন।', 'Invoice number স্বয়ংক্রিয়ভাবে তৈরি হবে।', 'প্রথমে Date, Company এবং Customer নির্বাচন করুন।', 'Company এবং customer selection GST নির্ধারণে সাহায্য করে।'] },
				{ title: 'Invoice number generation', points: ['নম্বর company-এর ভিত্তিতে তৈরি হয়, হাতে লেখা হয় না।', 'Company-তে prefix এবং unique number থাকলে সেই setup ব্যবহার করা হয়।', 'ঐ company-এর আগের invoice থাকলে latest invoice number থেকে next number তৈরি হয়।', 'Prefix খালি হলেও unique number থাকলে unique value বা next numeric sequence ব্যবহার হয়।', 'Company numbering configured না থাকলে backend fallback value নির্ধারণ করে।'] },
				{ title: 'প্রোফর্মা থেকে লোড করুন', points: ['প্রোফর্মা থেকে Invoice খুললে header data স্বয়ংক্রিয়ভাবে copy হয়।', 'প্রোফর্মার plan rows invoice temp list-এ আসে।', 'Save করার আগে loaded rows দেখে নিন।', 'এতে final invoice approved proforma-এর সাথে মিলে থাকে।'] },
				{ title: 'প্ল্যান যোগ ও সম্পাদনা করুন', points: ['Plan dropdown থেকে একটি plan নির্বাচন করুন।', 'Unit Price, Unit, Discount এবং Total (SP) পরীক্ষা বা পরিবর্তন করুন।', 'Description নির্বাচিত plan থেকে লোড হয়।', 'Save Plan-এ ক্লিক করে current plan invoice-এ সংরক্ষণ করুন।', 'প্রয়োজন হলে একাধিক plan rows যোগ করুন।'] },
				{ title: 'Installment schedule', points: ['Plan one-time হলে installment rows স্বয়ংক্রিয়ভাবে দেখা যাবে।', 'প্রতিটি installment-এর জন্য title, percentage, amount এবং due date পূরণ করুন।', 'Schedule selected plan-এর সাথে save হয়।', 'Saved installments Plans section-এ দেখা যায়।'] },
				{ title: 'Total এবং GST দেখুন', points: ['Total Cost saved plan rows থেকে calculate হয়।', 'Company এবং customer state অনুযায়ী GST স্বয়ংক্রিয়ভাবে প্রয়োগ হয়।', 'প্রযোজ্য হলে CGST/SGST বা IGST দেখা যায়।', 'Payable Amount total ও GST অনুযায়ী update হয়।', 'Summary section-এ final amount যাচাই করুন।'] },
				{ title: 'Save, update এবং print', points: ['সব value যাচাই করে Generate Invoice বা Edit Invoice-এ ক্লিক করুন।', 'নতুন record হলে system invoice তৈরি করে এবং plans save করে।', 'পুরোনো record হলে header এবং rows update হয়।', 'Generate Pdf থেকে printable invoice খোলা যায়।'] }
			]
		},
		ne: {
			title: 'इन्वोइस कार्यप्रवाह मार्गदर्शिका',
			intro: 'यो मार्गदर्शिकाको प्रयोग गरेर नयाँ इन्वोइस बनाउनुहोस्, प्रॉफर्माबाट डेटा ल्याउनुहोस्, प्लान थप्नुहोस्, GST जाँच्नुहोस्, र अन्तिम इन्वोइस save वा print गर्नुहोस्।',
			sections: [
				{ title: 'इन्वोइस बनाउनुहोस्', points: ['Add Invoice मा क्लिक गर्नुहोस्।', 'Invoice number स्वतः बनाइन्छ।', 'पहिले Date, Company, र Customer छान्नुहोस्।', 'Company र customer selection ले GST निर्धारणमा मद्दत गर्छ।'] },
				{ title: 'Invoice number generation', points: ['नम्बर company को आधारमा बनाइन्छ, हातले लेखिँदैन।', 'Company मा prefix र unique number दुवै भएमा त्यही setup प्रयोग हुन्छ।', 'त्यही company को अघिल्लो invoice छ भने latest invoice number बाट next number बनाइन्छ।', 'Prefix खाली भए पनि unique number भए unique value वा next numeric sequence प्रयोग हुन्छ।', 'Company numbering configured नभए backend ले fallback value तय गर्छ।'] },
				{ title: 'प्रॉफर्माबाट लोड गर्नुहोस्', points: ['प्रॉफर्माबाट Invoice खोल्दा header data स्वतः copy हुन्छ।', 'प्रॉफर्माका plan rows invoice temp list मा आउँछन्।', 'Save गर्नु अघि loaded rows जाँच्न सक्नुहुन्छ।', 'यसले final invoice approved proforma सँग मिल्छ।'] },
				{ title: 'प्लान थप्नु र सम्पादन गर्नु', points: ['Plan dropdown बाट plan छान्नुहोस्।', 'Unit Price, Unit, Discount, र Total (SP) जाँच्नुहोस् वा परिवर्तन गर्नुहोस्।', 'Description selected plan बाट load हुन्छ।', 'Save Plan मा क्लिक गरेर current plan invoice मा राख्नुहोस्।', 'आवश्यक भएमा धेरै plan rows थप्न सकिन्छ।'] },
				{ title: 'Installment schedule', points: ['Plan one-time भए installment rows स्वतः देखिन्छन्।', 'हरेक installment का लागि title, percentage, amount, र due date भर्नुहोस्।', 'Schedule selected plan सँगै save हुन्छ।', 'Saved installments Plans section मा देख्न सकिन्छ।'] },
				{ title: 'Total र GST हेर्नुहोस्', points: ['Total Cost saved plan rows बाट calculate हुन्छ।', 'Company र customer state अनुसार GST स्वतः लागू हुन्छ।', 'लागू भएमा CGST/SGST वा IGST देखिन्छ।', 'Payable Amount total र GST अनुसार update हुन्छ।', 'Summary section मा final amount जाँच्नुहोस्।'] },
				{ title: 'Save, update, र print', points: ['सबै value जाँचेर Generate Invoice वा Edit Invoice मा क्लिक गर्नुहोस्।', 'नयाँ record भए system ले invoice बनाउँछ र plans save गर्छ।', 'पुरानो record भए header र rows update हुन्छन्।', 'Generate Pdf बाट printable invoice खोल्न सकिन्छ।'] }
			]
		}
	};
	$scope.invoiceDueSummary = {
		balance: 0,
		label: 'Due',
		note: 'Saved plan total minus invoice total.'
	};
	// console.log('proforma_id:',$scope.proforma_id);
	function clearSavedProformaInvoiceUrl() {
		if (!$scope.proforma_id && !($scope.x && $scope.x.proforma_id)) {
			return;
		}

		$scope.proforma_id = '';
		if ($scope.x) {
			$scope.x.proforma_id = '';
		}

		if ($state && $state.current && $state.current.name === 'invoice') {
			$state.go('invoice_view', {}, {
				location: 'replace',
				notify: false,
				inherit: false
			});
		}
	}

	function toNumber(value) {
		let parsed = parseFloat(value);
		return isNaN(parsed) ? 0 : parsed;
	}

	function parseInvoiceBillingDate(value) {
		var text = String(value || '').trim();
		if (!text) {
			return null;
		}

		var parts = text.split(/[\/.-]/);
		if (parts.length !== 3) {
			return null;
		}

		var first = parseInt(parts[0], 10);
		var second = parseInt(parts[1], 10);
		var third = parseInt(parts[2], 10);
		var year, month, day;

		if (String(parts[0]).length === 4) {
			year = first;
			month = second - 1;
			day = third;
		} else {
			day = first;
			month = second - 1;
			year = third;
		}

		var date = new Date(year, month, day);
		if (isNaN(date.getTime())) {
			return null;
		}
		return date;
	}

	function getRepeatingPlanBillingMonths(plan) {
		if (!isRepeatingInvoicePlan(plan)) {
			return 1;
		}

		var startDate = parseInvoiceBillingDate(plan && plan.start_date);
		var endDate = parseInvoiceBillingDate(plan && plan.end_date);
		if (!startDate || !endDate || endDate.getTime() <= startDate.getTime()) {
			return 1;
		}

		var months = ((endDate.getFullYear() - startDate.getFullYear()) * 12) + (endDate.getMonth() - startDate.getMonth());
		if (endDate.getDate() > startDate.getDate()) {
			months += 1;
		}
		return Math.max(months, 1);
	}

	function refreshInvoiceDueSummary() {
		$scope.invoiceDueSummary = $scope.invoiceDueSummary || {};

		let planTotal = 0;
		angular.forEach($scope.invoice_plans || [], function (plan) {
			planTotal += getInvoicePlanCost(plan);
		});

		let planCost = toNumber($scope.x && $scope.x.plan_cost);
		let carryForwardOpeningDue = parseInvoiceAmount($scope.x && $scope.x.__carry_forward_opening_due);
		let rawBalance = carryForwardOpeningDue > 0 ? carryForwardOpeningDue - planCost : planTotal - planCost;
		let balance = Math.abs(rawBalance);

		$scope.invoiceDueSummary.balance = balance;
		$scope.invoiceDueSummary.label = rawBalance >= 0 ? 'Due' : 'Carry Forward';
		$scope.invoiceDueSummary.note = carryForwardOpeningDue > 0
			? 'Carry-forward due is compared with the invoice total.'
			: rawBalance >= 0
			? 'Saved plan total is higher than the invoice total.'
			: 'Invoice total is higher than the saved plan total.';
		$scope.x = $scope.x || {};
		$scope.x.due = balance;
	}

	function buildPageLinks(currentPage, totalPages) {
		let pages = [];
		let start = 1;
		let end = totalPages;

		if (totalPages > 7) {
			start = currentPage - 2;
			end = currentPage + 2;

			if (start < 1) {
				end += (1 - start);
				start = 1;
			}
			if (end > totalPages) {
				start -= (end - totalPages);
				end = totalPages;
			}

			if (start < 1) {
				start = 1;
			}
		}

		for (let i = start; i <= end; i++) {
			pages.push(i);
		}

		return pages;
	}

	function refreshPagerMeta() {
		let perPage = parseInt($scope.itemsPerPage, 10) || 1;
		$scope.totalPages = Math.max(1, Math.ceil(($scope.total_count || 0) / perPage));
		$scope.pageLinks = buildPageLinks($scope.pageno || 1, $scope.totalPages);
	}

	function allowSelect2TypingInsideInvoiceModal() {
		if (typeof $ === 'undefined' || !$.fn || !$.fn.modal || !$.fn.modal.Constructor) {
			return;
		}

		var ModalConstructor = $.fn.modal.Constructor;
		if (ModalConstructor.prototype._invoiceSelect2FocusPatched) {
			return;
		}

		ModalConstructor.prototype.enforceFocus = function () {
			var modalThis = this;
			$(document)
				.off('focusin.bs.modal')
				.on('focusin.bs.modal', function (e) {
					var $target = $(e.target);
					var isInsideModal = modalThis.$element[0] === e.target || modalThis.$element.has(e.target).length;
					var isSelect2Input =
						$target.closest('.select2-container, .select2-dropdown, .select2-drop, .select2-search').length > 0 ||
						$target.is('.select2-input, .select2-search__field');

					if (!isInsideModal && !isSelect2Input) {
						modalThis.$element.trigger('focus');
					}
				});
		};

		ModalConstructor.prototype._invoiceSelect2FocusPatched = true;

		$(document).off('select2:open.invoice select2-open.invoice');
		$(document).on('select2:open.invoice select2-open.invoice', function () {
			setTimeout(function () {
				var $search = $('.select2-container-active .select2-input, .select2-drop-active .select2-input, .select2-container--open .select2-search__field');
				if ($search.length) {
					$search.focus();
				}
			}, 0);
		});
	}

	function initInvoiceSelect2() {
		$timeout(function () {
			if (!$.fn.select2) {
				return;
			}

			let $modal = $('#invoiceFormModal');
			let isSelect2V4 = !!($.fn.select2 && $.fn.select2.amd);

			allowSelect2TypingInsideInvoiceModal();

			syncInvoiceSelect2Values();

			$(select2Selectors).each(function () {
				let $select = $(this);
				if (!$select.is('select')) {
					return;
				}
				if ($select.data('select2')) {
					$select.select2('destroy');
				}
				let options = {
					width: '100%'
				};
				if (isSelect2V4 && $select.closest('#invoiceFormModal').length) {
					options.dropdownParent = $modal;
				}
				$select.select2(options);
			});

			syncInvoiceSelect2Values();
		}, 0);
	}

	function queueInvoiceSelect2Init(delay) {
		$timeout(function () {
			initInvoiceSelect2();
		}, delay || 0);
	}

	function showInvoiceFormModal(delay, attempts) {
		$timeout(function () {
			let $modal = $('#invoiceFormModal');
			if (!$modal.length) {
				if ((attempts || 0) < 10) {
					showInvoiceFormModal(50, (attempts || 0) + 1);
				}
				return;
			}
			$modal.modal('show');
			queueInvoiceSelect2Init();
		}, delay || 0);
	}

	function syncSelect2Value(selector, value) {
		let $select = $(selector);
		if (!$select.length) {
			return;
		}
		let normalizedValue = (value === undefined || value === null) ? '' : String(value);
		$select.val(normalizedValue);
		if ($select.data('select2')) {
			if ($.fn.select2 && $.fn.select2.amd) {
				$select.trigger('change.select2');
			} else {
				$select.select2('val', normalizedValue, false);
			}
		}
	}

	function syncInvoiceSelect2Values() {
		syncSelect2Value('#invoice_filter_company_select', $scope.qx && $scope.qx.project_id);
		syncSelect2Value('#invoice_filter_customer_select', $scope.qx && $scope.qx.c_id);
		syncSelect2Value('#invoice_filter_per_page_select', $scope.itemsPerPage);
		syncSelect2Value('#invoice_customer_select', $scope.x && $scope.x.c_id);
		syncSelect2Value('#invoice_company_select', $scope.x && $scope.x.project_id);
		syncSelect2Value('#invoice_plan_select', $scope.selected && ($scope.selected.plan || $scope.selected.plan_id));
	}

	function collectDescendantCompanies(rootId, companies) {
		var byId = {};
		var childrenMap = {};
		var queue = [String(rootId || '').trim()];
		var seen = {};
		var allowed = [];

		angular.forEach(companies || [], function (company) {
			if (!company || company.com_id === undefined || company.com_id === null) {
				return;
			}

			var companyId = String(company.com_id).trim();
			if (!companyId) {
				return;
			}

			if (company.com_name && !company.name) {
				company.name = company.com_name;
			}
			if (company.name && !company.com_name) {
				company.com_name = company.name;
			}

			byId[companyId] = company;

			var parentId = String(company.parent || '').trim();
			if (!parentId) {
				return;
			}
			if (!childrenMap[parentId]) {
				childrenMap[parentId] = [];
			}
			childrenMap[parentId].push(company);
		});

		while (queue.length) {
			var currentId = queue.shift();
			if (!currentId || seen[currentId]) {
				continue;
			}
			seen[currentId] = true;

			if (byId[currentId]) {
				allowed.push(byId[currentId]);
			}

			angular.forEach(childrenMap[currentId] || [], function (child) {
				if (child && child.com_id !== undefined && child.com_id !== null) {
					queue.push(String(child.com_id).trim());
				}
			});
		}

		return allowed;
	}

	function loadInvoiceCompanies() {
		var loggedInComId = String(localStorage.getItem('com_id') || '').trim();
		if (!loggedInComId) {
			$scope.companies = [];
			queueInvoiceSelect2Init();
			return;
		}

		$http.get(rootUrl + 'company_master/view?data=com_id,com_name,parent').success(function (data) {
			var allCompanies = angular.isArray(data) ? data : [];
			var filteredCompanies = collectDescendantCompanies(loggedInComId, allCompanies);

			$scope.companies = filteredCompanies;
			if ($scope.companies.length === 1) {
				$scope.x.project_id = String($scope.companies[0].com_id || '');
			}
			queueInvoiceSelect2Init();
		}).error(function () {
			$scope.companies = [];
			queueInvoiceSelect2Init();
		});
	}

	function ensureInvoicePlanOption(plan) {
		if (!plan || !plan.plan_id) {
			return;
		}

		$scope.plan_data = $scope.plan_data || [];
		let planExists = false;
		angular.forEach($scope.plan_data, function (planItem) {
			if (String(planItem.plan_id) === String(plan.plan_id)) {
				planExists = true;
			}
		});

		if (!planExists) {
			$scope.plan_data.push({
				plan_id: plan.plan_id,
				name: plan.name || plan.plan_name || ('Plan #' + plan.plan_id)
			});
		}
	}

	function normalizePlanType(value) {
		value = String(value || '').toLowerCase();
		value = value.replace(/[_-]+/g, ' ');
		value = value.replace(/\s+/g, ' ').trim();
		return value;
	}

	function normalizeInvoicePaymentModel(value) {
		var normalized = normalizePlanType(value);
		if (normalized === 'partly' || normalized === 'partial' || normalized === 'partially') {
			return 'partly';
		}
		if (normalized === 'final') {
			return 'final';
		}
		return 'advance';
	}

	function parseInvoiceAmount(value) {
		var parsed = parseFloat(String(value || '').replace(/,/g, '').replace(/[^\d.-]/g, ''));
		return isNaN(parsed) ? 0 : parsed;
	}

	function formatInvoiceAmount(value) {
		var amount = parseInvoiceAmount(value);
		return (amount % 1 === 0) ? String(amount) : String(parseFloat(amount.toFixed(2)));
	}

	function firstInvoiceValue() {
		for (var i = 0; i < arguments.length; i++) {
			var value = arguments[i];
			if (value !== undefined && value !== null && String(value).trim() !== '') {
				return value;
			}
		}
		return '';
	}

	function getSubscriptionInstallmentSourcePercent(installment) {
		installment = installment || {};
		var sourcePercent = firstInvoiceValue(
			installment._source_percentage,
			installment._source_percent,
			installment.percentage,
			installment.percent
		);
		if (sourcePercent === '') {
			return null;
		}
		return parseInvoiceAmount(sourcePercent);
	}

	function getSubscriptionInstallmentAmount(installment, planTotal) {
		installment = installment || {};
		var directAmount = parseInvoiceAmount(firstInvoiceValue(
			installment._source_amount,
			installment.amount,
			installment.installment_amount,
			installment.due_amount,
			installment.remaining_due,
			installment.payable_amount
		));

		var sourcePercent = getSubscriptionInstallmentSourcePercent(installment);
		if (planTotal > 0 && sourcePercent !== null) {
			var percentAmount = (planTotal * sourcePercent) / 100;
			if (directAmount <= 0) {
				return percentAmount;
			}

			var directPercent = (directAmount / planTotal) * 100;
			if (Math.abs(directPercent - sourcePercent) > 0.5) {
				return percentAmount;
			}
		}

		return directAmount;
	}

	function getSubscriptionPlanPercentBase(plan) {
		plan = plan || {};

		var planId = plan.plan_id || plan.plan || plan.subscribed_plan_id || '';
		var masterSp = 0;

		angular.forEach($scope.plan_data || [], function (planItem) {
			if (!masterSp && planId && String(planItem.plan_id) === String(planId)) {
				masterSp = parseInvoiceAmount(planItem.sp);
			}
		});

		return parseInvoiceAmount(plan.sp) ||
			parseInvoiceAmount(plan.total) ||
			parseInvoiceAmount(plan._plan_sp) ||
			parseInvoiceAmount(plan.plan_sp) ||
			parseInvoiceAmount(plan.master_sp) ||
			masterSp;
	}

	function sumInvoiceInstallmentAmounts(installments, planTotal) {
		var total = 0;
		angular.forEach(installments || [], function (installment) {
			total += getSubscriptionInstallmentAmount(installment, planTotal);
		});
		return total;
	}

	function sumInvoiceEnteredInstallmentAmounts(installments) {
		var total = 0;
		angular.forEach(installments || [], function (installment) {
			total += parseInvoiceAmount(installment && installment.amount);
		});
		return total;
	}

	function getInvoicePlanCost(plan) {
		if (plan && isOneTimePlanType(plan.type) && angular.isArray(plan.installments) && plan.installments.length) {
			return sumInvoiceInstallmentAmounts(plan.installments);
		}
		if (isRepeatingInvoicePlan(plan)) {
			return parseInvoiceAmount(plan && plan.sp) * getRepeatingPlanBillingMonths(plan);
		}
		return parseInvoiceAmount(plan && plan.sp);
	}

	function recalculateInvoicePlanCostFromPlans() {
		var total = 0;
		angular.forEach($scope.invoice_plans || [], function (plan) {
			total += getInvoicePlanCost(plan);
		});
		$scope.x = $scope.x || {};
		$scope.x.plan_cost = total;
		return total;
	}

	function getCarryForwardInvoiceAmount(plan) {
		if (!plan || !isRepeatingInvoicePlan(plan)) {
			return 0;
		}

		var subscriptionBaseSp = parseInvoiceAmount(firstInvoiceValue(
			plan._subscription_base_sp,
			plan._plan_sp,
			plan.sp,
			plan.total
		));
		var carryForwardDue = parseInvoiceAmount(firstInvoiceValue(
			plan._carry_forward_due_amount,
			plan._previous_due,
			plan.remaining_due,
			plan.due_amount
		));
		var explicitCarryForwardPlanCost = parseInvoiceAmount(plan._carry_forward_plan_cost);
		if (explicitCarryForwardPlanCost > 0) {
			return explicitCarryForwardPlanCost;
		}
		var billDueOnly = plan._bill_due_only === 1 || plan._bill_due_only === '1' || plan._bill_due_only === true
			|| plan.bill_due_only === 1 || plan.bill_due_only === '1' || plan.bill_due_only === true;
		var billDueAfterEnd = plan.bill_due_after_end === 1 || plan.bill_due_after_end === '1' || plan.bill_due_after_end === true
			|| plan.bill_due_after_end === 'yes' || plan._bill_due_after_end === true;
		var currentStartDate = normalizeInvoiceDateForPicker(firstInvoiceValue(plan.start_date));
		var currentEndDate = normalizeInvoiceDateForPicker(firstInvoiceValue(plan.end_date));
		var defaultStartDate = normalizeInvoiceDateForPicker(firstInvoiceValue(
			plan._default_repeating_start_date,
			plan.next_invoice_start_date
		));
		var defaultEndDate = normalizeInvoiceDateForPicker(firstInvoiceValue(
			plan._default_repeating_end_date,
			plan.next_invoice_end_date
		));
		var usingDefaultCarryDates = defaultStartDate && defaultEndDate
			&& currentStartDate === defaultStartDate
			&& currentEndDate === defaultEndDate;
		var cycleMonths = getRepeatingPlanBillingMonths(plan);
		var currentCycleAmount = subscriptionBaseSp * cycleMonths;

		if (carryForwardDue > 0) {
			if (billDueOnly || billDueAfterEnd) {
				if (usingDefaultCarryDates) {
					return carryForwardDue;
				}
				return carryForwardDue + currentCycleAmount;
			}

			if (currentStartDate || currentEndDate) {
				return carryForwardDue + currentCycleAmount;
			}
			return carryForwardDue;
		}

		return currentCycleAmount;
	}

	$scope.refresh_repeating_plan_cost = function () {
		if (!$scope.selected || !isRepeatingInvoicePlan($scope.selected)) {
			return;
		}

		var repeatingCost = getCarryForwardInvoiceAmount($scope.selected);
		if (repeatingCost > 0) {
			$scope.x = $scope.x || {};
			$scope.x.plan_cost = formatInvoiceAmount(repeatingCost);
		}
		refreshInvoiceDueSummary();
	};

	function getSubscriptionInstallmentPercentValue(installment, planTotal) {
		if (!installment) {
			return null;
		}

		var hasAmount = firstInvoiceValue(
			installment.amount,
			installment.installment_amount,
			installment.due_amount,
			installment.remaining_due,
			installment.payable_amount
		) !== '';
		var amount = getSubscriptionInstallmentAmount(installment, planTotal);
		if (hasAmount && planTotal > 0) {
			return (amount / planTotal) * 100;
		}

		var directPercent = installment.percent;
		if ((directPercent === undefined || directPercent === null || String(directPercent).trim() === '') && installment.percentage !== undefined && installment.percentage !== null) {
			directPercent = installment.percentage;
		}

		if (directPercent !== undefined && directPercent !== null && String(directPercent).trim() !== '') {
			return parseInvoiceAmount(directPercent);
		}

		return null;
	}

	function normalizeSubscriptionInstallmentPercent(installment, planTotal) {
		var percent = getSubscriptionInstallmentPercentValue(installment, planTotal);
		if (percent !== null) {
			var formattedPercent = formatInvoiceAmount(percent);
			installment.percent = formattedPercent;
			installment.percentage = formattedPercent;
		}
		return installment;
	}

	function attachInstallmentPercentBase(installment, planTotal) {
		if (!installment) {
			return installment;
		}

		if (planTotal > 0) {
			installment._percent_base = planTotal;
		}
		return installment;
	}

	function getInstallmentPercentBase(row) {
		return getSubscriptionPlanPercentBase($scope.selected);
	}

	function setInvoiceInstallmentPercentFromEnteredAmount(row, percentBase) {
		var amount = parseInvoiceAmount(row && row.amount);
		var formattedPercent = percentBase > 0 ? formatInvoiceAmount((amount / percentBase) * 100) : '';
		row.percent = formattedPercent;
		row.percentage = formattedPercent;
	}

	$scope.refresh_installment_percentages = function () {
		var percentBase = getSubscriptionPlanPercentBase($scope.selected);
		angular.forEach($scope.generatedRows || [], function (row) {
			attachInstallmentPercentBase(row, percentBase);
			if (row && row._manual_amount_override) {
				setInvoiceInstallmentPercentFromEnteredAmount(row, percentBase);
			} else {
				normalizeSubscriptionInstallmentPercent(row, percentBase);
			}
		});
	};

	$scope.update_installment_percent_from_amount = function (row) {
		if (!row) {
			return;
		}

		if (row.amount === undefined || row.amount === null || String(row.amount).trim() === '') {
			row.percent = '';
			row.percentage = '';
			return;
		}

		var percentBase = getInstallmentPercentBase(row);
		var amount = parseInvoiceAmount(row.amount);
		if (percentBase <= 0) {
			return;
		}

		row._manual_amount_override = true;
		setInvoiceInstallmentPercentFromEnteredAmount(row, percentBase);
		if ($scope.selected && isOneTimePlanType($scope.selected.type)) {
			$scope.selected._installment_total = formatInvoiceAmount(sumInvoiceEnteredInstallmentAmounts($scope.generatedRows));
		}
	};

	function getSubscriptionInstallmentId(installment) {
		if (!installment) {
			return '';
		}
		return installment.subscribed_plan_installment_id || installment.installment_no || '';
	}

	function getUniqueSubscriptionInstallments(installments) {
		var uniqueInstallments = [];
		var seen = {};

		angular.forEach(installments || [], function (installment, index) {
			var installmentId = getSubscriptionInstallmentId(installment);
			var mergeKey = installmentId ? String(installmentId) : ('row-' + index);
			if (seen[mergeKey]) {
				return;
			}
			seen[mergeKey] = true;
			uniqueInstallments.push(installment);
		});

		return uniqueInstallments;
	}

	function getSubscriptionInstallmentTitle(installment) {
		if (!installment) {
			return '';
		}
		return installment.title || (installment.installment_no ? ('Installment #' + installment.installment_no) : 'Installment');
	}

	function getCurrentInvoiceDateValue() {
		var date = new Date();
		var day = ('0' + date.getDate()).slice(-2);
		var month = ('0' + (date.getMonth() + 1)).slice(-2);
		var year = date.getFullYear();
		return day + '/' + month + '/' + year;
	}

	function parseInvoiceDateValue(value) {
		var text = String(value || '').trim();
		var parts = text.split(/[\/.-]/);
		if (parts.length !== 3) {
			return null;
		}
		var day = parseInt(parts[0], 10);
		var month = parseInt(parts[1], 10) - 1;
		var year = parseInt(parts[2], 10);
		var date = new Date(year, month, day);
		if (isNaN(date.getTime())) {
			return null;
		}
		return date;
	}

	function normalizeInvoiceDateForPicker(value) {
		var text = String(value || '').trim();
		if (!text) {
			return '';
		}

		var isoMatch = text.match(/^(\d{4})-(\d{2})-(\d{2})$/);
		if (isoMatch) {
			return isoMatch[3] + '/' + isoMatch[2] + '/' + isoMatch[1];
		}

		return text;
	}

	function formatInvoiceDateObject(date) {
		if (!(date instanceof Date) || isNaN(date.getTime())) {
			return '';
		}

		var day = ('0' + date.getDate()).slice(-2);
		var month = ('0' + (date.getMonth() + 1)).slice(-2);
		return day + '/' + month + '/' + date.getFullYear();
	}

	function getRepeatingDefaultDateRange() {
		var startDate = new Date();
		var endDate = new Date(startDate.getTime());
		endDate.setMonth(endDate.getMonth() + 1);

		return {
			start_date: formatInvoiceDateObject(startDate),
			end_date: formatInvoiceDateObject(endDate)
		};
	}

	function getRepeatingNextDateRange(referenceEndDate) {
		var endDate = parseInvoiceDateValue(referenceEndDate);
		if (!endDate) {
			return getRepeatingDefaultDateRange();
		}

		var startDate = new Date(endDate.getTime());
		startDate.setDate(startDate.getDate() + 1);
		var nextEndDate = new Date(startDate.getTime());
		nextEndDate.setMonth(nextEndDate.getMonth() + 1);

		return {
			start_date: formatInvoiceDateObject(startDate),
			end_date: formatInvoiceDateObject(nextEndDate)
		};
	}

	function isRepeatingInvoicePlan(plan) {
		if (!plan) {
			return false;
		}

		var paymentMode = normalizePlanType(firstInvoiceValue(plan.payment_mode, plan.payment_type));
		if (paymentMode === 'repeating' || paymentMode === 'repeat' || paymentMode === 'recurring') {
			return true;
		}

		var planType = normalizePlanType(firstInvoiceValue(plan.type, plan.plan_type));
		return !!planType && !isOneTimePlanType(planType);
	}

	function applyRepeatingPlanDates(plan, sourcePlan, preserveExisting) {
		if (!plan) {
			return plan;
		}

		sourcePlan = sourcePlan || {};
		if (isRepeatingInvoicePlan(plan)) {
			var defaultDates = getRepeatingDefaultDateRange();
			plan.start_date = normalizeInvoiceDateForPicker(firstInvoiceValue(
				preserveExisting ? plan.start_date : '',
				defaultDates.start_date
			));
			plan.end_date = normalizeInvoiceDateForPicker(firstInvoiceValue(
				preserveExisting ? plan.end_date : '',
				defaultDates.end_date
			));
		} else {
			plan.start_date = '';
			plan.end_date = '';
		}

		return plan;
	}

	function buildMergedSubscriptionInstallment(installments, plan) {
		var selectedInstallments = getUniqueSubscriptionInstallments(installments || []);
		var planTotal = getSubscriptionPlanPercentBase(plan);
		var totalAmount = 0;
		var totalPercent = 0;
		var hasPercent = false;
		var installmentNumbers = [];
		var installmentTitles = [];
		var sourceInstallmentIds = [];

		angular.forEach(selectedInstallments, function (installment) {
			totalAmount += getSubscriptionInstallmentAmount(installment, planTotal);
			var installmentPercent = getSubscriptionInstallmentPercentValue(installment, planTotal);
			if (installmentPercent !== null) {
				totalPercent += installmentPercent;
				hasPercent = true;
			}
			var installmentNo = installment && (installment.installment_no || installment.subscribed_plan_installment_id);
			if (installmentNo) {
				installmentNumbers.push(installmentNo);
			}
			var sourceInstallmentId = installment && (installment.subscribed_plan_installment_id || installment.installment_no);
			if (sourceInstallmentId) {
				sourceInstallmentIds.push(sourceInstallmentId);
			}
			installmentTitles.push(getSubscriptionInstallmentTitle(installment));
		});

		var mergedPercent = null;
		if (planTotal > 0 && totalAmount > 0) {
			mergedPercent = (totalAmount / planTotal) * 100;
		} else if (hasPercent) {
			mergedPercent = totalPercent;
		}

		return {
			installment_no: installmentNumbers.join(','),
			source_installment_ids: sourceInstallmentIds.join(','),
			title: selectedInstallments.length > 1 ? '' : (installmentTitles[0] || ''),
			percent: mergedPercent !== null ? formatInvoiceAmount(mergedPercent) : '',
			amount: formatInvoiceAmount(totalAmount),
			_percent_base: planTotal > 0 ? planTotal : ''
		};
	}

	function stripInvoiceInstallmentDueDates(rows) {
		rows = angular.isArray(rows) ? rows : [];
		return angular.copy(rows).map(function (row) {
			delete row.due_date;
			return row;
		});
	}

	function extractDueOnlyPlanDetails(description) {
		description = description ? String(description) : '';
		var marker = 'data-due-only-plan-details="1"';
		var markerIndex = description.indexOf(marker);
		if (markerIndex < 0) {
			return '';
		}

		var divStart = description.lastIndexOf('<div', markerIndex);
		return divStart >= 0 ? description.substring(divStart) : '';
	}

	function stripTrailingInvoiceBreaks(description) {
		description = description ? String(description) : '';
		return description
			.replace(/(?:<br\s*\/?>|\s|&nbsp;)+$/ig, '')
			.trim();
	}

	function getInvoiceDescriptionTextFingerprint(description) {
		return String(description || '')
			.replace(/<[^>]+>/g, ' ')
			.replace(/&nbsp;/gi, ' ')
			.replace(/\s+/g, ' ')
			.trim()
			.toLowerCase();
	}

	function appendUniqueInvoiceDescription(baseDescription, extraDescription) {
		baseDescription = stripTrailingInvoiceBreaks(baseDescription);
		extraDescription = stripTrailingInvoiceBreaks(extraDescription);

		if (!extraDescription) {
			return baseDescription;
		}
		if (!baseDescription) {
			return extraDescription;
		}

		var baseFingerprint = getInvoiceDescriptionTextFingerprint(baseDescription);
		var extraFingerprint = getInvoiceDescriptionTextFingerprint(extraDescription);
		if (extraFingerprint && baseFingerprint.indexOf(extraFingerprint) !== -1) {
			return baseDescription;
		}

		return baseDescription + '<br><br>' + extraDescription;
	}

	function buildSubscriptionLoadedDescription(masterDescription, generatedDescription, fallbackDescription) {
		masterDescription = stripTrailingInvoiceBreaks(masterDescription);
		generatedDescription = stripTrailingInvoiceBreaks(generatedDescription);
		fallbackDescription = stripTrailingInvoiceBreaks(fallbackDescription);

		var dueDetails = extractDueOnlyPlanDetails(generatedDescription);
		var generatedBaseDescription = generatedDescription;
		if (dueDetails) {
			generatedBaseDescription = stripTrailingInvoiceBreaks(generatedDescription.replace(dueDetails, ''));
		}

		var baseDescription = firstInvoiceValue(
			masterDescription,
			generatedBaseDescription,
			fallbackDescription,
			generatedDescription,
			''
		);

		if (dueDetails) {
			return appendUniqueInvoiceDescription(baseDescription, dueDetails);
		}

		return baseDescription;
	}

	function validateInvoiceInstallmentTitles(installments) {
		var rows = installments || [];
		for (var i = 0; i < rows.length; i++) {
			if (!String(rows[i] && rows[i].title ? rows[i].title : '').trim()) {
				messages('warning', 'Warning!', 'Please enter installment title #' + (i + 1) + '.', 3000);
				return false;
			}
		}
		return true;
	}

	function applyInstallmentAmountToSelectedPlan(plan, amount) {
		var selectedAmount = formatInvoiceAmount(amount);
		plan._installment_total = selectedAmount;
		plan.unit = 1;
		var percentBase = getSubscriptionPlanPercentBase(plan);
		if (percentBase > 0) {
			plan._percent_base = percentBase;
		}
		return plan;
	}

	function normalizeSubscriptionPlanList(plans) {
		var safePlans = angular.copy(plans || []);
		angular.forEach(safePlans, function (plan) {
			var installments = plan.installments || [];
			var planTotal = getSubscriptionPlanPercentBase(plan);
			plan.installments = installments.filter(function (installment) {
				var paidStatus = installment && installment.paid_status !== undefined && installment.paid_status !== null
					? parseInt(installment.paid_status, 10)
					: (installment && installment.payment_status !== undefined && installment.payment_status !== null
						? parseInt(installment.payment_status, 10)
						: 0);
				return paidStatus !== 1;
			});
			angular.forEach(plan.installments, function (installment) {
				installment._source_amount = firstInvoiceValue(
					installment.amount,
					installment.installment_amount,
					installment.due_amount,
					installment.remaining_due,
					installment.payable_amount
				);
				installment._source_percentage = firstInvoiceValue(installment.percentage, installment.percent);
				attachInstallmentPercentBase(installment, planTotal);
				normalizeSubscriptionInstallmentPercent(installment, planTotal);
			});
		});
		return safePlans;
	}

	$scope.reset_subscription_builder = function () {
		$scope.subscriptionBuilder.loading = false;
		$scope.subscriptionBuilder.visible = false;
		$scope.subscriptionBuilder.mode = '';
		$scope.subscriptionBuilder.oneTimePlans = [];
		$scope.subscriptionBuilder.repeatingPlans = [];
		$scope.subscriptionBuilder.plans = [];
		$scope.subscriptionBuilder.selectedPlanId = '';
		$scope.subscriptionBuilder.selectedPlan = null;
		$scope.subscriptionBuilder.selectedInstallments = {};
		$scope.subscriptionBuilder.error = '';
	};

	$scope.set_subscription_mode = function (mode) {
		$scope.subscriptionBuilder.mode = mode;
		$scope.subscriptionBuilder.plans = mode === 'one_time'
			? ($scope.subscriptionBuilder.oneTimePlans || [])
			: ($scope.subscriptionBuilder.repeatingPlans || []);
		$scope.subscriptionBuilder.selectedPlanId = '';
		$scope.subscriptionBuilder.selectedPlan = null;
		$scope.subscriptionBuilder.selectedInstallments = {};

		if ($scope.subscriptionBuilder.plans.length === 1) {
			$scope.select_subscription_plan($scope.subscriptionBuilder.plans[0]);
		}
	};

	$scope.select_subscription_plan = function (plan) {
		if (!plan) {
			return;
		}

		$scope.subscriptionBuilder.selectedPlanId = plan.subscribed_plan_id;
		$scope.subscriptionBuilder.selectedPlan = plan;
		$scope.subscriptionBuilder.selectedPlan.payment_model = normalizeInvoicePaymentModel(plan.payment_model || plan.subscription);
		$scope.subscriptionBuilder.selectedPlan.subscription = normalizeInvoicePaymentModel(plan.subscription || plan.payment_model);
		$scope.subscriptionBuilder.selectedInstallments = {};
	};

	function isOneTimePlanType(value) {
		var normalized = normalizePlanType(value);
		var underscored = normalized.replace(/\s+/g, '_');
		return normalized === 'one time' || underscored.indexOf('one_ti') !== -1 || underscored.indexOf('onetime') !== -1;
	}

	$scope.should_show_repeating_plan_dates = function (plan) {
		return isRepeatingInvoicePlan(plan || $scope.selected);
	};

	function isActiveSubscribedPlan(plan) {
		if (!plan) {
			return false;
		}

		var currentValue = plan.is_current;
		if (currentValue !== undefined && currentValue !== null && String(currentValue).trim() !== '') {
			var currentText = String(currentValue).toLowerCase();
			if (currentText === '0' || currentText === 'false' || currentText === 'no') {
				return false;
			}
		}

		var status = firstInvoiceValue(
			plan.subscription_status,
			plan.subscribed_plan_status,
			plan.plan_subscription_status
		);
		if (status !== '') {
			return normalizePlanType(status) === 'active';
		}

		return true;
	}

	function populate_invoice_plan_form_from_subscription(plan, installments) {
		var selectedPlan = angular.copy(plan || {});
		var subscribedPlanPricing = angular.copy($scope.subscriptionBuilder.selectedPlan || selectedPlan || {});
		var selectedInstallments = angular.copy(installments || []);
		var planId = selectedPlan.plan_id || selectedPlan.subscribed_plan_id || '';
		var planMasterPlan = null;
		var planMasterSp = '';
		var planMasterDescription = '';
		var isSeoSubscribedPlan = /seo/i.test([
			subscribedPlanPricing.name,
			subscribedPlanPricing.plan_name,
			subscribedPlanPricing.display_name
		].join(' '));

		angular.forEach($scope.plan_data || [], function (planItem) {
			if (!planMasterPlan && String(planItem.plan_id) === String(planId)) {
				planMasterPlan = angular.copy(planItem);
				planMasterSp = planItem.sp;
				planMasterDescription = firstInvoiceValue(planItem.invoice_plan_description, planItem.plan_description, planItem.description, '');
			}
		});

		var generatedDescription = firstInvoiceValue(
			selectedPlan.invoice_plan_description,
			selectedPlan.plan_description,
			selectedPlan.description,
			subscribedPlanPricing.invoice_plan_description,
			subscribedPlanPricing.plan_description,
			subscribedPlanPricing.description,
			''
		);

		if (planMasterPlan) {
			selectedPlan = angular.extend(planMasterPlan, selectedPlan);
			selectedPlan._plan_sp = planMasterSp;
		}

		if (!selectedPlan.plan_id) {
			selectedPlan.plan_id = planId;
		}
		if (!selectedPlan.name && selectedPlan.plan_name) {
			selectedPlan.name = selectedPlan.plan_name;
		}
		selectedPlan.invoice_plan_description = buildSubscriptionLoadedDescription(
			planMasterDescription,
			generatedDescription,
			firstInvoiceValue(selectedPlan.description, subscribedPlanPricing.description, '')
		);
		selectedPlan.plan_description = selectedPlan.invoice_plan_description;
		ensureInvoicePlanOption(selectedPlan);

		var previousDueAmount = isOneTimePlanType(selectedPlan.type || selectedPlan.plan_type)
			? 0
			: parseInvoiceAmount(firstInvoiceValue(selectedPlan.remaining_due, selectedPlan.due_amount));
		var billDueOnly = selectedPlan.bill_due_only === 1 || selectedPlan.bill_due_only === '1' || selectedPlan.bill_due_only === true;
		var subscriptionSpAmount = parseInvoiceAmount(firstInvoiceValue(selectedPlan.sp, selectedPlan.total));
		var selectedPlanTotal = firstInvoiceValue(selectedPlan.sp, selectedPlan.total);

		$scope.selected = $scope.selected || {};
		$scope.selected.plan = String(selectedPlan.plan_id || '');
		$scope.selected.plan_id = String(selectedPlan.plan_id || '');
		$scope.selected.name = selectedPlan.name || selectedPlan.plan_name || '';
		$scope.selected.mrp = selectedPlan.mrp || selectedPlan.unit_price || selectedPlan.price || '';
		$scope.selected.unit = selectedPlan.unit || 1;
		$scope.selected.type = selectedPlan.type || selectedPlan.plan_type || '';
		$scope.selected.payment_model = normalizeInvoicePaymentModel(selectedPlan.payment_model || selectedPlan.subscription);
		$scope.selected.subscription = normalizeInvoicePaymentModel(selectedPlan.subscription || selectedPlan.payment_model);
		$scope.selected.discount = selectedPlan.discount || '';
		$scope.selected.sp = selectedPlanTotal;
		if (isSeoSubscribedPlan) {
			$scope.selected.mrp = firstInvoiceValue(subscribedPlanPricing.mrp, subscribedPlanPricing.unit_price, subscribedPlanPricing.price, $scope.selected.mrp);
			$scope.selected.unit = firstInvoiceValue(subscribedPlanPricing.unit, $scope.selected.unit, 1);
			$scope.selected.discount = firstInvoiceValue(subscribedPlanPricing.discount, $scope.selected.discount, 0);
			$scope.selected.sp = firstInvoiceValue(subscribedPlanPricing.sp, subscribedPlanPricing.total, $scope.selected.sp);
			selectedPlanTotal = $scope.selected.sp;
			subscriptionSpAmount = parseInvoiceAmount(firstInvoiceValue(subscribedPlanPricing.sp, subscribedPlanPricing.total, selectedPlanTotal));
		}
		$scope.selected._plan_sp = selectedPlan._plan_sp || '';
		$scope.selected._percent_base = getSubscriptionPlanPercentBase($scope.selected);
		$scope.selected._subscription_base_sp = subscriptionSpAmount > 0 ? formatInvoiceAmount(subscriptionSpAmount) : '';
		$scope.selected.subscription_meta = angular.extend({}, selectedPlan.subscription_meta || {}, {
			source_subscribed_plan_id: selectedPlan.subscribed_plan_id || subscribedPlanPricing.subscribed_plan_id || '',
			original_plan_sp: subscriptionSpAmount > 0 ? formatInvoiceAmount(subscriptionSpAmount) : selectedPlanTotal
		});
		$scope.selected._previous_due = previousDueAmount > 0 ? formatInvoiceAmount(previousDueAmount) : '';
		$scope.selected.opening_due = selectedPlan.opening_due || '';
		$scope.selected.remaining_due = selectedPlan.remaining_due || selectedPlan.due_amount || '';
		$scope.selected.due_invoice_id = selectedPlan.due_invoice_id || '';
		$scope.selected.due_invoice_date = selectedPlan.due_invoice_date || '';
		$scope.selected._bill_due_only = billDueOnly;
		$scope.selected._bill_due_after_end = selectedPlan.bill_due_after_end === 1 || selectedPlan.bill_due_after_end === '1' || selectedPlan.bill_due_after_end === true;
		$scope.selected.billable_sp = selectedPlan.billable_sp || '';
		$scope.selected._carry_forward_due_amount = previousDueAmount > 0 ? formatInvoiceAmount(previousDueAmount) : '';
		$scope.selected._default_repeating_start_date = selectedPlan.next_invoice_start_date || '';
		$scope.selected._default_repeating_end_date = selectedPlan.next_invoice_end_date || '';
		var billablePlanCost = parseInvoiceAmount(selectedPlan.billable_sp);
		$scope.selected._carry_forward_plan_cost = billablePlanCost > 0
			? formatInvoiceAmount(billablePlanCost)
			: (previousDueAmount > 0 && billDueOnly ? formatInvoiceAmount(previousDueAmount) : '');
		if ($scope.selected._bill_due_after_end && previousDueAmount > 0) {
			var defaultRepeatDates = selectedPlan.next_invoice_start_date && selectedPlan.next_invoice_end_date
				? {
					start_date: selectedPlan.next_invoice_start_date,
					end_date: selectedPlan.next_invoice_end_date
				}
				: getRepeatingNextDateRange(selectedPlan.end_date || selectedPlan.due_invoice_date || selectedPlan.start_date);
			$scope.selected.start_date = defaultRepeatDates.start_date;
			$scope.selected.end_date = defaultRepeatDates.end_date;
			$scope.selected._default_repeating_start_date = defaultRepeatDates.start_date;
			$scope.selected._default_repeating_end_date = defaultRepeatDates.end_date;
		}
		$scope.selected._from_subscription = true;
		$scope.selected._has_subscription_installments = selectedInstallments.length > 0;
		$scope.selected.invoice_plan_description = selectedPlan.invoice_plan_description || '';
		applyRepeatingPlanDates($scope.selected, selectedPlan, !!($scope.selected.start_date || $scope.selected.end_date));

		if (selectedInstallments.length) {
			$scope.generatedRows = selectedInstallments;
			angular.forEach($scope.generatedRows, function (row) {
				attachInstallmentPercentBase(row, getSubscriptionPlanPercentBase($scope.selected));
				normalizeSubscriptionInstallmentPercent(row, getSubscriptionPlanPercentBase($scope.selected));
			});
		} else {
			$scope.generatedRows = [];
		}

		if (!isSeoSubscribedPlan) {
			$scope.calculateDiscount();
		}
		$scope.refresh_repeating_plan_cost();
		queueInvoiceSelect2Init(50);
	}

	$scope.should_show_invoice_installment_form = function () {
		if ($scope.selected && $scope.selected._from_subscription && !$scope.selected._has_subscription_installments) {
			return false;
		}
		return angular.isArray($scope.generatedRows) && $scope.generatedRows.length > 0;
	};

	$scope.toggle_subscription_installment = function (installment) {
		if (!installment) {
			return;
		}

		var installmentId = installment.subscribed_plan_installment_id || installment.installment_no;
		if (!installmentId && installmentId !== 0) {
			return;
		}

		var key = String(installmentId);
		if ($scope.subscriptionBuilder.selectedInstallments[key]) {
			delete $scope.subscriptionBuilder.selectedInstallments[key];
		} else {
			$scope.subscriptionBuilder.selectedInstallments[key] = true;
		}
	};

	$scope.get_selected_subscription_installment_ids = function () {
		var ids = [];
		angular.forEach($scope.subscriptionBuilder.selectedInstallments, function (value, key) {
			if (value) {
				ids.push(parseInt(key, 10));
			}
		});
		return ids.filter(function (id) {
			return !isNaN(id) && id > 0;
		});
	};

	$scope.build_subscription_invoice = function () {
		$scope.x = $scope.x || {};
		let liveCustomerId = ($('#invoice_customer_select').val() || '').toString().trim();

		if (liveCustomerId) {
			$scope.x.c_id = liveCustomerId;
		}

		if (!$scope.x || !$scope.x.c_id) {
			messages('warning', 'Warning!', 'Please select a Customer first.', 3000);
			return;
		}

		$('#buildsubscriptionbtn').attr('disabled', true);
		$scope.reset_subscription_builder();
		$scope.subscriptionBuilder.visible = true;
		$scope.subscriptionBuilder.loading = true;

		$http.get(rootUrl + 'customer/get_subscribed_plans?c_id=' + encodeURIComponent($scope.x.c_id) + '&subscription_status=active&is_current=1&inv_date=' + encodeURIComponent($scope.x.inv_date || getCurrentInvoiceDateValue()))
			.then(function (response) {
				var responsePlans = response.data || [];
				var subscriptions = angular.isArray(responsePlans) ? responsePlans.filter(isActiveSubscribedPlan) : [];
				if (!angular.isArray(subscriptions) || !subscriptions.length) {
					$scope.subscriptionBuilder.visible = false;
					messages('warning', 'Warning!', 'No active subscribed plans found for this customer.', 4000);
					return;
				}

				var selection = {
					one_time_plans: [],
					repeating_plans: []
				};

				angular.forEach(subscriptions, function (subscription) {
					subscription.installments = subscription.installments || [];
					var planType = normalizePlanType(subscription.type);
					if (planType === 'one time') {
						selection.one_time_plans.push(subscription);
					} else {
						selection.repeating_plans.push(subscription);
					}
				});

				$scope.subscriptionBuilder.oneTimePlans = normalizeSubscriptionPlanList(selection.one_time_plans);
				$scope.subscriptionBuilder.repeatingPlans = normalizeSubscriptionPlanList(selection.repeating_plans);
				$scope.subscriptionBuilder.mode = '';
				$scope.subscriptionBuilder.plans = [];
				$scope.subscriptionBuilder.selectedPlanId = '';
				$scope.subscriptionBuilder.selectedPlan = null;
				$scope.subscriptionBuilder.selectedInstallments = {};
				$scope.subscriptionBuilder.error = '';
			}, function () {
				$scope.subscriptionBuilder.visible = false;
				messages('danger', 'Warning!', 'Unable to load subscription data for invoice drafting.', 4000);
			}).finally(function () {
				$scope.subscriptionBuilder.loading = false;
				$('#buildsubscriptionbtn').attr('disabled', false);
			});
	};

	$scope.submit_subscription_invoice = function () {
		if (!$scope.x || !$scope.x.c_id) {
			messages('warning', 'Warning!', 'Please select a Customer first.', 3000);
			return;
		}

		if (!$scope.subscriptionBuilder.mode) {
			messages('warning', 'Warning!', 'Please select One Time or Repeating.', 3000);
			return;
		}

		if (!$scope.subscriptionBuilder.selectedPlanId) {
			messages('warning', 'Warning!', 'Please select a subscription plan.', 3000);
			return;
		}

		var selectedPlan = angular.copy($scope.subscriptionBuilder.selectedPlan || {});
		selectedPlan.subscription = normalizeInvoicePaymentModel(selectedPlan.subscription || selectedPlan.payment_model);
		selectedPlan.payment_model = normalizeInvoicePaymentModel(selectedPlan.payment_model || selectedPlan.subscription);

		var selectedInstallments = [];
		if ($scope.subscriptionBuilder.mode === 'one_time') {
			var planInstallments = selectedPlan.installments || [];
			if (planInstallments.length > 0) {
				var selectedInstallmentIds = $scope.get_selected_subscription_installment_ids();
				if (!selectedInstallmentIds.length) {
					messages('warning', 'Warning!', 'Please select one or more installments for the selected plan.', 3000);
					return;
				}

				var selectedInstallmentMap = {};
				angular.forEach(selectedInstallmentIds, function (installmentId) {
					selectedInstallmentMap[String(installmentId)] = true;
				});

				var selectedInstallmentSources = [];
				angular.forEach(planInstallments, function (installment) {
					var installmentId = getSubscriptionInstallmentId(installment);
					if (installmentId && selectedInstallmentMap[String(installmentId)]) {
						selectedInstallmentSources.push(installment);
					}
				});

				if (!selectedInstallmentSources.length) {
					messages('warning', 'Warning!', 'Selected installment details were not found. Please select the installments again.', 3000);
					return;
				}

				if (selectedInstallmentSources.length) {
					var uniqueSelectedInstallments = getUniqueSubscriptionInstallments(selectedInstallmentSources);
					var selectedPlanPercentBase = getSubscriptionPlanPercentBase(selectedPlan);
					var selectedInstallmentTotal = sumInvoiceInstallmentAmounts(uniqueSelectedInstallments, selectedPlanPercentBase);
					selectedPlan = applyInstallmentAmountToSelectedPlan(selectedPlan, selectedInstallmentTotal);
					var mergedInstallment = buildMergedSubscriptionInstallment(uniqueSelectedInstallments, selectedPlan);
					selectedInstallments = [mergedInstallment];
				}
			}
		}

		populate_invoice_plan_form_from_subscription(selectedPlan, selectedInstallments);
		$scope.subscriptionBuilder.visible = false;
		$scope.subscriptionBuilder.error = '';
		messages('success', 'Success!', 'Subscription plan loaded into the Add Plan form. Review and click Save Plan when ready.', 3500);
	};

	function buildListUrl() {
		let params = [];
		if ($scope.qx.project_id) {
			params.push('project_id=' + encodeURIComponent($scope.qx.project_id));
		}
		if ($scope.qx.c_id) {
			params.push('c_id=' + encodeURIComponent($scope.qx.c_id));
		}
		if ($scope.qx.invoice_number) {
			params.push('invoice_number=' + encodeURIComponent($scope.qx.invoice_number));
		}
		if ($scope.qx.inv_date) {
			params.push('inv_date=' + encodeURIComponent($scope.qx.inv_date));
		}

		let url = rootUrl + module + '/view_paginated/' + $scope.itemsPerPage + '/' + $scope.pageno;
		if (params.length) {
			url += '?' + params.join('&');
		}
		return url;
	}

	$scope.loader = function (pageno) {
		pageno = parseInt(pageno, 10) || 1;
		if (pageno < 1) {
			pageno = 1;
		}

		$scope.pageno = pageno;
		$scope.loading = true;

		$http.get(buildListUrl()).then(function (response) {
			let data = response.data || {};
			if (angular.isArray(data)) {
				$scope.datadb = data || [];
				$scope.total_count = ($scope.datadb || []).length;
			} else {
				$scope.datadb = data.data || [];
				$scope.total_count = toNumber(data.total_count);
			}
			refreshPagerMeta();
		}, function () {
			$scope.datadb = [];
			$scope.total_count = 0;
			refreshPagerMeta();
		}).finally(function () {
			$scope.loading = false;
		});
	};

	$scope.apply_filters = function () {
		$scope.loader(1);
	};

	$scope.clear_filters = function () {
		$scope.qx = {
			project_id: '',
			c_id: '',
			invoice_number: '',
			inv_date: ''
		};
		$scope.itemsPerPage = '15';
		$scope.acSuggestions = {};
		$scope.acActive = {};
		angular.forEach(acTimers, function (t, f) {
			if (t) { $timeout.cancel(t); acTimers[f] = null; }
		});
		$timeout(function () {
			$('#invoice_filter_company_select').val('').trigger('change');
			$('#invoice_filter_customer_select').val('').trigger('change');
			$('#invoice_filter_inv_date').val('');
			if ($('#invoice_filter_inv_date').data('datepicker')) {
				$('#invoice_filter_inv_date').datepicker('clearDates');
			}
			$('#invoice_filter_per_page_select').val($scope.itemsPerPage).trigger('change');
		}, 0);
		$scope.loader(1);
	};

	$scope.on_items_per_page_change = function () {
		$scope.loader(1);
	};

	$scope.on_ac_input_change = function (qxField) {
		if (acTimers[qxField]) { $timeout.cancel(acTimers[qxField]); }
		var query = String(($scope.qx && $scope.qx[qxField]) || '').trim();
		if (query.length < 3) {
			$scope.acSuggestions[qxField] = [];
			$scope.acActive[qxField] = false;
			return;
		}
		acTimers[qxField] = $timeout(function () {
			acTimers[qxField] = null;
			$http.get(rootUrl + 'invoice/autocomplete?field=' + qxField + '&query=' + encodeURIComponent(query))
				.success(function (response) {
					$scope.acSuggestions[qxField] = angular.isArray(response) ? response : [];
					$scope.acActive[qxField] = $scope.acSuggestions[qxField].length > 0;
				});
		}, 300);
	};

	$scope.select_ac_suggestion = function (qxField, value) {
		$scope.qx[qxField] = value;
		$scope.acSuggestions[qxField] = [];
		$scope.acActive[qxField] = false;
	};

	$scope.close_ac_delayed = function (qxField) {
		$timeout(function () {
			$scope.acActive[qxField] = false;
		}, 200);
	};

	$scope.init = function () {
		$http.get(rootUrl + "customer/view?data=name,c_id").success(function (data) {
			if (data.length === 1) {
				$scope.x.c_id = data[0]['c_id'];
				$scope.customers = data;
			} else {
				$scope.customers = data;
			}
			queueInvoiceSelect2Init();
		});
		if (String(localStorage.getItem('com_id') || '').trim()) {
			loadInvoiceCompanies();
		} else {
			$http.get(rootUrl + 'dashboard/fetch_userdata').success(function (data) {
				if (data && data.com_id !== undefined && data.com_id !== null && String(data.com_id).trim() !== '') {
					localStorage.setItem('com_id', String(data.com_id));
				}
				loadInvoiceCompanies();
			}).error(function () {
				loadInvoiceCompanies();
			});
		}
		$http.get(rootUrl + "plan_master/view").success(function (data) {
			$scope.plan_data = data;
			// console.log('plans', data);
			queueInvoiceSelect2Init();
		});
		// Fetching Quotation Data if invoice is generated from quotation
		if ($scope.proforma_id) {
			resetInvoiceFormState({ clearTemp: false });
			$http.get(rootUrl + 'proforma_invoice/view?proforma_id=' + $scope.proforma_id).success(function (data) {
				// console.log(data);
				if (data && data.length) {
					applyProformaInvoiceCopy(data[0]);
					showInvoiceFormModal(0);
					$timeout(function () {
						$('#invoice_customer_select').val(String($scope.x.c_id || '')).trigger('change');
						$('#invoice_company_select').val(String($scope.x.project_id || '')).trigger('change');
						reapplyProformaInvoiceTotals();
						queueInvoiceSelect2Init();
					}, 0);
					loadProformaPlansIntoInvoice($scope.proforma_id, { preserveProformaTotals: true });
				}

			});

		}
		$scope.loader(1);
		queueInvoiceSelect2Init();
	}

	$scope.today_date = function () {
		// setting the current date as default
		$scope.x = $scope.x || {};
		$scope.x.inv_date = getCurrentInvoiceDateValue();
		// end of setting current date
	}

	function getDefaultInvoicePlanSelection() {
		return {
			plan: '',
			plan_id: '',
			payment_model: 'advance',
			subscription: 'advance'
		};
	}

	function resetInvoiceFormState(options) {
		options = options || {};
		$scope.formMode = 'add';
		$scope.isEditingInvoice = false;
		$scope.x = {};
		$scope.temp_data = {};
		$scope.invoice_plans = [];
		$scope.generatedRows = [];
		$scope.selected = getDefaultInvoicePlanSelection();
		$scope.editingTempInvoicePlanId = '';
		preserveProformaInvoiceCopy = false;
		proformaInvoiceCopySnapshot = null;
		planSelectionRequestSeq++;
		$scope.reset_subscription_builder();
		$scope.today_date();

		$('#loader1').css('display', 'none');
		$('#proformainvoicebtn').attr('disabled', false);

		if ($scope.invoice_form) {
			$scope.invoice_form.$setPristine();
			$scope.invoice_form.$setUntouched();
		}

		$timeout(function () {
			$('#invoice_customer_select, #invoice_company_select, #invoice_plan_select').val('').trigger('change');
			queueInvoiceSelect2Init();
		}, 0);

		if (options.clearTemp !== false) {
			clear_invoice_temp_plans_silently();
		}
	}

	function inferInvoiceGstType(row) {
		var gstType = String(row && (row.gst || row.gst_type || row.tax_type || '')).trim();
		if (gstType) {
			gstType = gstType.toLowerCase();
			if (gstType === 'cgst' || gstType === 'sgst' || gstType === 'cgst_sgst' || gstType === 'cgst/sgst') {
				return 'cgst_sgst';
			}
			if (gstType === 'igst') {
				return 'igst';
			}
		}

		if (row && row.igst !== undefined && row.igst !== null && String(row.igst).trim() !== '') {
			return 'igst';
		}

		if ((row && row.cgst !== undefined && row.cgst !== null && String(row.cgst).trim() !== '') ||
			(row && row.sgst !== undefined && row.sgst !== null && String(row.sgst).trim() !== '')) {
			return 'cgst_sgst';
		}

		return '';
	}

	function shouldPreserveProformaInvoiceCopy() {
		return preserveProformaInvoiceCopy && $scope.x && $scope.x.proforma_id;
	}

	function markProformaInvoiceCopyTotalsSyncing() {
		syncingProformaInvoiceCopyTotals = true;
		$timeout(function () {
			syncingProformaInvoiceCopyTotals = false;
		}, 0);
	}

	function applyProformaInvoiceCopy(row) {
		row = row || {};
		preserveProformaInvoiceCopy = true;
		proformaInvoiceCopySnapshot = angular.copy(row);
		markProformaInvoiceCopyTotalsSyncing();

		$scope.x = angular.extend({}, $scope.x || {}, angular.copy(row));
		$scope.x.proforma_id = firstInvoiceValue(row.proforma_id, $scope.proforma_id);
		$scope.x.invoice_id = '';
		$scope.x.invoice_number = '';
		$scope.x.inv_date = firstInvoiceValue(row.invoice_date, row.inv_date, getCurrentInvoiceDateValue());
		$scope.x.c_id = String(firstInvoiceValue(row.c_id, ''));
		$scope.x.project_id = String(firstInvoiceValue(row.project_id, row.invoice_com, row.com_id, ''));
		$scope.x.plan_cost = firstInvoiceValue(row.plan_cost, '');
		$scope.x.amt_extax = firstInvoiceValue(row.amt_extax, row.plan_cost, '');
		$scope.x.cgst = firstInvoiceValue(row.cgst, '');
		$scope.x.sgst = firstInvoiceValue(row.sgst, '');
		$scope.x.igst = firstInvoiceValue(row.igst, '');
		$scope.x.pay_amount = firstInvoiceValue(row.pay_amount, '');
		$scope.x.due = '0';
		$scope.x.gst = inferInvoiceGstType(row);
		$scope.invoiceDueSummary = {
			balance: 0,
			label: 'Due',
			note: ''
		};
	}

	function reapplyProformaInvoiceTotals() {
		if (!shouldPreserveProformaInvoiceCopy() || !proformaInvoiceCopySnapshot) {
			return;
		}

		markProformaInvoiceCopyTotalsSyncing();
		$scope.x.plan_cost = firstInvoiceValue(proformaInvoiceCopySnapshot.plan_cost, '');
		$scope.x.amt_extax = firstInvoiceValue(proformaInvoiceCopySnapshot.amt_extax, proformaInvoiceCopySnapshot.plan_cost, '');
		$scope.x.cgst = firstInvoiceValue(proformaInvoiceCopySnapshot.cgst, '');
		$scope.x.sgst = firstInvoiceValue(proformaInvoiceCopySnapshot.sgst, '');
		$scope.x.igst = firstInvoiceValue(proformaInvoiceCopySnapshot.igst, '');
		$scope.x.pay_amount = firstInvoiceValue(proformaInvoiceCopySnapshot.pay_amount, '');
		$scope.x.due = '0';
		$scope.x.gst = inferInvoiceGstType(proformaInvoiceCopySnapshot);
		$scope.invoiceDueSummary = {
			balance: 0,
			label: 'Due',
			note: ''
		};
	}

	$scope.init();
	$scope.generatedRows = [];

	$('.date').datepicker({
		format: 'dd/mm/yyyy',
		autoclose: true
	});
	$scope.today_date();

	$scope.update_call = function (y) {
		var row = y || {};
		$scope.formMode = 'edit';
		$scope.x = angular.copy(row);
		$scope.temp_data = {};
		$scope.isEditingInvoice = true;
		$scope.selected = {
			plan: '',
			plan_id: '',
			payment_model: 'advance',
			subscription: 'advance'
		};
		planSelectionRequestSeq++;
		$scope.generatedRows = [];
		$scope.invoice_plans = [];
		$scope.q_id = row.q_id || '';
		$scope.x.c_id = String(row.c_id || row.customer_id || '');
		$scope.x.project_id = String(row.project_id || row.com_id || row.invoice_com || '');
		$scope.x.gst = inferInvoiceGstType(row) || $scope.x.gst || '';
		$scope.x.plan_cost = row.plan_cost !== undefined && row.plan_cost !== null && row.plan_cost !== ''
			? row.plan_cost
			: ($scope.x.plan_cost || '');
		$scope.x.amt_extax = row.amt_extax !== undefined && row.amt_extax !== null && row.amt_extax !== ''
			? row.amt_extax
			: ($scope.x.amt_extax || '');
		$scope.x.cgst = row.cgst !== undefined && row.cgst !== null && row.cgst !== ''
			? row.cgst
			: ($scope.x.cgst || '');
		$scope.x.sgst = row.sgst !== undefined && row.sgst !== null && row.sgst !== ''
			? row.sgst
			: ($scope.x.sgst || '');
		$scope.x.igst = row.igst !== undefined && row.igst !== null && row.igst !== ''
			? row.igst
			: ($scope.x.igst || '');
		$scope.x.due = row.due !== undefined && row.due !== null && row.due !== ''
			? row.due
			: ($scope.x.due || '');
		$scope.x.pay_amount = row.pay_amount !== undefined && row.pay_amount !== null && row.pay_amount !== ''
			? row.pay_amount
			: ($scope.x.pay_amount || '');
		refreshInvoiceDueSummary();
		if (row.invoice_id) {
			$scope.get_plans_to_temp(row.invoice_id);
		}
		showInvoiceFormModal(0);
		$timeout(function () {
			$('#invoice_customer_select').val($scope.x.c_id || '').trigger('change');
			$('#invoice_company_select').val($scope.x.project_id || '').trigger('change');
			queueInvoiceSelect2Init();
		}, 0);
	}

	$scope.get_plans_to_temp = function (invoice_id) {
		$http.get(rootUrl + module + '/copy_plans_to_temp?invoice_id=' + invoice_id).success(function (data) {
			console.log(data);
			if (data == 1) {
				$http.get(rootUrl + module + "/get_temp_plans").success(function (data) {
					$scope.invoice_plans = data;
					if (!$scope.isEditingInvoice) {
						recalculateInvoicePlanCostFromPlans();
					}
					refreshInvoiceDueSummary();
				});


			}
		});
	}

	$scope.on_invoice_customer_change = function (c_id) {
		if (!c_id) {
			$scope.x.project_id = '';
			$scope.cust_state = '';
			$scope.com_gst = '';
			$scope.com_state = '';
			return;
		}
		if (shouldPreserveProformaInvoiceCopy()) {
			return;
		}

		$http.get(rootUrl + 'customer/view?c_id=' + c_id).success(function (data) {
			if (data && data.length) {
				$scope.cust_state = data[0]['state'];
				var invoiceCompanyId = data[0]['invoice_com'] || data[0]['com_id'] || data[0]['project_id'] || '';
				if (invoiceCompanyId) {
					$scope.x.project_id = String(invoiceCompanyId);
					$timeout(function () {
						$('#invoice_company_select').val(String(invoiceCompanyId)).trigger('change');
						queueInvoiceSelect2Init();
					}, 0);
				}
			}
		});
	};

	$scope.check_com_gst = function (com_id) {
		if (!com_id) {
			$scope.com_gst = '';
			$scope.com_state = '';
			if (!shouldPreserveProformaInvoiceCopy()) {
				$scope.x.gst = '';
			}
			return;
		}

		$http.get(rootUrl + 'company_master/view?id=' + com_id + '&data=gst_no,state').success(function (data) {
			if (data && data.length) {
				$scope.com_gst = data[0]['gst_no'];
				$scope.com_state = data[0]['state'];
			}
			if (shouldPreserveProformaInvoiceCopy()) {
				reapplyProformaInvoiceTotals();
				return;
			}
			if ($scope.com_gst) {
				if ($scope.com_state && $scope.cust_state) {
					$scope.x.gst = ($scope.com_state === $scope.cust_state)
						? 'cgst_sgst'
						: 'igst';
				}
			} else {
				$scope.x.gst = '';
			}
		});
	};

	$scope.get_proforma_plans_details = function (invoice_id) {
		$http.get(rootUrl + module + '/get_invoice_details?invoice_id=' + invoice_id).success(function (data) {
			if (!$scope.invoice_plans_data) {
				$scope.invoice_plans_data = [];
			}
			$scope.invoice_plans_data = data;
			console.log(data);
		});
	}

	$scope.plan_selected = function (y) {
		y = (y === undefined || y === null) ? '' : String(y).trim();
		let requestSeq = ++planSelectionRequestSeq;

		if (!y) {
			$scope.selected = getDefaultInvoicePlanSelection();
			$scope.generatedRows = [];
			$scope.editingTempInvoicePlanId = '';
			queueInvoiceSelect2Init();
			return;
		}

		$http.get(rootUrl + 'plan_master/view?plan_id=' + y).success(function (data) {
			if (requestSeq !== planSelectionRequestSeq || !data || !data.length) {
				return;
			}
			if (!$scope.selected) {
				$scope.selected = {}; // initialize if empty
			}
			angular.extend($scope.selected, data[0]); // merge new keys/values
			$scope.selected.plan = y;
			$scope.selected.plan_id = String(data[0].plan_id || y);
			$scope.selected.unit = 1;
			$scope.selected.payment_model = normalizeInvoicePaymentModel($scope.selected.payment_model || $scope.selected.subscription);
			$scope.selected.subscription = normalizeInvoicePaymentModel($scope.selected.subscription || $scope.selected.payment_model);
			$scope.selected._plan_sp = data[0] && data[0].sp ? data[0].sp : '';
			applyRepeatingPlanDates($scope.selected, data[0], false);

			// ? Calculate discount only if both values exist
			$scope.calculateDiscount();
			$scope.refresh_installment_percentages();
			queueInvoiceSelect2Init();
		});

	}

	function clear_invoice_temp_plans_silently() {
		return $http.get(rootUrl + module + '/delete_temp_plans');
	}

	function loadProformaPlansIntoInvoice(proformaId, options) {
		options = options || {};
		if (!proformaId) {
			return;
		}
		proformaId = String(proformaId);
		if (loadingProformaPlanId === proformaId) {
			return;
		}
		loadingProformaPlanId = proformaId;

		$scope.invoice_plans = [];
		clear_invoice_temp_plans_silently().then(function () {
			$http.get(rootUrl + 'proforma_invoice/get_proforma_invoice_details?proforma_id=' + encodeURIComponent(proformaId)).then(function (response) {
				var data = response.data || [];
				var seenPlans = {};
				var proformaPlans = (data || []).filter(function (plan) {
					return String(plan && plan.proforma_id || '') === String(proformaId);
				}).filter(function (plan, index) {
					var planKey = plan && plan.pip_id ? String(plan.pip_id) : ('row-' + index);
					if (seenPlans[planKey]) {
						return false;
					}
					seenPlans[planKey] = true;
					return true;
				});

				if (!proformaPlans.length) {
					if (options.preserveProformaTotals) {
						reapplyProformaInvoiceTotals();
					} else {
						recalculateInvoicePlanCostFromPlans();
						refreshInvoiceDueSummary();
					}
					loadingProformaPlanId = '';
					return;
				}

				$http.post(rootUrl + module + '/save_temp_plans', { plans: proformaPlans }).then(function (response) {
					if (response.data == 1) {
						return $http.get(rootUrl + module + "/get_temp_plans").then(function (response) {
							$scope.invoice_plans = response.data;
							if (options.preserveProformaTotals) {
								reapplyProformaInvoiceTotals();
							} else {
								recalculateInvoicePlanCostFromPlans();
								refreshInvoiceDueSummary();
							}
						});
					}
				}).finally(function () {
					loadingProformaPlanId = '';
					if (options.preserveProformaTotals) {
						$timeout(reapplyProformaInvoiceTotals, 0);
						$timeout(reapplyProformaInvoiceTotals, 500);
					}
				});
			}, function () {
				loadingProformaPlanId = '';
			});
		}, function () {
			loadingProformaPlanId = '';
		});
	}

	$scope.open_new_form = function () {
		resetInvoiceFormState();
		showInvoiceFormModal(0);
	};

	$scope.filter_new = function () {
		resetInvoiceFormState();
	}

	$scope.filter_plans = function () {
		$scope.reset_subscription_builder();
		$scope.selected = getDefaultInvoicePlanSelection();
		$scope.generatedRows = [];
		$scope.editingTempInvoicePlanId = '';
		planSelectionRequestSeq++;
		queueInvoiceSelect2Init();
	}

	$scope.$watchGroup(['x.c_id', 'x.project_id'], function (value) {
		if ($scope.isEditingInvoice) {
			return;
		}
		if (shouldPreserveProformaInvoiceCopy()) {
			return;
		}
		let c_id = value[0];
		let com_id = value[1];
		if (c_id) {
			$http.get(rootUrl + 'customer/view?c_id=' + c_id).success(function (data) {
				if (data && data.length) {
					$scope.cust_state = data[0]['state'];
					if (!com_id) {
						var invoiceCompanyId = data[0]['invoice_com'] || data[0]['com_id'] || data[0]['project_id'] || '';
						if (invoiceCompanyId) {
							$scope.x.project_id = String(invoiceCompanyId);
							$timeout(function () {
								$('#invoice_company_select').val(String(invoiceCompanyId)).trigger('change');
							}, 0);
						}
					}
				}
				if ($scope.com_gst) {
					if ($scope.com_state && $scope.cust_state) {
						$scope.x.gst = ($scope.com_state === $scope.cust_state)
							? "cgst_sgst"
							: "igst";
					}
				} else {
					$scope.x.gst = "";
				}

			});
		}
		if (com_id) {
			$http.get(rootUrl + 'company_master/view?id=' + com_id + '&data=gst_no,state').success(function (data) {
				if (data && data.length) {
					$scope.com_gst = data[0]['gst_no'];
					$scope.com_state = data[0]['state'];
				}
				if ($scope.com_gst) {
					if ($scope.com_state && $scope.cust_state) {
						$scope.x.gst = ($scope.com_state === $scope.cust_state)
							? "cgst_sgst"
							: "igst";
					}
				} else {
					$scope.x.gst = "";
				}
			});
		}
	});

	$scope.save_data = function (x) {
		$('#proformainvoicebtn').attr('disabled', true);
		$.ajax({
			type: "POST",
			url: rootUrl + module + "/save",
			data: $('#invoice_form').serialize(),
			beforeSend: function () {
				$('#loader1').css('display', 'inline');
			},
			success: function (data) {
				data = (data || '').trim();
				function finishSaveRefresh() {
					clearSavedProformaInvoiceUrl();
					$scope.isEditingInvoice = false;
					$('#invoiceFormModal').modal('hide');
					$scope.filter_new();
					$scope.temp_data = {};
					$scope.invoice_plans = {};
					$scope.today_date();
					$scope.loader($scope.pageno || 1);
				}
				if (data == "1") {
					messages("success", "Success!", "invoice Saved Successfully", 3000);
					finishSaveRefresh();
				} else if (data == '2') {
					messages('success', 'Success!', "Data Updated Successfully", 4000);
					finishSaveRefresh();
				}
				else if (data == "0") {
					messages("warning", "Info!", "No Data Affected", 3000);
				}
				else {
					messages("danger", "Warning!", data, 6000);
				}
				$('#loader1').css('display', 'none');
				$('#proformainvoicebtn').attr('disabled', false);
			}
		});
	}

	// /temp data
	$scope.get_temp_plans = function (options) {
		options = options || {};
		$http.get(rootUrl + module + "/get_temp_plans").success(function (data) {
			$scope.invoice_plans = data;
			$timeout(function () {
				var planCostOverride = parseInvoiceAmount(options.planCostOverride);
				if (planCostOverride > 0) {
					$scope.x = $scope.x || {};
					$scope.x.plan_cost = formatInvoiceAmount(planCostOverride);
					$scope.x.__carry_forward_opening_due = formatInvoiceAmount(planCostOverride);
				} else {
					if ($scope.x) {
						$scope.x.__carry_forward_opening_due = '';
					}
					recalculateInvoicePlanCostFromPlans();
				}
				refreshInvoiceDueSummary();
			}, 500);
		});
	}

	$scope.add_plans = function (a, row) {
		if (a) {
			var carryForwardPlanCost = getCarryForwardInvoiceAmount(a);
			var installmentRows = $scope.should_show_invoice_installment_form() ? stripInvoiceInstallmentDueDates($scope.generatedRows || []) : [];
			$scope.refresh_installment_percentages();
			if (!validateInvoiceInstallmentTitles(installmentRows)) {
				return;
			}
			if (!a.temp_ip_id && $scope.editingTempInvoicePlanId) {
				a.temp_ip_id = $scope.editingTempInvoicePlanId;
			}
			if (!a.temp_ip_id && installmentRows.length && installmentRows[0].temp_ip_id) {
				a.temp_ip_id = installmentRows[0].temp_ip_id;
			}
			if (!a.subscription) {
				a.subscription = a.payment_model || 'advance';
			}
			applyRepeatingPlanDates(a, a, true);
			// Updating plan to existing proforma
			$http.post(rootUrl + module + "/add_plans", { plans: a, invoice_id: $scope.x.invoice_id, installments: installmentRows }).success(function (data) {
				data = (data || '').trim();
				if (data == '1') {
					messages('success', 'Success!', 'Plans Added Successfully', 3000);
					$scope.filter_plans();
					$scope.get_temp_plans({ planCostOverride: carryForwardPlanCost });
				} else if (data == '2') {
					messages('success', 'Success!', 'Plans Updated Successfully', 3000);
					$scope.filter_plans();
					$scope.get_temp_plans({ planCostOverride: carryForwardPlanCost });

				} else {
					messages('warning', 'Warning!', 'No Data Affected', 3000);
				}
			});
		} else {
			messages('warning', 'Warning!', 'Please Fill Out The Details', 3000);
		}
	};

	function recalculateInvoiceDue() {
		refreshInvoiceDueSummary();
	}

	$scope.edit_plan = function (p) {
		console.log('p', p.plan_id);
		$scope.selected = p;
		$scope.editingTempInvoicePlanId = p && p.temp_ip_id ? p.temp_ip_id : '';
		$scope.selected.plan = p.plan_id;
		$scope.selected.plan_id = p.plan_id;
		$scope.selected.payment_model = normalizeInvoicePaymentModel($scope.selected.payment_model || $scope.selected.subscription);
		$scope.selected.subscription = normalizeInvoicePaymentModel($scope.selected.subscription || $scope.selected.payment_model);
		applyRepeatingPlanDates($scope.selected, p, true);
		$scope.generatedRows = p.installments;
		$scope.selected._percent_base = getSubscriptionPlanPercentBase($scope.selected);
		angular.forEach($scope.generatedRows || [], function (row) {
			attachInstallmentPercentBase(row, getSubscriptionPlanPercentBase($scope.selected));
			normalizeSubscriptionInstallmentPercent(row, getSubscriptionPlanPercentBase($scope.selected));
		});
		queueInvoiceSelect2Init();

	}
	$scope.delete_plan = function (p) {
		console.log(p);
		if (confirm("Are you sure you want to delete this plan?")) {
			$http.get(rootUrl + module + '/delete_temp_plans?id=' + p.temp_ip_id).success(function (data) {
				if (data == 1) {
					messages('success', 'Success', 'Plan Deleted Successfully.', 3000);
					$scope.get_temp_plans();
				} else {
					messages('danger', 'Danger', 'Plan Could Not Be Deleted.', 3000);
				}
			});
		}
	};

	$scope.calculateDiscount = function (unitForm = false) {
		if ($scope.selected && $scope.selected.mrp && $scope.selected.unit && $scope.selected.sp) {
			const mrp = parseFloat($scope.selected.mrp);
			const unit = parseFloat($scope.selected.unit);
			const sp = parseFloat($scope.selected.sp);

			if (!isNaN(mrp) && !isNaN(sp) && !isNaN(unit)) {
				let price = mrp * unit;
				if (price > sp) {
					if (unitForm === true) {
						$scope.selected.sp = price;
						$scope.selected.discount = 0;
					} else {
						$scope.selected.discount = price - sp;
					}
				} else {
					$scope.selected.discount = 0;
				}

			} else {
				$scope.selected.discount = '';
			}
		} else {
			$scope.selected.discount = '';
		}
	};

	$scope.unit_change = function () {
		if ($scope.selected && isOneTimePlanType($scope.selected.type)) {
			if ($scope.selected._from_subscription && !$scope.selected._has_subscription_installments) {
				$scope.generatedRows = [];
				$scope.calculateDiscount(true);
				return;
			}

			let num = parseFloat($scope.selected.unit) || 0;
			if (!$scope.generatedRows) {
				$scope.generatedRows = [];
			}
			// console.log('generated:', $scope.generatedRows);
			if ($scope.generatedRows.length === 0) {
				for (i = 1; i <= num; i++) {
					let row = {
						title: "",
						percent: "",
						amount: ""
					};

					$scope.generatedRows.push(row);
				}
			} else {
				actualNum = num - $scope.generatedRows.length;
				for (i = 1; i <= actualNum; i++) {
					$scope.generatedRows.push({
						title: "",
						percent: "",
						amount: ""
					});
				}
			}

		} else if ($scope.selected && $scope.selected.unit && $scope.selected.mrp) {
			$scope.calculateDiscount(true);
		}
	}

	// $scope.calculate_total = function (plans) {
	// 	if (!$scope.x) {
	// 		$scope.x = {};
	// 	}
	// 	let total = 0;
	// 	angular.forEach(plans, function (plan, index) {
	// 		total += parseFloat(plan.sp) || 0;
	// 	});
	// 	$scope.x.plan_cost = total;
	// 	$scope.x.pay_amount = total;
	// }


	$scope.$watch('x.project_id', function (newVal, oldVal) {
		if ($scope.isEditingInvoice) {
			return;
		}
		if (shouldPreserveProformaInvoiceCopy()) {
			return;
		}
		if (newVal !== oldVal) {
			$scope.x.invoice_number = '';
		}
	});

	$scope.init();
	allowSelect2TypingInsideInvoiceModal();

	$scope.$watch('x.plan_cost', function (newVal, oldVal) {
		if ($scope.isEditingInvoice) {
			return;
		}
		if (newVal === oldVal) {
			return;
		}
		if (shouldPreserveProformaInvoiceCopy() && syncingProformaInvoiceCopyTotals) {
			return;
		}
		let new_cost = newVal;

		if (new_cost !== undefined && new_cost !== null && new_cost !== '') {
			let cost = parseFloat(new_cost) || 0;

			if ($scope.x.gst === 'cgst_sgst') {

				let cgstPer = 9, sgstPer = 9;

				$scope.x.amt_extax = cost;
				$scope.x.cgst = $scope.percent(cost, cgstPer);
				$scope.x.sgst = $scope.percent(cost, sgstPer);

				$scope.x.pay_amount = cost + $scope.x.cgst + $scope.x.sgst;

			} else if ($scope.x.gst === 'igst') {

				let igstPer = 18;

				$scope.x.amt_extax = cost;
				$scope.x.igst = $scope.percent(cost, igstPer);
				$scope.x.pay_amount = cost + $scope.x.igst;
			} else {
				$scope.x.amt_extax = cost;
				$scope.x.pay_amount = cost
			}

			recalculateInvoiceDue();
			refreshInvoiceDueSummary();
			return; // Stop here, do not run next block
		}
	});


	$scope.$watch('x.pay_amount', function (newVal, oldVal) {
		if ($scope.isEditingInvoice) {
			return;
		}
		if (shouldPreserveProformaInvoiceCopy()) {
			return;
		}
		if (newVal === undefined || newVal === null || newVal === '') {
			recalculateInvoiceDue();
			refreshInvoiceDueSummary();
			return;
		}

		recalculateInvoiceDue();
		refreshInvoiceDueSummary();
	});

	$scope.$watchGroup(['selected.payment_model', 'selected.subscription', 'selected.sp', 'x.plan_cost'], function () {
		if ($scope.isEditingInvoice) {
			return;
		}
		if (shouldPreserveProformaInvoiceCopy()) {
			return;
		}
		recalculateInvoiceDue();
		refreshInvoiceDueSummary();
	});

	$scope.$watch('selected.sp', function () {
		if ($scope.generatedRows && $scope.generatedRows.length) {
			$scope.refresh_installment_percentages();
		}
	});

	$scope.$watchGroup(['selected.start_date', 'selected.end_date'], function () {
		if ($scope.selected && isRepeatingInvoicePlan($scope.selected)) {
			$scope.refresh_repeating_plan_cost();
		}
	});

	$scope.$watchCollection('invoice_plans', function () {
		if (shouldPreserveProformaInvoiceCopy()) {
			return;
		}
		refreshInvoiceDueSummary();
	});

	$(document)
		.off('shown.bs.modal.invoiceSelect2', '#invoiceFormModal')
		.on('shown.bs.modal.invoiceSelect2', '#invoiceFormModal', function () {
			queueInvoiceSelect2Init();
		});

	$scope.$watchCollection('companies', function () {
		queueInvoiceSelect2Init();
	});

	$scope.$watchCollection('customers', function () {
		queueInvoiceSelect2Init();
	});

	$scope.$watchCollection('plan_data', function () {
		ensureInvoicePlanOption($scope.selected);
		queueInvoiceSelect2Init();
	});

	$scope.$watchGroup(['x.project_id', 'x.c_id', 'selected.plan'], function () {
		ensureInvoicePlanOption($scope.selected);
		queueInvoiceSelect2Init();
	});

	$scope.$on('$destroy', function () {
		if ($.fn.select2) {
			$(select2Selectors).each(function () {
				let $select = $(this);
				if ($select.data('select2')) {
					$select.select2('destroy');
				}
			});
		}
		$(document).off('shown.bs.modal.invoiceSelect2', '#invoiceFormModal');
	});


	$scope.percent = function (value, rate) {
		return parseFloat((value * rate) / 100);
	};

	$scope.generate_pdf = function (id, download) {
		$scope.ID = id;
		$scope.isDownload = download;
		if ($scope.ID) {
			const url = rootUrl + module + `/generate_pdf?download=${$scope.isDownload}&id=${$scope.ID}`;
			window.open(url, "_blank");
		}
	}

	$scope.options = {
		height: 200,
		toolbar: [
			// Remove 'style' button group completely
			['font', ['bold', 'italic', 'underline']],
			['para', ['ol']],
			['insert', ['link']],
			['view', ['codeview']],
			['para', ['justifyLeft', 'justifyCenter', 'justifyRight', 'justifyFull']]
		]
	};

	$scope.cleanHTML = function (html) {
		return html ? html.replace(/<[^>]+>/g, '') : '';
	};

}]);
app.controller('proforma_invoice', ['$scope', '$rootScope', '$http', '$timeout', '$state', '$stateParams', '$element', function ($scope, $rootScope, $http, $timeout, $state, $stateParams, $element) {
	let module = 'proforma_invoice';
	let rootUrl = $rootScope.site_url;
	let select2Selectors = '#proforma_company_select, #proforma_customer_select, #proforma_plan_select';
	let tempCopyCacheKey = 'proforma_invoice_temp_copy_cache';
	let companiesLoaded = false;
	let pendingInvoiceCompanyId = '';
	let controllerInstanceId = 'proforma-' + Date.now() + '-' + Math.floor(Math.random() * 100000);

	$http.get(rootUrl + module + "/index").success(function (data) {
		if (data == 0) {
			window.location.assign('login.html');
		} else if (data == 2) {
			messages("success", "Privilege not assigned.", 1000);
			window.location.assign('index.html');
		}
	});

	$scope.pageno = 1;
	$scope.total_count = 0;
	$scope.totalPages = 1;
	$scope.itemsPerPage = '15';
	$scope.pageLinks = [];
	$scope.qx = {
		project_id: '',
		c_id: '',
		inv_generated: '',
		invoice_date: ''
	};
	$scope.datadb = [];
	$scope.loading = false;
	$scope.loadingPageno = null;
	$scope.x = {};
	$scope.paymentModelOptions = [
		{ value: 'advance', label: 'Advance' },
		{ value: 'partly', label: 'Partially' },
		{ value: 'final', label: 'Final' },
	];
	$scope.selected = {
		payment_model: 'advance',
		subscription: 'advance'
	};
	$scope.generatedRows = [];
	$scope.proforma_invoice_plans = [];
	$scope.proforma_invoice_plans_data = [];
	$scope.subscriptionBuilder = {
		loading: false,
		visible: false,
		mode: '',
		oneTimePlans: [],
		repeatingPlans: [],
		plans: [],
		selectedPlanId: '',
		selectedPlan: null,
		selectedInstallments: {},
		error: ''
	};
	$scope.saveInProgress = false;
	$scope.editingTempProformaPlanId = '';
	$scope.q_id = $stateParams.q_id;
	$scope.loggedInComId = '';
	$scope.helpLanguage = 'en';
	$scope.helpLanguages = [
		{ code: 'en', label: 'English' },
		{ code: 'hi', label: 'Hindi' },
		{ code: 'bn', label: 'Bengali' },
		{ code: 'ne', label: 'Nepali' }
	];
	$scope.helpDocs = {
		en: {
			title: 'Proforma Invoice Workflow Guide',
			intro: 'Use this guide to create a proforma invoice from a quotation, from subscribed plans, or by adding plans manually. Save the proforma as a draft billing record, review GST and totals, and convert it into a final invoice only after the customer is ready.',
			sections: [
				{
					title: 'Start from the list',
					points: [
						'Use the filter bar to search proformas by company, customer, date, or invoice generated status.',
						'Click Add Proforma to open a new draft.',
						'Use the pencil icon to edit an existing proforma.',
						'Use the PDF and Generate Invoice buttons from the list after the proforma is approved.'
					]
				},
				{
					title: 'Fill general details',
					points: [
						'Select the Proforma Date, Customer, and Company first.',
						'If the customer is linked to a default invoice company, that company may be selected automatically.',
						'The company and customer state decide the GST type automatically.',
						'Add notes if you want to include terms, remarks, or internal billing comments.',
						'This page does not generate the final invoice number.'
					]
				},
				{
					title: 'Add or edit plan rows',
					points: [
						'Choose a plan from the Plan dropdown to load its default pricing and service details.',
						'Edit Unit Price, Unit, date range, description, discount, or Total (SP) if needed.',
						'Click Save Plan to add the current row into the proforma draft list.',
						'Use the Edit button in the Plans table to bring a saved draft row back into the form.',
						'Use Delete if you want to remove an unwanted draft row before saving the proforma.'
					]
				},
				{
					title: 'Build from subscription',
					points: [
						'Select the customer first, then click Build From Subscription.',
						'Choose whether you want One Time or Repeating subscribed plans.',
						'Select the subscribed plan you want to bill.',
						'For one-time plans with installments, select the installments that should be billed now.',
						'Click Generate Proforma to load that subscription into the plan draft form.',
						'Review the loaded row and click Save Plan to add it to the proforma.'
					]
				},
				{
					title: 'Installments and repeating plans',
					points: [
						'One-time plans can create installment rows inside the plan draft.',
						'Enter or review installment title, percentage, amount, and due date before saving the plan.',
						'Repeating subscribed plans use the billing cycle and previous invoice history to prepare the next draft.',
						'Due or carry-forward logic is applied when the subscription rules allow it.'
					]
				},
				{
					title: 'Review totals and GST',
					points: [
						'The Total Cost is calculated from the saved draft plan rows.',
						'GST is applied automatically based on the selected company and customer state.',
						'The form shows CGST and SGST or IGST when GST is applicable.',
						'Check payable amount, tax totals, and notes before saving the proforma.'
					]
				},
				{
					title: 'Save the proforma',
					points: [
						'Click Save Proforma after checking the header, plans, installments, totals, GST, and notes.',
						'The system saves the proforma header together with the draft plan rows.',
						'When editing, the existing proforma is updated with your latest draft rows.',
						'After a successful save, the form closes and the list refreshes.'
					]
				},
				{
					title: 'Convert to invoice',
					points: [
						'Use Generate PDF when you want a printable proforma copy.',
						'Use Generate Invoice after the customer confirms the proforma.',
						'The invoice page copies the saved proforma plans into invoice draft rows.',
						'Final invoice totals, due handling, and billing completion are done on the invoice page.'
					]
				}
			]
		},
		hi: {
			title: 'प्रोफॉर्मा इनवॉइस कार्यप्रवाह मार्गदर्शिका',
			intro: 'इस मार्गदर्शिका की मदद से प्रोफॉर्मा इनवॉइस बनाएं, प्लान और इंस्टॉलमेंट जोड़ें, GST जांचें, और बाद में फाइनल इनवॉइस बनाएं।',
			sections: [
				{
					title: 'प्रोफॉर्मा इनवॉइस बनाएं',
					points: [
						'फॉर्म खोलने के लिए Add Proforma पर क्लिक करें।',
						'सबसे पहले Company, Customer और Invoice Date चुनें।',
						'Company चुनने पर GST नियम लागू होती है।',
						'प्लान जोड़ने से पहले सामान्य जानकारी भरें।'
					]
				},
				{
					title: 'एक या अधिक प्लान जोड़ें',
					points: [
						'Plan dropdown से एक plan चुनें।',
						'Unit Price, Unit, Discount और Total (SP) की जांच या बदलाव करें।',
						'Type और description plan details से अपने आप लोड हो जाते हैं।',
						'Save Plan पर क्लिक करके current plan को proforma में जोड़ें।',
						'जरूरत हो तो यही प्रक्रिया दोहराकर कई plans जोड़ें।'
					]
				},
				{
					title: 'इंस्टॉलमेंट शेड्यूल',
					points: [
						'अगर चुना गया plan one-time है, तो installment rows अपने आप दिखेंगी।',
						'हर installment के लिए title, percentage, amount और due date भरें।',
						'यह शेड्यूल plan के साथ ही सेव होता है।',
						'Saved installments को Plans section में देखा जा सकता है।'
					]
				},
				{
					title: 'Total और GST जांचें',
					points: [
						'Total Cost saved plan rows से calculate होता है।',
						'Company और customer state के आधार पर GST अपने आप लगता है।',
						'GST लागू होने पर CGST/SGST या IGST values दिखती हैं।',
						'Payable amount total और GST के आधार पर update होता है।',
						'Extra terms या remarks के लिए Notes का उपयोग करें।'
					]
				},
				{
					title: 'Proforma सेव या अपडेट करें',
					points: [
						'सभी plan rows और summary values जांचने के बाद Save Proforma पर क्लिक करें।',
						'नया record होने पर system proforma बनाता है और plans save करता है।',
						'पुराने record में यही form header और plan rows update करता है।',
						'Successful save के बाद modal अपने आप बंद हो जाता है।'
					]
				},
				{
					title: 'Edit, view और manage करें',
					points: [
						'List में pencil icon से existing proforma edit करें।',
						'View Plans में eye icon से read-only plan details modal खोलें।',
						'Invoice Generated checkbox से list में status mark करें।',
						'Table के ऊपर filters से company, customer, date या status के आधार पर search करें।'
					]
				},
				{
					title: 'PDF या final invoice बनाएं',
					points: [
						'Generate PDF printable proforma document खोलता है।',
						'Generate Invoice selected proforma को invoice page पर भेजता है।',
						'Invoice page पर proforma data final invoice flow में copy हो जाता है।',
						'यह step तब उपयोग होता है जब customer proforma को invoice में convert करने के लिए तैयार हो।'
					]
				}
			]
		},
		bn: {
			title: 'প্রোফর্মা ইনভয়েস ওয়ার্কফ্লো গাইড',
			intro: 'এই গাইড ব্যবহার করে প্রোফর্মা ইনভয়েস তৈরি, প্ল্যান ও installment যোগ, GST দেখা এবং পরে ফাইনাল ইনভয়েস তৈরি করুন।',
			sections: [
				{
					title: 'প্রোফর্মা ইনভয়েস তৈরি করুন',
					points: [
						'ফর্ম খুলতে Add Proforma-তে ক্লিক করুন।',
						'প্রথমে Company, Customer এবং Invoice Date নির্বাচন করুন।',
						'Company নির্বাচন করলে GST rule প্রয়োগ হয়।',
						'প্ল্যান যোগ করার আগে সাধারণ তথ্য পূরণ করুন।'
					]
				},
				{
					title: 'এক বা একাধিক প্ল্যান যোগ করুন',
					points: [
						'Plan dropdown থেকে একটি plan নির্বাচন করুন।',
						'Unit Price, Unit, Discount এবং Total (SP) পরীক্ষা বা পরিবর্তন করুন।',
						'Type এবং description plan details থেকে স্বয়ংক্রিয়ভাবে আসে।',
						'Save Plan-এ ক্লিক করে current plan proforma-তে যোগ করুন।',
						'প্রয়োজনে একইভাবে অনেকগুলো plans যোগ করা যায়।'
					]
				},
				{
					title: 'Installment schedule',
					points: [
						'নির্বাচিত plan one-time হলে installment rows স্বয়ংক্রিয়ভাবে দেখাবে।',
						'প্রতিটি installment-এর জন্য title, percentage, amount এবং due date পূরণ করুন।',
						'এই schedule plan-এর সাথে সেভ হয়।',
						'Saved installments Plans section-এ দেখা যায়।'
					]
				},
				{
					title: 'Total এবং GST যাচাই করুন',
					points: [
						'Total Cost saved plan rows থেকে calculate হয়।',
						'Company এবং customer state অনুযায়ী GST স্বয়ংক্রিয়ভাবে প্রয়োগ হয়।',
						'GST প্রযোজ্য হলে CGST/SGST বা IGST values দেখায়।',
						'Payable amount total এবং GST-এর উপর নির্ভর করে update হয়।',
						'অতিরিক্ত terms বা remarks-এর জন্য Notes ব্যবহার করুন।'
					]
				},
				{
					title: 'Proforma save বা update করুন',
					points: [
						'সব plan row এবং summary value দেখে Save Proforma-এ ক্লিক করুন।',
						'নতুন record হলে system proforma তৈরি করে এবং plans save করে।',
						'পুরনো record হলে এই form header এবং plan rows update করে।',
						'Successful save-এর পরে modal নিজে থেকেই বন্ধ হয়ে যায়।'
					]
				},
				{
					title: 'Edit, view এবং manage করুন',
					points: [
						'List-এর pencil icon দিয়ে existing proforma edit করুন।',
						'View Plans-এর eye icon দিয়ে read-only plan details modal খুলুন।',
						'Invoice Generated checkbox দিয়ে list-এ status mark করুন।',
						'Table-এর উপরের filters দিয়ে company, customer, date বা status অনুযায়ী search করুন।'
					]
				},
				{
					title: 'PDF বা final invoice তৈরি করুন',
					points: [
						'Generate PDF printable proforma document খুলে দেয়।',
						'Generate Invoice selected proforma-কে invoice page-এ পাঠায়।',
						'Invoice page-এ proforma data final invoice flow-এ copy হয়।',
						'এই step তখন ব্যবহার করা হয় যখন customer proforma-কে invoice-এ convert করতে প্রস্তুত।'
					]
				}
			]
		},
		ne: {
			title: 'प्रोफर्मा इनभ्वाइस कार्यप्रवाह मार्गदर्शिका',
			intro: 'यो मार्गदर्शिकाको प्रयोग गरेर प्रोफर्मा इनभ्वाइस बनाउनुहोस्, प्लान र installment थप्नुहोस्, GST हेर्नुहोस् र पछि final invoice बनाउनुहोस्।',
			sections: [
				{
					title: 'प्रोफर्मा इनभ्वाइस बनाउनुहोस्',
					points: [
						'फारम खोल्न Add Proforma मा click गर्नुहोस्।',
						'सबैभन्दा पहिले Company, Customer र Invoice Date select गर्नुहोस्।',
						'Company select गर्दा GST rule लागू हुन्छ।',
						'प्लान थप्नु अघि सामान्य विवरण भर्नुहोस्।'
					]
				},
				{
					title: 'एक वा धेरै प्लान थप्नुहोस्',
					points: [
						'Plan dropdown बाट एउटा plan select गर्नुहोस्।',
						'Unit Price, Unit, Discount र Total (SP) जाँच वा परिवर्तन गर्नुहोस्।',
						'Type र description plan details बाट automatic आउँछ।',
						'Save Plan मा click गरेर current plan proforma मा थप्नुहोस्।',
						'आवश्यक भए यही प्रक्रिया दोहोर्याएर धेरै plans थप्न सकिन्छ।'
					]
				},
				{
					title: 'Installment schedule',
					points: [
						'चयन गरिएको plan one-time भए installment rows automatic देखिन्छन्।',
						'हरेक installment का लागि title, percentage, amount र due date भर्नुहोस्।',
						'यो schedule plan सँगै save हुन्छ।',
						'Saved installments Plans section मा हेर्न सकिन्छ।'
					]
				},
				{
					title: 'Total र GST जाँच गर्नुहोस्',
					points: [
						'Total Cost saved plan rows बाट calculate हुन्छ।',
						'Company र customer state अनुसार GST automatic लागू हुन्छ।',
						'GST लागू हुँदा CGST/SGST वा IGST values देखिन्छ।',
						'Payable amount total र GST अनुसार update हुन्छ।',
						'अतिरिक्त terms वा remarks का लागि Notes प्रयोग गर्नुहोस्।'
					]
				},
				{
					title: 'Proforma save वा update गर्नुहोस्',
					points: [
						'सबै plan row र summary value जाँचेर Save Proforma मा click गर्नुहोस्।',
						'नयाँ record भए system ले proforma बनाउँछ र plans save गर्छ।',
						'पुरानो record भए यो form ले header र plan rows update गर्छ।',
						'Successful save पछि modal आफैं बन्द हुन्छ।'
					]
				},
				{
					title: 'Edit, view र manage गर्नुहोस्',
					points: [
						'List मा pencil icon प्रयोग गरेर existing proforma edit गर्नुहोस्।',
						'View Plans को eye icon प्रयोग गरेर read-only plan details modal खोल्नुहोस्।',
						'Invoice Generated checkbox बाट list मा status mark गर्नुहोस्।',
						'Table माथिका filters प्रयोग गरेर company, customer, date वा status अनुसार search गर्नुहोस्।'
					]
				},
				{
					title: 'PDF वा final invoice बनाउनुहोस्',
					points: [
						'Generate PDF ले printable proforma document खोल्छ।',
						'Generate Invoice ले selected proforma लाई invoice page मा पठाउँछ।',
						'Invoice page मा proforma data final invoice flow मा copy हुन्छ।',
						'यो चरण तब प्रयोग हुन्छ जब customer proforma लाई invoice मा convert गर्न तयार हुन्छ।'
					]
				}
			]
		}
	};

	function readTempCopyCache() {
		try {
			return JSON.parse(sessionStorage.getItem(tempCopyCacheKey) || '{}') || {};
		} catch (e) {
			return {};
		}
	}

	function writeTempCopyCache(cache) {
		try {
			sessionStorage.setItem(tempCopyCacheKey, JSON.stringify(cache || {}));
		} catch (e) {
			// Ignore storage failures and fall back to runtime state.
		}
	}

	function markTempCopied(proforma_id) {
		if (!proforma_id) {
			return;
		}

		let cache = readTempCopyCache();
		cache[String(proforma_id)] = true;
		writeTempCopyCache(cache);
	}

	function clearTempCopied(proforma_id) {
		if (!proforma_id) {
			try {
				sessionStorage.removeItem(tempCopyCacheKey);
			} catch (e) {
				// Ignore storage failures.
			}
			return;
		}

		let cache = readTempCopyCache();
		delete cache[String(proforma_id)];
		writeTempCopyCache(cache);
	}

	function hasTempCopied(proforma_id) {
		let cache = readTempCopyCache();
		return !!cache[String(proforma_id || '')];
	}

	function loadTempPlans() {
		return $http.get(rootUrl + module + "/get_temp_plans").success(function (data) {
			$scope.proforma_invoice_plans = data || [];
			recalculatePlanCost();
			$scope.initSelect2();
		});
	}

	function copyPlansToTempIfNeeded(proforma_id) {
		if (!proforma_id) {
			return;
		}

		if (hasTempCopied(proforma_id)) {
			loadTempPlans();
			return;
		}

		$http.get(rootUrl + 'quotation/get_quotation_details?q_id=' + proforma_id + '&data=discount,mrp,plan_id as plan,plan_id,sp,subscription,type,unit').success(function (data) {
			$http.post(rootUrl + module + '/save_temp_plans', { plans: data }).success(function (data) {
				if (data == 1) {
					markTempCopied(proforma_id);
					loadTempPlans();
				}
			});
		});
	}

	function toNumber(value) {
		let parsed = parseFloat(value);
		return isNaN(parsed) ? 0 : parsed;
	}

	function parseProformaAmount(value) {
		let parsed = parseFloat(String(value || '').replace(/,/g, '').replace(/[^\d.-]/g, ''));
		return isNaN(parsed) ? 0 : parsed;
	}

	function formatProformaAmount(value) {
		let amount = parseProformaAmount(value);
		return (amount % 1 === 0) ? String(amount) : String(parseFloat(amount.toFixed(2)));
	}

	function parseProformaMeta(meta) {
		if (!meta) {
			return null;
		}
		if (typeof meta === 'string') {
			try {
				meta = JSON.parse(meta);
			} catch (err) {
				return null;
			}
		}
		return angular.isObject(meta) ? meta : null;
	}

	function buildProformaDueCalculationText(meta, plan) {
		meta = parseProformaMeta(meta);
		if (!meta) {
			return '';
		}

		var dueAmount = parseProformaAmount(meta.due_amount);
		if (dueAmount <= 0 && meta.open_due) {
			dueAmount = parseProformaAmount(meta.open_due.due_amount);
		}
		if (dueAmount <= 0) {
			return '';
		}

		var planSp = parseProformaAmount(meta.original_plan_sp);
		if (planSp <= 0) {
			planSp = parseProformaAmount(plan && (plan.sp || plan._subscription_base_sp || plan._plan_sp));
		}

		var billableAmount = parseProformaAmount(meta.billable_plan_cost);
		if (billableAmount <= 0) {
			billableAmount = dueAmount;
		}

		if (planSp > 0 && billableAmount > dueAmount) {
			return 'Due calculation: Plan SP ' + formatProformaAmount(planSp) + ' + Due ' + formatProformaAmount(dueAmount) + ' = Plan Cost ' + formatProformaAmount(billableAmount) + '.';
		}

		if (planSp > 0) {
			return 'Due calculation: Plan Cost is Due ' + formatProformaAmount(dueAmount) + ' only. Plan SP ' + formatProformaAmount(planSp) + ' is not included for this proforma.';
		}

		return 'Due calculation: Plan Cost is Due ' + formatProformaAmount(dueAmount) + ' only.';
	}

	function firstProformaValue() {
		for (var i = 0; i < arguments.length; i++) {
			var value = arguments[i];
			if (value !== undefined && value !== null && String(value).trim() !== '') {
				return value;
			}
		}
		return '';
	}

	function normalizeProformaPaymentModel(value) {
		var normalized = normalizeProformaPlanType(value);
		if (normalized === 'partly' || normalized === 'partial' || normalized === 'partially') {
			return 'partly';
		}
		if (normalized === 'final') {
			return 'final';
		}
		return 'advance';
	}

	function ensureProformaPlanOption(plan) {
		if (!plan || !plan.plan_id) {
			return;
		}

		$scope.plan_data = $scope.plan_data || [];
		var planExists = false;
		angular.forEach($scope.plan_data, function (planItem) {
			if (String(planItem.plan_id) === String(plan.plan_id)) {
				planExists = true;
			}
		});

		if (!planExists) {
			$scope.plan_data.push({
				plan_id: plan.plan_id,
				name: plan.name || plan.plan_name || ('Plan #' + plan.plan_id),
				sp: plan.sp || plan.plan_sp || ''
			});
		}
	}

	function getProformaPlanPercentBase(plan) {
		plan = plan || {};

		let planId = plan.plan_id || plan.plan || plan.subscribed_plan_id || '';
		let masterSp = 0;

		angular.forEach($scope.plan_data || [], function (planItem) {
			if (!masterSp && planId && String(planItem.plan_id) === String(planId)) {
				masterSp = parseProformaAmount(planItem.sp);
			}
		});

		return parseProformaAmount(plan.sp) ||
			parseProformaAmount(plan.total) ||
			parseProformaAmount(plan._percent_base) ||
			parseProformaAmount(plan._plan_sp) ||
			parseProformaAmount(plan.plan_sp) ||
			parseProformaAmount(plan.master_sp) ||
			masterSp;
	}

	function normalizeProformaPlanType(value) {
		value = String(value || '').toLowerCase();
		value = value.replace(/[_-]+/g, ' ');
		value = value.replace(/\s+/g, ' ').trim();
		return value;
	}

	function isOneTimeProformaPlan(plan) {
		return normalizeProformaPlanType(plan && plan.type) === 'one time';
	}

	function sumProformaInstallmentAmounts(installments) {
		let total = 0;
		angular.forEach(installments || [], function (installment) {
			total += parseProformaAmount(installment && installment.amount);
		});
		return total;
	}

	function getSubscriptionInstallmentSourcePercent(installment) {
		installment = installment || {};
		var sourcePercent = firstProformaValue(
			installment._source_percentage,
			installment._source_percent,
			installment.percentage,
			installment.percent
		);
		if (sourcePercent === '') {
			return null;
		}
		return parseProformaAmount(sourcePercent);
	}

	function getSubscriptionInstallmentAmount(installment, planTotal) {
		installment = installment || {};
		var directAmount = parseProformaAmount(firstProformaValue(
			installment._source_amount,
			installment.amount,
			installment.installment_amount,
			installment.due_amount,
			installment.remaining_due,
			installment.payable_amount
		));

		var sourcePercent = getSubscriptionInstallmentSourcePercent(installment);
		if (planTotal > 0 && sourcePercent !== null) {
			var percentAmount = (planTotal * sourcePercent) / 100;
			if (directAmount <= 0) {
				return percentAmount;
			}

			var directPercent = (directAmount / planTotal) * 100;
			if (Math.abs(directPercent - sourcePercent) > 0.5) {
				return percentAmount;
			}
		}

		return directAmount;
	}

	function sumProformaSubscriptionInstallmentAmounts(installments, planTotal) {
		var total = 0;
		angular.forEach(installments || [], function (installment) {
			total += getSubscriptionInstallmentAmount(installment, planTotal);
		});
		return total;
	}

	function getSubscriptionInstallmentId(installment) {
		if (!installment) {
			return '';
		}
		return installment.subscribed_plan_installment_id || installment.installment_no || '';
	}

	function getUniqueSubscriptionInstallments(installments) {
		var uniqueInstallments = [];
		var seen = {};

		angular.forEach(installments || [], function (installment, index) {
			var installmentId = getSubscriptionInstallmentId(installment);
			var mergeKey = installmentId ? String(installmentId) : ('row-' + index);
			if (seen[mergeKey]) {
				return;
			}
			seen[mergeKey] = true;
			uniqueInstallments.push(installment);
		});

		return uniqueInstallments;
	}

	function getSubscriptionInstallmentTitle(installment) {
		if (!installment) {
			return '';
		}
		return installment.title || (installment.installment_no ? ('Installment #' + installment.installment_no) : 'Installment');
	}

	function getSubscribedInstallmentDueDate(installment) {
		if (!installment) {
			return '';
		}
		return normalizeProformaDateForPicker(firstProformaValue(
			installment.subscribed_plan_due_date,
			installment.source_due_date,
			installment.installment_due_date,
			installment.due_date,
			''
		));
	}

	function buildSubscribedInstallmentDueDateMap(plan) {
		var dueDates = {};
		angular.forEach((plan && plan.installments) || [], function (installment) {
			var installmentId = getSubscriptionInstallmentId(installment);
			var dueDate = getSubscribedInstallmentDueDate(installment);
			if (installmentId && dueDate) {
				dueDates[String(installmentId)] = dueDate;
			}
		});
		return dueDates;
	}

	function applySubscribedInstallmentDueDates(installments, sourcePlan) {
		var dueDates = buildSubscribedInstallmentDueDateMap(sourcePlan);
		return angular.copy(installments || []).map(function (installment) {
			var installmentId = getSubscriptionInstallmentId(installment);
			if (installmentId && dueDates[String(installmentId)]) {
				installment.due_date = dueDates[String(installmentId)];
			} else {
				installment.due_date = getSubscribedInstallmentDueDate(installment);
			}
			return installment;
		});
	}

	function getCurrentProformaDateValue() {
		var date = new Date();
		var day = ('0' + date.getDate()).slice(-2);
		var month = ('0' + (date.getMonth() + 1)).slice(-2);
		return day + '/' + month + '/' + date.getFullYear();
	}

	function parseProformaDateValue(value) {
		var text = String(value || '').trim();
		var parts = text.split(/[\/.-]/);
		if (parts.length !== 3) {
			return null;
		}

		var day;
		var month;
		var year;
		if (parts[0].length === 4) {
			year = parseInt(parts[0], 10);
			month = parseInt(parts[1], 10) - 1;
			day = parseInt(parts[2], 10);
		} else {
			day = parseInt(parts[0], 10);
			month = parseInt(parts[1], 10) - 1;
			year = parseInt(parts[2], 10);
		}

		var date = new Date(year, month, day);
		if (isNaN(date.getTime())) {
			return null;
		}
		return date;
	}

	function normalizeProformaDateForPicker(value) {
		var text = String(value || '').trim();
		if (!text) {
			return '';
		}

		var isoMatch = text.match(/^(\d{4})-(\d{2})-(\d{2})$/);
		if (isoMatch) {
			return isoMatch[3] + '/' + isoMatch[2] + '/' + isoMatch[1];
		}

		return text;
	}

	function formatProformaDateObject(date) {
		if (!(date instanceof Date) || isNaN(date.getTime())) {
			return '';
		}

		var day = ('0' + date.getDate()).slice(-2);
		var month = ('0' + (date.getMonth() + 1)).slice(-2);
		return day + '/' + month + '/' + date.getFullYear();
	}

	function refreshProformaInstallmentDatepickers(delay) {
		$timeout(function () {
			if (!$.fn.datepicker) {
				return;
			}

			$('.proforma-installment-due-date').each(function () {
				var $el = $(this);
				var value = normalizeProformaDateForPicker($el.val());
				if ($el.data('datepicker')) {
					$el.datepicker('destroy');
				}
				$el.datepicker({
					format: 'dd/mm/yyyy',
					autoclose: true
				});
				if (value) {
					$el.datepicker('update', value);
					$el.val(value).trigger('change');
				}
			});
		}, delay || 0);
	}

	function getRepeatingDefaultDateRange() {
		var startDate = new Date();
		var endDate = new Date(startDate.getTime());
		endDate.setMonth(endDate.getMonth() + 1);

		return {
			start_date: formatProformaDateObject(startDate),
			end_date: formatProformaDateObject(endDate)
		};
	}

	function applyRepeatingPlanDates(plan, sourcePlan, preserveExisting) {
		if (!plan) {
			return plan;
		}

		sourcePlan = sourcePlan || {};
		if (isRepeatingProformaPlan(plan)) {
			var defaultDates = getRepeatingDefaultDateRange();
			plan.start_date = normalizeProformaDateForPicker(firstProformaValue(
				preserveExisting ? plan.start_date : '',
				sourcePlan.start_date,
				sourcePlan.next_invoice_start_date,
				defaultDates.start_date
			));
			plan.end_date = normalizeProformaDateForPicker(firstProformaValue(
				preserveExisting ? plan.end_date : '',
				sourcePlan.end_date,
				sourcePlan.next_invoice_end_date,
				defaultDates.end_date
			));
		} else {
			plan.start_date = '';
			plan.end_date = '';
		}

		return plan;
	}

	function getLatestInstallmentDueDate(installments) {
		var latestDate = null;
		var latestValue = '';
		angular.forEach(installments || [], function (installment) {
			var dueDate = getSubscribedInstallmentDueDate(installment);
			var parsedDate = parseProformaDateValue(dueDate);
			if (!dueDate) {
				return;
			}
			if (!parsedDate) {
				if (!latestValue) {
					latestValue = dueDate;
				}
				return;
			}
			if (!latestDate || parsedDate.getTime() > latestDate.getTime()) {
				latestDate = parsedDate;
				latestValue = dueDate;
			}
		});
		return latestValue;
	}

	function buildMergedSubscriptionInstallment(installments, plan) {
		var selectedInstallments = getUniqueSubscriptionInstallments(installments || []);
		var planTotal = getProformaPlanPercentBase(plan);
		var totalAmount = 0;
		var totalPercent = 0;
		var hasPercent = false;
		var installmentNumbers = [];
		var installmentTitles = [];
		var sourceInstallmentIds = [];

		angular.forEach(selectedInstallments, function (installment) {
			totalAmount += getSubscriptionInstallmentAmount(installment, planTotal);
			var installmentPercent = getProformaInstallmentPercentValue(installment, planTotal);
			if (installmentPercent !== null) {
				totalPercent += installmentPercent;
				hasPercent = true;
			}
			var installmentNo = installment && (installment.installment_no || installment.subscribed_plan_installment_id);
			if (installmentNo) {
				installmentNumbers.push(installmentNo);
			}
			var sourceInstallmentId = installment && (installment.subscribed_plan_installment_id || installment.installment_no);
			if (sourceInstallmentId) {
				sourceInstallmentIds.push(sourceInstallmentId);
			}
			installmentTitles.push(getSubscriptionInstallmentTitle(installment));
		});

		var mergedPercent = null;
		if (planTotal > 0 && totalAmount > 0) {
			mergedPercent = (totalAmount / planTotal) * 100;
		} else if (hasPercent) {
			mergedPercent = totalPercent;
		}

		return {
			installment_no: installmentNumbers.join(','),
			source_installment_ids: sourceInstallmentIds.join(','),
			title: selectedInstallments.length > 1 ? '' : (installmentTitles[0] || ''),
			percent: mergedPercent !== null ? formatProformaAmount(mergedPercent) : '',
			amount: formatProformaAmount(totalAmount),
			due_date: getLatestInstallmentDueDate(selectedInstallments),
			_percent_base: planTotal > 0 ? planTotal : ''
		};
	}

	function applyInstallmentAmountToSelectedPlan(plan, amount) {
		var selectedAmount = formatProformaAmount(amount);
		plan._installment_total = selectedAmount;
		plan.unit = 1;
		var percentBase = getProformaPlanPercentBase(plan);
		if (percentBase > 0) {
			plan._percent_base = percentBase;
		}
		return plan;
	}

	function extractDueOnlyPlanDetails(description) {
		description = description ? String(description) : '';
		var marker = 'data-due-only-plan-details="1"';
		var markerIndex = description.indexOf(marker);
		if (markerIndex < 0) {
			return '';
		}

		var divStart = description.lastIndexOf('<div', markerIndex);
		return divStart >= 0 ? description.substring(divStart) : '';
	}

	function stripTrailingBreaks(description) {
		description = description ? String(description) : '';
		return description
			.replace(/(?:<br\s*\/?>|\s|&nbsp;)+$/ig, '')
			.trim();
	}

	function normalizeProformaPlanDescriptionFields(plan) {
		if (!plan) {
			return plan;
		}

		var normalizedDescription = stripTrailingBreaks(firstProformaValue(
			plan.invoice_plan_description,
			plan.plan_description,
			plan.description,
			''
		));

		plan.invoice_plan_description = normalizedDescription;
		plan.plan_description = normalizedDescription;

		return plan;
	}

	function syncProformaDescriptionFromEditor(plan) {
		if (!plan) {
			return plan;
		}

		var $editor = $('#proformaFormModal .proforma-description-editor .note-editor .note-editable').first();
		if ($editor.length) {
			var editorHtml = $editor.html();
			if (editorHtml !== undefined && editorHtml !== null) {
				plan.invoice_plan_description = editorHtml;
				plan.plan_description = editorHtml;
			}
		}

		return normalizeProformaPlanDescriptionFields(plan);
	}

	function getDescriptionTextFingerprint(description) {
		return String(description || '')
			.replace(/<[^>]+>/g, ' ')
			.replace(/&nbsp;/gi, ' ')
			.replace(/\s+/g, ' ')
			.trim()
			.toLowerCase();
	}

	function appendUniqueDescription(baseDescription, extraDescription) {
		baseDescription = stripTrailingBreaks(baseDescription);
		extraDescription = stripTrailingBreaks(extraDescription);

		if (!extraDescription) {
			return baseDescription;
		}
		if (!baseDescription) {
			return extraDescription;
		}

		var baseFingerprint = getDescriptionTextFingerprint(baseDescription);
		var extraFingerprint = getDescriptionTextFingerprint(extraDescription);
		if (extraFingerprint && baseFingerprint.indexOf(extraFingerprint) !== -1) {
			return baseDescription;
		}

		return baseDescription + '<br><br>' + extraDescription;
	}

	function buildSubscriptionLoadedDescription(masterDescription, generatedDescription, fallbackDescription) {
		masterDescription = stripTrailingBreaks(masterDescription);
		generatedDescription = stripTrailingBreaks(generatedDescription);
		fallbackDescription = stripTrailingBreaks(fallbackDescription);

		var dueDetails = extractDueOnlyPlanDetails(generatedDescription);
		var generatedBaseDescription = generatedDescription;
		if (dueDetails) {
			generatedBaseDescription = stripTrailingBreaks(generatedDescription.replace(dueDetails, ''));
		}

		var baseDescription = firstProformaValue(
			masterDescription,
			generatedBaseDescription,
			fallbackDescription,
			generatedDescription,
			''
		);

		if (dueDetails) {
			return appendUniqueDescription(baseDescription, dueDetails);
		}

		return baseDescription;
	}

	function getProformaPlanCost(plan) {
		if (plan && isRepeatingProformaPlan(plan) && plan.subscription_meta) {
			var meta = plan.subscription_meta;
			if (typeof meta === 'string') {
				try {
					meta = JSON.parse(meta);
				} catch (err) {
					meta = null;
				}
			}
			if (meta && meta.billable_plan_cost !== undefined && meta.billable_plan_cost !== null && meta.billable_plan_cost !== '') {
				return parseProformaAmount(meta.billable_plan_cost);
			}
		}

		if (plan && isOneTimeProformaPlan(plan) && angular.isArray(plan.installments) && plan.installments.length) {
			return sumProformaInstallmentAmounts(plan.installments);
		}

		return parseProformaAmount(plan && plan.sp);
	}

	function validateProformaInstallmentTitles(installments) {
		let rows = installments || [];
		for (let i = 0; i < rows.length; i++) {
			if (!String(rows[i] && rows[i].title ? rows[i].title : '').trim()) {
				messages('warning', 'Warning!', 'Please enter installment title #' + (i + 1) + '.', 3000);
				return false;
			}
		}
		return true;
	}

	function getProformaInstallmentPercentValue(installment, planTotal) {
		if (!installment) {
			return null;
		}

		let hasAmount = installment.amount !== undefined && installment.amount !== null && String(installment.amount).trim() !== '';
		let amount = parseProformaAmount(installment.amount);
		if (hasAmount && planTotal > 0) {
			return (amount / planTotal) * 100;
		}

		let directPercent = installment.percent;
		if ((directPercent === undefined || directPercent === null || String(directPercent).trim() === '') && installment.percentage !== undefined && installment.percentage !== null) {
			directPercent = installment.percentage;
		}

		if (directPercent !== undefined && directPercent !== null && String(directPercent).trim() !== '') {
			return parseProformaAmount(directPercent);
		}

		return null;
	}

	function attachProformaInstallmentPercentBase(installment, planTotal) {
		if (installment && planTotal > 0) {
			installment._percent_base = planTotal;
		}
		return installment;
	}

	function normalizeProformaInstallmentPercent(installment, planTotal) {
		let percent = getProformaInstallmentPercentValue(installment, planTotal);
		if (percent !== null) {
			let formattedPercent = formatProformaAmount(percent);
			installment.percent = formattedPercent;
			installment.percentage = formattedPercent;
		}
		return installment;
	}

	function getProformaInstallmentPercentBase(row) {
		return getProformaPlanPercentBase($scope.selected);
	}

	$scope.refresh_installment_percentages = function () {
		let percentBase = getProformaPlanPercentBase($scope.selected);
		angular.forEach($scope.generatedRows || [], function (row) {
			attachProformaInstallmentPercentBase(row, percentBase);
			normalizeProformaInstallmentPercent(row, percentBase);
		});
	};

	$scope.update_installment_percent_from_amount = function (row) {
		if (!row) {
			return;
		}

		if (row.amount === undefined || row.amount === null || String(row.amount).trim() === '') {
			row.percent = '';
			row.percentage = '';
			return;
		}

		let percentBase = getProformaInstallmentPercentBase(row);
		if (percentBase <= 0) {
			return;
		}

		normalizeProformaInstallmentPercent(row, percentBase);
		if ($scope.selected && isOneTimeProformaPlan($scope.selected)) {
			$scope.selected._installment_total = formatProformaAmount(sumProformaInstallmentAmounts($scope.generatedRows));
		}
	};

	function buildPageLinks(currentPage, totalPages) {
		let pages = [];
		let start = 1;
		let end = totalPages;

		if (totalPages > 7) {
			start = currentPage - 2;
			end = currentPage + 2;

			if (start < 1) {
				end += (1 - start);
				start = 1;
			}
			if (end > totalPages) {
				start -= (end - totalPages);
				end = totalPages;
			}

			if (start < 1) {
				start = 1;
			}
		}

		for (let i = start; i <= end; i++) {
			pages.push(i);
		}

		return pages;
	}

	function refreshPagerMeta() {
		let perPage = parseInt($scope.itemsPerPage, 10) || 1;
		$scope.totalPages = Math.max(1, Math.ceil(($scope.total_count || 0) / perPage));
		$scope.pageLinks = buildPageLinks($scope.pageno || 1, $scope.totalPages);
	}

	function normalizeProformaListRows(rows) {
		let seen = {};
		let normalizedRows = [];

		angular.forEach(rows || [], function (row) {
			if (!row) {
				return;
			}

			row.inv_generated = normalizeInvGeneratedValue(row.inv_generated);

			if (row.proforma_id !== undefined && row.proforma_id !== null && row.proforma_id !== '') {
				let key = String(row.proforma_id);
				if (seen[key]) {
					return;
				}
				seen[key] = true;
			}

			normalizedRows.push(row);
		});

		return normalizedRows;
	}

	function refreshProformaListAfterSave() {
		$scope.datadb = [];
		$scope.total_count = 0;
		refreshPagerMeta();
		$scope.loader(1);
	}

	function recalculatePlanCost() {
		let total = 0;
		angular.forEach($scope.proforma_invoice_plans, function (plan) {
			let planAmount = getProformaPlanCost(plan);
			let hasInstallments = plan && angular.isArray(plan.installments) && plan.installments.length > 0;
			if (!isOneTimeProformaPlan(plan) && hasInstallments && !isMonthlySubscriptionPlan(plan) && plan && plan.subscription_meta) {
				let meta = plan.subscription_meta;
				if (typeof meta === 'string') {
					try {
						meta = JSON.parse(meta);
					} catch (err) {
						meta = null;
					}
				}
				if (meta && meta.selected_installment_total !== undefined && meta.selected_installment_total !== null && meta.selected_installment_total !== '') {
					let selectedTotal = parseFloat(meta.selected_installment_total);
					if (!isNaN(selectedTotal)) {
						planAmount = selectedTotal;
					}
				}
			}
			total += planAmount;
		});
		$scope.x.plan_cost = total;
	}

	function buildCompanyChildrenMap(companies) {
		let childrenMap = {};

		angular.forEach(companies || [], function (company) {
			let parentId = (company && company.parent !== undefined && company.parent !== null)
				? String(company.parent).trim()
				: '';

			if (!parentId) {
				return;
			}

			if (!childrenMap[parentId]) {
				childrenMap[parentId] = [];
			}

			childrenMap[parentId].push(company);
		});

		return childrenMap;
	}

	function collectDescendantCompanies(rootComId, companies) {
		let rootId = String(rootComId || '').trim();
		if (!rootId) {
			return [];
		}

		let childrenMap = buildCompanyChildrenMap(companies);
		let byId = {};
		let queue = [rootId];
		let seen = {};
		let allowed = [];

		angular.forEach(companies || [], function (company) {
			if (!company || company.com_id === undefined || company.com_id === null) {
				return;
			}
			byId[String(company.com_id).trim()] = company;
		});

		while (queue.length) {
			let currentId = queue.shift();
			if (!currentId || seen[currentId]) {
				continue;
			}
			seen[currentId] = true;

			if (byId[currentId]) {
				allowed.push(byId[currentId]);
			}

			angular.forEach(childrenMap[currentId] || [], function (child) {
				if (child && child.com_id !== undefined && child.com_id !== null) {
					queue.push(String(child.com_id).trim());
				}
			});
		}

		return allowed;
	}

	function loadInvoiceCompanies() {
		return $http.get(rootUrl + "company_master/view?data=name,com_id,parent").success(function (data) {
			let allCompanies = angular.isArray(data) ? data : [];
			let rootComId = String($scope.loggedInComId || '').trim();
			let filteredCompanies = rootComId ? collectDescendantCompanies(rootComId, allCompanies) : allCompanies;

			$scope.companies = filteredCompanies;
			companiesLoaded = true;
			if ($scope.companies.length === 1) {
				$scope.x.project_id = $scope.companies[0]['com_id'];
			}
			if (pendingInvoiceCompanyId) {
				selectInvoiceCompany(pendingInvoiceCompanyId);
				pendingInvoiceCompanyId = '';
			}
			$scope.initSelect2();
		});
	}

	function companyOptionExists(companyId) {
		companyId = String(companyId || '').trim();
		if (!companyId) {
			return false;
		}

		let exists = false;
		angular.forEach($scope.companies || [], function (company) {
			if (!exists && company && String(company.com_id).trim() === companyId) {
				exists = true;
			}
		});
		return exists;
	}

	function syncProformaSelectValue(selector, value, delay) {
		$timeout(function () {
			let textValue = String(value || '').trim();
			let $select = $(selector);
			if (!$select.length) {
				return;
			}

			if (($select.val() || '').toString() !== textValue) {
				$select.val(textValue);
			}
			// Namespaced trigger refreshes only the select2 display; a plain
			// 'change' would re-enter the Angular handlers and re-fire the
			// c_id/project_id watcher, causing the modal to flicker.
			$select.trigger('change.select2');
		}, delay || 0);
	}

	function syncProformaSelectModelsFromDom() {
		$scope.x = $scope.x || {};
		$scope.selected = $scope.selected || {};

		let customerId = ($('#proforma_customer_select').val() || '').toString().trim();
		let companyId = ($('#proforma_company_select').val() || '').toString().trim();
		let planId = ($('#proforma_plan_select').val() || '').toString().trim();

		if (customerId) {
			$scope.x.c_id = customerId;
		}
		if (companyId) {
			$scope.x.project_id = companyId;
		}
		if (planId) {
			$scope.selected.plan = planId;
		}
	}

	function selectInvoiceCompany(companyId, attempt) {
		companyId = String(companyId || '').trim();
		if (!companyId) {
			return;
		}

		$scope.x = $scope.x || {};
		$scope.x.project_id = companyId;

		if (!companiesLoaded || !companyOptionExists(companyId)) {
			pendingInvoiceCompanyId = companyId;
			if ((attempt || 0) < 8) {
				$timeout(function () {
					selectInvoiceCompany(companyId, (attempt || 0) + 1);
				}, 100);
			}
			return;
		}

		syncProformaSelectValue('#proforma_company_select', companyId, 0);
	}

	function applyProformaDefaults() {
		$scope.x = angular.extend({
			inv_generated: '0'
		}, $scope.x || {});
	}

	function normalizeInvGeneratedValue(value) {
		return (value === '1' || value === 1 || value === true) ? '1' : '0';
	}

	function normalizeGstType(value) {
		return (value || '').toString().toLowerCase().trim();
	}

	function normalizePlanType(value) {
		value = (value || '').toString().toLowerCase();
		value = value.replace(/[_-]+/g, ' ');
		value = value.replace(/\s+/g, ' ').trim();
		return value;
	}

	function isOneTimePlanType(value) {
		var normalized = normalizePlanType(value);
		var underscored = normalized.replace(/\s+/g, '_');
		return normalized === 'one time' || underscored.indexOf('one_ti') !== -1 || underscored.indexOf('onetime') !== -1;
	}

	function isRepeatingProformaPlan(plan) {
		if (!plan) {
			return false;
		}

		var paymentMode = normalizePlanType(plan.payment_mode || plan.payment_type || '');
		if (paymentMode === 'repeating' || paymentMode === 'repeat' || paymentMode === 'recurring') {
			return true;
		}

		var planType = normalizePlanType(plan.type || plan.plan_type || '');
		return !!planType && !isOneTimePlanType(planType);
	}

	function isMonthlySubscriptionPlan(plan) {
		var typeText = '';
		if (plan) {
			typeText = (plan.type || plan.payment_mode || plan.subscription || '').toString().toLowerCase();
		}
		return typeText.indexOf('monthly') !== -1;
	}

	function getGstDisplayLabel(value) {
		switch (normalizeGstType(value)) {
			case 'cgst_sgst':
				return 'CGST/SGST';
			case 'igst':
				return 'IGST';
			case 'ugst':
				return 'CGST/UGST';
			default:
				return '';
		}
	}

	function getGstComponentLabels(value) {
		switch (normalizeGstType(value)) {
			case 'cgst_sgst':
				return {
					first: 'CGST',
					second: 'SGST'
				};
			case 'ugst':
				return {
					first: 'CGST',
					second: 'UGST'
				};
			case 'igst':
				return {
					first: 'IGST'
				};
			default:
				return {};
		}
	}

	function resetGstAmounts() {
		$scope.x = $scope.x || {};
		$scope.x.amt_extax = 0;
		$scope.x.cgst = 0;
		$scope.x.sgst = 0;
		$scope.x.igst = 0;
		$scope.x.ugst = 0;
		$scope.x.pay_amount = 0;
	}

	function recalculateGstSummary() {
		$scope.x = $scope.x || {};

		let baseAmount = parseFloat($scope.x.plan_cost) || 0;
		let gstType = normalizeGstType($scope.x.gst);
		let taxAmount = 0;

		$scope.x.amt_extax = baseAmount;
		$scope.x.cgst = 0;
		$scope.x.sgst = 0;
		$scope.x.igst = 0;
		$scope.x.ugst = 0;

		if (gstType === 'cgst_sgst') {
			$scope.x.cgst = $scope.percent(baseAmount, 9);
			$scope.x.sgst = $scope.percent(baseAmount, 9);
			taxAmount = $scope.x.cgst + $scope.x.sgst;
		} else if (gstType === 'igst') {
			$scope.x.igst = $scope.percent(baseAmount, 18);
			taxAmount = $scope.x.igst;
		} else if (gstType === 'ugst') {
			$scope.x.cgst = $scope.percent(baseAmount, 9);
			$scope.x.sgst = $scope.percent(baseAmount, 9);
			$scope.x.ugst = $scope.x.sgst;
			taxAmount = $scope.x.cgst + $scope.x.sgst;
		}

		$scope.x.pay_amount = baseAmount + taxAmount;
	}

	$scope.should_show_repeating_plan_dates = function (plan) {
		return isRepeatingProformaPlan(plan || $scope.selected);
	};

	function normalizeQuotationForProforma(quotation) {
		if (!quotation) {
			return;
		}

		$scope.x = angular.extend({}, $scope.x, quotation, {
			q_id: quotation.q_id || $scope.q_id || '',
			project_id: quotation.project_id || quotation.com_id || quotation.project || '',
			c_id: quotation.c_id || quotation.customer_id || '',
			invoice_date: $scope.x.invoice_date || quotation.invoice_date || quotation.date || '',
			inv_generated: normalizeInvGeneratedValue(quotation.inv_generated)
		});

		$scope.today_date();
		$scope.initSelect2();
		$timeout(function () {
			$('#proformaFormModal').modal('show');
		}, 0);
	}

	$scope.initSelect2 = function () {
		$timeout(function () {
			if (!$.fn.select2) {
				return;
			}

			$(select2Selectors).each(function () {
				let $select = $(this);
				if (!$select.data('select2')) {
					$select.select2({
						width: '100%'
					});
				} else {
					// Already initialized: just refresh the displayed label
					// (options may have re-rendered). Destroy/recreate here
					// made the dropdowns visibly flicker.
					$select.trigger('change.select2');
				}
				$select.off('change.proformaAngular').on('change.proformaAngular', function () {
					let value = ($(this).val() || '').toString();
					let id = this.id;
					$timeout(function () {
						$scope.x = $scope.x || {};
						$scope.selected = $scope.selected || {};
						if (id === 'proforma_customer_select' && String($scope.x.c_id || '') !== value) {
							$scope.x.c_id = value;
						} else if (id === 'proforma_company_select' && String($scope.x.project_id || '') !== value) {
							$scope.x.project_id = value;
						} else if (id === 'proforma_plan_select' && value && String($scope.selected.plan || '') !== value) {
							$scope.selected.plan = value;
							$scope.plan_selected(value);
						}
					}, 0);
				});
			});
		}, 0);
	};

	function buildListUrl() {
		let params = [];
		if ($scope.qx.search_text) {
			params.push('search_text=' + encodeURIComponent($scope.qx.search_text));
		}
		if ($scope.qx.project_id) {
			params.push('project_id=' + encodeURIComponent($scope.qx.project_id));
		}
		if ($scope.qx.c_id) {
			params.push('c_id=' + encodeURIComponent($scope.qx.c_id));
		}
		if ($scope.qx.inv_generated) {
			params.push('inv_generated=' + encodeURIComponent($scope.qx.inv_generated));
		}
		if ($scope.qx.invoice_date) {
			params.push('invoice_date=' + encodeURIComponent($scope.qx.invoice_date));
		}

		let url = rootUrl + module + '/view_paginated/' + $scope.itemsPerPage + '/' + $scope.pageno;
		if (params.length) {
			url += '?' + params.join('&');
		}
		return url;
	}

	$scope.loader = function (pageno) {
		pageno = parseInt(pageno, 10) || 1;
		if (pageno < 1) {
			pageno = 1;
		}

		// The pagination control also writes "pageno" and fires on-page-change,
		// so a single user action can ask for the same page twice. Skip the
		// duplicate request while the same page is already being fetched.
		if ($scope.loading && $scope.loadingPageno === pageno) {
			return;
		}

		$scope.pageno = pageno;
		$scope.loadingPageno = pageno;
		$scope.loading = true;

		$http.get(buildListUrl()).then(function (response) {
			let data = response.data || {};
			if (angular.isArray(data)) {
				$scope.datadb = normalizeProformaListRows(data);
				$scope.total_count = ($scope.datadb || []).length;
			} else {
				$scope.datadb = normalizeProformaListRows(data.data);
				$scope.total_count = toNumber(data.total_count);
			}
			refreshPagerMeta();
		}, function () {
			$scope.datadb = [];
			$scope.total_count = 0;
			refreshPagerMeta();
		}).finally(function () {
			$scope.loading = false;
			$scope.loadingPageno = null;
		});
	};

	$scope.apply_filters = function () {
		$scope.loader(1);
	};

	$scope.clear_filters = function () {
		$scope.qx = {
			project_id: '',
			c_id: '',
			inv_generated: '',
			invoice_date: ''
		};
		$scope.itemsPerPage = '15';
		$scope.loader(1);
	};

	$scope.on_items_per_page_change = function () {
		$scope.loader(1);
	};

	$scope.init = function () {
		applyProformaDefaults();
		$scope.loader(1);

		$http.get(rootUrl + "customer/view?data=name,c_id").success(function (data) {
			if (data.length === 1) {
				$scope.x.c_id = data[0]['c_id'];
				$scope.customers = data;
			} else {
				$scope.customers = data;
			}
			$scope.initSelect2();
		});

		$http.get(rootUrl + "dashboard/fetch_userdata").success(function (data) {
			$scope.loggedInComId = (data && data.com_id !== undefined && data.com_id !== null)
				? String(data.com_id).trim()
				: '';
			loadInvoiceCompanies();
		});

		$http.get(rootUrl + "plan_master/view").success(function (data) {
			$scope.plan_data = data;
			$scope.initSelect2();
		});

		if ($scope.q_id) {
			$http.get(rootUrl + 'quotation/view?q_id=' + $scope.q_id).success(function (data) {
				if (data && data.length) {
					normalizeQuotationForProforma(data[0]);
				}
			});
			copyPlansToTempIfNeeded($scope.q_id);
		}
	};

	$scope.today_date = function () {
		$scope.x = $scope.x || {};
		let d = new Date();
		let day = ("0" + d.getDate()).slice(-2);
		let month = ("0" + (d.getMonth() + 1)).slice(-2);
		let year = d.getFullYear();
		$scope.x.invoice_date = day + "/" + month + "/" + year;
	};

	$scope.update_call = function (y) {
		console.log(y);
		$scope.selected = {};
		$scope.generatedRows = [];

		if (!y || !y.proforma_id) {
			return;
		}

		$scope.q_id = y.q_id || '';
		$scope.x = angular.copy(y);
		$scope.x.inv_generated = normalizeInvGeneratedValue($scope.x.inv_generated);

		$http.get(rootUrl + module + '/view?proforma_id=' + y.proforma_id).success(function (data) {
			if (data && data.length) {
				$scope.x = angular.extend({}, $scope.x, data[0]);
				$scope.x.inv_generated = normalizeInvGeneratedValue($scope.x.inv_generated);
				$scope.initSelect2();
			}
		});

		$scope.get_proforma_plans_details(y.proforma_id);

		if (hasTempCopied(y.proforma_id)) {
			loadTempPlans();
		} else {
			$scope.get_plans_to_temp(y.proforma_id);
		}

		$timeout(function () {
			$scope.initSelect2();
		}, 0);
	};

	$scope.get_plans_to_temp = function (proforma_id) {
		$http.get(rootUrl + module + '/copy_plans_to_temp?proforma_id=' + proforma_id).success(function (data) {
			if (data == 1) {
				markTempCopied(proforma_id);
				loadTempPlans();
			}
		});
	};

	$scope.get_proforma_plans_details = function (proforma_id) {
		$http.get(rootUrl + module + '/get_proforma_invoice_details?proforma_id=' + proforma_id).success(function (data) {
			$scope.proforma_invoice_plans_data = data || [];
		});
	};

	$scope.update_inv_generated = function (row) {
		if (!row || !row.proforma_id) {
			return;
		}

		let invGenerated = (row.inv_generated === '1' || row.inv_generated === 1 || row.inv_generated === true) ? '1' : '0';

		$http.post(rootUrl + module + '/update_inv_generated', {
			proforma_id: row.proforma_id,
			inv_generated: invGenerated
		}).then(function (response) {
			let data = (response.data || '').toString().trim();
			if (data === '1' || data === '2') {
				messages('success', 'Success!', 'Invoice generated status updated successfully.', 2500);
				return;
			}

			messages('warning', 'Warning!', data || 'Unable to update invoice generated status.', 4000);
			$scope.loader($scope.pageno || 1);
		}, function () {
			messages('danger', 'Warning!', 'Unable to update invoice generated status.', 4000);
			$scope.loader($scope.pageno || 1);
		});
	};

	function syncTempPlans(callback) {
		return $http.get(rootUrl + module + "/get_temp_plans").then(function (response) {
			$scope.proforma_invoice_plans = response.data || [];
			angular.forEach($scope.proforma_invoice_plans, function (plan) {
				normalizeProformaPlanDescriptionFields(plan);
			});
			recalculatePlanCost();
			let total = $scope.x.plan_cost || 0;

			if (typeof callback === 'function') {
				callback($scope.proforma_invoice_plans, total);
			}

			return $scope.proforma_invoice_plans;
		});
	}

	$scope.plan_selected = function (y) {
		if (!y) { return; }
		$http.get(rootUrl + 'plan_master/view?plan_id=' + y).success(function (data) {
			if (!$scope.selected) {
				$scope.selected = {};
			}
			if (data && data.length) {
				angular.extend($scope.selected, data[0]);
				normalizeProformaPlanDescriptionFields($scope.selected);
				$scope.selected.unit = 1;
				$scope.selected.payment_model = String($scope.selected.payment_model || $scope.selected.subscription || 'advance');
				$scope.selected.subscription = String($scope.selected.subscription || $scope.selected.payment_model || 'advance');
				$scope.selected._plan_sp = data[0] && data[0].sp ? data[0].sp : '';
				$scope.selected._due_calculation_text = '';
				applyRepeatingPlanDates($scope.selected, data[0], false);
				$scope.calculateDiscount();
				$scope.refresh_installment_percentages();
				var selectedPlanIdAtLoad = String(y);
				$timeout(function () {
					if (String($scope.selected && $scope.selected.plan || '') !== selectedPlanIdAtLoad) {
						return;
					}
					var newDescription = $scope.selected.invoice_plan_description || '';
					var $editable = $('#proformaFormModal .proforma-description-editor .note-editor .note-editable').first();
					if ($editable.length) {
						$editable.html(newDescription);
					}
				}, 0);
			}
		});
	};

	$scope.filter_new = function () {
		$scope.x = {};
	 applyProformaDefaults();
		$scope.temp_data = {};
		$scope.proforma_invoice_plans = [];
		$scope.reset_subscription_builder();
		$scope.selected = {
			payment_model: 'advance',
			subscription: 'advance'
		};
		$scope.generatedRows = [];
		$scope.editingTempProformaPlanId = '';
		$scope.q_id = '';
		clearTempCopied();
		$scope.today_date();
		$scope.initSelect2();
		$http.get(rootUrl + module + '/delete_temp_plans').success(function (data) {
			if (data != 1) {
				messages('danger', 'Danger', 'Plan Could Not Be Deleted.', 3000);
			}
		});
	};

	$scope.filter_plans = function () {
		$scope.reset_subscription_builder();
		$scope.selected = {
			payment_model: 'advance',
			subscription: 'advance'
		};
		$scope.generatedRows = [];
		$scope.editingTempProformaPlanId = '';
		$scope.initSelect2();
	};

	$scope.$watchGroup(['x.c_id', 'x.project_id'], function (newValues, oldValues) {
		let c_id = newValues[0];
		let com_id = newValues[1];
		// Only react to the field that actually changed. Reacting on every
		// firing re-fetched the customer after selectInvoiceCompany() wrote
		// x.project_id, which re-triggered the whole cycle (flicker).
		if (c_id && c_id !== oldValues[0]) {
			$http.get(rootUrl + 'customer/view?c_id=' + c_id).success(function (data) {
				if (data && data.length) {
					$scope.cust_state = data[0]['state'];
					if (data[0]['invoice_com']) {
						selectInvoiceCompany(data[0]['invoice_com']);
					}
				}
				if ($scope.com_gst) {
					if ($scope.com_state && $scope.cust_state) {
						$scope.x.gst = ($scope.com_state === $scope.cust_state)
							? "cgst_sgst"
							: "igst";
					}
				} else {
					$scope.x.gst = "";
				}
			});
		}
		if (com_id !== oldValues[1]) {
			$scope.check_com_gst(com_id);
		}
	});

	$scope.check_com_gst = function (com_id) {
		if (!com_id) {
			$scope.com_gst = '';
			$scope.com_state = '';
			$scope.x = $scope.x || {};
			$scope.x.gst = '';
			return;
		}

		$http.get(rootUrl + 'company_master/view?id=' + com_id + '&data=gst_no,state').success(function (data) {
			if (!data || !data.length) {
				$scope.com_gst = '';
				$scope.com_state = '';
				$scope.x.gst = '';
				return;
			}

			$scope.com_gst = data[0]['gst_no'];
			$scope.com_state = data[0]['state'];
			if ($scope.com_gst) {
				if ($scope.com_state && $scope.cust_state) {
					$scope.x.gst = ($scope.com_state === $scope.cust_state)
						? "cgst_sgst"
						: "igst";
				}
			} else {
				$scope.x.gst = "";
			}
		});
	};

	$scope.save_data = function () {
		if ($scope.saveInProgress) {
			return;
		}

		$scope.saveInProgress = true;
		$('#proformainvoicebtn').attr('disabled', true);

		syncTempPlans(function () {
			$.ajax({
				type: "POST",
				url: rootUrl + module + "/save",
				data: $('#proforma_invoice_form').serialize(),
				dataType: "text",
				beforeSend: function () {
					$('#loader').css('display', 'inline');
				},
				success: function (data) {
					data = (data || '').trim();
					if (data == "1") {
						messages("success", "Success!", "proforma_invoice Saved Successfully", 3000);
						$('#proformaFormModal').modal('hide');
						clearTempCopied();
						$scope.filter_new();
						$scope.temp_data = {};
						$scope.proforma_invoice_plans = {};
						$scope.today_date();
						refreshProformaListAfterSave();
					} else if (data == '2') {
						messages('success', 'Success!', "Data Updated Successfully", 4000);
						$('#proformaFormModal').modal('hide');
						clearTempCopied();
						$scope.filter_new();
						$scope.temp_data = {};
						$scope.proforma_invoice_plans = {};
						$scope.today_date();
						refreshProformaListAfterSave();
					}
					else if (data == "0") {
						messages("warning", "Info!", "No Data Affected", 3000);
					}
					else {
						messages("danger", "Warning!", data, 6000);
					}
					$('#loader').css('display', 'none');
					$scope.saveInProgress = false;
					$('#proformainvoicebtn').attr('disabled', false);
				},
				error: function () {
					$('#loader').css('display', 'none');
					$scope.saveInProgress = false;
					$('#proformainvoicebtn').attr('disabled', false);
				}
			});
		}).catch(function () {
			$scope.saveInProgress = false;
			$('#loader').css('display', 'none');
			$('#proformainvoicebtn').attr('disabled', false);
			messages("danger", "Warning!", "Unable to prepare the proforma for saving. Please try again.", 4000);
		});
	};

	$scope.reset_subscription_builder = function () {
		$scope.subscriptionBuilder.loading = false;
		$scope.subscriptionBuilder.visible = false;
		$scope.subscriptionBuilder.mode = '';
		$scope.subscriptionBuilder.oneTimePlans = [];
		$scope.subscriptionBuilder.repeatingPlans = [];
		$scope.subscriptionBuilder.plans = [];
		$scope.subscriptionBuilder.selectedPlanId = '';
		$scope.subscriptionBuilder.selectedPlan = null;
		$scope.subscriptionBuilder.selectedInstallments = {};
		$scope.subscriptionBuilder.error = '';
	};

	$scope.set_subscription_mode = function (mode) {
		$scope.subscriptionBuilder.mode = mode;
		$scope.subscriptionBuilder.plans = mode === 'one_time'
			? ($scope.subscriptionBuilder.oneTimePlans || [])
			: ($scope.subscriptionBuilder.repeatingPlans || []);
		$scope.subscriptionBuilder.selectedPlanId = '';
		$scope.subscriptionBuilder.selectedPlan = null;
		$scope.subscriptionBuilder.selectedInstallments = {};

		if ($scope.subscriptionBuilder.plans.length === 1) {
			$scope.select_subscription_plan($scope.subscriptionBuilder.plans[0]);
		}
	};

	$scope.select_subscription_plan = function (plan) {
		if (!plan) {
			return;
		}
		$scope.subscriptionBuilder.selectedPlanId = plan.subscribed_plan_id;
		$scope.subscriptionBuilder.selectedPlan = plan;
		$scope.subscriptionBuilder.selectedPlan.payment_model = normalizeProformaPaymentModel(plan.payment_model || plan.subscription);
		$scope.subscriptionBuilder.selectedPlan.subscription = normalizeProformaPaymentModel(plan.subscription || plan.payment_model);
		$scope.subscriptionBuilder.selectedInstallments = {};
	};

	$scope.toggle_subscription_installment = function (installment) {
		if (!installment) {
			return;
		}

		var installmentId = installment.subscribed_plan_installment_id || installment.installment_no;
		if (!installmentId && installmentId !== 0) {
			return;
		}

		var key = String(installmentId);
		if ($scope.subscriptionBuilder.selectedInstallments[key]) {
			delete $scope.subscriptionBuilder.selectedInstallments[key];
		} else {
			$scope.subscriptionBuilder.selectedInstallments[key] = true;
		}
	};

	$scope.get_selected_subscription_installment_ids = function () {
		var ids = [];
		angular.forEach($scope.subscriptionBuilder.selectedInstallments, function (value, key) {
			if (value) {
				ids.push(parseInt(key, 10));
			}
		});
		return ids.filter(function (id) {
			return !isNaN(id) && id > 0;
		});
	};

	$scope.normalize_subscription_plan_list = function (plans) {
		var safePlans = angular.copy(plans || []);
		angular.forEach(safePlans, function (plan) {
			var installments = plan.installments || [];
			var planTotal = getProformaPlanPercentBase(plan);
			plan.installments = installments.filter(function (installment) {
				var paidStatus = installment && installment.paid_status !== undefined && installment.paid_status !== null
					? parseInt(installment.paid_status, 10)
					: (installment && installment.payment_status !== undefined && installment.payment_status !== null
						? parseInt(installment.payment_status, 10)
						: 0);
				return paidStatus !== 1;
			});
			angular.forEach(plan.installments, function (installment) {
				installment._source_amount = firstProformaValue(
					installment.amount,
					installment.installment_amount,
					installment.due_amount,
					installment.remaining_due,
					installment.payable_amount
				);
				installment._source_percentage = firstProformaValue(installment.percentage, installment.percent);
				attachProformaInstallmentPercentBase(installment, planTotal);
				normalizeProformaInstallmentPercent(installment, planTotal);
			});
		});
		return safePlans;
	};

	function populate_proforma_plan_form_from_subscription(plan, installments, meta) {
		var selectedPlan = angular.copy(plan || {});
		var subscribedPlanPricing = angular.copy($scope.subscriptionBuilder.selectedPlan || selectedPlan || {});
		var subscriptionMeta = parseProformaMeta(meta || selectedPlan.subscription_meta);
		var selectedInstallments = angular.copy(installments || []);
		var planId = selectedPlan.plan_id || selectedPlan.subscribed_plan_id || '';
		var planMasterPlan = null;
		var planMasterSp = '';
		var hasRepeatingDueBillableMeta = subscriptionMeta
			&& parseProformaAmount(subscriptionMeta.due_amount) > 0
			&& subscriptionMeta.billable_plan_cost !== undefined
			&& subscriptionMeta.billable_plan_cost !== null
			&& subscriptionMeta.billable_plan_cost !== '';
		var isSeoSubscribedPlan = /seo/i.test([
			subscribedPlanPricing.name,
			subscribedPlanPricing.plan_name,
			subscribedPlanPricing.display_name
		].join(' '));

		angular.forEach($scope.plan_data || [], function (planItem) {
			if (!planMasterPlan && String(planItem.plan_id) === String(planId)) {
				planMasterPlan = angular.copy(planItem);
				planMasterSp = planItem.sp;
			}
		});

		var generatedDescription = firstProformaValue(
			selectedPlan.invoice_plan_description,
			selectedPlan.plan_description,
			selectedPlan.description,
			subscribedPlanPricing.invoice_plan_description,
			subscribedPlanPricing.plan_description,
			subscribedPlanPricing.description,
			''
		);

		if (planMasterPlan) {
			selectedPlan = angular.extend(planMasterPlan, selectedPlan);
			selectedPlan._plan_sp = planMasterSp;
		}

		if (!selectedPlan.plan_id) {
			selectedPlan.plan_id = planId;
		}
		if (!selectedPlan.name && selectedPlan.plan_name) {
			selectedPlan.name = selectedPlan.plan_name;
		}
		selectedPlan.invoice_plan_description = stripTrailingBreaks(firstProformaValue(
			generatedDescription,
			selectedPlan.description,
			subscribedPlanPricing.description,
			''
		));
		selectedPlan.plan_description = selectedPlan.invoice_plan_description;
		ensureProformaPlanOption(selectedPlan);

		var selectedPlanTotal = firstProformaValue(selectedPlan.sp, selectedPlan.total);
		var subscriptionSpAmount = parseProformaAmount(firstProformaValue(selectedPlan.sp, selectedPlan.total));

		$scope.selected = $scope.selected || {};
		$scope.selected.plan = String(selectedPlan.plan_id || '');
		$scope.selected.plan_id = String(selectedPlan.plan_id || '');
		$scope.selected.name = selectedPlan.name || selectedPlan.plan_name || '';
		$scope.selected.mrp = selectedPlan.mrp || selectedPlan.unit_price || selectedPlan.price || '';
		$scope.selected.unit = selectedPlan.unit || 1;
		$scope.selected.type = selectedPlan.type || selectedPlan.plan_type || '';
		$scope.selected.payment_model = normalizeProformaPaymentModel(selectedPlan.payment_model || selectedPlan.subscription);
		$scope.selected.subscription = normalizeProformaPaymentModel(selectedPlan.subscription || selectedPlan.payment_model);
		$scope.selected.discount = firstProformaValue(selectedPlan.discount, '');
		$scope.selected.sp = selectedPlanTotal;
		if (isSeoSubscribedPlan && !hasRepeatingDueBillableMeta) {
			$scope.selected.mrp = firstProformaValue(subscribedPlanPricing.mrp, subscribedPlanPricing.unit_price, subscribedPlanPricing.price, $scope.selected.mrp);
			$scope.selected.unit = firstProformaValue(subscribedPlanPricing.unit, $scope.selected.unit, 1);
			$scope.selected.discount = firstProformaValue(subscribedPlanPricing.discount, $scope.selected.discount, 0);
			$scope.selected.sp = firstProformaValue(subscribedPlanPricing.sp, subscribedPlanPricing.total, $scope.selected.sp);
			selectedPlanTotal = $scope.selected.sp;
			subscriptionSpAmount = parseProformaAmount(firstProformaValue(subscribedPlanPricing.sp, subscribedPlanPricing.total, selectedPlanTotal));
		}
		$scope.selected._plan_sp = selectedPlan._plan_sp || '';
		$scope.selected._percent_base = getProformaPlanPercentBase($scope.selected);
		$scope.selected._subscription_base_sp = subscriptionSpAmount > 0 ? formatProformaAmount(subscriptionSpAmount) : '';
		$scope.selected.opening_due = selectedPlan.opening_due || '';
		$scope.selected.remaining_due = selectedPlan.remaining_due || selectedPlan.due_amount || '';
		$scope.selected.due_invoice_id = selectedPlan.due_invoice_id || '';
		$scope.selected.due_invoice_date = selectedPlan.due_invoice_date || '';
		$scope.selected.invoice_plan_description = selectedPlan.invoice_plan_description || '';
		$scope.selected.subscription_meta = subscriptionMeta ? angular.toJson(subscriptionMeta) : (selectedPlan.subscription_meta || '');
		$scope.selected._due_calculation_text = buildProformaDueCalculationText(subscriptionMeta || selectedPlan.subscription_meta, $scope.selected);
		$scope.selected._from_subscription = true;
		$scope.selected._has_subscription_installments = selectedInstallments.length > 0;
		applyRepeatingPlanDates($scope.selected, selectedPlan, false);
		if (meta && meta.billable_plan_cost !== undefined && meta.billable_plan_cost !== null && meta.billable_plan_cost !== '') {
			$scope.x = $scope.x || {};
			$scope.x.plan_cost = formatProformaAmount(meta.billable_plan_cost);
			recalculateGstSummary();
		}

		if (selectedInstallments.length) {
			$scope.generatedRows = selectedInstallments;
			angular.forEach($scope.generatedRows, function (row) {
				row.due_date = normalizeProformaDateForPicker(getSubscribedInstallmentDueDate(row));
				attachProformaInstallmentPercentBase(row, getProformaPlanPercentBase($scope.selected));
				normalizeProformaInstallmentPercent(row, getProformaPlanPercentBase($scope.selected));
			});
		} else {
			$scope.generatedRows = [];
		}

		if (!isSeoSubscribedPlan) {
			$scope.calculateDiscount();
		}
		$scope.refresh_installment_percentages();
		refreshProformaInstallmentDatepickers(50);
		$scope.initSelect2();
	}

	$scope.build_subscription_proforma = function () {
		syncProformaSelectModelsFromDom();

		if (!$scope.x || !$scope.x.c_id) {
			messages('warning', 'Warning!', 'Please select a Customer first.', 3000);
			return;
		}

		$('#buildsubscriptionbtn').attr('disabled', true);
		$scope.reset_subscription_builder();
		$scope.subscriptionBuilder.visible = true;
		$scope.subscriptionBuilder.loading = true;
		$http.post(rootUrl + module + '/build_subscription_proforma', {
			c_id: $scope.x.c_id
		}).then(function (response) {
			let data = response.data || {};
			if (!data.error) {
				var selection = data.selection || {};
				$scope.subscriptionBuilder.oneTimePlans = $scope.normalize_subscription_plan_list(selection.one_time_plans || []);
				$scope.subscriptionBuilder.repeatingPlans = $scope.normalize_subscription_plan_list(selection.repeating_plans || []);
				$scope.subscriptionBuilder.mode = '';
				$scope.subscriptionBuilder.plans = [];
				$scope.subscriptionBuilder.selectedPlanId = '';
				$scope.subscriptionBuilder.selectedPlan = null;
				$scope.subscriptionBuilder.selectedInstallments = {};
				$scope.subscriptionBuilder.error = '';
			} else {
				$scope.subscriptionBuilder.visible = false;
				messages('warning', 'Warning!', data.msg || 'Unable to build subscription based proforma draft.', 4000);
			}
		}, function () {
			$scope.subscriptionBuilder.visible = false;
			messages('danger', 'Warning!', 'Unable to build subscription based proforma draft.', 4000);
		}).finally(function () {
			$scope.subscriptionBuilder.loading = false;
			$('#buildsubscriptionbtn').attr('disabled', false);
		});
	};

	$scope.submit_subscription_proforma = function () {
		if (!$scope.x || !$scope.x.c_id) {
			messages('warning', 'Warning!', 'Please select a Customer first.', 3000);
			return;
		}

		if (!$scope.subscriptionBuilder.mode) {
			messages('warning', 'Warning!', 'Please select One Time or Repeating.', 3000);
			return;
		}

		if (!$scope.subscriptionBuilder.selectedPlanId) {
			messages('warning', 'Warning!', 'Please select a subscription plan.', 3000);
			return;
		}

		var selectedPlan = angular.copy($scope.subscriptionBuilder.selectedPlan || {});
		selectedPlan.subscription = normalizeProformaPaymentModel(selectedPlan.subscription || selectedPlan.payment_model);
		selectedPlan.payment_model = normalizeProformaPaymentModel(selectedPlan.payment_model || selectedPlan.subscription);

		var selectedInstallmentIds = [];
		if ($scope.subscriptionBuilder.mode === 'one_time') {
			var planInstallments = selectedPlan.installments || [];
			if (planInstallments.length > 0) {
				selectedInstallmentIds = $scope.get_selected_subscription_installment_ids();
				if (!selectedInstallmentIds.length) {
					messages('warning', 'Warning!', 'Please select one or more installments for the selected plan.', 3000);
					return;
				}
			}
		}

		$scope.subscriptionBuilder.loading = true;
		$('#subscriptionBuilderGenerateBtn').attr('disabled', true);
		$http.post(rootUrl + module + '/build_subscription_proforma', {
			c_id: $scope.x.c_id,
			mode: $scope.subscriptionBuilder.mode,
			subscribed_plan_id: $scope.subscriptionBuilder.selectedPlanId,
			installment_ids: selectedInstallmentIds,
			preview_only: 1
		}).then(function (response) {
			let data = response.data || {};
			if (!data.error) {
				var generated = data.generated_plans && data.generated_plans.length ? data.generated_plans[0] : {};
				var generatedPlan = angular.copy(generated.plan || data.plan || selectedPlan);
				var generatedInstallments = applySubscribedInstallmentDueDates(generated.installments || data.installments || [], selectedPlan);
				var generatedMeta = generated.meta || data.subscription_meta || null;

				if ($scope.subscriptionBuilder.mode === 'one_time' && generatedInstallments.length) {
					var uniqueSelectedInstallments = getUniqueSubscriptionInstallments(generatedInstallments);
					var selectedPlanPercentBase = getProformaPlanPercentBase(generatedPlan);
					var selectedInstallmentTotal = sumProformaSubscriptionInstallmentAmounts(uniqueSelectedInstallments, selectedPlanPercentBase);
					generatedPlan = applyInstallmentAmountToSelectedPlan(generatedPlan, selectedInstallmentTotal);
					generatedInstallments = [buildMergedSubscriptionInstallment(uniqueSelectedInstallments, generatedPlan)];
				}

				populate_proforma_plan_form_from_subscription(generatedPlan, generatedInstallments, generatedMeta);
				$scope.subscriptionBuilder.visible = false;
				$scope.subscriptionBuilder.error = '';
				messages('success', 'Success!', 'Subscription plan loaded into the Add Plan form. Review and click Save Plan when ready.', 3500);
			} else {
				if (data.action === 'choose_mode' || data.action === 'choose_plan' || data.action === 'choose_installments') {
					var selection = data.selection || {};
					var responseMode = data.mode || $scope.subscriptionBuilder.mode;
					$scope.subscriptionBuilder.oneTimePlans = selection.one_time_plans ? $scope.normalize_subscription_plan_list(selection.one_time_plans) : $scope.subscriptionBuilder.oneTimePlans;
					$scope.subscriptionBuilder.repeatingPlans = selection.repeating_plans ? $scope.normalize_subscription_plan_list(selection.repeating_plans) : $scope.subscriptionBuilder.repeatingPlans;
					if (data.action === 'choose_mode') {
						$scope.subscriptionBuilder.mode = '';
						$scope.subscriptionBuilder.plans = [];
						$scope.subscriptionBuilder.selectedPlanId = '';
						$scope.subscriptionBuilder.selectedPlan = null;
						$scope.subscriptionBuilder.selectedInstallments = {};
					} else if (data.action === 'choose_plan') {
						$scope.set_subscription_mode(responseMode);
					} else if (data.action === 'choose_installments' && data.selected_plan) {
						$scope.subscriptionBuilder.selectedPlan = data.selected_plan;
						$scope.subscriptionBuilder.selectedPlan.installments = data.installments || $scope.normalize_subscription_plan_list([data.selected_plan])[0].installments;
					}
					messages('warning', 'Warning!', data.msg || 'Please complete the subscription selection.', 4000);
				} else {
					messages('warning', 'Warning!', data.msg || 'Unable to build subscription based proforma draft.', 4000);
				}
			}
		}, function () {
			messages('danger', 'Warning!', 'Unable to build subscription based proforma draft.', 4000);
		}).finally(function () {
			$scope.subscriptionBuilder.loading = false;
			$('#subscriptionBuilderGenerateBtn').attr('disabled', false);
		});
	};

	$scope.get_temp_plans = function () {
		syncTempPlans();
	}

	$scope.add_plans = function (a, row) {
		if (a) {
			syncProformaDescriptionFromEditor(a);
			$scope.refresh_installment_percentages();
			var installmentRows = ($scope.generatedRows || []);
			if (!validateProformaInstallmentTitles(installmentRows)) {
				return;
			}
			if (!a.temp_p_id && $scope.editingTempProformaPlanId) {
				a.temp_p_id = $scope.editingTempProformaPlanId;
			}
			if (!a.temp_p_id && installmentRows.length && installmentRows[0].temp_p_id) {
				a.temp_p_id = installmentRows[0].temp_p_id;
			}
			if (!a.subscription) {
				a.subscription = a.payment_model || 'advance';
			}
			applyRepeatingPlanDates(a, a, true);
			$http.post(rootUrl + module + "/add_plans", { plans: a, proforma_id: $scope.x.proforma_id, c_id: $scope.x.c_id, project_id: $scope.x.project_id, installments: installmentRows }).success(function (data) {
				data = (data || '').toString().trim();
				if (data == '1') {
					messages('success', 'Success!', 'Plans Added Successfully', 3000);
					$scope.filter_plans();
					$scope.get_temp_plans();
				} else if (data == '2') {
					messages('success', 'Success!', 'Plans Updated Successfully', 3000);
					$scope.filter_plans();
					$scope.get_temp_plans();
				} else {
					messages('warning', 'Warning!', data || 'No Data Affected', 3000);
				}
			});
		} else {
			messages('warning', 'Warning!', 'Please Fill Out The Details', 3000);
		}
	};

	$scope.edit_plan = function (p) {
		normalizeProformaPlanDescriptionFields(p);
		$scope.selected = p;
		$scope.editingTempProformaPlanId = p && p.temp_p_id ? p.temp_p_id : '';
		$scope.selected.plan = p.plan_id;
		normalizeProformaPlanDescriptionFields($scope.selected);
		$scope.selected.payment_model = normalizeProformaPaymentModel($scope.selected.payment_model || $scope.selected.subscription);
		$scope.selected.subscription = normalizeProformaPaymentModel($scope.selected.subscription || $scope.selected.payment_model);
		$scope.selected._percent_base = getProformaPlanPercentBase($scope.selected);
		$scope.selected._due_calculation_text = buildProformaDueCalculationText($scope.selected.subscription_meta, $scope.selected);
		applyRepeatingPlanDates($scope.selected, p, true);
		$scope.generatedRows = p.installments;
		$timeout(function () {
			var desc = $scope.selected.invoice_plan_description || '';
			var $editable = $('#proformaFormModal .proforma-description-editor .note-editor .note-editable').first();
			if ($editable.length) {
				$editable.html(desc);
			}
		}, 0);
		angular.forEach($scope.generatedRows || [], function (row) {
			attachProformaInstallmentPercentBase(row, getProformaPlanPercentBase($scope.selected));
			normalizeProformaInstallmentPercent(row, getProformaPlanPercentBase($scope.selected));
		});
		$scope.initSelect2();
	};

	$scope.$watch('selected.invoice_plan_description', function (value) {
		if (!$scope.selected) {
			return;
		}

		$scope.selected.plan_description = value || '';
	});

	$scope.delete_plan = function (p) {
		if (confirm("Are you sure you want to delete this plan?")) {
			$http.get(rootUrl + module + '/delete_temp_plans?id=' + p.temp_p_id).success(function (data) {
				if (data == 1) {
					messages('success', 'Success', 'Plan Deleted Successfully.', 3000);
					$scope.get_temp_plans();
				} else {
					messages('danger', 'Danger', 'Plan Could Not Be Deleted.', 3000);
				}
			});
		}
	};

	$scope.calculateDiscount = function (unitForm = false) {
		if ($scope.selected && $scope.selected.mrp && $scope.selected.unit && $scope.selected.sp) {
			const mrp = parseFloat($scope.selected.mrp);
			const unit = parseFloat($scope.selected.unit);
			const sp = parseFloat($scope.selected.sp);

			if (!isNaN(mrp) && !isNaN(sp) && !isNaN(unit)) {
				let price = mrp * unit;
				if (price > sp) {
					if (unitForm === true) {
						$scope.selected.sp = price;
						$scope.selected.discount = 0;
					} else {
						$scope.selected.discount = price - sp;
					}
				} else {
					$scope.selected.discount = 0;
				}
			} else {
				$scope.selected.discount = '';
			}
		} else {
			$scope.selected.discount = '';
		}
	};

	$scope.unit_change = function () {
		if ($scope.selected && $scope.selected.type && $scope.selected.type == 'One TIme') {
			let num = parseFloat($scope.selected.unit) || 0;
			if (!$scope.generatedRows) {
				$scope.generatedRows = [];
			}
			if ($scope.generatedRows.length === 0) {
				for (let i = 1; i <= num; i++) {
					let row = {
						title: "",
						percent: "",
						amount: "",
						due_date: ""
					};
					$('.datepicker').datepicker();

					if (i === 1) {
						let today = new Date();
						let dd = String(today.getDate()).padStart(2, '0');
						let mm = String(today.getMonth() + 1).padStart(2, '0');
						let yyyy = today.getFullYear();

						row.due_date = dd + '/' + mm + '/' + yyyy;
					}

					$scope.generatedRows.push(row);
				}
			} else {
				let actualNum = num - $scope.generatedRows.length;
				for (let j = 1; j <= actualNum; j++) {
					$scope.generatedRows.push({
						title: "",
						percent: "",
						amount: "",
						due_date: ""
					});
				}
			}
		} else if ($scope.selected && $scope.selected.unit && $scope.selected.mrp) {
			$scope.calculateDiscount(true);
		}
	};

	$scope.$watchGroup(['x.plan_cost', 'x.gst'], function () {
		recalculateGstSummary();
	});

	$scope.$watch('selected.sp', function () {
		if ($scope.generatedRows && $scope.generatedRows.length) {
			$scope.refresh_installment_percentages();
		}
	});

	$scope.getGstDisplayLabel = getGstDisplayLabel;
	$scope.getGstComponentLabels = getGstComponentLabels;

	$scope.percent = function (value, rate) {
		return parseFloat((value * rate) / 100);
	};

	$scope.generate_pdf = function (id, download) {
		$scope.ID = id;
		$scope.isDownload = download;
		if ($scope.ID) {
			const url = rootUrl + module + `/generate_pdf?download=${$scope.isDownload}&id=${$scope.ID}`;
			window.open(url, "_blank");
		}
	};

	$scope.generate_invoice = function (id) {
		$state.go('invoice', { proforma_id: id });
	};

	$scope.options = {
		height: 200,
		toolbar: [
			['font', ['bold', 'italic', 'underline']],
			['para', ['ol']],
			['insert', ['link']],
			['view', ['codeview']],
			['para', ['justifyLeft', 'justifyCenter', 'justifyRight', 'justifyFull']]
		]
	};

	function moveModalsToBody() {
		$('#plans_modal, #proformaFormModal').each(function () {
			if (this.parentNode === document.body && $(this).data('proformaControllerOwner') !== controllerInstanceId) {
				$(this).remove();
			}
		});
		$timeout(function () {
			$('#plans_modal, #proformaFormModal').each(function () {
				let owner = $(this).data('proformaControllerOwner');
				if (this.parentNode === document.body && owner !== controllerInstanceId) {
					$(this).remove();
					return;
				}
				if ($element && $element[0] && !$.contains($element[0], this) && owner !== controllerInstanceId) {
					return;
				}
				$(this).data('proformaControllerOwner', controllerInstanceId);
				if (this.parentNode !== document.body) {
					$(this).appendTo(document.body);
				}
			});
		}, 0);
	}

	function bindPlanModalFixes() {
		$(document)
			.off('shown.bs.modal.proformaPlans', '#plans_modal')
			.on('shown.bs.modal.proformaPlans', '#plans_modal', function () {
				$(document).off('focusin.bs.modal');
				$('.modal-backdrop').remove();
				$('body').removeClass('modal-open').css('padding-right', '');
				$(this).css({
					'z-index': '2000',
					'pointer-events': 'auto'
				});
			});

		$(document)
			.off('shown.bs.modal.proformaForm', '#proformaFormModal')
			.on('shown.bs.modal.proformaForm', '#proformaFormModal', function () {
				$(document).off('focusin.bs.modal');
				$('.modal-backdrop').remove();
				$('body').removeClass('modal-open').css('padding-right', '');
				$(this).css({
					'z-index': '2000',
					'pointer-events': 'auto'
				});
			});
	}

	$scope.cleanHTML = function (html) {
		return html ? html.replace(/<[^>]+>/g, '') : '';
	};

	$scope.$watchCollection('companies', function () {
		$scope.initSelect2();
	});

	$scope.$watchCollection('customers', function () {
		$scope.initSelect2();
	});

	$scope.$watchCollection('plan_data', function () {
		$scope.initSelect2();
	});

	$scope.$watchGroup(['x.project_id', 'x.c_id', 'selected.plan'], function (values) {
		syncProformaSelectValue('#proforma_company_select', values[0], 0);
		syncProformaSelectValue('#proforma_customer_select', values[1], 0);
		syncProformaSelectValue('#proforma_plan_select', values[2], 0);
	});

	$scope.$on('$destroy', function () {
		if ($.fn.select2) {
			$(select2Selectors).each(function () {
				let $select = $(this);
				if ($select.data('select2')) {
					$select.select2('destroy');
				}
			});
		}
		$(document)
			.off('shown.bs.modal.proformaPlans', '#plans_modal')
			.off('shown.bs.modal.proformaForm', '#proformaFormModal');
		$('#plans_modal, #proformaFormModal').each(function () {
			if ($(this).data('proformaControllerOwner') === controllerInstanceId) {
				$(this).remove();
			}
		});
		$('.modal-backdrop').remove();
		$('body').removeClass('modal-open').css('padding-right', '');
	});

	moveModalsToBody();
	bindPlanModalFixes();
	$scope.init();
	$scope.today_date();

	$('.date').datepicker({
		format: 'dd/mm/yyyy',
		autoclose: true
	});
	// $('.date').datepicker({
	// 	format: 'dd/mm/yyyy',
	// 	autoclose: true
	// });
		$('#inv_date').datepicker({
		format: 'dd/mm/yyyy',
		autoclose: true
	});
}]);
app.controller('service_master', ['$scope', '$rootScope', '$http', '$timeout', '$stateParams', '$state', function ($scope, $rootScope, $http, $timeout, $stateParams, $state) {
	module = 'service_master/';
	rootUrl = $rootScope.site_url;
	$http.get(rootUrl + module + "index").success(function (data) {
		if (data == 0) { window.location.assign('login.html'); }
		else if (data == 2) { messages("success", "Privilege not assigned.", 1000); window.location.assign('index.html'); }
	});

	function initServiceSelect2() {
		if (typeof $ === 'undefined' || !$.fn || !$.fn.select2) {
			return;
		}

		setTimeout(function () {
			$('.service-master-root select').each(function () {
				var $el = $(this);

				if ($el.data('select2')) {
					$el.select2('destroy');
				}

				if ($el.prop('disabled')) {
					return;
				}

				$el.select2({
					width: '100%',
					allowClear: false,
					minimumResultsForSearch: 0
				});
			});
		}, 0);
	}

	function bindServiceSelect2Events() {
		if (typeof $ === 'undefined') {
			return;
		}

		$(document)
			.off('shown.bs.modal.serviceMasterSelect2', '#serviceMasterModal')
			.on('shown.bs.modal.serviceMasterSelect2', '#serviceMasterModal', function () {
				initServiceSelect2();
			});
	}

	function resetServiceModalDefaults() {
		$scope.x = {
			name: '',
			description: '',
			term_condition: '',
			status: '1'
		};

		$scope.$applyAsync();
		initServiceSelect2();
	}

	$scope.pageno = 1;
	$scope.total_count = 0;
	$scope.itemsPerPage = '15';
	$scope.search_text = '';
	$scope.qx = { status: '' };
	$scope.x = {};
	$scope.datadb = [];
	$scope.service_modal_title = "Add Service";

	$scope.options = {
		height: 200,
		toolbar: [
			['font', ['bold', 'italic', 'underline']],
			['para', ['ol', 'paragraph']],
			['insert', ['link']],
			['view', ['codeview']]
		]
	};

	$scope.loader = function(pageno)
	{
		if(!pageno)
			pageno = 1;
		$scope.pageno = pageno;

		var params = ["per_page=" + encodeURIComponent($scope.itemsPerPage), "page=" + encodeURIComponent(pageno)];
		if($scope.search_text)
			params.push("search=" + encodeURIComponent($scope.search_text));
		if($scope.qx.status !== undefined && $scope.qx.status !== "")
			params.push("status=" + encodeURIComponent($scope.qx.status));

		$http.get(rootUrl + module + "view?" + params.join("&")).success(function(response){
			if(response && response.data !== undefined)
			{
				$scope.datadb = response.data || [];
				$scope.total_count = response.total_count || 0;
			}
			else
			{
				$scope.datadb = response || [];
				$scope.total_count = ($scope.datadb || []).length;
			}
			initServiceSelect2();
		});
	};

	$scope.apply_filters = function()
	{
		$scope.loader(1);
	};

	$scope.clear_filters = function()
	{
		$scope.search_text = '';
		$scope.qx = { status: '' };
		$scope.itemsPerPage = '15';
		$scope.$applyAsync();
		$('#serviceMasterSearchText').val('');
		$('#serviceMasterStatus').val('').trigger('change.select2');
		$('#serviceMasterPerPage').val('15').trigger('change.select2');
		initServiceSelect2();
		$scope.loader(1);
	};

	$scope.on_items_per_page_change = function()
	{
		$scope.loader(1);
	};

	$scope.update_call = function(y){
		$scope.service_modal_title = "Edit Service";
		$scope.x = angular.copy(y);
		$scope.x.status = String($scope.x.status);
		initServiceSelect2();
	}

	$scope.open_service_modal = function(mode, y)
	{
		if(mode=="edit" && y)
		{
			$scope.update_call(y);
		}
		else
		{
			$scope.service_modal_title = "Add Service";
			$scope.filter_new(false);
		}
		$('#serviceMasterModal').modal('show');
		initServiceSelect2();
		bindServiceSelect2Events();
	};

	$scope.filter_new = function(refreshList){
		resetServiceModalDefaults();
		$scope.service_modal_title = "Add Service";
		if(refreshList!==false)
			$scope.loader($scope.pageno || 1);
	}

	$scope.save_data = function () {
		$('#servicebtn').attr('disabled', true);
		$.ajax({
			type: "POST",
			url: rootUrl + module + "save",
			data: $("#serviceForm").serialize(),
			dataType: "json",
			beforeSend: function () {
				$('#loader1').css('display', 'inline');
			},
			success: function (data) {
				if (data.status == "success") {
					messages("success", "Success!", data.msg, 3000);
					$scope.loader($scope.pageno || 1);
					resetServiceModalDefaults();
					$scope.service_modal_title = "Add Service";
					$('#serviceMasterModal').modal('hide');
				}
				else if (data.status == "warning") {
					messages("warning", "Info!", data.msg, 3000);
				}
				else {
					messages("danger", "Warning!", data.msg, 6000);
				}
				$('#loader1').css('display', 'none');
				$('#servicebtn').attr('disabled', false);
			}
		});
	}

	$scope.delete_data = function(id)
	{
		if(confirm("Deleting Service may hamper your data associated with it."))
		{
			if(confirm("Are you Sure to DELETE ??"))
			{
				$http.get(rootUrl + module + "delete?service_id=" + id).success(function(data){
					if(data=="1")
					{
						messages("success", "Success!","Service Deleted Successfully", 3000);
					}
					else
					{
						messages("danger", "Warning!","Service not Deleted", 4000);
					}
					$scope.loader($scope.pageno || 1);
				});
			}
		}
	}

	$scope.pageChangeHandler = function(newPageNumber)
	{
		$scope.loader(newPageNumber);
	};

	$scope.loader(1);
	bindServiceSelect2Events();
	initServiceSelect2();

}]);
//blank line is required
app.controller('project_assign', ['$scope', '$rootScope', '$http', function ($scope, $rootScope, $http) {

	rootUrl = $rootScope.site_url;
	module = "project_assign/";
	$http.get(rootUrl + module + "/index").success(function (data) { if (data == 0) { window.location.assign('login.html'); } else if (data == 2) { messages("success", "Privilege not assigned.", 1000); window.location.assign('index.html'); } });

	function initProjectAssignSelect2() {
		if (typeof $ === 'undefined' || !$.fn || !$.fn.select2) {
			return;
		}

		setTimeout(function () {
			var $modal = $('#projectAssignModal');
			var isSelect2V4 = !!($.fn.select2 && $.fn.select2.amd);

			$('.project-assign-select2').each(function () {
				var $el = $(this);
				var inModal = $el.closest('#projectAssignModal').length > 0;

				if (!$el.is('select')) {
					return;
				}

				if ($el.data('select2')) {
					$el.select2('destroy');
				}

				var options = {
					width: '100%',
					allowClear: false,
					minimumResultsForSearch: 0
				};

				if ($el.prop('multiple')) {
					options.closeOnSelect = false;
				}

				if (isSelect2V4) {
					options.dropdownParent = inModal ? $modal : $(document.body);
				}

				$el.select2(options);
			});
		}, 0);
	}

	function allowSelect2TypingInsideModal() {
		if (typeof $ === 'undefined' || !$.fn || !$.fn.modal || !$.fn.modal.Constructor) {
			return;
		}

		var ModalConstructor = $.fn.modal.Constructor;
		if (ModalConstructor.prototype._projectAssignSelect2FocusPatched) {
			return;
		}

		ModalConstructor.prototype.enforceFocus = function () {
			var modalThis = this;
			$(document)
				.off('focusin.bs.modal')
				.on('focusin.bs.modal', function (e) {
					var $target = $(e.target);
					var isInsideModal = modalThis.$element[0] === e.target || modalThis.$element.has(e.target).length;
					var isSelect2Input =
						$target.closest('.select2-container, .select2-dropdown, .select2-drop, .select2-search').length > 0 ||
						$target.is('.select2-input, .select2-search__field');

					if (!isInsideModal && !isSelect2Input) {
						modalThis.$element.trigger('focus');
					}
				});
		};

		ModalConstructor.prototype._projectAssignSelect2FocusPatched = true;

		$(document).off('select2:open.projectAssign select2-open.projectAssign');
		$(document).on('select2:open.projectAssign select2-open.projectAssign', function () {
			setTimeout(function () {
				var $search = $('.select2-container-active .select2-input, .select2-drop-active .select2-input, .select2-container--open .select2-search__field');
				if ($search.length) {
					$search.focus();
				}
			}, 0);
		});
	}

	$scope.on_employee_change = function (emp_id) {
		$http.get(rootUrl + 'project_assign/get_cust?emp_id=' + emp_id).success(function (data) {
			$scope.projects = data || [];
			initProjectAssignSelect2();
		});
	}

	$scope.pageno = 1;
	$scope.total_count = 0;
	$scope.itemsPerPage = '15';
	$scope.qx = {};
	$scope.x = { emp_id: [] };
	$scope.datadb = [];
	$scope.project_assign_modal_title = "Add Project Assign";

	$scope.loader = function (pageno) {
		if (!pageno)
			pageno = 1;
		$scope.pageno = pageno;

		var params = [];

		if ($scope.qx.emp_id)
			params.push("emp_id=" + encodeURIComponent($scope.qx.emp_id));
		if ($scope.qx.project_id)
			params.push("project_id=" + encodeURIComponent($scope.qx.project_id));
		if ($scope.qx.status !== undefined && $scope.qx.status !== '')
			params.push("status=" + encodeURIComponent($scope.qx.status));

		var url = rootUrl + "project_assign/view/" + $scope.itemsPerPage + "/" + pageno;
		if (params.length)
			url += "?" + params.join("&");

		$http.get(url).success(function (response) {
			if (response && response.data !== undefined) {
				$scope.datadb = response.data;
				$scope.total_count = response.total_count || 0;
			}
			else {
				$scope.datadb = response || [];
				$scope.total_count = ($scope.datadb || []).length;
			}
			initProjectAssignSelect2();
		});
	};

	$scope.apply_filters = function () {
		$scope.loader(1);
	};

	$scope.clear_filters = function () {
		$scope.qx = {};
		$scope.itemsPerPage = '15';
		$scope.loader(1);
	};

	$scope.on_items_per_page_change = function () {
		$scope.loader(1);
	};

	$http.get(rootUrl + "hr_staff_details/view_staff?data=emp_id,staff_name&st=1").success(function (data) {
		$scope.employees = data;
		initProjectAssignSelect2();
	});

	$http.get(rootUrl + "customer/view?data=c_id,company_name&status=1").success(function (data) {
		$scope.customers = data;
		$scope.projects = data || [];
		initProjectAssignSelect2();
	});

	// $http.get(rootUrl + 'customer/view').success(function (data) {
	// 	$scope.projects = data || [];
	// 	initProjectAssignSelect2();
	// });

	function normalizeEmpIds(value) {
		if (angular.isArray(value)) {
			return value.map(function (v) { return String(v); });
		}
		if (value === undefined || value === null || value === '') {
			return [];
		}
		return [String(value)];
	}

	$scope.update_call = function (y) {
		$scope.x = angular.copy(y);
		$scope.x.emp_id = normalizeEmpIds($scope.x.emp_id);
	}

	$scope.open_project_assign_modal = function (mode, y) {
		if (mode == "edit" && y) {
			$scope.project_assign_modal_title = "Edit Project Assign";
			$scope.update_call(y);
		}
		else {
			$scope.project_assign_modal_title = "Add Project Assign";
			$scope.filter_new(false);
		}
		$('#projectAssignModal').modal('show');
		initProjectAssignSelect2();
	}

	$scope.filter_new = function (refreshList) {
		$scope.x = { emp_id: [] };
		initProjectAssignSelect2();
		if (refreshList !== false)
			$scope.loader($scope.pageno || 1);
	}

	$scope.save_data = function (x) {
		function normalizeResponse(data) {
			if (data === undefined || data === null) {
				return '';
			}
			return String(data).trim();
		}

		function isAssignedSuccessMessage(msg) {
			var lower = (msg || '').toLowerCase();
			return lower.indexOf('successfully assigned') !== -1 || lower.indexOf('remaining employees are assigned') !== -1;
		}

		$('#submitbtn').attr('disabled', true);
		$.ajax({
			type: "POST",
			url: rootUrl + "project_assign/save_data",
			data: $("#form1").serialize(),
			beforeSend: function () {
				$('#webprogress').css('display', 'inline');
			},
			success: function (data) {
				var response = normalizeResponse(data);

				if (response == "1") {
					messages("success", "Success!", "Project Assigned Successfully", 4000);
					$scope.loader($scope.pageno || 1);
					$scope.filter_new(false);
					$('#projectAssignModal').modal('hide');
				}
				else if (isAssignedSuccessMessage(response)) {
					messages("success", "Success!", response, 6000);
					$scope.loader($scope.pageno || 1);
					$scope.filter_new(false);
					$('#projectAssignModal').modal('hide');
				}
				else if (response == "0") {
					messages("warning", "Info!", "No Data Affected", 10000);
				}
				else {
					messages("warning", "Warning!", response, 10000);
				}
			},
			complete: function () {
				$('#webprogress').css('display', 'none');
				$('#submitbtn').attr('disabled', false);
			}
		});
	}

	$scope.delete_data = function (id) {
		if (confirm("Deleting Project may hamper your data associated with it. You will loose the data related with this Project.")) {
			if (confirm("Are you Sure to DELETE ??")) {
				$http.get(rootUrl + "project_assign/delete_data?id=" + id).success(function (data) {
					if (data == "1") {
						messages("success", "Success!", "Assigned Project Deleted Successfully", 4000);
						$scope.loader($scope.pageno || 1);
					}
					else {
						messages("danger", "Warning!", "Assigned Project not Deleted", 10000);
					}
				})
			}
		}
	}

	$scope.loader(1);
	allowSelect2TypingInsideModal();

	$('#projectAssignModal').on('shown.bs.modal', function () {
		// Select2 v3 appends search input outside modal; disable Bootstrap focus trap for this modal.
		$(document).off('focusin.bs.modal');
		initProjectAssignSelect2();
	});
	initProjectAssignSelect2();

}]);
app.directive('todoPriorityDragSource', [function () {
    return {
        restrict: 'A',
        link: function (scope, element, attrs) {
            element.attr('draggable', 'true');
            element.on('dragstart', function (event) {
                var bucket = attrs.todoPriorityBucket || '';
                var index = parseInt(attrs.todoPriorityDragSource, 10);
                scope.$applyAsync(function () {
                    scope.todoPriorityDragStart(bucket, index, event);
                });
            });
        }
    };
}]);

app.directive('todoPriorityDropTarget', [function () {
    return {
        restrict: 'A',
        link: function (scope, element, attrs) {
            element.on('dragover', function (event) {
                event.preventDefault();
            });
            element.on('drop', function (event) {
                event.preventDefault();
                var bucket = attrs.todoPriorityBucket || '';
                var index = parseInt(attrs.todoPriorityDropTarget, 10);
                scope.$applyAsync(function () {
                    scope.todoPriorityDrop(bucket, index, event);
                });
            });
        }
    };
}]);

app.controller('to_do_list', ['$scope', '$rootScope', '$http', '$timeout', function ($scope, $rootScope, $http, $timeout) {
    var rootUrl = $rootScope.site_url;
    var module = 'to_do_list/';
    var priorityBuckets = ['hot', 'warm', 'cold'];

    $scope.pageno = 1;
    $scope.total_count = 0;
    $scope.itemsPerPage = '20';
    $scope.datadb = [];
    $scope.qx = {
        q: '',
        emp_id: '',
        is_completed: '',
        status: '1'
    };
    $scope.x = {};
    $scope.employees = [];
    $scope.is_admin = false;
    $scope.current_emp_id = '';

    $scope.todoBoard = {
        hot: [],
        warm: [],
        cold: [],
        drag: {
            bucket: '',
            index: -1
        }
    };

    $http.get(rootUrl + module + 'index').success(function (data) {
        if (data == 0) {
            window.location.assign('login.html');
        } else if (data == 2) {
            messages('warning', 'Warning!', 'Privilege not assigned.', 2000);
            window.location.assign('index.html');
        }
    });

    function setAdminFlag() {
        var type = (localStorage.getItem('type') || '').toUpperCase();
        $scope.is_admin = (type === 'A' || type === 'ADMINISTRATOR');
        $scope.current_emp_id = String(localStorage.getItem('emp_id') || '');
    }

    function initTodoSelect2() {
        if (typeof $ === 'undefined' || !$.fn || !$.fn.select2) {
            return;
        }

        $timeout(function () {
            var $modal = $('#toDoModal');
            var isSelect2V4 = !!($.fn.select2 && $.fn.select2.amd);

            $('.todo-select2').each(function () {
                var $el = $(this);
                var inModal = $el.closest('#toDoModal').length > 0;

                if (!$el.is('select')) {
                    return;
                }

                // Clean up any stale inline Select2 containers before rebuilding.
                $el.nextAll('.select2, .select2-container').remove();
                $el.removeClass('select2-hidden-accessible');
                $el.removeAttr('data-select2-id');
                $el.removeAttr('tabindex');

                if ($el.data('select2')) {
                    $el.select2('destroy');
                }

                var options = {
                    width: '100%',
                    allowClear: false,
                    minimumResultsForSearch: 0
                };

                if (isSelect2V4) {
                    options.dropdownParent = inModal ? $modal : $(document.body);
                }

                $el.select2(options);
            });
        }, 0, false);
    }

    function allowSelect2TypingInsideModal() {
        if (typeof $ === 'undefined' || !$.fn || !$.fn.modal || !$.fn.modal.Constructor) {
            return;
        }

        var ModalConstructor = $.fn.modal.Constructor;
        if (ModalConstructor.prototype._todoSelect2FocusPatched) {
            return;
        }

        ModalConstructor.prototype.enforceFocus = function () {
            var modalThis = this;
            $(document)
                .off('focusin.bs.modal')
                .on('focusin.bs.modal', function (e) {
                    var $target = $(e.target);
                    var inModal = modalThis.$element[0] === e.target || modalThis.$element.has(e.target).length;
                    var inSelect2 = $target.closest('.select2-container, .select2-dropdown, .select2-drop, .select2-search').length > 0 ||
                        $target.is('.select2-input, .select2-search__field');

                    if (inModal || inSelect2) {
                        return;
                    }

                    modalThis.$element.trigger('focus');
                });
        };

        ModalConstructor.prototype._todoSelect2FocusPatched = true;

        $(document).off('select2:open.todo select2-open.todo');
        $(document).on('select2:open.todo select2-open.todo', function () {
            setTimeout(function () {
                var $search = $('.select2-container--open .select2-search__field, .select2-drop-active .select2-input');
                if ($search.length) {
                    $search.focus();
                }
            }, 0);
        });
    }

    function parseJsonResponse(data) {
        if (angular.isObject(data)) return data;
        if (!data) return {};
        try {
            return JSON.parse(data);
        } catch (e) {
            return { error: 1, msg: String(data) };
        }
    }

    function normalizePriorityType(row) {
        var raw = '';
        if (row && row.priority_type !== undefined && row.priority_type !== null) raw = row.priority_type;
        else if (row && row.priority_level !== undefined && row.priority_level !== null) raw = row.priority_level;
        else if (row && row.task_priority !== undefined && row.task_priority !== null) raw = row.task_priority;
        else if (row && row.priority_tag !== undefined && row.priority_tag !== null) raw = row.priority_tag;

        raw = String(raw || '').trim().toLowerCase();
        if (raw === 'hot' || raw === 'high' || raw === '1') return 'hot';
        if (raw === 'warm' || raw === 'medium' || raw === '2') return 'warm';
        if (raw === 'cold' || raw === 'low' || raw === '3') return 'cold';
        return 'warm';
    }

    function labelOfPriority(priorityType) {
        if (priorityType === 'hot') return 'Hot';
        if (priorityType === 'cold') return 'Cold';
        return 'Warm';
    }

    function boardCount(bucket) {
        return ($scope.todoBoard[bucket] || []).length;
    }

    function rebuildBoard(rows) {
        $scope.todoBoard.hot = [];
        $scope.todoBoard.warm = [];
        $scope.todoBoard.cold = [];

        angular.forEach(rows || [], function (row) {
            var p = normalizePriorityType(row);
            row._priority_type = p;
            row._priority_label = labelOfPriority(p);

            if (p === 'hot') $scope.todoBoard.hot.push(row);
            else if (p === 'cold') $scope.todoBoard.cold.push(row);
            else $scope.todoBoard.warm.push(row);
        });
    }

    function getFlattenedBoardState() {
        var orderedIds = [];
        var orderedTypes = [];
        var orderedRows = [];
        var rank = 0;

        angular.forEach(priorityBuckets, function (bucket) {
            angular.forEach($scope.todoBoard[bucket] || [], function (task) {
                rank += 1;
                orderedIds.push(task.todo_id);
                orderedTypes.push(bucket);
                orderedRows.push({
                    todo_id: task.todo_id,
                    priority_type: bucket,
                    priority_rank: rank
                });
            });
        });

        return {
            ordered_ids: orderedIds,
            ordered_types: orderedTypes,
            ordered_rows: orderedRows
        };
    }

    function syncDatadbFromBoard() {
        var flattened = [];
        var total = 0;

        angular.forEach(priorityBuckets, function (bucket) {
            total += ($scope.todoBoard[bucket] || []).length;
        });

        var rank = total;
        angular.forEach(priorityBuckets, function (bucket) {
            angular.forEach($scope.todoBoard[bucket] || [], function (task) {
                task._priority_type = bucket;
                task._priority_label = labelOfPriority(bucket);
                task.priority = rank;
                rank -= 1;
                flattened.push(task);
            });
        });

        $scope.datadb = flattened;
        $scope.total_count = flattened.length;
    }

    function persistPriorityType(task, targetBucket, done) {
        if (!task || !task.todo_id) {
            if (done) done(false);
            return;
        }

        $.ajax({
            type: 'POST',
            url: rootUrl + module + 'save_data',
            data: {
                todo_id: task.todo_id,
                title: task.title || '',
                priority_type: targetBucket,
                priority_level: targetBucket,
                task_priority: targetBucket
            },
            success: function (data) {
                var response = parseJsonResponse(data);
                if (response && String(response.error) === '0') {
                    if (done) done(true);
                    return;
                }
                if (done) done(false);
            },
            error: function () {
                if (done) done(false);
            }
        });
    }

    function persistBoardOrder(done) {
        var boardState = getFlattenedBoardState();
        $.ajax({
            type: 'POST',
            url: rootUrl + module + 'reorder',
            data: {
                'ordered_ids[]': boardState.ordered_ids,
                'ordered_priority_types[]': boardState.ordered_types,
                ordered_priority_map: JSON.stringify(boardState.ordered_rows)
            },
            success: function (data) {
                var response = parseJsonResponse(data);
                if (response && String(response.error) === '0') {
                    if (done) done(true);
                    return;
                }
                if (done) done(false);
            },
            error: function () {
                if (done) done(false);
            }
        });
    }

    function canDragTask(task) {
        if (!task) return false;
        if ($scope.is_admin) return true;
        var owner = String(task.emp_id || '');
        if (!owner) return true;
        return owner === String($scope.current_emp_id || '');
    }

    function readFilters() {
        var params = [];
        if ($scope.qx.q) params.push('q=' + encodeURIComponent($scope.qx.q));
        if ($scope.qx.emp_id) params.push('emp_id=' + encodeURIComponent($scope.qx.emp_id));
        if ($scope.qx.is_completed !== '') params.push('is_completed=' + encodeURIComponent($scope.qx.is_completed));
        if ($scope.qx.status !== '') params.push('status=' + encodeURIComponent($scope.qx.status));
        return params;
    }

    $scope.getPriorityType = function (row) {
        return normalizePriorityType(row);
    };

    $scope.getPriorityLabel = function (row) {
        return labelOfPriority(normalizePriorityType(row));
    };

    $scope.getPriorityClass = function (row) {
        return 'todo-priority--' + normalizePriorityType(row);
    };

    $scope.getBoardCount = function (bucket) {
        return boardCount(bucket);
    };

    $scope.todoPriorityDragStart = function (bucket, index, event) {
        var list = $scope.todoBoard[bucket] || [];
        var task = list[index];
        if (!task || !canDragTask(task)) {
            $scope.todoBoard.drag.bucket = '';
            $scope.todoBoard.drag.index = -1;
            if (event && event.preventDefault) event.preventDefault();
            return;
        }
        $scope.todoBoard.drag.bucket = bucket;
        $scope.todoBoard.drag.index = index;
    };

    $scope.todoPriorityDrop = function (targetBucket, targetIndex, event) {
        if (event && event.preventDefault) event.preventDefault();
        if (priorityBuckets.indexOf(targetBucket) === -1) return;

        var fromBucket = $scope.todoBoard.drag.bucket;
        var fromIndex = $scope.todoBoard.drag.index;
        if (!fromBucket || fromIndex < 0) return;

        var fromList = $scope.todoBoard[fromBucket] || [];
        if (!fromList.length || fromIndex >= fromList.length) return;

        var movedTask = fromList[fromIndex];
        if (!canDragTask(movedTask)) {
            messages('warning', 'Warning!', 'You can drag only tasks created by you.', 3000);
            return;
        }

        fromList.splice(fromIndex, 1);

        var toList = $scope.todoBoard[targetBucket] || [];
        var insertAt = targetIndex;
        if (isNaN(insertAt) || insertAt < 0 || insertAt > toList.length) {
            insertAt = toList.length;
        }
        toList.splice(insertAt, 0, movedTask);

        movedTask._priority_type = targetBucket;
        movedTask._priority_label = labelOfPriority(targetBucket);

        $scope.todoBoard.drag.bucket = '';
        $scope.todoBoard.drag.index = -1;

        // Keep UI responsive even if backend priority_type support is partial.
        syncDatadbFromBoard();

        persistBoardOrder(function (ordered) {
            if (!ordered) {
                messages('warning', 'Warning!', 'Unable to save task order.', 4000);
                return;
            }

            persistPriorityType(movedTask, targetBucket, function (ok) {
                if (!ok) {
                    messages('warning', 'Warning!', 'Priority type save not supported by API yet. UI moved, but refresh may restore old bucket.', 5000);
                    return;
                }
                $scope.$applyAsync(function () {
                    $scope.loader($scope.pageno || 1);
                });
            });
        });
    };

    $scope.loader = function (pageno) {
        if (!pageno) pageno = 1;
        $scope.pageno = pageno;

        var params = readFilters();
        var url = rootUrl + module + 'view/' + $scope.itemsPerPage + '/' + pageno;
        if (params.length) url += '?' + params.join('&');

        $http.get(url).success(function (response) {
            $scope.datadb = (response && response.data) ? response.data : [];
            $scope.total_count = (response && response.total_count) ? response.total_count : 0;
            if (response && response.is_admin !== undefined) {
                $scope.is_admin = String(response.is_admin) === '1';
            }
            rebuildBoard($scope.datadb);
            initTodoSelect2();
        });
    };

    $scope.apply_filters = function () {
        $scope.loader(1);
    };

    $scope.clear_filters = function () {
        $scope.qx = {
            q: '',
            emp_id: '',
            is_completed: '',
            status: '1'
        };
        $scope.loader(1);
        initTodoSelect2();
    };

    $scope.on_items_per_page_change = function () {
        $scope.loader(1);
    };

    $scope.open_modal = function (mode, row) {
        if (mode === 'edit' && row) {
            $scope.x = {
                todo_id: row.todo_id,
                title: row.title,
                priority_type: normalizePriorityType(row)
            };
        } else {
            $scope.x = {
                priority_type: 'warm'
            };
        }
        $('#toDoModal').modal('show');
        initTodoSelect2();
    };

    $scope.save_data = function () {
        if (!$scope.x.priority_type) {
            $scope.x.priority_type = 'warm';
        }
        $('#todoSubmitBtn').attr('disabled', true);

        $.ajax({
            type: 'POST',
            url: rootUrl + module + 'save_data',
            data: $('#toDoForm').serialize(),
            beforeSend: function () {
                $('#todoProgress').css('display', 'inline');
            },
            success: function (data) {
                var response = parseJsonResponse(data);

                if (response && String(response.error) === '0') {
                    messages('success', 'Success!', response.msg || 'Saved successfully.', 3000);
                    $('#toDoModal').modal('hide');
                    $scope.$applyAsync(function () {
                        $scope.loader($scope.pageno || 1);
                    });
                } else {
                    messages('warning', 'Warning!', (response && response.msg) ? response.msg : 'Unable to save task.', 6000);
                }
            },
            complete: function () {
                $('#todoProgress').css('display', 'none');
                $('#todoSubmitBtn').attr('disabled', false);
            }
        });
    };

    $scope.toggle_completed = function (row) {
        var nextValue = row.is_completed == '1' ? 0 : 1;
        $.ajax({
            type: 'POST',
            url: rootUrl + module + 'toggle_status',
            data: {
                todo_id: row.todo_id,
                is_completed: nextValue
            },
            success: function (data) {
                var response = parseJsonResponse(data);

                if (response && String(response.error) === '0') {
                    messages('success', 'Success!', 'Task updated.', 2000);
                    $scope.$applyAsync(function () {
                        $scope.loader($scope.pageno || 1);
                    });
                } else {
                    messages('warning', 'Warning!', (response && response.msg) ? response.msg : 'Unable to update task.', 5000);
                }
            }
        });
    };

    $scope.delete_data = function (todo_id) {
        if (confirm('Archive this task from active list?')) {
            $http.get(rootUrl + module + 'delete_data?id=' + todo_id).success(function (data) {
                if (data == '1') {
                    messages('success', 'Success!', 'Task archived successfully.', 3000);
                    $scope.loader($scope.pageno || 1);
                } else {
                    messages('warning', 'Warning!', 'Task could not be archived.', 5000);
                }
            });
        }
    };

    $http.get(rootUrl + 'hr_staff_details/view?data=emp_id,staff_name&st=1').success(function (data) {
        $scope.employees = data || [];
        initTodoSelect2();
    });

    allowSelect2TypingInsideModal();
    $('#toDoModal').on('shown.bs.modal', function () {
        $(document).off('focusin.bs.modal');
        initTodoSelect2();
    });
    setAdminFlag();
    $scope.loader(1);
}]);
app.controller('ftp_credentials', ['$scope', '$rootScope', '$http', '$timeout', function ($scope, $rootScope, $http, $timeout) {
    rootUrl = $rootScope.site_url;
    module = 'ftp_credentials/';

    $http.get(rootUrl + module + 'index').success(function (data) {
        if (data == 0) {
            window.location.assign('login.html');
        }
    });

    $scope.pageno = 1;
    $scope.total_count = 0;
    $scope.itemsPerPage = '15';
    $scope.qx = {};
    $scope.x = {
        server_type: 'our_server',
        status: '1'
    };
    $scope.assign = {
        fc_id: '',
        title: '',
        emp_ids: []
    };
    $scope.assignPrompt = {
        row: null
    };
    $scope.datadb = [];
    $scope.customers = [];
    $scope.employees = [];
    $scope.ftp_credentials_modal_title = 'Add FTP Credentials';

    $scope.showFtpPassword = false;
    $scope.showDbPassword = false;
    $scope.showServerLoginPassword = false;
    $scope.showWebmailPassword = false;
    $scope.showDomainPassword = false;
    $scope.visibleProtectedFields = {};
    $scope.revealedProtectedFields = {};
    $scope.loadingProtectedFields = {};

    function protectedFieldKey(fcId, field) {
        return String(fcId || '') + ':' + String(field || '');
    }

    function resetProtectedStates() {
        $scope.visibleProtectedFields = {};
        $scope.revealedProtectedFields = {};
        $scope.loadingProtectedFields = {};
    }

    function copyText(value, label) {
        if (!value) {
            return;
        }

        if (navigator.clipboard && navigator.clipboard.writeText) {
            navigator.clipboard.writeText(value).then(function () {
                messages('success', 'Copied!', label + ' copied to clipboard.', 2000);
            }, function () {
                messages('warning', 'Warning!', 'Could not copy to clipboard.', 3000);
            });
            return;
        }

        var $temp = $('<input>');
        $('body').append($temp);
        $temp.val(value).select();
        document.execCommand('copy');
        $temp.remove();
        messages('success', 'Copied!', label + ' copied to clipboard.', 2000);
    }

    function initFtpCredentialsSelect2() {
        if (typeof $ === 'undefined' || !$.fn || !$.fn.select2) {
            return;
        }

        $timeout(function () {
            var $mainModal = $('#ftpCredentialsModal');
            var $assignModal = $('#assignEmployeesModal');
            var isSelect2V4 = !!($.fn.select2 && $.fn.select2.amd);

            $('.ftp-credentials-select2').each(function () {
                var $el = $(this);
                var inMainModal = $el.closest('#ftpCredentialsModal').length > 0;
                var inAssignModal = $el.closest('#assignEmployeesModal').length > 0;

                if (!$el.is('select')) {
                    return;
                }

                if ($el.data('select2')) {
                    $el.select2('destroy');
                }

                var options = {
                    width: '100%',
                    allowClear: false,
                    minimumResultsForSearch: 0
                };

                if ($el.prop('multiple')) {
                    options.closeOnSelect = false;
                }

                if (isSelect2V4) {
                    if (inAssignModal) {
                        options.dropdownParent = $assignModal;
                    } else if (inMainModal) {
                        options.dropdownParent = $mainModal;
                    } else {
                        options.dropdownParent = $(document.body);
                    }
                }

                $el.select2(options);
            });
        }, 0);
    }

    function scheduleFtpCredentialsSelect2Init() {
        $timeout(function () {
            initFtpCredentialsSelect2();
            refreshSelect2Values();
        }, 0);
    }

    function allowSelect2TypingInsideModal() {
        if (typeof $ === 'undefined' || !$.fn || !$.fn.modal || !$.fn.modal.Constructor) {
            return;
        }

        var ModalConstructor = $.fn.modal.Constructor;
        if (ModalConstructor.prototype._ftpCredentialsSelect2FocusPatched) {
            return;
        }

        ModalConstructor.prototype.enforceFocus = function () {
            var modalThis = this;
            $(document)
                .off('focusin.bs.modal')
                .on('focusin.bs.modal', function (e) {
                    var $target = $(e.target);
                    var isInsideModal = modalThis.$element[0] === e.target || modalThis.$element.has(e.target).length;
                    var isSelect2Input =
                        $target.closest('.select2-container, .select2-dropdown, .select2-drop, .select2-search').length > 0 ||
                        $target.is('.select2-input, .select2-search__field');

                    if (!isInsideModal && !isSelect2Input) {
                        modalThis.$element.trigger('focus');
                    }
                });
        };

        ModalConstructor.prototype._ftpCredentialsSelect2FocusPatched = true;
    }

    function refreshSelect2Values() {
        if (typeof $ === 'undefined' || !$.fn || !$.fn.select2) {
            return;
        }

        $timeout(function () {
            if ($scope.x.customer_id) {
                $('select[name="customer_id"]').val(String($scope.x.customer_id)).trigger('change');
            }
            if ($scope.assign.emp_ids && $scope.assign.emp_ids.length) {
                $('select[name="emp_ids[]"]').val($scope.assign.emp_ids).trigger('change');
            }
        }, 100);
    }

    function getCustomerNameById(customerId) {
        var id = String(customerId || '');
        if (!id || !$scope.customers || !$scope.customers.length) {
            return '';
        }

        for (var i = 0; i < $scope.customers.length; i++) {
            if (String($scope.customers[i].c_id) === id) {
                return $scope.customers[i].company_name || '';
            }
        }

        return '';
    }

    function hasProtectedValue(row, field) {
        var flagValue = row ? row['has_' + field] : '';
        var maskedValue = row ? row[field + '_masked'] : '';
        var rawValue = row ? row[field] : '';

        if (!row) {
            return false;
        }

        if (String(flagValue) === '1') {
            return true;
        }

        if (maskedValue && maskedValue !== '-') {
            return true;
        }

        return rawValue !== undefined && rawValue !== null && String(rawValue).trim() !== '';
    }

    function revealProtectedField(row, field, label, onSuccess) {
        var key = protectedFieldKey(row.fc_id, field);
        if (!row || !row.fc_id || !hasProtectedValue(row, field)) {
            return;
        }

        if ($scope.revealedProtectedFields[key] !== undefined) {
            if (typeof onSuccess === 'function') {
                onSuccess($scope.revealedProtectedFields[key]);
            }
            return;
        }

        if ($scope.loadingProtectedFields[key]) {
            return;
        }

        $scope.loadingProtectedFields[key] = true;

        $.ajax({
            type: 'POST',
            url: rootUrl + module + 'reveal_field',
            data: {
                fc_id: row.fc_id,
                field: field
            },
            success: function (res) {
                $scope.$applyAsync(function () {
                    $scope.loadingProtectedFields[key] = false;
                    if (String(res.error) === '0') {
                        $scope.revealedProtectedFields[key] = res.value || '';
                        if (typeof onSuccess === 'function') {
                            onSuccess($scope.revealedProtectedFields[key]);
                        }
                    } else {
                        messages('danger', 'Warning!', res.msg || ('Unable to reveal ' + label + '.'), 5000);
                    }
                });
            },
            error: function () {
                $scope.$applyAsync(function () {
                    $scope.loadingProtectedFields[key] = false;
                    messages('danger', 'Warning!', 'Unable to reveal ' + label + '.', 5000);
                });
            }
        });
    }

    function populateEditForm(row) {
        $scope.x = angular.copy(row || {});
        $scope.x.customer_id = $scope.x.customer_id ? String($scope.x.customer_id) : '';
        $scope.x.status = ($scope.x.status == '0') ? '0' : '1';
        if (!$scope.x.server_type) {
            $scope.x.server_type = 'our_server';
        }
        $scope.showFtpPassword = false;
        $scope.showDbPassword = false;
        $scope.showServerLoginPassword = false;
        $scope.showWebmailPassword = false;
        $scope.showDomainPassword = false;
        refreshSelect2Values();
    }

    $scope.loader = function (pageno) {
        if (!pageno)
            pageno = 1;

        $scope.pageno = pageno;

        var params = [];
        if ($scope.qx.customer_id)
            params.push('customer_id=' + encodeURIComponent($scope.qx.customer_id));
        if ($scope.qx.server_type)
            params.push('server_type=' + encodeURIComponent($scope.qx.server_type));
        if ($scope.qx.status !== undefined && $scope.qx.status !== '')
            params.push('status=' + encodeURIComponent($scope.qx.status));

        var url = rootUrl + module + 'view/' + $scope.itemsPerPage + '/' + pageno;
        if (params.length)
            url += '?' + params.join('&');

        $http.get(url).success(function (response) {
            if (response && response.data !== undefined) {
                $scope.datadb = response.data || [];
                $scope.total_count = response.total_count || 0;
            } else {
                $scope.datadb = response || [];
                $scope.total_count = ($scope.datadb || []).length;
            }
            resetProtectedStates();
        });
    };

    $scope.load_customers = function () {
        $http.get(rootUrl + 'customer/view?data=c_id,company_name&status=1').success(function (data) {
            $scope.customers = data || [];
            scheduleFtpCredentialsSelect2Init();
        });
    };

    $scope.load_employees = function () {
        $http.get(rootUrl + 'hr_staff_details/view_staff?data=emp_id,staff_name&st=1').success(function (data) {
            $scope.employees = data || [];
            scheduleFtpCredentialsSelect2Init();
        });
    };

    $scope.apply_filters = function () {
        $scope.loader(1);
    };

    $scope.clear_filters = function () {
        $scope.qx = {};
        $scope.itemsPerPage = '15';
        $scope.loader(1);
        scheduleFtpCredentialsSelect2Init();
    };

    $scope.on_items_per_page_change = function () {
        $scope.loader(1);
    };

    $scope.getProtectedDisplay = function (row, field) {
        var key = protectedFieldKey(row.fc_id, field);
        if ($scope.visibleProtectedFields[key] && $scope.revealedProtectedFields[key] !== undefined) {
            return $scope.revealedProtectedFields[key] || '-';
        }

        if (hasProtectedValue(row, field)) {
            if (row && row[field + '_masked']) {
                return row[field + '_masked'];
            }
            return '********';
        }

        return '-';
    };

    $scope.toggleProtectedField = function (row, field, label) {
        var key = protectedFieldKey(row.fc_id, field);
        if (!hasProtectedValue(row, field)) {
            return;
        }

        if ($scope.visibleProtectedFields[key]) {
            $scope.visibleProtectedFields[key] = false;
            return;
        }

        revealProtectedField(row, field, label, function () {
            $scope.visibleProtectedFields[key] = true;
        });
    };

    $scope.copyProtectedField = function (row, field, label) {
        revealProtectedField(row, field, label, function (value) {
            copyText(value, label);
        });
    };

    $scope.filter_new = function () {
        $scope.x = {
            server_type: 'our_server',
            status: '1'
        };
        $scope.showFtpPassword = false;
        $scope.showDbPassword = false;
        $scope.showServerLoginPassword = false;
        $scope.showWebmailPassword = false;
        $scope.showDomainPassword = false;
        refreshSelect2Values();
    };

    $scope.update_call = function (row) {
        if (!row || !row.fc_id) {
            return;
        }

        $http.get(rootUrl + module + 'view_data?fc_id=' + row.fc_id + '&for_edit=1').success(function (res) {
            if (res && String(res.error) === '0' && res.data) {
                populateEditForm(res.data);
                scheduleFtpCredentialsSelect2Init();
            } else {
                messages('danger', 'Warning!', (res && res.msg) ? res.msg : 'Unable to load FTP credentials for editing.', 6000);
                $('#ftpCredentialsModal').modal('hide');
            }
        }).error(function () {
            messages('danger', 'Warning!', 'Unable to load FTP credentials for editing.', 6000);
            $('#ftpCredentialsModal').modal('hide');
        });
    };

    $scope.ask_assign_after_save = function (row) {
        $scope.assignPrompt.row = row || null;
        $('#assignAfterSaveModal').modal('show');
    };

    $scope.confirm_assign_after_save = function () {
        var row = $scope.assignPrompt.row;
        $scope.assignPrompt.row = null;
        $('#assignAfterSaveModal').modal('hide');

        if (row && row.fc_id) {
            $scope.open_assign_modal(row);
        }
    };

    $scope.cancel_assign_after_save = function () {
        $scope.assignPrompt.row = null;
        $('#assignAfterSaveModal').modal('hide');
    };

    $scope.open_ftp_credentials_modal = function (mode, row) {
        if (mode === 'edit' && row) {
            $scope.ftp_credentials_modal_title = 'Edit FTP Credentials';
            $('#ftpCredentialsModal').modal('show');
            $scope.update_call(row);
        } else {
            $scope.ftp_credentials_modal_title = 'Add FTP Credentials';
            $scope.filter_new();
            $('#ftpCredentialsModal').modal('show');
        }

        scheduleFtpCredentialsSelect2Init();
    };

    $scope.open_assign_modal = function (row) {
        $scope.assign.fc_id = row.fc_id;
        $scope.assign.title = (row.company_name || '') + ' - ' + (row.server_type === 'our_server' ? 'Our Server' : 'Customer Server');
        $scope.assign.emp_ids = [];

        $http.get(rootUrl + module + 'assigned_employees?fc_id=' + row.fc_id).success(function (res) {
            var ids = (res && res.data) ? res.data : [];
            $scope.assign.emp_ids = ids.map(function (id) { return String(id); });
            $('#assignEmployeesModal').modal('show');
            scheduleFtpCredentialsSelect2Init();
        });
    };

    $scope.save_assigned_employees = function () {
        $('#submitbtnAssign').attr('disabled', true);

        $.ajax({
            type: 'POST',
            url: rootUrl + module + 'save_assigned_employees',
            data: $('#assignEmployeesForm').serialize(),
            beforeSend: function () {
                $('#assignLoader').css('display', 'inline');
            },
            success: function (res) {
                if (String(res.error) === '0') {
                    messages('success', 'Success!', res.msg || 'Access updated successfully.', 3000);
                    $scope.$applyAsync(function () {
                        $('#assignEmployeesModal').modal('hide');
                        $scope.loader($scope.pageno || 1);
                    });
                } else {
                    messages('danger', 'Warning!', res.msg || 'Unable to update employee access.', 6000);
                }
                $('#assignLoader').css('display', 'none');
                $('#submitbtnAssign').attr('disabled', false);
            },
            error: function () {
                messages('danger', 'Warning!', 'Unable to update employee access.', 6000);
                $('#assignLoader').css('display', 'none');
                $('#submitbtnAssign').attr('disabled', false);
            }
        });
    };

    $scope.save_data = function () {
        $('#submitbtnFtp').attr('disabled', true);

        $.ajax({
            type: 'POST',
            url: rootUrl + module + 'save_data',
            data: $('#ftpCredentialsForm').serialize(),
            beforeSend: function () {
                $('#ftpLoader').css('display', 'inline');
            },
            success: function (data) {
                if (String(data.error) === '0') {
                    var savedFcId = parseInt(data.fc_id, 10) || 0;
                    var assignRow = {
                        fc_id: savedFcId,
                        company_name: getCustomerNameById($scope.x.customer_id),
                        server_type: $scope.x.server_type || 'our_server'
                    };

                    messages('success', 'Success!', data.msg || 'Saved Successfully', 3000);
                    $scope.$applyAsync(function () {
                        $('#ftpCredentialsModal').modal('hide');
                        $scope.loader($scope.pageno || 1);
                        $scope.filter_new();
                        scheduleFtpCredentialsSelect2Init();

                        if (savedFcId > 0) {
                            $scope.ask_assign_after_save(assignRow);
                        }
                    });
                } else {
                    messages('danger', 'Warning!', data.msg || 'Unable to save FTP credentials.', 6000);
                }

                $('#ftpLoader').css('display', 'none');
                $('#submitbtnFtp').attr('disabled', false);
            },
            error: function () {
                messages('danger', 'Warning!', 'Unable to save FTP credentials.', 6000);
                $('#ftpLoader').css('display', 'none');
                $('#submitbtnFtp').attr('disabled', false);
            }
        });
    };

    $scope.delete_data = function (id) {
        if (confirm('Deleting FTP Credentials may affect related operations.')) {
            if (confirm('Are you Sure to DELETE ??')) {
                $http.get(rootUrl + module + 'delete_data?id=' + id).success(function (data) {
                    if (String(data) === '1') {
                        messages('success', 'Success!', 'FTP Credentials Deleted Successfully', 3000);
                    } else {
                        messages('danger', 'Warning!', 'FTP Credentials not Deleted', 4000);
                    }
                    $scope.loader($scope.pageno || 1);
                });
            }
        }
    };

    $scope.copyPassword = function (value, label) {
        copyText(value, label);
    };

    $scope.loader(1);
    $scope.load_customers();
    $scope.load_employees();
    allowSelect2TypingInsideModal();

    $('#ftpCredentialsModal, #assignEmployeesModal, #assignAfterSaveModal').on('shown.bs.modal', function () {
        $(document).off('focusin.bs.modal');
        scheduleFtpCredentialsSelect2Init();
    });

    $scope.$watchCollection('customers', function () {
        scheduleFtpCredentialsSelect2Init();
    });

    $scope.$watchCollection('employees', function () {
        scheduleFtpCredentialsSelect2Init();
    });

    $scope.$watch('x.customer_id', function () {
        refreshSelect2Values();
    });

    $scope.$watchCollection('assign.emp_ids', function () {
        refreshSelect2Values();
    });

    scheduleFtpCredentialsSelect2Init();
}]);
app.controller('server_credentials', ['$scope', '$rootScope', '$http', function ($scope, $rootScope, $http) {
    rootUrl = $rootScope.site_url;
    module = 'server_credentials/';

    $http.get(rootUrl + module + 'index').success(function (data) {
        if (data == 0) {
            window.location.assign('login.html');
        }
    });

    $scope.pageno = 1;
    $scope.total_count = 0;
    $scope.itemsPerPage = '15';
    $scope.qx = {};
    $scope.datadb = [];
    $scope.customers = [];
    $scope.visibleFtp = {};
    $scope.visibleDb = {};
    $scope.visibleWebmail = {};
    $scope.visibleDomain = {};

    function initSelect2() {
        if (typeof $ === 'undefined' || !$.fn || !$.fn.select2) {
            return;
        }
        setTimeout(function () {
            $('.server-credentials-select2').each(function () {
                var $el = $(this);
                if (!$el.is('select')) {
                    return;
                }
                if ($el.data('select2')) {
                    $el.select2('destroy');
                }
                $el.select2({
                    width: '100%',
                    allowClear: false,
                    minimumResultsForSearch: 0
                });
            });
        }, 0);
    }

    $scope.loader = function (pageno) {
        if (!pageno)
            pageno = 1;

        $scope.pageno = pageno;

        var params = [];
        if ($scope.qx.customer_id)
            params.push('customer_id=' + encodeURIComponent($scope.qx.customer_id));
        if ($scope.qx.server_type)
            params.push('server_type=' + encodeURIComponent($scope.qx.server_type));

        var url = rootUrl + module + 'view/' + $scope.itemsPerPage + '/' + pageno;
        if (params.length)
            url += '?' + params.join('&');

        $http.get(url).success(function (response) {
            if (response && response.data !== undefined) {
                $scope.datadb = response.data || [];
                $scope.total_count = response.total_count || 0;
            } else {
                $scope.datadb = response || [];
                $scope.total_count = ($scope.datadb || []).length;
            }
        });
    };

    $scope.load_customers = function () {
        $http.get(rootUrl + 'customer/view?data=c_id,company_name&status=1').success(function (data) {
            $scope.customers = data || [];
            initSelect2();
        });
    };

    $scope.apply_filters = function () {
        $scope.loader(1);
    };

    $scope.clear_filters = function () {
        $scope.qx = {};
        $scope.itemsPerPage = '15';
        $scope.loader(1);
        initSelect2();
    };

    $scope.on_items_per_page_change = function () {
        $scope.loader(1);
    };

    $scope.toggleFtpPassword = function (id) {
        $scope.visibleFtp[id] = !$scope.visibleFtp[id];
    };

    $scope.toggleDbPassword = function (id) {
        $scope.visibleDb[id] = !$scope.visibleDb[id];
    };

    $scope.toggleWebmailPassword = function (id) {
        $scope.visibleWebmail[id] = !$scope.visibleWebmail[id];
    };

    $scope.toggleDomainPassword = function (id) {
        $scope.visibleDomain[id] = !$scope.visibleDomain[id];
    };

    $scope.copyValue = function (value, label) {
        if (!value) {
            return;
        }

        if (navigator.clipboard && navigator.clipboard.writeText) {
            navigator.clipboard.writeText(value).then(function () {
                messages('success', 'Copied!', label + ' copied to clipboard.', 2000);
            }, function () {
                messages('warning', 'Warning!', 'Could not copy to clipboard.', 3000);
            });
            return;
        }

        var $temp = $('<input>');
        $('body').append($temp);
        $temp.val(value).select();
        document.execCommand('copy');
        $temp.remove();
        messages('success', 'Copied!', label + ' copied to clipboard.', 2000);
    };

    $scope.loader(1);
    $scope.load_customers();
    initSelect2();
}]);
app.controller('employee_rating', ['$scope', '$rootScope', '$http', function ($scope, $rootScope, $http) {
	rootUrl = $rootScope.site_url;
	module = "employee_rating/";

	function initEmployeeRatingSelect2() {
		if (typeof $ === 'undefined' || !$.fn || !$.fn.select2) {
			return;
		}

		setTimeout(function () {
			var $modal = $('#employeeRatingModal');
			var isSelect2V4 = !!($.fn.select2 && $.fn.select2.amd);

			$('.employee-rating-select2').each(function () {
				var $el = $(this);
				var inModal = $el.closest('#employeeRatingModal').length > 0;

				if (!$el.is('select')) {
					return;
				}

				if ($el.data('select2')) {
					$el.select2('destroy');
				}

				var options = {
					width: '100%',
					allowClear: false,
					minimumResultsForSearch: 0
				};

				if (isSelect2V4) {
					options.dropdownParent = inModal ? $modal : $(document.body);
				}

				$el.select2(options);
			});
		}, 0);
	}

	function allowSelect2TypingInsideEmployeeRatingModal() {
		if (typeof $ === 'undefined' || !$.fn || !$.fn.modal || !$.fn.modal.Constructor) {
			return;
		}

		var ModalConstructor = $.fn.modal.Constructor;
		if (ModalConstructor.prototype._employeeRatingSelect2FocusPatched) {
			return;
		}

		ModalConstructor.prototype.enforceFocus = function () {
			var modalThis = this;
			$(document)
				.off('focusin.bs.modal')
				.on('focusin.bs.modal', function (e) {
					var $target = $(e.target);
					var isInsideModal = modalThis.$element[0] === e.target || modalThis.$element.has(e.target).length;
					var isSelect2Input =
						$target.closest('.select2-container, .select2-dropdown, .select2-drop, .select2-search').length > 0 ||
						$target.is('.select2-input, .select2-search__field');

					if (!isInsideModal && !isSelect2Input) {
						modalThis.$element.trigger('focus');
					}
				});
		};

		ModalConstructor.prototype._employeeRatingSelect2FocusPatched = true;

		$(document).off('select2:open.employeeRating select2-open.employeeRating');
		$(document).on('select2:open.employeeRating select2-open.employeeRating', function () {
			setTimeout(function () {
				var $search = $('.select2-container-active .select2-input, .select2-drop-active .select2-input, .select2-container--open .select2-search__field');
				if ($search.length) {
					$search.focus();
				}
			}, 0);
		});
	}

	$scope.x = {};
	$scope.employee_rows = [];
	$scope.saved_ratings = [];
	$scope.isSaving = false;
	$scope.isEditMode = false;
	$scope.editing_rating_id = null;
	$scope.currentUserType = String(localStorage.getItem("type") || "").trim();
	$scope.currentUserEmpId = String(localStorage.getItem("emp_id") || "").trim();
	$scope.currentUserName = String(localStorage.getItem("staff_name") || localStorage.getItem("username") || "").trim();
	$scope.isHrRatingUser = false;
	$scope.isGlobalRatingUser = false;
	$scope.isGradeAUser = false;
	$scope.isProjectRatingAdmin = false;
	$scope.ratingMode = 'project';
	$scope.showSavedRatingsTable = false;
	$scope.filtered_ratings = [];
	$scope.ratingScale = [];
	for (var rs = 1; rs <= 10; rs++) {
		$scope.ratingScale.push(rs);
	}
	$scope.default_filters = function () {
		return {
			emp_id: '',
			rated_by: '',
			period: 'all',
			month: '',
			rating_min: '',
			rating_max: ''
		};
	};
	$scope.filters = $scope.default_filters();
	$scope.totalRatingsAll = 0;
	$scope.filterOptions = {
		employees: [],
		raters: []
	};
	$scope.ratingAnalytics = {
		totalRatings: 0,
		avgRating: 0,
		lowCount: 0,
		midCount: 0,
		highCount: 0,
		bucketDistribution: [],
		scoreDistribution: [],
		topEmployees: [],
		allEmployees: []
	};

	$scope.init_distribution = function () {
		var dist = [];
		for (var i = 10; i >= 1; i--) {
			dist.push({
				score: i,
				count: 0,
				percent: 0
			});
		}
		$scope.ratingAnalytics.scoreDistribution = dist;
	}

	$scope.init_distribution();

	$scope.is_type_a_user = function () {
		var type = String($scope.currentUserType || "").trim().toUpperCase();
		return type === "A" || type === "ADMINISTRATOR";
	};

	$scope.first_non_empty_value = function (values) {
		var i;
		for (i = 0; i < values.length; i++) {
			if (values[i] !== undefined && values[i] !== null && String(values[i]).trim() !== "") {
				return values[i];
			}
		}
		return "";
	};

	$scope.get_rater_display_name = function (row) {
		if (!row) {
			return "-";
		}

		return String($scope.first_non_empty_value([
			row.rated_by_name,
			row.rater_name,
			row.created_by_name,
			row.created_by_staff_name,
			row.rated_by_username,
			row.created_by_username,
			row.user_name,
			row.username,
			row.rated_by,
			row.created_by
		]) || "-");
	};

	$scope.row_is_editable_by_current_user = function (row) {
		if (!row) {
			return false;
		}

		if ($scope.is_type_a_user()) {
			return true;
		}

		var rowRaterEmpId = String($scope.first_non_empty_value([
			row.rated_by_emp_id,
			row.created_by_emp_id,
			row.rater_emp_id,
			row.rating_by_emp_id,
			row.updated_by_emp_id,
			row.user_emp_id,
			row.rated_by_user_id,
			row.created_by_user_id,
			row.rated_by,
			row.created_by,
			row.user_id,
			row.emp_rated_by,
			row.created_emp_id
		]) || "").trim();
		if (rowRaterEmpId && $scope.currentUserEmpId && rowRaterEmpId === $scope.currentUserEmpId) {
			return true;
		}

		var rowRaterUsername = String($scope.first_non_empty_value([
			row.rated_by_username,
			row.created_by_username,
			row.rater_username,
			row.user_name,
			row.username
		]) || "").trim().toLowerCase();
		var currentUsername = String($scope.currentUserName || "").trim().toLowerCase();
		if (rowRaterUsername && currentUsername && rowRaterUsername === currentUsername) {
			return true;
		}

		var rowRaterName = String($scope.get_rater_display_name(row) || "").trim().toLowerCase();
		if (rowRaterName && currentUsername && rowRaterName === currentUsername) {
			return true;
		}

		return false;
	};

	$scope.decorate_saved_rating_row = function (row) {
		if (!row) {
			return row;
		}

		row.rated_by_display = $scope.get_rater_display_name(row);
		row.can_edit = $scope.row_is_editable_by_current_user(row);
		var ratingNum = parseFloat(row.rating);
		if (!isNaN(ratingNum)) {
			row.rating = ratingNum;
		}
		return row;
	};

	$http.get(rootUrl + module + "index").success(function (data) {
		if (data == 0) {
			window.location.assign('login.html');
		} else if (data == 2) {
			messages("success", "Privilege not assigned.", 1000);
			window.location.assign('index.html');
		}
	});

	$scope.init = function () {
		$http.get(rootUrl + module + "rating_context").success(function (ctx) {
			$scope.isHrRatingUser = !!(ctx && (ctx.is_hr_rating_user == 1 || ctx.is_hr_rating_user == '1'));
			$scope.isGlobalRatingUser = !!(ctx && (ctx.is_global_rating_user == 1 || ctx.is_global_rating_user == '1'));
			$scope.isGradeAUser = !!(ctx && (ctx.is_grade_a_user == 1 || ctx.is_grade_a_user == '1'));
			$scope.isProjectRatingAdmin = false;
			$scope.ratingMode = 'global';
			if ($scope.isGlobalRatingUser) {
				$scope.load_active_employees();
			}
		}).error(function () {
			$scope.isHrRatingUser = false;
			$scope.isGlobalRatingUser = false;
			$scope.isGradeAUser = false;
			$scope.isProjectRatingAdmin = false;
			$scope.ratingMode = 'global';
		});
		$scope.load_filter_options();
		$scope.load_rating_data();
	}

	$scope.prepare_employee_rows = function (rows) {
		$scope.employee_rows = rows || [];
		angular.forEach($scope.employee_rows, function (row) {
			var parsedRating = Math.round(parseFloat(row.rating) * 10) / 10;
			row.rating = (parsedRating >= 1 && parsedRating <= 10) ? parsedRating : 1;
			row.rating_comment = row.rating_comment ? row.rating_comment : "";
			row.not_required = '1';
			row.is_rated_today = (row.is_rated_today == 1 || row.is_rated_today == '1') ? '1' : '0';
		});
	}

	$scope.load_active_employees = function () {
		$http.get(rootUrl + module + "active_employees").success(function (data) {
			$scope.prepare_employee_rows(data || []);
		});
	}

	$scope.load_filter_options = function () {
		$http.get(rootUrl + module + "filter_options").success(function (data) {
			$scope.filterOptions.employees = (data && angular.isArray(data.employees)) ? data.employees.map(function (e) {
				return {
					id: String(e.emp_id),
					name: ((e.staff_name && String(e.staff_name).trim() !== '') ? e.staff_name : '-') + ' (' + e.emp_id + ')'
				};
			}) : [];
			$scope.filterOptions.raters = (data && angular.isArray(data.raters)) ? data.raters.map(function (r) {
				return {
					id: String(r.creator_id),
					name: (r.rater_name && String(r.rater_name).trim() !== '') ? r.rater_name : '-'
				};
			}) : [];
			initEmployeeRatingSelect2();
		});
	}

	$scope.build_filter_query = function () {
		var f = $scope.filters || $scope.default_filters();
		var params = [];
		var add = function (key, value) {
			if (value !== undefined && value !== null && String(value) !== '') {
				params.push(key + '=' + encodeURIComponent(value));
			}
		};
		var range = $scope.compute_date_range();
		add('emp_id', f.emp_id);
		add('rated_by', f.rated_by);
		add('date_from', range.from);
		add('date_to', range.to);
		add('rating_min', f.rating_min);
		add('rating_max', f.rating_max);
		return params.length ? ('?' + params.join('&')) : '';
	}

	$scope.has_active_filters = function () {
		var f = $scope.filters || {};
		return !!(f.emp_id || f.rated_by || (f.period && f.period !== 'all') || f.month ||
			(f.rating_min !== '' && f.rating_min !== null && f.rating_min !== undefined) ||
			(f.rating_max !== '' && f.rating_max !== null && f.rating_max !== undefined));
	}

	$scope.load_rating_data = function () {
		var query = $scope.build_filter_query();
		$http.get(rootUrl + module + "rating_data" + query).success(function (data) {
			var rows = angular.isArray(data) ? data.map(function (row) {
				return $scope.decorate_saved_rating_row(row);
			}) : [];
			$scope.saved_ratings = rows;
			$scope.filtered_ratings = rows;
			if (!$scope.has_active_filters()) {
				$scope.totalRatingsAll = rows.length;
			}
			$scope.build_rating_analytics();
		});
	}

	$scope.month_bounds = function (year, monthIndex) {
		var mm = ('0' + (monthIndex + 1)).slice(-2);
		var lastDay = new Date(year, monthIndex + 1, 0).getDate();
		return {
			from: year + '-' + mm + '-01',
			to: year + '-' + mm + '-' + ('0' + lastDay).slice(-2)
		};
	}

	// Turn the Period dropdown / Month-Year picker into a yyyy-mm-dd range
	// for the existing date_from/date_to server filter.
	$scope.compute_date_range = function () {
		var f = $scope.filters || {};

		// A specific month/year selection takes precedence over the period.
		if (f.month) {
			var parts = String(f.month).split('/'); // mm/yyyy
			if (parts.length === 2) {
				var mm = parseInt(parts[0], 10);
				var yyyy = parseInt(parts[1], 10);
				if (!isNaN(mm) && !isNaN(yyyy) && mm >= 1 && mm <= 12) {
					return $scope.month_bounds(yyyy, mm - 1);
				}
			}
		}

		var now = new Date();
		if (f.period === 'this_month') {
			return $scope.month_bounds(now.getFullYear(), now.getMonth());
		}
		if (f.period === 'last_month') {
			var prev = new Date(now.getFullYear(), now.getMonth() - 1, 1);
			return $scope.month_bounds(prev.getFullYear(), prev.getMonth());
		}
		return { from: '', to: '' };
	}

	$scope.apply_filters = function () {
		$scope.load_rating_data();
	}

	$scope.clear_filters = function () {
		$scope.filters = $scope.default_filters();
		$scope.load_rating_data();
	}

	$scope.build_rating_analytics = function () {
		var rows = $scope.filtered_ratings || [];
		var total = 0;
		var sum = 0;
		var low = 0;
		var mid = 0;
		var high = 0;
		var scoreMap = {};
		var scoreEmployeeMap = {};
		var employeeMap = {};
		var i;

		for (i = 1; i <= 10; i++) {
			scoreMap[i] = 0;
			scoreEmployeeMap[i] = {};
		}

		var map_keys = function (obj) {
			var out = [];
			angular.forEach(obj, function (val, key) {
				if (val) {
					out.push(key);
				}
			});
			out.sort();
			return out;
		};

		var compact_names = function (arr, maxCount) {
			if (!arr || !arr.length) {
				return "-";
			}
			if (arr.length <= maxCount) {
				return arr.join(", ");
			}
			return arr.slice(0, maxCount).join(", ") + " +" + (arr.length - maxCount) + " more";
		};

		angular.forEach(rows, function (r) {
			var rating = parseFloat(r.rating);
			if (isNaN(rating) || rating < 1 || rating > 10) {
				return;
			}

			var scoreBucket = Math.round(rating);
			if (scoreBucket < 1) scoreBucket = 1;
			if (scoreBucket > 10) scoreBucket = 10;

			total += 1;
			sum += rating;
			scoreMap[scoreBucket] += 1;
			var rowStaffName = (r.staff_name && String(r.staff_name).trim() !== "") ? r.staff_name : "-";
			scoreEmployeeMap[scoreBucket][rowStaffName] = true;

			if (rating < 5) {
				low += 1;
			} else if (rating < 8) {
				mid += 1;
			} else {
				high += 1;
			}

			var empKey = String(r.emp_id || '');
			if (!employeeMap[empKey]) {
				employeeMap[empKey] = {
					emp_id: r.emp_id,
					staff_name: rowStaffName,
					sum: 0,
					count: 0
				};
			}
			employeeMap[empKey].sum += rating;
			employeeMap[empKey].count += 1;
		});

		var avg = total > 0 ? (sum / total) : 0;
		$scope.ratingAnalytics.totalRatings = total;
		$scope.ratingAnalytics.avgRating = avg;
		$scope.ratingAnalytics.lowCount = low;
		$scope.ratingAnalytics.midCount = mid;
		$scope.ratingAnalytics.highCount = high;
		$scope.ratingAnalytics.bucketDistribution = [
			{
				key: 'high',
				label: 'High (8-10)',
				count: high,
				percent: total > 0 ? Math.round((high / total) * 100) : 0
			},
			{
				key: 'mid',
				label: 'Mid (5-7)',
				count: mid,
				percent: total > 0 ? Math.round((mid / total) * 100) : 0
			},
			{
				key: 'low',
				label: 'Low (1-4)',
				count: low,
				percent: total > 0 ? Math.round((low / total) * 100) : 0
			}
		];

		var distribution = [];
		for (i = 10; i >= 1; i--) {
			var count = scoreMap[i] || 0;
			var scoreEmployeeList = map_keys(scoreEmployeeMap[i]);
			distribution.push({
				score: i,
				count: count,
				percent: total > 0 ? Math.round((count / total) * 100) : 0,
				employeeNamesText: compact_names(scoreEmployeeList, 5)
			});
		}
		$scope.ratingAnalytics.scoreDistribution = distribution;

		var empList = [];
		angular.forEach(employeeMap, function (e) {
			var avgEmp = e.count > 0 ? (e.sum / e.count) : 0;
			empList.push({
				emp_id: e.emp_id,
				staff_name: e.staff_name,
				avg: avgEmp,
				count: e.count,
				avgPercent: Math.round((avgEmp / 10) * 100)
			});
		});
		empList.sort(function (a, b) {
			if (b.avg === a.avg) {
				return b.count - a.count;
			}
			return b.avg - a.avg;
		});
		$scope.ratingAnalytics.allEmployees = empList;
		$scope.ratingAnalytics.topEmployees = empList.slice(0, 10);
	}

	$scope.open_employee_avg_modal = function () {
		$('#employeeAverageModal').modal('show');
	}

	$scope.close_employee_avg_modal = function () {
		$('#employeeAverageModal').modal('hide');
	}

	$scope.score_distribution_tooltip = function (s) {
		if (!s) {
			return "";
		}
		return "Score: " + s.score +
			"\nCount: " + s.count +
			"\nEmployees: " + (s.employeeNamesText || "-");
	}

	$scope.employee_chart_tooltip = function (e) {
		if (!e) {
			return "";
		}
		return "Employee: " + (e.staff_name || "-") +
			"\nAvg Rating: " + (Math.round((e.avg || 0) * 100) / 100) + " / 10" +
			"\nRatings Count: " + (e.count || 0);
	}

	$scope.open_add_rating_modal = function (mode, row) {
		if (mode === 'edit' && row) {
			if (!row.can_edit && !$scope.is_type_a_user()) {
				messages("warning", "Warning!", "You cannot edit this rating row.", 3000);
				return;
			}
			$scope.edit_rating(row);
			return;
		}
		$scope.reset_form();
		$('#employeeRatingModal').modal('show');
		initEmployeeRatingSelect2();
	}

	$scope.close_add_rating_modal = function () {
		$('#employeeRatingModal').modal('hide');
	}

	$scope.toggle_saved_ratings = function () {
		$scope.showSavedRatingsTable = !$scope.showSavedRatingsTable;
	}

	$scope.rating_row_class = function (row) {
		var rating = parseFloat((row && row.rating) ? row.rating : 0);
		if (rating >= 8) return 'employee-rating-row-high';
		if (rating >= 5) return 'employee-rating-row-mid';
		return 'employee-rating-row-low';
	}

	$scope.toggle_not_required = function (row) {
		if (row.not_required == '1') {
			row.rating = "";
		} else if (!row.rating) {
			row.rating = 1;
		}
	}

	$scope.edit_rating = function (row) {
		if (!row || !row.rating_id || (!row.can_edit && !$scope.is_type_a_user())) {
			return;
		}
		$scope.showSavedRatingsTable = false;
		$('#employeeRatingModal').modal('show');
		$scope.isEditMode = true;
		$scope.editing_rating_id = row.rating_id;

		$http.get(rootUrl + module + "rating_row?rating_id=" + row.rating_id).success(function (res) {
			if (!res || !res.rating_id) {
				$scope.employee_rows = [];
				messages("danger", "Warning!", "Selected rating row not found.", 4000);
				return;
			}
			var parsedRating = Math.round(parseFloat(res.rating) * 10) / 10;
			$scope.employee_rows = [{
				rating_id: res.rating_id,
				emp_id: res.emp_id,
				staff_name: res.staff_name,
				rated_by_display: $scope.get_rater_display_name(res),
				rating: (parsedRating >= 1 && parsedRating <= 10) ? parsedRating : 1,
				rating_comment: res.rating_comment ? res.rating_comment : "",
				not_required: '0'
			}];
		});
	}

	$scope.reset_form = function () {
		if ($scope.isGlobalRatingUser) {
			$scope.load_active_employees();
		} else {
			$scope.employee_rows = [];
		}
		$scope.isEditMode = false;
		$scope.editing_rating_id = null;
		initEmployeeRatingSelect2();
	}

	$scope.cancel_edit = function () {
		$scope.reset_form();
	}

	$scope.save_ratings = function () {
		if (!$scope.isGlobalRatingUser && !$scope.isEditMode) {
			messages("warning", "Warning!", "You do not have permission to rate employees.", 3000);
			return;
		}
		$scope.isSaving = true;
		if ($scope.isEditMode) {
			var row = $scope.employee_rows.length ? $scope.employee_rows[0] : null;
			if (!row || !$scope.editing_rating_id) {
				$scope.isSaving = false;
				messages("warning", "Warning!", "No selected row found for update.", 3000);
				return;
			}

			$http({
				method: "POST",
				url: rootUrl + module + "update_rating",
				data: $.param({
					rating_id: $scope.editing_rating_id,
					rating: row.rating,
					rating_comment: row.rating_comment ? row.rating_comment : "",
					not_required: row.not_required
				}),
				headers: {
					'Content-Type': 'application/x-www-form-urlencoded; charset=UTF-8'
				}
			}).success(function (res) {
				if (res && res.error == 0) {
					messages("success", "Success!", "Employee rating updated successfully.", 3000);
					$scope.reset_form();
					$scope.load_rating_data();
					$scope.close_add_rating_modal();
				} else {
					messages("danger", "Warning!", (res && res.msg) ? res.msg : "Employee rating could not be updated.", 4000);
				}
			}).error(function () {
				messages("danger", "Warning!", "Employee rating could not be updated.", 4000);
			}).finally(function () {
				$scope.isSaving = false;
			});
			return;
		}

		var payload = {
			rows: angular.toJson($scope.employee_rows)
		};

		$http({
			method: "POST",
			url: rootUrl + module + "save_ratings",
			data: $.param(payload),
			headers: {
				'Content-Type': 'application/x-www-form-urlencoded; charset=UTF-8'
			}
		}).success(function (res) {
			if (res && res.error == 0) {
				messages("success", "Success!", (res && res.msg) ? res.msg : "Employee ratings saved successfully.", 3000);
				$scope.reset_form();
				$scope.load_rating_data();
				$scope.close_add_rating_modal();
			} else {
				messages("danger", "Warning!", (res && res.msg) ? res.msg : "Employee ratings could not be saved.", 4000);
			}
		}).error(function () {
			messages("danger", "Warning!", "Employee ratings could not be saved.", 4000);
		}).finally(function () {
			$scope.isSaving = false;
		});
	}

	$scope.init();
	allowSelect2TypingInsideEmployeeRatingModal();

	$('#employeeRatingModal').on('shown.bs.modal', function () {
		$(document).off('focusin.bs.modal');
		initEmployeeRatingSelect2();
	});
	initEmployeeRatingSelect2();
}]);
app.controller('custom_notification', ['$scope', '$rootScope', '$http', '$timeout', function ($scope, $rootScope, $http, $timeout) {
    rootUrl = $rootScope.site_url;
    module = 'custom_notification/';

    $http.get(rootUrl + module + 'index').success(function (data) {
        if (data == 0) {
            window.location.assign('login.html');
        }
    });

    $scope.notificationTypes = [
        { value: 'notification', label: 'General Notification' },
        { value: 'new_announcement', label: 'New Announcement' },
        { value: 'task', label: 'Task Update' },
        { value: 'new_task_assigned', label: 'New Task Assigned' },
        { value: 'attendance_mark', label: 'Attendance Mark' },
        { value: 'checked_in_late', label: 'Checked In Late' },
        { value: 'start_timer', label: 'Start Timer' },
        { value: 'stop_timer', label: 'Stop Timer' },
        { value: 'meeting_starts_soon', label: 'Meeting Starts Soon' },
        { value: 'leave_request_rec', label: 'Leave Request Received' },
        { value: 'leave_approved', label: 'Leave Approved' },
        { value: 'leave_reject', label: 'Leave Not Approved' },
        { value: 'salary_credited', label: 'Salary Credited' },
        { value: 'teammate_birthday', label: 'Teammate Birthday' },
        { value: 'birthday_wish', label: 'Birthday Wish' }
    ];

    $scope.datadb = [];
    $scope.employees = [];
    $scope.custom_notification_modal_title = 'Add Notification';
    $scope.x = {
        notification_id: '',
        name: '',
        type: 'notification',
        title: '',
        description: '',
        route: '',
        image: '',
        emp_ids: []
    };
    $scope.sending = {};
    $scope.sendTo = {
        notification_id: '',
        name: '',
        emp_ids: []
    };
    $scope.sendingTo = false;

    // Stored under hrpayrollui's own assets/uploads/, so a path relative to
    // this app's own root resolves correctly without needing img_url.
    $scope.build_image_url = function (image_name) {
        if (!image_name) return '';
        return 'assets/uploads/custom_notification/' + image_name;
    };

    $scope.build_image_thumb_url = function (image_name) {
        if (!image_name) return '';
        return 'assets/uploads/custom_notification/thumb/' + image_name;
    };

    function refreshSelect2Values() {
        if (typeof $ === 'undefined' || !$.fn || !$.fn.select2) {
            return;
        }

        $timeout(function () {
            if ($scope.x.type) {
                $('select[name="type"]').val(String($scope.x.type)).trigger('change');
            }
            if ($scope.x.emp_ids && $scope.x.emp_ids.length) {
                $('select[name="emp_ids[]"]').val($scope.x.emp_ids).trigger('change');
            }
            $('select[name="send_to_emp_ids[]"]').val($scope.sendTo.emp_ids || []).trigger('change');
        }, 100);
    }

    function initCustomNotificationSelect2() {
        if (typeof $ === 'undefined' || !$.fn || !$.fn.select2) {
            return;
        }

        $timeout(function () {
            var isSelect2V4 = !!($.fn.select2 && $.fn.select2.amd);

            $('.custom-notification-select2').each(function () {
                var $el = $(this);
                if (!$el.is('select')) {
                    return;
                }

                if ($el.data('select2')) {
                    $el.select2('destroy');
                }

                var options = {
                    width: '100%',
                    allowClear: false,
                    minimumResultsForSearch: 0
                };

                if ($el.prop('multiple')) {
                    options.closeOnSelect = false;
                }

                if (isSelect2V4) {
                    var $ownModal = $el.closest('.modal');
                    options.dropdownParent = $ownModal.length ? $ownModal : $(document.body);
                }

                $el.select2(options);
            });
        }, 0);
    }

    function scheduleCustomNotificationSelect2Init() {
        $timeout(function () {
            initCustomNotificationSelect2();
            refreshSelect2Values();
        }, 0);
    }

    function allowSelect2TypingInsideModal() {
        if (typeof $ === 'undefined' || !$.fn || !$.fn.modal || !$.fn.modal.Constructor) {
            return;
        }

        var ModalConstructor = $.fn.modal.Constructor;
        if (ModalConstructor.prototype._customNotificationSelect2FocusPatched) {
            return;
        }

        ModalConstructor.prototype.enforceFocus = function () {
            var modalThis = this;
            $(document)
                .off('focusin.bs.modal')
                .on('focusin.bs.modal', function (e) {
                    var $target = $(e.target);
                    var isInsideModal = modalThis.$element[0] === e.target || modalThis.$element.has(e.target).length;
                    var isSelect2Input =
                        $target.closest('.select2-container, .select2-dropdown, .select2-drop, .select2-search').length > 0 ||
                        $target.is('.select2-input, .select2-search__field');

                    if (!isInsideModal && !isSelect2Input) {
                        modalThis.$element.trigger('focus');
                    }
                });
        };

        ModalConstructor.prototype._customNotificationSelect2FocusPatched = true;
    }

    $scope.getTypeLabel = function (value) {
        for (var i = 0; i < $scope.notificationTypes.length; i++) {
            if ($scope.notificationTypes[i].value === value) {
                return $scope.notificationTypes[i].label;
            }
        }
        return value;
    };

    $scope.loader = function () {
        $http.get(rootUrl + module + 'view_data').success(function (data) {
            $scope.datadb = data || [];
        });
    };

    $scope.load_employees = function () {
        $http.get(rootUrl + 'hr_staff_details/view_staff?data=emp_id,staff_name&st=1').success(function (data) {
            $scope.employees = data || [];
            scheduleCustomNotificationSelect2Init();
        });
    };

    $scope.filter_new = function () {
        $scope.x = {
            notification_id: '',
            name: '',
            type: 'notification',
            title: '',
            description: '',
            route: '',
            image: '',
            emp_ids: []
        };
        var $form = $('#customNotificationForm');
        if ($form.length) {
            $form[0].reset();
        }
        refreshSelect2Values();
    };

    $scope.isAllSelected = function () {
        return $scope.employees.length > 0 && $scope.x.emp_ids.length === $scope.employees.length;
    };

    $scope.toggleSelectAll = function () {
        if ($scope.isAllSelected()) {
            $scope.x.emp_ids = [];
        } else {
            $scope.x.emp_ids = $scope.employees.map(function (e) { return String(e.emp_id); });
        }
        refreshSelect2Values();
    };

    $scope.open_custom_notification_modal = function (mode, row) {
        if (mode === 'edit' && row) {
            $scope.custom_notification_modal_title = 'Edit Notification';
            $('#customNotificationModal').modal('show');
            $scope.update_call(row);
        } else {
            $scope.custom_notification_modal_title = 'Add Notification';
            $scope.filter_new();
            $('#customNotificationModal').modal('show');
        }

        scheduleCustomNotificationSelect2Init();
    };

    $scope.update_call = function (row) {
        if (!row || !row.notification_id) {
            return;
        }

        $http.get(rootUrl + module + 'view_data?id=' + row.notification_id + '&for_edit=1').success(function (res) {
            if (res && String(res.error) === '0' && res.data) {
                var $form = $('#customNotificationForm');
                if ($form.length) {
                    $form[0].reset();
                }
                $scope.x = {
                    notification_id: res.data.notification_id,
                    name: res.data.name,
                    type: res.data.type,
                    title: res.data.title,
                    description: res.data.description,
                    route: res.data.route,
                    image: res.data.image,
                    emp_ids: (res.data.emp_ids || []).map(function (id) { return String(id); })
                };
                scheduleCustomNotificationSelect2Init();
            } else {
                messages('danger', 'Warning!', (res && res.msg) ? res.msg : 'Unable to load notification for editing.', 6000);
                $('#customNotificationModal').modal('hide');
            }
        }).error(function () {
            messages('danger', 'Warning!', 'Unable to load notification for editing.', 6000);
            $('#customNotificationModal').modal('hide');
        });
    };

    $scope.save_data = function () {
        $('#submitbtnCustomNotification').attr('disabled', true);

        $('#customNotificationForm').ajaxForm({
            type: 'POST',
            url: rootUrl + module + 'save_data',
            beforeSend: function () {
                $('#customNotificationLoader').css('display', 'inline');
            },
            success: function (data) {
                $scope.$applyAsync(function () {
                    if (String(data.error) === '0') {
                        messages('success', 'Success!', data.msg || 'Saved Successfully', 3000);
                        $('#customNotificationModal').modal('hide');
                        $scope.loader();
                        $scope.filter_new();
                    } else {
                        messages('danger', 'Warning!', data.msg || 'Unable to save notification.', 6000);
                    }

                    $('#customNotificationLoader').css('display', 'none');
                    $('#submitbtnCustomNotification').attr('disabled', false);
                });
            },
            error: function () {
                $scope.$applyAsync(function () {
                    messages('danger', 'Warning!', 'Unable to save notification.', 6000);
                    $('#customNotificationLoader').css('display', 'none');
                    $('#submitbtnCustomNotification').attr('disabled', false);
                });
            }
        }).submit();
    };

    $scope.send_notification = function (row) {
        if (!row || !row.notification_id) {
            return;
        }

        if (!confirm('Send "' + row.name + '" to ' + (row.recipient_count || 0) + ' saved employee(s) now?')) {
            return;
        }

        $scope.sending[row.notification_id] = true;

        $.ajax({
            type: 'POST',
            url: rootUrl + module + 'send_notification',
            data: { notification_id: row.notification_id },
            success: function (data) {
                $scope.$applyAsync(function () {
                    $scope.sending[row.notification_id] = false;
                    if (String(data.error) === '0') {
                        messages('success', 'Sent!', data.msg || 'Notification sent successfully.', 4000);
                    } else {
                        messages('danger', 'Warning!', data.msg || 'Unable to send notification.', 6000);
                    }
                });
            },
            error: function () {
                $scope.$applyAsync(function () {
                    $scope.sending[row.notification_id] = false;
                    messages('danger', 'Warning!', 'Unable to send notification.', 6000);
                });
            }
        });
    };

    $scope.open_send_to_modal = function (row) {
        if (!row || !row.notification_id) {
            return;
        }

        $scope.sendTo = {
            notification_id: row.notification_id,
            name: row.name,
            emp_ids: []
        };
        $('#sendToModal').modal('show');
        scheduleCustomNotificationSelect2Init();
    };

    $scope.isSendToAllSelected = function () {
        return $scope.employees.length > 0 && $scope.sendTo.emp_ids.length === $scope.employees.length;
    };

    $scope.toggleSendToSelectAll = function () {
        if ($scope.isSendToAllSelected()) {
            $scope.sendTo.emp_ids = [];
        } else {
            $scope.sendTo.emp_ids = $scope.employees.map(function (e) { return String(e.emp_id); });
        }
        refreshSelect2Values();
    };

    $scope.getEmpName = function (empId) {
        for (var i = 0; i < $scope.employees.length; i++) {
            if (String($scope.employees[i].emp_id) === String(empId)) {
                return $scope.employees[i].staff_name;
            }
        }
        return empId;
    };

    // Submitted as multipart form data (not a plain $.ajax data object) so
    // each employee's optional emp_images[<emp_id>] file input - rendered
    // one per selected employee - actually reaches the server alongside the
    // recipient list. Anyone without their own file falls back server-side
    // to the notification's saved image.
    $scope.submit_send_to = function () {
        if (!$scope.sendTo.emp_ids.length) {
            return;
        }

        $scope.sendingTo = true;
        $('#submitbtnSendTo').attr('disabled', true);

        $('#sendToForm').ajaxForm({
            type: 'POST',
            url: rootUrl + module + 'send_notification_to',
            data: {
                notification_id: $scope.sendTo.notification_id,
                emp_ids: $scope.sendTo.emp_ids
            },
            beforeSend: function () {
                $('#sendToLoader').css('display', 'inline');
            },
            success: function (data) {
                $scope.$applyAsync(function () {
                    $scope.sendingTo = false;
                    $('#sendToLoader').css('display', 'none');
                    $('#submitbtnSendTo').attr('disabled', false);
                    if (String(data.error) === '0') {
                        messages('success', 'Sent!', data.msg || 'Notification sent successfully.', 4000);
                        $('#sendToModal').modal('hide');
                    } else {
                        messages('danger', 'Warning!', data.msg || 'Unable to send notification.', 6000);
                    }
                });
            },
            error: function () {
                $scope.$applyAsync(function () {
                    $scope.sendingTo = false;
                    $('#sendToLoader').css('display', 'none');
                    $('#submitbtnSendTo').attr('disabled', false);
                    messages('danger', 'Warning!', 'Unable to send notification.', 6000);
                });
            }
        }).submit();
    };

    $scope.delete_data = function (id) {
        if (confirm('Are you Sure to DELETE this saved notification ??')) {
            $http.get(rootUrl + module + 'delete_data?id=' + id).success(function (data) {
                if (String(data) === '1') {
                    messages('success', 'Success!', 'Notification Deleted Successfully', 3000);
                } else {
                    messages('danger', 'Warning!', 'Notification not Deleted', 4000);
                }
                $scope.loader();
            });
        }
    };

    $scope.loader();
    $scope.load_employees();
    allowSelect2TypingInsideModal();

    $('#customNotificationModal, #sendToModal').on('shown.bs.modal', function () {
        $(document).off('focusin.bs.modal');
        scheduleCustomNotificationSelect2Init();
    });

    $scope.$watchCollection('employees', function () {
        scheduleCustomNotificationSelect2Init();
    });

    $scope.$watchCollection('x.emp_ids', function () {
        refreshSelect2Values();
    });

    $scope.$watchCollection('sendTo.emp_ids', function () {
        refreshSelect2Values();
    });

    $scope.$watch('x.type', function () {
        refreshSelect2Values();
    });

    scheduleCustomNotificationSelect2Init();
}]);
app.controller('notifications', ['$scope', '$rootScope', '$http', function ($scope, $rootScope, $http) {
    var rootUrl = $rootScope.site_url;
    var module = 'emp_notifications/';

    var typeIcons = {
        notification: 'fa-bell',
        task: 'fa-tasks',
        attendance_mark: 'fa-clock-o',
        start_timer: 'fa-play-circle',
        stop_timer: 'fa-stop-circle',
        teammate_birthday: 'fa-birthday-cake',
        birthday_wish: 'fa-birthday-cake',
        leave_request_rec: 'fa-file-text-o',
        new_task_assigned: 'fa-user-plus',
        meeting_starts_soon: 'fa-calendar',
        new_announcement: 'fa-bullhorn',
        salary_credited: 'fa-money',
        checked_in_late: 'fa-exclamation-triangle',
        leave_reject: 'fa-times-circle',
        leave_approved: 'fa-check-circle'
    };

    $scope.datadb = [];
    $scope.loading = false;
    $scope.loadingMore = false;
    $scope.total = 0;
    $scope.unread = 0;
    $scope.lightboxImage = null;

    var pageSize = 20;

    $scope.openImage = function (imageUrl, $event) {
        if ($event) {
            $event.stopPropagation();
        }
        $scope.lightboxImage = imageUrl;
    };

    $scope.closeImage = function () {
        $scope.lightboxImage = null;
    };

    $scope.iconFor = function (type) {
        return typeIcons[type] || 'fa-bell';
    };

    $scope.cleanText = function (text) {
        return (text === null || text === undefined ? '' : String(text)).replace(/\s+/g, ' ').trim();
    };

    $scope.timeAgo = function (created_at) {
        if (!created_at) {
            return '';
        }
        if (typeof moment === 'function') {
            return moment(created_at).fromNow();
        }
        return created_at;
    };

    $scope.resolveRoute = function (route) {
        route = (route || '').toString().trim();
        // Empty or the generic "dashboard" fallback means no real destination
        // was configured for this notification - stay on the Notifications
        // page instead of sending the user to an unrelated dashboard.
        if (!route || route.toLowerCase() === 'dashboard') {
            return '#/notifications';
        }
        if (route.indexOf('#/') === 0) {
            return route;
        }
        if (route.indexOf('/') === 0) {
            return '#' + route;
        }
        return '#/' + route;
    };

    $scope.hasMore = function () {
        return $scope.datadb.length < $scope.total;
    };

    function fetchPage() {
        return $http.get(rootUrl + module + 'list_data?limit=' + pageSize + '&offset=' + $scope.datadb.length);
    }

    $scope.load = function () {
        $scope.loading = true;
        fetchPage().success(function (res) {
            $scope.loading = false;
            if (res && String(res.error) === '0') {
                $scope.datadb = res.data || [];
                $scope.total = res.total || 0;
                $scope.unread = res.unread || 0;
                if (typeof window.refreshNotificationBell === 'function') {
                    window.refreshNotificationBell();
                }
            }
        }).error(function () {
            $scope.loading = false;
        });
    };

    $scope.loadMore = function () {
        if ($scope.loadingMore || !$scope.hasMore()) {
            return;
        }
        $scope.loadingMore = true;
        fetchPage().success(function (res) {
            $scope.loadingMore = false;
            if (res && String(res.error) === '0') {
                $scope.datadb = $scope.datadb.concat(res.data || []);
                $scope.total = res.total || 0;
                $scope.unread = res.unread || 0;
            }
        }).error(function () {
            $scope.loadingMore = false;
        });
    };

    $scope.open = function (row) {
        if (String(row.is_read) !== '1') {
            row.is_read = 1;
            if ($scope.unread > 0) {
                $scope.unread--;
            }
            $http.get(rootUrl + module + 'mark_read?id=' + row.emp_notification_id);
            if (typeof window.refreshNotificationBell === 'function') {
                window.refreshNotificationBell();
            }
        }
        window.location.hash = $scope.resolveRoute(row.route);
    };

    $scope.markAllRead = function () {
        if (!$scope.unread) {
            return;
        }
        $http.get(rootUrl + module + 'mark_all_read').success(function () {
            angular.forEach($scope.datadb, function (row) {
                row.is_read = 1;
            });
            $scope.unread = 0;
            if (typeof window.refreshNotificationBell === 'function') {
                window.refreshNotificationBell();
            }
        });
    };

    $scope.load();
}]);


