/*******************************************************************************
 * This file is part of GECAMed.
 * 
 * GECAMed is free software: you can redistribute it and/or modify
 * it under the terms of the GNU Lesser General Public License (L-GPL) as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 * 
 * GECAMed is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU Lesser General Public License for more details.
 * 
 * You should have received a copy of the GNU Lesser General Public License (L-GPL)
 * along with GECAMed.  If not, see <http://www.gnu.org/licenses/>.
 * 
 * GECAMed is Copyrighted by the Centre de Recherche Public Henri Tudor (http://www.tudor.lu)
 * (c) CRP Henri Tudor, Luxembourg, 2008
 *******************************************************************************/
package lu.tudor.santec.gecamed.billing.ejb.session.beans;

import java.math.BigInteger;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Date;
import java.util.HashSet;
import java.util.Iterator;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Set;

import javax.ejb.EJB;
import javax.ejb.Remote;
import javax.ejb.Stateless;
import javax.persistence.NoResultException;
import javax.persistence.Query;

import lu.tudor.santec.gecamed.billing.ejb.entity.beans.Act;
import lu.tudor.santec.gecamed.billing.ejb.entity.beans.Invoice;
import lu.tudor.santec.gecamed.billing.ejb.entity.beans.Memo;
import lu.tudor.santec.gecamed.billing.ejb.entity.beans.Suffix;
import lu.tudor.santec.gecamed.billing.ejb.entity.beans.TrashedAct;
import lu.tudor.santec.gecamed.billing.ejb.entity.beans.TrashedInvoice;
import lu.tudor.santec.gecamed.billing.ejb.session.interfaces.InvoiceInterface;
import lu.tudor.santec.gecamed.billing.utils.BillingAdminSettings;
import lu.tudor.santec.gecamed.billing.utils.InvoiceWorkflow;
import lu.tudor.santec.gecamed.core.ejb.session.beans.GECAMedSessionBean;
import lu.tudor.santec.gecamed.core.utils.querybuilder.HibernateQueryFactory;
import lu.tudor.santec.gecamed.core.utils.querybuilder.WhereClause;
import lu.tudor.santec.gecamed.office.ejb.entity.beans.Physician;
import lu.tudor.santec.gecamed.patient.ejb.entity.beans.Hospitalisation;
import lu.tudor.santec.gecamed.patient.ejb.entity.beans.HospitalisationClass;
import lu.tudor.santec.gecamed.patient.ejb.entity.beans.Insurance;
import lu.tudor.santec.gecamed.patient.ejb.entity.beans.Passage;
import lu.tudor.santec.gecamed.patient.ejb.entity.beans.Patient;
import lu.tudor.santec.gecamed.usermanagement.ejb.session.interfaces.LoginInterface;

import org.apache.log4j.Logger;
import org.hibernate.Hibernate;

//***************************************************************************
//* Interface Definition and Members                                        *
//***************************************************************************

@Stateless
@Remote (InvoiceInterface.class)
public class InvoiceBean extends GECAMedSessionBean implements InvoiceInterface
	{
//	private static final long serialVersionUID = 1L;

	private static Logger m_Logger = Logger.getLogger (InvoiceBean.class.getName());

	@EJB
	private LoginInterface	m_LoginBean;

	
//	@PersistenceContext (unitName="gecam")
//	EntityManager m_EntityManager;
	
//***************************************************************************
//* Class Primitives                                                        *
//***************************************************************************
//---------------------------------------------------------------------------
/**
 * deletes the specified collection of orphaned Acts. Orphaned Acts are acts
 * which are no longer contained in invoice's set of acts but which do still
 * exist in the database. Entity manager somehow 'forgets' to delete them when
 * invoice is saved.
 * @param p_OrphanedActs specifies the orphaned Acts to be explicitely
 * deleted from database.
 */
//---------------------------------------------------------------------------

private void deleteOrphanedActs (Collection <Act> p_OrphanedActs) throws Exception
	{
	Iterator <Act> 	l_ActIterator;
	Act				l_Act;

	if (p_OrphanedActs ==  null) return;

	l_ActIterator = p_OrphanedActs.iterator();
	while (l_ActIterator.hasNext())
		{
		l_Act = l_ActIterator.next();
		m_EntityManager.remove(l_Act);
		}
	}

//---------------------------------------------------------------------------
/**
 * deletes the specified collection of orphaned Memos. Orphaned Memos are memos
 * which are no longer contained in invoice's set of memos but which do still
 * exist in the database. Entity manager somehow 'forgets' to delete them when
 * invoice is saved.
 * @param p_OrphanedMemos specifies the orphaned Memos to be explicitely
 * deleted from database.
 */
//---------------------------------------------------------------------------

private void deleteOrphanedMemos (Collection <Memo> p_OrphanedMemos) throws Exception
	{
	Iterator <Memo> 	l_MemoIterator;
	Memo				l_Memo;

	if (p_OrphanedMemos ==  null) return;

	l_MemoIterator = p_OrphanedMemos.iterator();
	while (l_MemoIterator.hasNext())
		{
		l_Memo = l_MemoIterator.next();
		m_EntityManager.remove(l_Memo);
		}
	}

//---------------------------------------------------------------------------
/**
 * fetches the hospital passage corresponding to the specified accession number.
 * Accession Number are unique identifiers in hospitals to identify medical
 * acts.
 * @param p_AccessionNumber specifies the accession number of the hospital
 * passage to fetch
 * @return the passage with the specified accession number if available,
 * <code>null</code> otherwise.
 */
//---------------------------------------------------------------------------

private Passage getPassageByAccessionNumber (String p_AccessionNumber) throws Exception
	{
	Passage l_Passage;
	
	if (p_AccessionNumber == null) return null;
	
	try	{
		l_Passage = (Passage) m_EntityManager.createNamedQuery(Passage.FIND_PASSAGE_BY_ACCESSION_NUMBER) 
											 .setParameter("accessionNumber", p_AccessionNumber)
											 .setMaxResults(1)
											 .getSingleResult();
		}
	catch (NoResultException p_Exception)
		{
		l_Passage = null;
		}
		
	return l_Passage;
	}

//---------------------------------------------------------------------------
//***************************************************************************
//* Class Body                                                              *
//***************************************************************************
//---------------------------------------------------------------------------
/**
 * The initializeFactors method fetches all Suffixes and initializes static
 * factors of Act Bean on server side.
 * @see Act.setFactors () method.
 */
//---------------------------------------------------------------------------

@SuppressWarnings("unchecked")
public void initializeFactors ()
	{
	Collection <Suffix>	l_Suffixes;

	try	{
		l_Suffixes = m_EntityManager.createNamedQuery (Suffix.c_AllSuffixes).getResultList();
		Act.setAllSuffixes (l_Suffixes);
		}
	catch (Exception p_Exception)
		{
		m_Logger.error("Failed to initialize Factors!",p_Exception);
		}
	}

//---------------------------------------------------------------------------

public Suffix getSuffix (char letter)
	{
	return (Suffix) m_EntityManager.createNamedQuery(Suffix.c_SuffixByLetter)
			.setParameter("letter", String.valueOf(letter))
			.getSingleResult();
	}

//---------------------------------------------------------------------------
/**
 * returns all third party paying insurances defined in the database.
 * @return collection containing all third party paying insurances defined in 
 * the database.
 */
//---------------------------------------------------------------------------

@SuppressWarnings("unchecked")
public Collection <Insurance> getThirdPartyPayingInsurances() throws Exception
	{
	Collection<Insurance> 	l_Insurances;

	l_Insurances = m_EntityManager.createNamedQuery (Insurance.c_ThirdPartyPayingInsurances).getResultList();

    return l_Insurances;
	}

//---------------------------------------------------------------------------
//***************************************************************************
//* Main Invoice Methods                                                    *
//***************************************************************************
//---------------------------------------------------------------------------
/**
 * Returns a very specific invoice identified by the specified Id.
 * @param p_ID specifies the Id of the invoice to get from database.
 * @return The invoice object with the specified Id, <code>null</code> if
 * no such invoice exists.
 */
//---------------------------------------------------------------------------

//@RolesAllowed("gecam")

public Invoice getInvoiceByID (Integer p_ID) throws Exception
	{
	Invoice l_Invoice;

	try	{
		l_Invoice = (Invoice) m_EntityManager.createNamedQuery(Invoice.c_InvoiceById)
								   			 .setParameter (Invoice.c_IdParameter,p_ID)
								   			 .getSingleResult();
		l_Invoice.updateFirstClassRequired();
		}
	catch (NoResultException p_Exception)
		{
		l_Invoice = null;
		}

	return l_Invoice;
	}

//---------------------------------------------------------------------------
/**
 * Returns the Act with the specified accession number.
 * @param p_AccessionNumber specifies the accession number of Act to fetch.
 * @return The Act with the specified accession number if available, <code>
 * null</code> otherwise.
 */
//---------------------------------------------------------------------------

//@RolesAllowed("gecam")
public Act getActByAccessionNumber (String p_AccessionNumber) throws Exception
	{
	Act	l_Act = null;

	if (p_AccessionNumber == null) return l_Act;

	try	{
		l_Act = (Act) m_EntityManager.createNamedQuery("getActByAccessionNumber")
								 	.setParameter ("accessionNumber",p_AccessionNumber)
								 	.setMaxResults(1)
								 	.getSingleResult();
		}
	catch (NoResultException p_Exception)
		{
		l_Act = null;
		}

	return l_Act;
	}

//---------------------------------------------------------------------------
/**
 * Returns the invoice containing an act with the specified accession
 * number.
 * @param p_AccessionNumber specifies the accession number of the act to get
 * invoice of.
 * @return the invoice containing the act with the specified accession number,
 * <code>null</code> otherwise.
 */
//---------------------------------------------------------------------------

//@RolesAllowed("gecam")

public Invoice	getInvoiceByAccessionNumber (String p_AccessionNumber) throws Exception
	{
	Act		l_Act;
	Invoice	l_Invoice;
	
	l_Act = this.getActByAccessionNumber (p_AccessionNumber);
	if (l_Act == null) return null;
	
	l_Invoice = this.getInvoiceByID (l_Act.getInvoiceId());

	return l_Invoice;
	}
	
	
//---------------------------------------------------------------------------
/**
 * Returns all the invoices issued for the specified patient.
 * @param p_Patient specifies the patient to get invoices for.
 * @return A collection holding all the invoices for the specified
 * patient, <code>null</null> if none are available.
 */
//---------------------------------------------------------------------------

//@RolesAllowed("gecam")
@SuppressWarnings("unchecked")
public Collection <Invoice> getInvoicesByPatient (Patient p_Patient) throws Exception
	{
	Collection <Invoice>	l_Invoices = null;

	try	{
		l_Invoices = m_EntityManager.createNamedQuery (Invoice.c_InvoicesByPatient)
								   .setParameter("patient",p_Patient)
								   .getResultList();
		}
	catch (NoResultException p_Exception)
		{
		l_Invoices = null;
		}

	return l_Invoices;
	}

//---------------------------------------------------------------------------
/**
 * Returns all the invoices issued for the specified patient by the specified
 * physician.
 * @param p_Patient specifies the patient to get invoices for.
 * @param p_Physician specifies the issuing physician.
 * @return A collection holding all the invoices for the specified
 * patient issued by the specified physician, <code>null</code> if none are available.
 */
//---------------------------------------------------------------------------

//@RolesAllowed("gecam")
@SuppressWarnings("unchecked")
public Collection <Invoice> getInvoicesByPatientAndPhysician (Patient p_Patient, Physician p_Physician) throws Exception
	{
	Collection <Invoice>	l_Invoices = null;

	try	{
		l_Invoices = m_EntityManager.createNamedQuery (Invoice.c_InvoicesByPatientAndPhysician)
								    .setParameter("patient",p_Patient)
								    .setParameter("physician",p_Physician)
								    .getResultList();
		}
	catch (NoResultException p_Exception)
		{
		l_Invoices = null;
		}

	return l_Invoices;
	}


//---------------------------------------------------------------------------
/**
 * Searches all invoices at the specified day for this patient and this physician.<br>
 * Searches the best invoice of the results and returns it. Searches at first for the
 * specified ID. If no invoice has the specified ID the invoice with the lowest state 
 * and the the latest date will be returned.
 * 
 * @param p_Patient specifies the patient to get invoices for.
 * @param p_Physician specifies the issuing physician.
 * @param p_Date the date of the invoice
 * @param p_OpenInvoiceId the ID to search for primary
 * @return the latest invoice at that date or <code>null</code> if there is none
 */
//---------------------------------------------------------------------------

public Invoice getInvoiceByPatientPhysicianAndDate (Patient p_Patient, Physician p_Physician, Date p_Date, Integer p_OpenInvoiceId)
	{
	List<?>	l_Results = null;
	Invoice l_Invoice = null;
	Invoice l_IteratedInvoice;
	
	try {
		l_Results = m_EntityManager.createNamedQuery(Invoice.c_InvoicesByPatientAndPhysicianOfToday)
									.setParameter("patient", p_Patient)
									.setParameter("physician", p_Physician)
									.setParameter("date", p_Date)
									.getResultList();
		
		if (l_Results != null)
			{
			// choose the best invoice
			for (Object o : l_Results)
				{
				if (o == null)
					continue;
				l_IteratedInvoice = (Invoice) o;
				
				if (l_IteratedInvoice.getId().equals(p_OpenInvoiceId))
					// if the invoice is the same as the edited one, return this one
					return l_IteratedInvoice;
				
				if (l_Invoice == null)
					{
					l_Invoice = l_IteratedInvoice;
					}
				else 
					{
					// find the one with the lowest state
					if (l_IteratedInvoice.getState() < l_Invoice.getState())
						l_Invoice = l_IteratedInvoice;
					}
				}
			}
		}
	catch (NoResultException p_Exception) 
		{
		m_Logger.error(p_Exception.getMessage(), p_Exception);
		}
	
	return l_Invoice;
	}

//---------------------------------------------------------------------------
/**
 * @author ferring
 * 
 * @param p_Patient the patient to filter by
 * @param p_Physician the physician to filter by
 * @param p_FromDate the start date to look for
 * @param p_ToDate the end date to look for
 * @param p_Code the rate shortage the invoice needs to contain
 * @param p_ExcludedInvoiceId the invoice with this id will be ignored in the search
 * @return returns the latest invoice that fits to all conditions
 */
//---------------------------------------------------------------------------

public Invoice getInvoiceByAct (Patient p_Patient, Physician p_Physician, Date p_FromDate, Date p_ToDate, String p_Code, Integer p_ExcludedInvoiceId)
	{
	Invoice l_Invoice;
	
	if (p_ExcludedInvoiceId == null)
		p_ExcludedInvoiceId = -1;
	
	try {
		l_Invoice = (Invoice) m_EntityManager.createNamedQuery(Invoice.c_InvoicesByActInTimeSpan)
					.setParameter("code", 		p_Code)
					.setParameter("patient", 	p_Patient)
					.setParameter("physician", 	p_Physician)
					.setParameter("fromDate", 	p_FromDate)
					.setParameter("toDate", 	p_ToDate)
					.setParameter("exclude", 	p_ExcludedInvoiceId)
					.setMaxResults(1)
					.getSingleResult();
		Hibernate.initialize(l_Invoice.getPatient());
		Hibernate.initialize(l_Invoice.getPhysician());
		return l_Invoice;
		}
	catch (NoResultException p_Exception)
		{
		return null;
		}
	}

//---------------------------------------------------------------------------
/**
 * Returns all invoices related to a specific hospitalisation
 * @param p_Hospitalisation specifies the hospitalisation to get invoices for.
 * @return A collection holding all the invoices for the specified
 * hospitalisation, <code>null</null> if none are available.
 */
//---------------------------------------------------------------------------

//@RolesAllowed("gecam")
@SuppressWarnings("unchecked")
public Collection <Invoice> getInvoicesByHospitalisation (Hospitalisation p_Hospitalisation) throws Exception
	{
	Collection<Invoice>	l_Invoices = null;

	try	{
		l_Invoices = m_EntityManager.createNamedQuery (Invoice.c_InvoicesByHospitalisation)
									.setParameter("hospitalisation", p_Hospitalisation)
									.getResultList();
		}
	catch (NoResultException p_Exception)
		{
		l_Invoices = null;
		}

	return l_Invoices;
	}

//---------------------------------------------------------------------------
/**
 * Returns the number of invoices matching the specified where clause.
 * @param p_Clause specifies the where clause which defines the search criteria.
 * @return The number of invoices matching the specified criteria.
 */
//---------------------------------------------------------------------------

//@RolesAllowed("gecam")
public Long getInvoiceCountByWhereClause (WhereClause p_Clause) throws Exception
	{
	Query					l_Query;
	String					l_QueryString;

	Long					l_Count;
	
	if (p_Clause == null) return 0L;

	l_QueryString = "SELECT COUNT(o) FROM Invoice o ";

	try	{
		l_Query = HibernateQueryFactory.buildQueryFromWhereClause (m_EntityManager,l_QueryString, p_Clause);
		l_Count = (Long) l_Query.getSingleResult();
		}
	catch (NoResultException p_Exception)
		{
		l_Count = 0L;
		}
	
	return l_Count;
	}

//---------------------------------------------------------------------------
/**
 * Returns the invoices matching the specified where clause.
 * @param p_Clause specifies the where clause which defines the search criteria.
 * @return A collection of invoices matching the specified criteria.
 */
//---------------------------------------------------------------------------

//@RolesAllowed("gecam")
@SuppressWarnings("unchecked")
public Collection <Invoice> getInvoicesByWhereClause (WhereClause p_Clause) throws Exception
	{
	Query					l_Query;
	String					l_QueryString;

	Collection <Invoice>	l_Invoices;

	l_Invoices = new LinkedHashSet <Invoice> ();
	
	if (p_Clause == null) return l_Invoices;

	l_QueryString = "SELECT DISTINCT o FROM Invoice o ";

	l_QueryString += "LEFT JOIN FETCH o.physician " +
	 				 "LEFT JOIN FETCH o.hospitalisationClass " +
	 				 "LEFT JOIN FETCH o.healthInsurance " +
	 				 "LEFT JOIN FETCH o.thirdPartyPayer " +
	 				 "LEFT JOIN FETCH o.settlement " +
	 				 "LEFT JOIN FETCH o.statement " +
	 				 "LEFT JOIN FETCH o.closer " +
	 				 "LEFT JOIN FETCH o.acts " +
	 				 "LEFT JOIN FETCH o.memos ";

	try	{
		l_Query = HibernateQueryFactory.buildQueryFromWhereClause (m_EntityManager,l_QueryString, p_Clause);
		l_Invoices = l_Query.getResultList();
		}
	catch (NoResultException p_Exception)
		{
		// Simply Return empty Collection
		}
	
	return l_Invoices;
	}

//---------------------------------------------------------------------------
/**
 * Fetches one or more lazy dependencies for the specified invoice. Which
 * dependencies are to be fetched is specified by the p_Dependencies collection.
 * @param p_Invoice specifies the invoice to fetch lazy dependencies for.
 * @param p_Dependencies specifies which dependencies are to be fetched. The
 * specified collection may hold the following values:
 * <ul>
 * <li>c_PatientDependency - Fetch Patient data.</li>
 * <li>c_HospitalisationDependency - Fetch Hospitalisation data.</li>
 * <li>c_CloserDependency - Fetch Closer data.</li>
 * <li>c_ModifierDependency - Fetch Modifier data.</li>
 * </ul>
 * @return the original invoice object having the specified lazy dependencies
 * initialized.
 */
//---------------------------------------------------------------------------

//@RolesAllowed("gecam")
public Invoice	fetchLazyDependencies (Invoice p_Invoice, Collection <Integer> p_Dependencies) throws Exception
	{
	Iterator <Integer>  l_DependencyIterator;
	Integer				l_Dependency;
	
	if ((p_Invoice == null) || (p_Invoice.getId() == null)) return null;
	
	if (p_Dependencies == null) return p_Invoice;
	
	int invoiceID = p_Invoice.getId();
	String invoiceNr = p_Invoice.formatInvoiceNumber(true, true);
	
	p_Invoice = getInvoiceByID(invoiceID);

	if (p_Invoice == null) {
		m_Logger.warn("Invoice with ID: "+invoiceID+" and Invoicenr: "+invoiceNr+ " not found in DB!");
		return null;
	}
	
	
	l_DependencyIterator = p_Dependencies.iterator();
	while (l_DependencyIterator.hasNext())
		{
		l_Dependency = l_DependencyIterator.next();
		switch (l_Dependency)
			{
			case InvoiceInterface.c_PatientDependency:
			
				if ((p_Invoice.getPatient() != null) && (!Hibernate.isInitialized(p_Invoice.getPatient())) )
					Hibernate.initialize (p_Invoice.getPatient());

				break;

			case InvoiceInterface.c_HospitalisationDependency:
				
				if ((p_Invoice.getHospitalisation() != null) && (!Hibernate.isInitialized(p_Invoice.getHospitalisation())) )
					Hibernate.initialize (p_Invoice.getHospitalisation());
				break;

			case InvoiceInterface.c_CloserDependency:
				
				if ((p_Invoice.getCloser() != null) && (!Hibernate.isInitialized(p_Invoice.getCloser())) )
					Hibernate.initialize (p_Invoice.getCloser());
				break;


			case InvoiceInterface.c_ModifierDependency:
				
				if ((p_Invoice.getModifier() != null) && (!Hibernate.isInitialized(p_Invoice.getModifier())) )
					Hibernate.initialize (p_Invoice.getModifier());
				break;		
			}
		}
	
	return p_Invoice;
	}

//---------------------------------------------------------------------------
/**
 * Fetches lazy patient dependency for specified invoice
 * @param p_Invoice specifies the invoice to fetch patient dependency for.
 * @return the specified invoice having its patient property properly set.
 */
//---------------------------------------------------------------------------

//@RolesAllowed("gecam")
public Invoice fetchPatientForInvoice (Invoice p_Invoice) throws Exception
	{
	if ((p_Invoice == null) || (p_Invoice.getId() == null)) return null;

	p_Invoice = getInvoiceByID(p_Invoice.getId());
	p_Invoice.updateFirstClassRequired();

	if ((p_Invoice != null) && (!Hibernate.isInitialized(p_Invoice.getPatient())) )
		Hibernate.initialize (p_Invoice.getPatient());

	return p_Invoice;
	}

//---------------------------------------------------------------------------
/**
 * Fetches lazy hospitalisation dependency for specified invoice
 * @param p_Invoice specifies the invoice to fetch hospitalisation dependency for.
 * @return the specified invoice having its hospitalisation property properly set.
 */
//---------------------------------------------------------------------------

//@RolesAllowed("gecam")

public Invoice fetchHospitalisationForInvoice (Invoice p_Invoice) throws Exception
	{
	if ((p_Invoice == null) || (p_Invoice.getId() == null)) return null;

	p_Invoice = getInvoiceByID(p_Invoice.getId());
	p_Invoice.updateFirstClassRequired();

	if ((p_Invoice != null) && (!Hibernate.isInitialized(p_Invoice.getHospitalisation())) )
		Hibernate.initialize (p_Invoice.getHospitalisation());

	return p_Invoice;
	}

//---------------------------------------------------------------------------
/**
 * Fetches lazy closer dependency for specified invoice. The closer is the
 * user who put this invoice into closed state.
 * @param p_Invoice specifies the invoice to fetch closer dependency for.
 * @return the specified invoice having its closer property properly set.
 */
//---------------------------------------------------------------------------

//@RolesAllowed("gecam")

public Invoice fetchCloserForInvoice (Invoice p_Invoice) throws Exception
	{
	if ((p_Invoice == null) || (p_Invoice.getId() == null)) return null;

	p_Invoice = getInvoiceByID(p_Invoice.getId());
	p_Invoice.updateFirstClassRequired();

	if ((p_Invoice != null) && (p_Invoice.getCloser() != null) && (!Hibernate.isInitialized(p_Invoice.getCloser())) )
		Hibernate.initialize (p_Invoice.getCloser());

	return p_Invoice;
	}

//---------------------------------------------------------------------------
/**
 * Fetches lazy modifier dependency for specified invoice. The modifier is the
 * user who last saved this invoice.
 * @param p_Invoice specifies the invoice to fetch modifier dependency for.
 * @return the specified invoice having its modifier property properly set.
 */
//---------------------------------------------------------------------------

//@RolesAllowed("gecam")

public Invoice fetchModifierForInvoice (Invoice p_Invoice) throws Exception
	{
	if ((p_Invoice == null) || (p_Invoice.getId() == null)) return null;

	p_Invoice = getInvoiceByID(p_Invoice.getId());
	p_Invoice.updateFirstClassRequired();

	if ((p_Invoice != null) && (p_Invoice.getModifier() != null) && (!Hibernate.isInitialized(p_Invoice.getModifier())) )
		Hibernate.initialize (p_Invoice.getModifier());

	return p_Invoice;
	}

//---------------------------------------------------------------------------
/**
 * The verifyActs method sets the verified flag of the specified acts
 * to the state of the verified flag for their corresponding passage
 * counterpart. For every act specified, the method will look up the
 * corresponding passage (via the accession number) and sets the state
 * of act's verified flag to the one of the found passage.
 * @param p_Unverified specifies the acts to set verified flags for.
 * @return the same list of acts but having their verified flags set to
 * the state of their passage counterpart.
 */
//---------------------------------------------------------------------------

//@RolesAllowed("gecam")

public Set <Act> verifyActs (Set <Act> p_Unverified) throws Exception
	{
	Iterator <Act>	l_ActIterator;
	Act				l_Act;
	Passage			l_Passage;

	if ((p_Unverified == null) || (p_Unverified.size() == 0)) return p_Unverified;
		
    l_ActIterator = p_Unverified.iterator();	
	while (l_ActIterator.hasNext())
		{
		l_Act = l_ActIterator.next();
		l_Passage = this.getPassageByAccessionNumber(l_Act.getAccessionNumber());
		if (l_Passage != null) l_Act.setVerified (l_Passage.getVerified());
		}
	
	return p_Unverified;
	}

//---------------------------------------------------------------------------
/**
 * Fetches lazy author dependency for specified memo. 
 * @param p_Memo specifies the memo to fetch author dependency for.
 * @return the specified memo having its author property properly set.
 */
//---------------------------------------------------------------------------

//@RolesAllowed("gecam")

public Memo fetchAuthorForMemo (Memo p_Memo) throws Exception
	{
	if ((p_Memo == null) || (p_Memo.getId() == null)) return null;
	p_Memo = m_EntityManager.find(Memo.class, p_Memo.getId());
	if ( (p_Memo != null) && (!Hibernate.isInitialized (p_Memo.getAuthor())) )
		Hibernate.initialize (p_Memo.getAuthor());

	return p_Memo;
	}

//---------------------------------------------------------------------------
/**
 * The method sets the specified acts for the specified invoice and saves
 * both of them. Normally, it ought to be sufficient to just save the invoice,
 * but since entity manager somehow 'forgets' to delete dependencies which are no
 * longer part of the invoice, we have to take care of it ourselves.
 * @param p_Invoice specifies the invoice to save acts for.
 * @param p_Acts specifies the set of acts to be saved for the specified invoice.
 * @return the original (and saved) invoice containing the specified set of acts.
 */
//---------------------------------------------------------------------------

//@RolesAllowed("gecam")
@SuppressWarnings("unchecked")
public Invoice saveActs (Invoice p_Invoice, Set <Act> p_Acts) throws Exception
	{
	Collection <Act> 	l_OldActs		= null;

	if (Act.factorsNotSet()) this.initializeFactors();

	if (p_Invoice == null) return null;

	for (Act act : p_Acts) {
		updateActChanges(act);
	}
	
	//========================================================================
	//= Do cumbersome housekeeping first. JBOSS does not delete old one to many
	//= dependencies, it merely sets the foreign keys of depending objects to
	//= null, thus leaving old objects stranded in database. Depending objects
	//= that shall be removed MUST be removed explicitely.
	//========================================================================

	p_Invoice.setActs(p_Acts);

	if (p_Invoice.isPersistent())
		{
		l_OldActs = m_EntityManager.createNamedQuery (Act.c_ActsByInvoiceId)
		   						  .setParameter("invoiceId",p_Invoice.getId())
		   						  .getResultList();

		if ((l_OldActs != null) && (p_Acts != null)) l_OldActs.removeAll (p_Acts);
		}

	//========================================================================
	//= Next step consists in monetizing every single attached to invoice
	//= in order to calculate total amount, and balance
	//========================================================================

	p_Invoice.monetize();

	//========================================================================
	//= Last but not least, store invoice with associated acts and memos.
	//========================================================================

	p_Invoice = m_EntityManager.merge (p_Invoice);
	p_Invoice.updateFirstClassRequired();

	if ((l_OldActs != null) && (l_OldActs.size() > 0)) this.deleteOrphanedActs(l_OldActs);

	return p_Invoice;
	}

//---------------------------------------------------------------------------

public void saveActs (Set<Act> acts)
	{
	for (Act a : acts) {
		updateActChanges(a);
		m_EntityManager.merge(a);		
	}
	}

//---------------------------------------------------------------------------

public Act saveAct (Act p_Act)
	{
	updateActChanges(p_Act);
	return m_EntityManager.merge(p_Act);
	}

//---------------------------------------------------------------------------

public TrashedAct saveTrashedAct (TrashedAct p_Act)
	{
	return m_EntityManager.merge(p_Act);
	}

//---------------------------------------------------------------------------
/**
 * The method sets the specified memos for the specified invoice and saves
 * both of them. Normally, it ought to be sufficient to just save the invoice,
 * but since entity manager somehow 'forgets' to delete dependencies which are no
 * longer part of the invoice, we have to take care of it ourselves.
 * @param p_Invoice specifies the invoice to save memos for.
 * @param p_Memos specifies the set of memos to be saved for the specified invoice.
 * @return the original (and saved) invoice containing the specified set of memos.
 */
//---------------------------------------------------------------------------

//@RolesAllowed("gecam")
@SuppressWarnings("unchecked")

public Invoice saveMemos (Invoice p_Invoice, Set <Memo> p_Memos) throws Exception
	{
	Collection <Memo> 	l_OldMemos	= null;

	if (p_Invoice == null) return null;

	//========================================================================
	//= Do cumbersome housekeeping first. JBOSS does not delete old one to many
	//= dependencies, it merely sets the foreign keys of depending objects to
	//= null, thus leaving old objects stranded in database. Depending objects
	//= that shall be removed MUST be removed explicitely.
	//========================================================================

	p_Invoice.setMemos(p_Memos);

	if (p_Invoice.isPersistent())
		{
		l_OldMemos = m_EntityManager.createNamedQuery (Memo.c_MemosByInvoiceId)
									.setParameter("invoiceId",p_Invoice.getId())
									.getResultList();

		if ((l_OldMemos != null) && (p_Memos != null)) l_OldMemos.removeAll (p_Memos);
		}

	//========================================================================
	//= Last but not least, store invoice with associated acts and memos.
	//========================================================================

	p_Invoice = m_EntityManager.merge (p_Invoice);
	p_Invoice.updateFirstClassRequired();

	if ((l_OldMemos != null) && (l_OldMemos.size() > 0)) this.deleteOrphanedMemos(l_OldMemos);

	return p_Invoice;
	}

//---------------------------------------------------------------------------
/**
 * The method saves the specified invoice into the database. You should use
 * this method only when there were no changes in invoice's dependencies, i.e
 * no changes regarding the number of acts or memos. If, there were, you better
 * use either saveActs or saveMemos methods.
 * @param p_Invoice specifies the invoice to be saved.
 * @return the saved invoice.
 * @see #saveActs (Invoice, Set <Act>)
 * @see #saveMemos(Invoice, Set <Memo>)
 */
//---------------------------------------------------------------------------

//@RolesAllowed("gecam")
public Invoice saveInvoice (Invoice p_Invoice) throws Exception
	{
	if (p_Invoice == null) return null;
	
	if (p_Invoice.getActs() != null) {
		for (Act  a : p_Invoice.getActs()) {
			updateActChanges(a);
		}
	}

	p_Invoice = m_EntityManager.merge (p_Invoice);
	p_Invoice.updateFirstClassRequired();

	return p_Invoice;
	}

//---------------------------------------------------------------------------
/**
 * The method deletes the specified invoice. Before doing so, the method
 * stores a copy of the invoice (and its dependencies) in the so called
 * invoice trash for later restore if necessary.
 * @param p_Invoice specifies the invoice to delete.
 */
//---------------------------------------------------------------------------

//@RolesAllowed("gecam")

public void deleteInvoice (Invoice p_Invoice) throws Exception
	{
	TrashedInvoice	l_TrashedInvoice;
	
	if (p_Invoice.isPersistent())
		{
		p_Invoice = getInvoiceByID(p_Invoice.getId());
		p_Invoice.updateFirstClassRequired();
		
		l_TrashedInvoice = new TrashedInvoice (p_Invoice);
		l_TrashedInvoice.setTrasher(m_LoginBean.getCurrentUser());
		l_TrashedInvoice.setTrashingDate(new Date ());		
		l_TrashedInvoice = m_EntityManager.merge(l_TrashedInvoice);
		
		m_EntityManager.remove(p_Invoice);
		}
	}

//---------------------------------------------------------------------------
/**
 * Returns a list of invoices that matches the given accident data
 * @param p_AccidentNumber specifies number of the accident
 * @param p_AccidentDate specifies the date of the accident
 * @param p_Patient specifies the patient involved in the accident
 * @return A list of invoices for the specified accident if any, <code>null</code>
 * otherwise
 */
//---------------------------------------------------------------------------

//@RolesAllowed("gecam")
@SuppressWarnings("unchecked")

public List<Invoice> getInvoicesForAccident (String p_AccidentNumber, Date p_AccidentDate, Patient p_Patient)
	{
	List<Invoice>	l_Invoices = null;
	
	
	try {
		l_Invoices = m_EntityManager.createNamedQuery (Invoice.c_InvoicesByAccident)
									.setParameter("patient",p_Patient)
									.setParameter("accidentNumber",p_AccidentNumber)
									.setParameter("accidentDate",p_AccidentDate)
									.getResultList();
		}
	catch (NoResultException p_Exception)
		{
		l_Invoices = null;
		}
	catch (Exception p_Exception)
		{
		m_Logger.error("Error while fetching invoices related to Accident!",p_Exception);
		}

	return l_Invoices;
	}

//---------------------------------------------------------------------------

public int getNoOfActsWithoutSetAmount ()
{
	BigInteger count;
	
	
	count	= (BigInteger) m_EntityManager.createNativeQuery(
			"SELECT COUNT(id) FROM billing.act WHERE amount IS NULL")
			.getSingleResult();
	
	count	= count.add((BigInteger) m_EntityManager.createNativeQuery(
			"SELECT COUNT(id) FROM billing.trashed_act WHERE amount IS NULL")
			.getSingleResult());
	
	return count.intValue();
}

//---------------------------------------------------------------------------

@SuppressWarnings("unchecked")
public List<Act> getActsWithoutSetAmount (int maxActs)
{
	Query q = m_EntityManager.createNamedQuery(Act.c_ActsWithoutAmount);
	
	if (maxActs > 0)
		q.setMaxResults(maxActs);
	
	return q.getResultList();
}

//---------------------------------------------------------------------------

@SuppressWarnings("unchecked")
public List<TrashedAct> getTrashedActsWithoutSetAmount (int maxActs)
{
	Query q = m_EntityManager.createNamedQuery(Act.c_TrashedActsWithoutAmount);
	
	if (maxActs > 0)
		q.setMaxResults(maxActs);
	
	return q.getResultList();
}

public int getNoOfInvoicesWithNegativeAmounts ()
{
	return ((BigInteger) m_EntityManager.createNativeQuery(
			  "SELECT COUNT(DISTINCT(i.id))\n"
			+ "FROM billing.invoice i, billing.act a\n"
			+ "WHERE a.invoice_id = i.id\n"
			+ "AND (\n"
			+ "     i.amount  < 0\n"
			+ "  OR i.balance < 0\n"
			+ "  OR a.amount  < 0)")
			.getSingleResult()).intValue();
}


@SuppressWarnings("unchecked")
public List<Invoice> getInvoicesWithNegativeAmount (int maxInvoices)
{
	Query q = m_EntityManager.createQuery(
			  "SELECT DISTINCT i\n"
			+ "FROM Invoice i, Act a\n"
			+ "WHERE a.invoiceId = i.id\n"
			+ "AND (\n"
			+ "     i.amount  < 0\n"
			+ "  OR i.balance < 0\n"
			+ "  OR a.amount  < 0)");
	
	if (maxInvoices > 0)
		q.setMaxResults(maxInvoices);
	
	return q.getResultList();
}


/**
 * Checks whether the given Invoice is applicable for the split invoice feature
 * - must be CNS Invoice
 * - 1st Class Hospitalization and not first Class required
 * - in open State
 * 
 * @param invoice the Invoice to check
 * @return True if splitable, else false.
 */
public Boolean isInvoiceSplittable (Invoice invoice){
	
	if (invoice.getHealthInsurance() == null || ! invoice.getHealthInsurance().getUcmAffiliated()) {
		m_Logger.info("Given Invoice is not CNS Affiliated!");
		return false;
	}
	
	if (! invoice.isFirstClass()) {
		m_Logger.info("Given Invoice is not First Class Hospitalized!");
		return false;
	}
	
	if (invoice.getFirstClassRequired()) {
		m_Logger.info("Given Invoice has First Class Required Flag Set!");
		return false;
	}
	
	if (invoice.getState() >= InvoiceWorkflow.c_ClosedState) {
		m_Logger.info("Given Invoice is not in editable state.");
		return false;
	}
	
//	int thirdpartyTreshold = 100;
//	try {
//		thirdpartyTreshold = (Integer) m_LoginBean.getAdminSettingValue(BillingAdminSettings.c_ModuleName, BillingAdminSettings.c_TiersPayantMinValue);		
//	} catch (Exception e) {
//		m_Logger.warn("Error reading Billing Admin Setting: " + BillingAdminSettings.c_TiersPayantMinValue);
//	}
//	if (invoice.getAmount() < thirdpartyTreshold) {
//		m_Logger.info("Given Invoice Ammount is below 3rd Party Treshhold.");
//		return false;
//	}
	
	return true;
}



/**
 * Splits the given First Class Hospitalized Invoice into two new Invoices,
 * one with 100%, the other one with 66% value on each FirstClass Hospitalized Act. 
 * the two new Invoices will be saved and the original Invoice will be deleted.
 * 
 * @param invoice First Class Hospitalized Invoice
 * @return List of two new Invoices created from the given one.
 */
public List<Invoice> splitFirstClassHospitalizedInvoice (Invoice invoice) throws Exception
{
	if (invoice.getHealthInsurance() == null || ! invoice.getHealthInsurance().getUcmAffiliated()) {
		throw new Exception("Given Invoice is not CNS Affiliated!");
	}
	
	if (! invoice.isFirstClass()) {
		throw new Exception("Given Invoice is not First Class Hospitalized!");
	}
	
	if (invoice.getFirstClassRequired()) {
		throw new Exception("Given Invoice has First Class Required Flag Set!");
	}
	
	List<Invoice> splittetInvoices = new ArrayList<Invoice>();
	
	// first Invoice for CNS 
	// -> set all first class hospitalization acts to HospitalisationClass.c_FirstClassSplittedCNS 
	Invoice cnsInvoice = invoice.clone(true);
	cnsInvoice.setId(null);
	cnsInvoice.setHospitalisationClass(getHospitalisationClass(HospitalisationClass.c_FirstClassSplittedCNS));
	cnsInvoice.setOldState(InvoiceWorkflow.c_OpenState);
	cnsInvoice.setState(InvoiceWorkflow.c_ClosedState);
	for (Act act : cnsInvoice.getActs()) {
		act.setId(null);
		act.setFixAmount(null);
		if (HospitalisationClass.c_FirstClass.equals(act.getHospitalisationClass()) ) {
			act.setHospitalisationClass(HospitalisationClass.c_FirstClassSplittedCNS, cnsInvoice);
		}
	}
	cnsInvoice.monetize();

	int thirdpartyTreshold = 100;
	try {
		thirdpartyTreshold = (Integer) m_LoginBean.getAdminSettingValue(BillingAdminSettings.c_ModuleName, BillingAdminSettings.c_TiersPayantMinValue);		
	} catch (Exception e) {
		m_Logger.warn("Error reading Billing Admin Setting: " + BillingAdminSettings.c_TiersPayantMinValue);
	}
	// if above threshold
	if (cnsInvoice.getAmount() > thirdpartyTreshold) {
		cnsInvoice.setThirdPartyPayer(invoice.getHealthInsurance());	
	}
	
	
	// 2nd Invoice for private payment
	// -> set all first class hospitalization acts to HospitalisationClass.c_FirstClassSplittedPrivate
	// and only add this ones to the invoice
	Invoice privateInvoice = invoice.clone(true);
	privateInvoice.setId(null);
	privateInvoice.setHospitalisationClass(getHospitalisationClass(HospitalisationClass.c_FirstClassSplittedPrivate));
	privateInvoice.setHealthInsurance(Insurance.INSURANCE_PRIV);
	privateInvoice.setThirdPartyPayer((Insurance) null);
	privateInvoice.setOldState(InvoiceWorkflow.c_OpenState);
	privateInvoice.setState(InvoiceWorkflow.c_ClosedState);
	Set<Act> privateActs = new HashSet<Act>();
	for (Act act : privateInvoice.getActs()) {
		act.setId(null);
		act.setFixAmount(null);
		if (HospitalisationClass.c_FirstClass.equals(act.getHospitalisationClass()) ) {
			act.setHospitalisationClass(HospitalisationClass.c_FirstClassSplittedPrivate, privateInvoice);	
			privateActs.add(act);
		}
	}
	privateInvoice.setActs(privateActs);
	privateInvoice.monetize();
	
	// save new first invoice
	cnsInvoice = saveActs(cnsInvoice, cnsInvoice.getActs());
	splittetInvoices.add(cnsInvoice);

	// save new 2nd invoice
	privateInvoice = saveActs(privateInvoice, privateInvoice.getActs());
	splittetInvoices.add(privateInvoice);
	
	// delete original Invoice.
	deleteInvoice(invoice);
		
	return splittetInvoices;
}

	public HospitalisationClass getHospitalisationClass(String p_Acronym) throws Exception {
		HospitalisationClass l_Class;
		try {
			l_Class = (HospitalisationClass) m_EntityManager.createNamedQuery(HospitalisationClass.c_ClassByAcronym).setParameter("acronym", p_Acronym).getSingleResult();
		} catch (NoResultException p_Exception) {
			l_Class = null;
		} catch (Exception p_Exception) {
			l_Class = null;
			throw (p_Exception);
		}
		return l_Class;
	}

//---------------------------------------------------------------------------

private void updateActChanges(Act p_Act) {
	if (p_Act != null && (!Act.CHANGED_NOT.equals(p_Act.getChanges())) ) {
		String l_User = m_LoginBean.getCurrentUserName();
		if (Act.CHANGED_RULEENGINE.equals(p_Act.getChanges())) {
			l_User += "_R";
		}
		p_Act.setChangedBy(l_User);
		p_Act.setChanges(Act.CHANGED_NOT);			
	}
}



//---------------------------------------------------------------------------
//***************************************************************************
//* End of Class															*
//***************************************************************************
	}
