/*******************************************************************************
 * 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.entity.beans;

import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener;
import java.io.Serializable;
import java.math.BigDecimal;
import java.math.RoundingMode;
import java.text.DateFormat;
import java.text.SimpleDateFormat;
import java.util.Calendar;
import java.util.Collection;
import java.util.Date;
import java.util.GregorianCalendar;
import java.util.HashSet;
import java.util.Hashtable;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;

import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.NamedQueries;
import javax.persistence.NamedQuery;
import javax.persistence.Table;
import javax.persistence.Transient;

import lu.tudor.santec.gecamed.billing.ejb.session.beans.NomenclatureBean;
import lu.tudor.santec.gecamed.billing.ejb.session.interfaces.NomenclatureInterface;
import lu.tudor.santec.gecamed.billing.utils.rules.RulesObjectsHolder;
import lu.tudor.santec.gecamed.core.ejb.entity.beans.GECAMedEntityBean;
import lu.tudor.santec.gecamed.core.ejb.entity.beans.NationalHoliday;
import lu.tudor.santec.gecamed.core.utils.GECAMedUtils;
import lu.tudor.santec.gecamed.core.utils.ManagerFactory;
import lu.tudor.santec.gecamed.office.ejb.entity.beans.Physician;
import lu.tudor.santec.gecamed.office.ejb.session.beans.OfficeManagerBean;
import lu.tudor.santec.gecamed.office.ejb.session.interfaces.OfficeManagerInterface;
import lu.tudor.santec.gecamed.patient.ejb.entity.beans.HospitalisationClass;

import org.apache.log4j.Level;
import org.apache.log4j.Logger;

//***************************************************************************
//* Class Definition and Members                                            *
//***************************************************************************

/**
 * The Rate class represents the applicable rates for medical acts as defined
 * by the UCM (Union des Caisses de Maladies).
 * @author nico.mack@tudor.lu
 * @since 06/05/04
 */

@Entity
@Table(name = "act", schema = "billing")

@NamedQueries 
	({
	@NamedQuery(name = Act.c_ActsByInvoiceId, 			query = "SELECT OBJECT(o) FROM Act o WHERE o.invoiceId = :invoiceId ORDER BY o.performedDate, o.code"),
	@NamedQuery(name = Act.c_ActByAccessionNumber, 		query = "SELECT OBJECT(o) FROM Act o WHERE o.accessionNumber = :accessionNumber"),
	@NamedQuery(name = Act.c_ActsWithoutAmount,			query = "SELECT OBJECT(o) FROM Act o WHERE o.amount IS NULL"),
	@NamedQuery(name = Act.c_TrashedActsWithoutAmount,	query = "SELECT OBJECT(o) FROM TrashedAct o WHERE o.amount IS NULL")
	})

public class Act extends GECAMedEntityBean implements Serializable 
	{
	private static final long serialVersionUID = 1L;
	private static final DateFormat df = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
	
	public static final Integer CHANGED_NOT 			= Integer.valueOf(0);
	public static final Integer CHANGED_USER 			= Integer.valueOf(1);
	public static final Integer CHANGED_RULEENGINE 		= Integer.valueOf(2);
	public static final int		DEFAULT_ADJUSTMENT		= 100;
	
	private Integer		m_InvoiceId;
	private Integer		m_PhysicianId;
	private String		m_Username;
	private String		m_AccessionNumber;
	private Date		m_PerformedDate;
	private transient Calendar	m_PerformedCalendar;
	private Boolean		m_ShowTime;
	private String		m_MedPrescCode;
	private String		m_Code;
	private String		m_AdditionalActInfo;
	private String		m_Label;
	private Integer		m_Quantity;
	private Double		m_Coefficient;
	private Double		m_OrgCoefficient;
	private Double		m_KeyValue;
	private Integer		m_FractionDigits;
	private Boolean		m_CAT;
	private Boolean  	m_CAC;
	private Boolean  	m_APCM;
	private Boolean  	m_ACM;
	private String		m_Suffixes;
	private String 		m_HospitalisationClass;
	private Double 		m_FixAmount;
	private Integer		m_Adjustment;
//	private Double		m_Amount;
	
	private Double		m_Majoration;
	private Double		m_MonetaryValue;
//	private Boolean		m_RoundToSingleDecimal;
	private Boolean 	m_Verified;
//	private Integer		m_OrderNo;
//	private Integer		m_TempOrderNo;
	
	private Integer 	m_Changes = CHANGED_NOT;
	
	private boolean		m_FirstClassRequired	= false;
	
	private transient Double	m_RuleValue;
	
	private static transient RulesObjectsHolder	m_RulesObjectHolder;
	
	private static transient Hashtable <Character,Suffix> 	m_AllSuffixes   	= null;
	private static transient Collection <NationalHoliday>	m_Holidays  	= null;
	private static transient HashSet<String>				m_NoneCnsCodes	= null;
	
	/** the logger Object for this class */
	private static Logger logger = Logger.getLogger(Act.class.getName());
	
	private List<PropertyChangeListener>	listeners	= new LinkedList<PropertyChangeListener>();
	
//***************************************************************************
//* Constants				                                              	*
//***************************************************************************	

//	private static transient final BigDecimal c_Zero = BigDecimal.valueOf(0);
//	private static transient final BigDecimal c_One  = BigDecimal.valueOf(1);
	
	public static transient final String c_RentalSuffix   	= "X";
	public static transient final String c_MaterialSuffix 	= "M";
//	public static transient final String c_AssistanceSuffix	= "P";
//	public static transient final String c_AnesthesieSuffix	= "A";
	public static transient final String c_NoSuffix 		= "-";
	
	public static transient final String c_ActsByInvoiceId 		 	= "getActsByInvoiceId";
	public static transient final String c_ActByAccessionNumber  	= "getActByAccessionNumber";
	public static transient final String c_HospClassForAct 		 	= "getHospitalisationClassForAct";
	public static transient final String c_ActsWithoutAmount	 	= "getActsWithoutAmount";
	public static transient final String c_TrashedActsWithoutAmount	= "getTrashedActsWithoutAmount";

//***************************************************************************
//* Constructor(s)                                                          *
//***************************************************************************
//---------------------------------------------------------------------------

public Act ()
	{
	m_AccessionNumber   	= new String();
	m_PerformedCalendar 	= null;
	m_PerformedDate 		= new Date();
	m_ShowTime	   			= null; //Boolean.valueOf (false);
	m_Code		   			= new String ();
	m_Label		   			= new String ();
	m_Quantity	   			= Integer.valueOf (0);
	m_Coefficient  			= new Double (0);
	m_KeyValue				= new Double (0);
	m_CAT   				= Boolean.valueOf (false);
	m_CAC	   				= Boolean.valueOf (false);
	m_APCM	   				= Boolean.valueOf (false);
	m_ACM	   				= Boolean.valueOf (false);
	m_Suffixes				= new String ();
	m_HospitalisationClass 	= null;

	m_Majoration			= Invoice.c_DefaultMajoration;
	m_MonetaryValue			= new Double (0);
//	m_RoundToSingleDecimal 	= true;
	m_FractionDigits		= Integer.valueOf(1);
	m_Verified				= false;
	m_Adjustment			= DEFAULT_ADJUSTMENT;	
	
	}

///**
// * The copy-constructor.
// * 
// * @param a The act to make a copy of
// */
//public Act (Act a)
//{
//	this.m_InvoiceId = a.m_InvoiceId;
//	this.m_PhysicianId = a.m_PhysicianId;
//	this.m_Username = a.m_Username;
//	this.m_AccessionNumber = a.m_AccessionNumber;
//	this.m_PerformedDate = a.m_PerformedDate;
//	this.m_ShowTime = a.m_ShowTime;
//	this.m_MedPrescCode = a.m_MedPrescCode;
//	this.m_Code = a.m_Code;
//	this.m_AdditionalActInfo = a.m_AdditionalActInfo;
//	this.m_Label = a.m_Label;
//	this.m_Quantity = a.m_Quantity;
//	this.m_Coefficient = a.m_Coefficient;
//	this.m_OrgCoefficient = a.m_OrgCoefficient;
//	this.m_KeyValue = a.m_KeyValue;
//	this.m_CAT = a.m_CAT;
//	this.m_CAC = a.m_CAC;
//	this.m_APCM = a.m_APCM;
//	this.m_ACM = a.m_ACM;
//	this.m_Suffixes = a.m_Suffixes;
//	this.m_HospitalisationClass = a.m_HospitalisationClass;
//	this.m_FixAmount = a.m_FixAmount;
//	this.m_Adjustment = a.m_Adjustment;
////	this.m_Amount = a.m_Amount;
//	this.m_Majoration = a.m_Majoration;
//	this.m_MonetaryValue = a.m_MonetaryValue;
//	this.m_RoundToSingleDecimal = a.m_RoundToSingleDecimal;
//	this.m_Verified = a.m_Verified;
////	this.m_OrderNo = a.m_OrderNo;
////	this.m_TempOrderNo = a.m_TempOrderNo;
//	m_Changes = a.m_Changes;
//}

//---------------------------------------------------------------------------
/**
 * The Act class defines a static Lookup table, intended to hold the
 * multiplication factors for the different Suffixes defined for Acts. The
 * static setFactors method should MUST be called at least once to set
 * this lookup table. Not doing so will entail all Suffixes being ignored.
 * @param p_Suffixes specifies all the defined Suffixes.
 */
//---------------------------------------------------------------------------

@Transient
public static void setAllSuffixes (Collection<Suffix> p_Suffixes)
	{
	Iterator <Suffix>	l_SuffixIterator;
	Suffix				l_Suffix;	
	
	if (p_Suffixes != null)
		{
		m_AllSuffixes = new Hashtable <Character,Suffix> ();
		l_SuffixIterator = p_Suffixes.iterator();
		while (l_SuffixIterator.hasNext())
			{
			l_Suffix = l_SuffixIterator.next();
			m_AllSuffixes.put(l_Suffix.getLetter(),l_Suffix);
			}			
		}	
	else m_AllSuffixes = null;
	}


@Transient
public static Map<Character, Suffix> getAllSuffixes()
{
	return m_AllSuffixes;
}

//---------------------------------------------------------------------------
/**
 * Method allows to check whether factors have already been set by calling
 * setFactors() method or not.
 * @return <code>true</code> if factors have NOT been set yet, <code>false</code>
 * if factors have already been set.
 * @see #setFactors (Collection<Suffix>)
 */
//---------------------------------------------------------------------------

@Transient
public static Boolean factorsNotSet ()
	{
	return (m_AllSuffixes == null);
	}

//---------------------------------------------------------------------------
/**
 * The Act class defines a static Lookup table, intended to hold all
 * National Holidays. The static setHolidays method should MUST be called at 
 * least once to set this lookup table.
 * @param p_Holidays specifies all the national holidays defined in the system.
 */
//---------------------------------------------------------------------------

@Transient
public static void setHolidays (Collection <NationalHoliday> p_Holidays)
	{
	m_Holidays = p_Holidays;
	}

//---------------------------------------------------------------------------
/**
 * Method allows to check whether holidays have already been set by calling
 * setHolidays() method or not.
 * @return <code>true</code> if holidays have NOT been set yet, <code>false</code>
 * if holidays have already been set.
 * @see #setHolidays (Collection <NationalHoliday>)
 */
//---------------------------------------------------------------------------

@Transient
public static Boolean holidaysNotSet ()
	{
	return (m_Holidays == null);
	}

	/**	
	 * Converts the suffixes specified for this act into a multiplication factor.
	 * If more than one suffix is specified, product of factors of every suffix
	 * is returned.
	 * @return the product of the factors of every suffix set for this act
	 */
	@Transient
	private double convertSuffixToFactor (boolean skipAllMajorations)
	{
		double factor = Invoice.c_DefaultMajoration.doubleValue();
		Suffix suffix;
		boolean minima = false;
		
		if (m_Suffixes != null)
		{
			for (char letter : m_Suffixes.toCharArray())
			{
				suffix = m_AllSuffixes.get(Character.valueOf(letter));
				if (suffix != null && suffix.getFactor() != null)
				{
					if (skipAllMajorations && suffix.getFactor().doubleValue() > 1.0)
						continue;
					
					if (m_OrgCoefficient != null && suffix.getMinimum() != null && suffix.getMinimum() > 0.0) {
						minima = true;
						// don't apply this suffix, as it's minimum is already set
						if (m_Quantity > 1) {
							// instead, divide it by its quantity
							factor /= m_Quantity;
						}
						
						if (m_Adjustment != 100) {
							// and devide it by its adjustment
							factor /= (m_Adjustment.intValue() / 100.0);
						}
					} else {
						factor *= suffix.getFactor().doubleValue();
					}
				}
			}
			
			// if minima was applied and B was set, we do not use the B Factor that was applied before.
			// see  #1759 Tariff 3N55BAV incorrectly calculated 
			if (minima && m_Suffixes.indexOf('B') >= 0) {
				factor = factor / m_AllSuffixes.get(Character.valueOf('B')).getFactor().doubleValue();
			}
			
		}
		
		return factor;
	}

//---------------------------------------------------------------------------
/**
 * Checks whether specified suffix is already set or not.
 * @param p_Suffix specifies the suffix to check status of.
 * @return <code>True</code> if specified suffix is set for this act,
 * <code>False</code> otherwise.
 */

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

@Transient
public boolean suffixAlreadySet (char p_Suffix)
	{
	if (m_Suffixes == null) return false;
	return (m_Suffixes.lastIndexOf(p_Suffix) >= 0);
	}

//---------------------------------------------------------------------------
/**
 * Rounds the specified value either to single or dual decimals. In single
 * decimal mode, the method rounds a value according to the rules stipulated 
 * by Article 4 of UCM Rules. This article states that monetary values are to 
 * be rounded to one decimal position. Value will be rounded to next higher value 
 * if second decimal is greater or equal than 5. Otherwise value will be round 
 * to next lower value. For instance a value of 1.15 will be rounded to 1.20 
 * wheras a value of 1.14 will be rounded to 1.10.
 * Dual decimal mode will only be used for rates not being managed by the UCM.
 * Value will be rounded to next higher value if third decimal is greater or equal 
 * than 5. 
 * @param: p_Value specifies the value to be rounded
 * @return: returns the rounded value.
 */
//---------------------------------------------------------------------------

@Transient
public double round (double p_Value)
	{
	try
	{
	return new BigDecimal(p_Value)
			// do this to remove the double rounding errors
			.setScale(m_FractionDigits + 6, RoundingMode.HALF_UP)
			// now do the real rounding
			.setScale(m_FractionDigits, RoundingMode.HALF_UP)
			.doubleValue();
	}
	catch (NumberFormatException e)
	{
		System.out.println("NumberFormatException for " + p_Value);
		return p_Value;
	}
//	int			l_Decimator;
//	long		l_Rounder;
//	
//	
//	l_Decimator = (int) Math.pow(10, (m_FractionDigits == null ? 1 : m_FractionDigits.intValue()));
//	
//	l_Rounder = (long) ((p_Value * l_Decimator) + (p_Value < 0 ? (-0.5) : 0.5));
//	p_Value = ((double) l_Rounder / l_Decimator);
//	
//	return p_Value;
	}

//---------------------------------------------------------------------------
/**
 * Computes the base value for this act. Rates for Acts in UCM nomenclature
 * are defined by a coefficient which has to be multiplied by a key value
 * to obtain a monetary value, using the rounding scheme specified in article
 * 4. This (unit) monetary value is the base value of the act.
 * @return: the base value of this act.
 * @see: round (float) method
 */
//---------------------------------------------------------------------------

@Transient
private double baseValue ()
	{
	double l_MonetaryValue;
  		
  	if ((m_Coefficient != null) && (m_KeyValue != null))
  		{
  		l_MonetaryValue = m_Coefficient * m_KeyValue;
   		l_MonetaryValue = this.round (l_MonetaryValue);
  		}
  	else l_MonetaryValue = 0;
	
  	return l_MonetaryValue;
	}

//---------------------------------------------------------------------------
/**
 * This method is the futur implementation of the original baseValue method.
 * This method relies on BigDecimal objects to compute the base value instead
 * of using doubles. Doubles are not exact values whereas BigDecimals are.
 */
//---------------------------------------------------------------------------

//@Transient
//private BigDecimal baseValue_bd ()
//	{
//	BigDecimal	l_KeyValue;
//	BigDecimal	l_Coefficient;
//	BigDecimal 	l_MonetaryValue;
//  	int			l_Decimals;
//	
//  	if ((m_Coefficient != null) && (m_KeyValue != null))
//  		{
//  		l_KeyValue    = new BigDecimal (m_KeyValue).setScale(4,RoundingMode.FLOOR);
//  		l_Coefficient = new BigDecimal (m_Coefficient).setScale(2,RoundingMode.FLOOR);
//  		
//  		l_Decimals = (this.roundedToSingleDecimal())?1:2;
//  		
//  		l_MonetaryValue = l_Coefficient.multiply(l_KeyValue).setScale(l_Decimals,RoundingMode.HALF_UP);
//   		}
//  	else l_MonetaryValue = new BigDecimal (0);
//	
//  	return l_MonetaryValue;
//	}

//***************************************************************************
//* Class Body				                                              	*
//***************************************************************************	

//---------------------------------------------------------------------------
/**
 * Sets the specified suffix for this act if not already set.
 * @param p_Suffix specifies the suffix to set.
 */
//---------------------------------------------------------------------------

@Transient
public void setSuffix (char p_Suffix)
	{
	if (m_AllSuffixes == null) return;
	
	if (m_AllSuffixes.containsKey(p_Suffix))
		{
		if (m_Suffixes == null) 
			m_Suffixes = new String ();
		if (!this.suffixAlreadySet(p_Suffix)) 
			m_Suffixes += p_Suffix;
		}
	}

//---------------------------------------------------------------------------
/**
 * Clears the specified suffix for this act if already set.
 * @param p_Suffix specifies the suffix to clear
 */
//---------------------------------------------------------------------------

@Transient
public void clearSuffix (char p_Suffix)
	{
	int		l_Index;
	String	l_Suffixes;
	
	if (m_AllSuffixes == null) return;
	
	if (m_AllSuffixes.containsKey(p_Suffix))
		{
		if (m_Suffixes == null) 
			m_Suffixes = new String ();
		
		l_Index = m_Suffixes.lastIndexOf(p_Suffix);
	
		if (l_Index >= 0)
			{
			l_Suffixes  = m_Suffixes.substring(0,l_Index);
			if (l_Index < m_Suffixes.length())
				l_Suffixes += m_Suffixes.substring(l_Index+1);
			m_Suffixes = l_Suffixes;
			}
		}
	}

//---------------------------------------------------------------------------
/**
 * Toggles the state of specified suffix. If suffix was already set, it will
 * be cleared, otherwise it will be set.
 * @param p_Suffix specifies the suffix to toggle.
 */
//---------------------------------------------------------------------------

@Transient
public void toggleSuffix (Character p_Suffix)
	{
	if ((m_Suffixes != null) && (this.suffixAlreadySet(p_Suffix))) 
		 this.clearSuffix (p_Suffix);
	else this.setSuffix   (p_Suffix); 
	}

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

//@Transient
//private Float getValueWithSuffix (Character p_Suffix)
//	{
//	float	l_Value;
//	
//	l_Value = this.monetize();
//	
//	if ( (!this.suffixAlreadySet(p_Suffix)) && 
//		 (m_Factors.containsKey(p_Suffix))	)
//		{
//		l_Value = this.round(l_Value * m_Factors.get(p_Suffix));
//		}
//	
//	return new Float(l_Value);
//	}
//
////---------------------------------------------------------------------------
///**
// * Checks whether this act rounds to single decimal, as stipulated by article
// * 4 of the UCM nomenclature, or not.
// * @return <code>true</code> of single decimal rounding is applied to this
// * act, <code>false</code> otherwise.
// */
////---------------------------------------------------------------------------
//
//@Transient
//public Boolean roundedToSingleDecimal ()
//	{
//	return Boolean.valueOf (m_FractionDigits);
//	}


	/**
	 * Calls the monetize method, but skips the majorations 
	 * (suffixes with factor > 1.0, 1. class and insurance majoration)
	 * 
	 * @return This act's value, without the majorations
	 */
	public double calculateAmountForMinimum()
	{
		return monetize(true);
	}


	/**
	 * Returns the actual monetary value of this act.The actual monetary value
	 * of this act is composed of this acts' base value multiplied by certain number
	 * of factors. The first factor involved is the one governed by this acts
	 * suffixes. Depending on which suffixes are set, this factor may be greater
	 * or smaller than 1. It should be noted that, if a majoration was explicitely
	 * set by calling the setMajoration method, the factor determined via suffixes
	 * will be ignored and specified majoration will be used instead.
	 * The next factor coming into play is the quantity property of this act. Monetary 
	 * value will be multiplied by quantity. Last but not least, the monetary value will 
	 * be multiplied with the provided p_Majoration parameter. Please note that
	 * p_Majoration parameter is independent from previously mention setMajoration
	 * method.
	 */
	@Transient
	public double monetize ()
	{
		return monetize(false);
	}
	
	
	@Transient
	private double monetize (boolean skipAllMajorations)
	{
		PropertyChangeEvent event = null;
		double l_MonetaryValue;
		double l_SuffixFactor;
		
		if (m_FixAmount != null)
		{
			l_MonetaryValue = m_FixAmount.doubleValue();
		}
		else
		{
			// for calculation see "nomenclature des actes et services des médecins" art. 4
			l_MonetaryValue = this.baseValue();
			if (l_MonetaryValue > 0.0)
			{
				l_SuffixFactor = convertSuffixToFactor(skipAllMajorations);
				if (l_SuffixFactor != 1.0)
				{
					l_MonetaryValue *= l_SuffixFactor;
				}
				
				if (!skipAllMajorations)
				{
					// value = value * (majoration * hosptialization class) -> 1.66 for 1st class, 1.0 for 2nd class
					if (m_Majoration != null && m_Majoration.doubleValue() != 1.0) {
						l_MonetaryValue *= m_Majoration.doubleValue();
					}
					
					if (! m_FirstClassRequired) {
						if (isFirstClass()) {
							l_MonetaryValue *= HospitalisationClass.c_FirstClassMajoration;
						} else if (isFirstClassSplittedCNS()) {
							l_MonetaryValue *= HospitalisationClass.c_FirstClassSplittedCNSMajoration;
						} else if (isFirstClassSplittedPrivate()) {
							l_MonetaryValue *= HospitalisationClass.c_FirstClassSplittedPrivateMajoration;
						}
					}
				}
				
			}
			
			// round
			l_MonetaryValue = this.round(l_MonetaryValue);

			if (m_Quantity != null)
			{
				l_MonetaryValue *= m_Quantity;
			}
			
			if (m_Adjustment != null && m_Adjustment.intValue() != 100)
			{
				l_MonetaryValue = l_MonetaryValue * m_Adjustment.intValue() / 100.0;
			}
			
			if (skipAllMajorations)
				return l_MonetaryValue;
		}
		
		if (m_MonetaryValue == null || l_MonetaryValue != m_MonetaryValue.doubleValue()	&& listeners.size() > 0) {
			event = new PropertyChangeEvent(this, "amount", m_MonetaryValue, l_MonetaryValue);
		}
		
		m_MonetaryValue = Double.valueOf(l_MonetaryValue);
		
		if (event != null)
		{
			for (PropertyChangeListener l : listeners)
				l.propertyChange(event);
		}
		
		return l_MonetaryValue;
	}

//---------------------------------------------------------------------------
/**
 * This method is the futur implementation of the original monetize method.
 * This method relies on BigDecimal objects to compute the base value instead
 * of using doubles. Doubles are not exact values whereas BigDecimals are.
 */
//---------------------------------------------------------------------------

//@Transient
//private double monetize_bd (double p_Majoration)
//	{
//  	BigDecimal	l_MonetaryValue;
//   	BigDecimal	l_SuffixFactor;
// 	BigDecimal  l_Majoration;
//   	
// 	l_MonetaryValue = this.baseValue_bd();
// 	l_Majoration    = new BigDecimal (p_Majoration);
// 	
// 	if (l_MonetaryValue.compareTo(c_Zero) > 0)
// 		{
// 		l_SuffixFactor = new BigDecimal (convertSuffixToFactor());
// 		
// 		if (l_SuffixFactor.compareTo(c_One) != 0)
//			{
//			l_MonetaryValue = l_MonetaryValue.multiply(l_SuffixFactor);
//			}
//		if (l_Majoration.compareTo(c_One) != 0)
//			{
//			l_MonetaryValue = l_MonetaryValue.multiply(l_Majoration);
//			}
// 		if ((m_Quantity != null) && (m_Quantity.intValue() != 1))
// 			{
// 			l_MonetaryValue = l_MonetaryValue.multiply(BigDecimal.valueOf(m_Quantity));
// 			}
// 		}
// 	
// 	m_MonetaryValue = l_MonetaryValue.doubleValue();
// 	return m_MonetaryValue;		
//	}

//***************************************************************************
//* Getter and Setter Methods	                                              *
//***************************************************************************	

//---------------------------------------------------------------------------
/**
 * Returns the ID of the invoice this act is associated with
 * @return The ID of the invoice this act is associated with
 */
//---------------------------------------------------------------------------

@Column(name = "invoice_id", updatable=false, insertable=false)
public Integer getInvoiceId() 
	{
	return m_InvoiceId;
	}

//---------------------------------------------------------------------------
/**
 * Sets the ID of the invoice this act is to be associated with
 * @param p_InvoiceId specifies the new invoice to associate this act with
 */
//---------------------------------------------------------------------------

public void setInvoiceId (Integer p_InvoiceId)
	{
	m_InvoiceId = p_InvoiceId;
	}

//---------------------------------------------------------------------------
/**
 * Returns the ID of the physician this act is associated with
 * @return The ID of the physician this act is associated with
 */
//---------------------------------------------------------------------------

@Column(name = "physician_id")
public Integer getPhysicianId() 
	{
	return m_PhysicianId;
	}

//---------------------------------------------------------------------------
/**
 * Sets the ID of the physician this act is to be associated with
 * @param p_PhysicianId specifies the new physician to associate this act with
 */
//---------------------------------------------------------------------------

public void setPhysicianId (Integer p_PhysicianId) 
	{
	m_PhysicianId = p_PhysicianId;
	}

//---------------------------------------------------------------------------
/**
 * Returns the accession number of this act. An accession numbers is a  unique 
 * number or combination of letters and numbers assigned to each act in hospitals.
 * @return accession number of act if defined, <code>NULL</code> otherwise
 */
//---------------------------------------------------------------------------

@Column (name ="accession_number")

public String getAccessionNumber() 
	{
	return m_AccessionNumber;
	}

//---------------------------------------------------------------------------
/**
 * Sets the accession number of this act
 * @param p_AccessionNumber specifies the accession number for this act
 */
//---------------------------------------------------------------------------

public void setAccessionNumber (String p_AccessionNumber)
	{
	m_AccessionNumber = p_AccessionNumber;
	}

//---------------------------------------------------------------------------
/**
 * Returns the date and time this act was performed
 * @return date and time this act was performed
 */
//---------------------------------------------------------------------------

@Column (name ="performed")

public Date getPerformedDate() 
	{
	return m_PerformedDate;
	}


@Transient
public Calendar getPerformedCalendar()
{
	if (m_PerformedCalendar == null)
	{
		m_PerformedCalendar	= new GregorianCalendar();
		m_PerformedCalendar.setTime(m_PerformedDate);
	}
	
	return m_PerformedCalendar;
}

//---------------------------------------------------------------------------
/**
 * Sets the date and time this act was performed
 * @param p_PerformedDate specifies the date this act was performed on.
 */
//---------------------------------------------------------------------------

public void setPerformedDate (Date p_PerformedDate) 
	{
	if (p_PerformedDate != null) {
		m_PerformedDate.setTime(p_PerformedDate.getTime());		
	} 
	m_PerformedCalendar	= null;
	}

//---------------------------------------------------------------------------
/**
 * Returns the state of the show performed time flag of this act
 * @return <b>true</b> if performed time is to be shown, <b>false</b> otherwise
 */
//---------------------------------------------------------------------------

@Column (name ="show_time")
public Boolean getShowTime() 
	{
	if (m_ShowTime == null)
		return false;
	
	return m_ShowTime;
	}

/**
 * Returns the state of the show performed time flag of this act
 * @return <b>true</b> if performed time is to be shown, <b>false</b> if not,
 * <b>null</b> if it hasn't been clicked at all.
 */
@Transient
public Boolean isShowTimeSet ()
	{
	return m_ShowTime;
	}

//---------------------------------------------------------------------------
/**
 * Sets set state of the show performed time flag
 * @param p_ShowTime specifies state to set show performed time flag to
 */
//---------------------------------------------------------------------------

public void setShowTime (Boolean p_ShowTime)
	{
	m_ShowTime = p_ShowTime;
	}


/**
 * @return The CNS code of the Medecin Prescrepteur
 */
@Column(name = "med_presc_code")
public String getMedPrescCode()
{
	return m_MedPrescCode;
}


/**
 * @param p_MedPrescCode The CNS code of the Medecin Prescrepteur
 */
public void setMedPrescCode (String p_MedPrescCode)
{
	this.m_MedPrescCode = p_MedPrescCode;
}


/**
 * Additional code info, describing the laterality or the tooth number.<br>
 * This info will be printed before the code description (label).
 * 
 * @return The additional code info
 */
@Column(name = "additional_act_info")
public String getAdditionalActInfo()
{
	return m_AdditionalActInfo;
}


/**
 * Additional code info, describing the laterality or the tooth number.<br>
 * This info will be printed before the code description (label).
 * 
 * @param p_ActInfo The additional code info
 */
public void setAdditionalActInfo (String p_ActInfo)
{
	this.m_AdditionalActInfo = p_ActInfo;
}

//---------------------------------------------------------------------------
/**
 * Returns the code associated with this act
 * @return The code of this act
 */
//---------------------------------------------------------------------------

@Column (name ="code")
public String getCode() 
	{
	return m_Code;
	}

//---------------------------------------------------------------------------
/**
 * Sets the code of this act
 * @param p_Code specifies the new code to set
 */
//---------------------------------------------------------------------------

public void setCode (String p_Code) 
	{
	m_Code = p_Code;
	}

//---------------------------------------------------------------------------
/**
* Returns the  username who created the act
* @return The  username who created the act
*/
//---------------------------------------------------------------------------

@Column (name ="username")
public String getUsername() 
	{
	return m_Username;
	}

//---------------------------------------------------------------------------
/**
* Sets this acts username who created the act
* @param p_Username specifies the username who created the act
*/
//---------------------------------------------------------------------------

public void setUsername(String p_Username) 
	{
	m_Username = p_Username;
	}

//---------------------------------------------------------------------------
/**
 * Returns the descriptive label of this act
 * @return The descriptive label of this act
 */
//---------------------------------------------------------------------------

@Column (name ="label")

public String getLabel() 
	{
	return m_Label;
	}

//---------------------------------------------------------------------------
/**
 * Sets this acts descriptive label
 * @param p_Label specifies the new label
 */
//---------------------------------------------------------------------------

public void setLabel(String p_Label) 
	{
	m_Label = p_Label;
	}

//---------------------------------------------------------------------------
/**
 * Returns the number of times this act was performed
 * @return This acts quantity
 */
//---------------------------------------------------------------------------

@Column (name ="quantity")

public Integer getQuantity() 
	{
	return m_Quantity;
	}

//---------------------------------------------------------------------------
/**
 * Sets the number of times this act was performed
 * @param p_Quantity specifies the new quantity
 */
//---------------------------------------------------------------------------

public void setQuantity (Integer p_Quantity) 
	{
//	System.out.println(toString() + " set quantity from " + m_Quantity + " to " + p_Quantity);
	m_Quantity = p_Quantity;
	}

//---------------------------------------------------------------------------
/**
 * Returns the coefficient associated with this act
 * @return This acts coefficient
 */
//---------------------------------------------------------------------------

@Column (name ="coefficient")

public Double getCoefficient() 
	{
	return m_Coefficient;
	}

//---------------------------------------------------------------------------
/**
 * Sets this acts' coefficient
 * @param p_Coefficient specifies the new coefficient
 */
//---------------------------------------------------------------------------

public void setCoefficient (Double p_Coefficient) 
	{
	m_Coefficient = p_Coefficient;
	}

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

@Column (name = "org_coefficient")
public Double getOrgCoefficient ()
	{
	return m_OrgCoefficient;
	}

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

public void setOrgCoefficient (Double p_OrgCoefficient)
	{
	m_OrgCoefficient = p_OrgCoefficient;
	}

//---------------------------------------------------------------------------
/**
 * Returns the key value that was applicable when this act was performed
 * @return applicable key value
 */
//---------------------------------------------------------------------------

@Column (name = "key_value")

public Double getKeyValue() 
	{
	return m_KeyValue;
	}

//---------------------------------------------------------------------------
/**
 * This method is required by hibernate to manage the database connection.<br>
 * Please use the method setKeyValue(Double, Integer) instead.
 * 
 * @param p_KeyValue specifies the new key value
 */
//---------------------------------------------------------------------------
@Deprecated
public void setKeyValue (Double p_KeyValue) 
	{
	m_KeyValue = p_KeyValue;
	}

//---------------------------------------------------------------------------
/**
 * Sets the key value to be applied on this act and defines, on which decimal
 * place the amount of this act is rounded to.
 * 
 * @param p_KeyValue The key value for this act
 * @param p_FractionDigits The decimal place to round to
 */
//---------------------------------------------------------------------------
@Transient
public void setKeyValue (Double p_KeyValue, Integer p_FractionDigits) 
	{
	m_KeyValue			= p_KeyValue;
	m_FractionDigits	= p_FractionDigits;
	}


/**
 * @return The decimal place this act is rounded to
 */
@Column(name = "fraction_digits")
public Integer getFractionDigits ()
{
	return m_FractionDigits;
}

/**This method is required by hibernate to manage the database connection.<br>
 * Please use the method setKeyValue(Double, Integer) instead.
 * 
 * @param digits The decimal place to round to
 */
@Deprecated
public void setFractionDigits (Integer digits)
{
	this.m_FractionDigits = digits;
}

//---------------------------------------------------------------------------
/**
 * Returns the state of the CAT (Cumulable A plein Tarif) flag of this act
 * @return <b>true</b> if CAT is applicable, <b>false</b> otherwise
 */
//---------------------------------------------------------------------------

@Column (name ="cat")

public Boolean getCAT() 
	{
	return m_CAT;
	}

//---------------------------------------------------------------------------
/**
 * Sets set state of the CAT flag
 * @param p_CAT specifies state to set CAT flag to
 */
//---------------------------------------------------------------------------

public void setCAT(Boolean p_CAT) 
	{
	m_CAT = p_CAT;
	}

//---------------------------------------------------------------------------
/**
 * Returns the state of the CAC (Cumulable Avec Consultation) flag of this act
 * @return <b>true</b> if CAC is applicable, <b>false</b> otherwise
 */
//---------------------------------------------------------------------------

@Column (name ="cac")

public Boolean getCAC () 
	{
	return m_CAC;
	}

//---------------------------------------------------------------------------
/**
 * Sets set state of the CAC flag
 * @param p_CAC specifies state to set CAC flag to
 */
//---------------------------------------------------------------------------

public void setCAC (Boolean p_CAC) 
	{
	m_CAC = p_CAC;
	}

//---------------------------------------------------------------------------
/**
 * Returns the state of the APCM (Accord Prealable du Conseil Medical) flag of this act
 * @return <b>true</b> if APCM is required, <b>false</b> otherwise
 */
//---------------------------------------------------------------------------

@Column (name ="apcm")

public Boolean getAPCM () 
	{
	return m_APCM;
	}

//---------------------------------------------------------------------------
/**
 * Sets set state of the APCM flag
 * @param p_APCM specifies state to set APCM flag to
 */
//---------------------------------------------------------------------------

public void setAPCM (Boolean p_APCM) 
	{
	m_APCM = p_APCM;
	}


//---------------------------------------------------------------------------
/**
 * Returns the state of the ACM (Accord du Conseil Medical) flag of this act
 * @return <b>true</b> if ACM is required, <b>false</b> otherwise
 */
//---------------------------------------------------------------------------

@Column (name ="acm")

public Boolean getACM () 
	{
	return m_ACM;
	}

//---------------------------------------------------------------------------
/**
 * Sets set state of the ACM flag
 * @param p_ACM specifies state to set ACM flag to
 */
//---------------------------------------------------------------------------

public void setACM (Boolean p_ACM) 
	{
	m_ACM = p_ACM;
	}

//---------------------------------------------------------------------------
/**
 * Returns the suffixes set for this act
 * @return the suffixes set for this act
 */
//---------------------------------------------------------------------------

@Column (name ="suffixes")

public String getSuffixes () 
	{
	return m_Suffixes;
	}

//---------------------------------------------------------------------------
/**
 * Sets the suffixes for this act
 * @param p_Suffixes the new suffixes to be set for this act
 */
//---------------------------------------------------------------------------

public void setSuffixes (String p_Suffixes) 
	{
	m_Suffixes = p_Suffixes;
	}

//---------------------------------------------------------------------------
/**
* @return A fix amount per quantity, if set
*/
//---------------------------------------------------------------------------

@Column (name ="fix_amount")
public Double getFixAmount() 
	{
	return m_FixAmount;
	}

//---------------------------------------------------------------------------
/**
* @param p_Amount specifies a fix amount per quantity 
*/
//---------------------------------------------------------------------------

public void setFixAmount (Double p_Amount) 
	{
	m_FixAmount = p_Amount;
	}

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

@Column (name ="amount")
public Double getAmount()
	{
	return m_MonetaryValue;
	}

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

public void setAmount(Double p_Amount)
	{
	PropertyChangeEvent	l_Event	= null;
	
	
   	if ((m_MonetaryValue != null && !m_MonetaryValue.equals(p_Amount) && listeners.size() > 0)
   			|| (m_MonetaryValue == null && p_Amount != null))
   		l_Event	= new PropertyChangeEvent(this, "amount", m_MonetaryValue, p_Amount);
   	
	this.m_MonetaryValue = p_Amount;
	

   	if (l_Event != null)
   		{
	   	for (PropertyChangeListener l : listeners)
	   		l.propertyChange(l_Event);
   		}
	}

//---------------------------------------------------------------------------
/**
* Defines the hospitalisation class
* @return 'A' for ambulant, '1' for class 1, '2' for class 2 or 
* null if class is the same as the class of the invoice.
*/
//---------------------------------------------------------------------------

@Column (name = "hospitalisation_class")
public String getHospitalisationClass ()
{
	return m_HospitalisationClass;
}

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

@Deprecated
public void setHospitalisationClass (String p_HospitalisationClass)
{
//	if (p_HospitalisationClass == null || p_HospitalisationClass.length() == 1)
//		m_HospitalisationClass = p_HospitalisationClass;
//	else if (p_HospitalisationClass.length() == 0)
//		m_HospitalisationClass = HospitalisationClass.c_Ambulant;
//	else
//	{
//		m_HospitalisationClass = p_HospitalisationClass.substring(0, 1);
//		log(Level.WARN, "String for hospitalisation class must contain exact 1 character. " +
//				"First character was set as hospitalisation class");
//	}
	if (p_HospitalisationClass == null || p_HospitalisationClass.length() <=3 )
		m_HospitalisationClass = p_HospitalisationClass;
	else if (p_HospitalisationClass.length() == 0)
		m_HospitalisationClass = HospitalisationClass.c_Ambulant;
	else
	{
		m_HospitalisationClass = p_HospitalisationClass.substring(0, Math.max(p_HospitalisationClass.length(),2));
		log(Level.WARN, "String for hospitalisation class must contain max 3 charactera. " +
				"cutting down to max 3 chars");
	}
}

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

public void setHospitalisationClass (String p_HospitalisationClass, Invoice p_Invoice)
{
	if (p_HospitalisationClass == null || (p_HospitalisationClass.equals(m_HospitalisationClass)
					&& (p_Invoice != null && m_FirstClassRequired == p_Invoice.getFirstClassRequired().booleanValue())))
		return;
	
	
	setHospitalisationClass(p_HospitalisationClass);
	
	if (p_Invoice != null) {
		setFirstClassRequired(p_Invoice.getFirstClassRequired());		
	}

	
	monetize();
}

//---------------------------------------------------------------------------
/**
* Returns the current majoration applicable for this act. Majoration is no
* intrinsic property of acts. It has to be explicitely set to be defined.
* If majoration was not explicitely set, the getMajoration method will return
* the multiplication factor resulting from set suffixes.
* @return the applicable majoration
*/
//---------------------------------------------------------------------------

@Column (name = "majoration")
public Double getMajoration() 
	{
//	return (m_Majoration != null)?m_Majoration:this.convertSuffixToFactor();
	return m_Majoration;
	}

//---------------------------------------------------------------------------
/**
* Explicitely sets the majoration applicable for this act.
* @param p_Majoration specifies the multiplication factor that base value will
* be multiplied with when monetizing this act.
*/
//---------------------------------------------------------------------------

public void setMajoration (Double p_Majoration) 
	{
	m_Majoration = p_Majoration;
	}

//---------------------------------------------------------------------------
/**
* Checks whether this invoice has its hospitalisation class set to first class
* or not.
* @return <code>true</code> if this invoice is first class, <code>false</code> 
* otherwise
*/
//---------------------------------------------------------------------------

@Transient
public Boolean isFirstClass ()
	{
	if (this.getHospitalisationClass() != null)
		 return (this.getHospitalisationClass().equals(HospitalisationClass.c_FirstClass)) ? Boolean.TRUE : Boolean.FALSE;
	else return Boolean.FALSE;	
	}

@Transient
public Boolean isFirstClassSplittedCNS ()
	{
	if (this.getHospitalisationClass() != null)
		 return (this.getHospitalisationClass().equals(HospitalisationClass.c_FirstClassSplittedCNS)) ? Boolean.TRUE : Boolean.FALSE;
	else return Boolean.FALSE;	
	}

@Transient
public Boolean isFirstClassSplittedPrivate ()
	{
	if (this.getHospitalisationClass() != null)
		 return (this.getHospitalisationClass().equals(HospitalisationClass.c_FirstClassSplittedPrivate)) ? Boolean.TRUE : Boolean.FALSE;
	else return Boolean.FALSE;	
	}

//---------------------------------------------------------------------------
/**
 * Returns the monetary value of this act, Monetary value is only available
 * if monetize method has been called at least once before.
 * @return Monetary value of this act if set, <code>0</code> otherwise.
 */
//---------------------------------------------------------------------------

@Transient
public Double getValue() 
	{
	return m_MonetaryValue;
	}

//---------------------------------------------------------------------------
/**
 * Empty method to satisfy bean specifications of getter and setter pairs.
 * Calling the method has no effect.
 */
//---------------------------------------------------------------------------

@Transient
public void setValue(Double p_Value) 
	{
	if (m_FixAmount == null && p_Value != null && p_Value.equals(monetize()))
		return;
	
	m_FixAmount = p_Value;
	
//	if (m_Quantity != null 
//			&& m_Quantity.intValue() != 0 
//			&& p_Value != null)
//		m_FixAmount /= m_Quantity.intValue();
//	
//	if (m_Adjustment != null
//			&& m_Adjustment.intValue() != 100
//			&& p_Value != null)
//		m_FixAmount /= m_Adjustment.intValue() / 100.0;
	}

//---------------------------------------------------------------------------
/**
 * Returns the current state of the verification flag of this act. Verification
 * flag is no intrinsic property of acts, i.e it has to be explicitely set to be 
 * defined.
 * @return the applicable majoration
 */
//---------------------------------------------------------------------------

@Transient
public boolean getVerified() 
	{
	return m_Verified;
	}

//---------------------------------------------------------------------------
/**
 * Explicitely sets the state of the verification flag.
 * @param p_Verified specifies the new state of the verification flag.
 */
//---------------------------------------------------------------------------

@Transient
public void setVerified (boolean p_Verified) 
	{
	m_Verified = p_Verified;
	}

//---------------------------------------------------------------------------
/**
* Returns the value of Adjustment (%) of this act
* @return 
*/
//---------------------------------------------------------------------------

@Column (name ="adjustment")

public Integer getAdjustment () 
	{
	return m_Adjustment;
	}

//---------------------------------------------------------------------------
/**
* Sets the value of Adjustment (%) of this act
* @param
*/
//---------------------------------------------------------------------------

public void setAdjustment (Integer p_Adjustment) 
	{
	m_Adjustment = p_Adjustment;
	}

//@Column (name = "order_no")
//public Integer getOrderNo ()
//{
//	return m_OrderNo;
//}
//
//
//public void setOrderNo (Integer p_OrderNo)
//{
//	this.m_OrderNo = p_OrderNo;
//}

//@Transient
//public void fixOrderNo ()
//{
//	m_OrderNo	= m_TempOrderNo;
//}
//
//
//@Transient
//public void setTempOrderNo (int p_OrderNo)
//{
//	this.m_TempOrderNo	= p_OrderNo;
//	if (m_OrderNo == null)
//		m_OrderNo	= m_TempOrderNo;
//}


@Transient
public void setPerformedTime (Date date)
{
	if (date == null) {
		return;
	}
	
	Calendar cal = new GregorianCalendar();
	cal.setTime(date);
	setPerformedTime(cal);
}


@Transient 
public void setPerformedTime (Calendar cal)
{
	if (cal == null || getPerformedCalendar() == null || m_PerformedDate == null) {
		return;
	}
	
	getPerformedCalendar().set(Calendar.HOUR_OF_DAY,	cal.get(Calendar.HOUR_OF_DAY));
	getPerformedCalendar().set(Calendar.MINUTE, 		cal.get(Calendar.MINUTE));
	getPerformedCalendar().set(Calendar.SECOND, 		cal.get(Calendar.SECOND));
	getPerformedCalendar().set(Calendar.MILLISECOND, 	cal.get(Calendar.MILLISECOND));
	m_PerformedDate.setTime(getPerformedCalendar().getTimeInMillis());
}

//---------------------------------------------------------------------------
//***************************************************************************
//* Rule Engine Helpers                                       				*
//***************************************************************************
//---------------------------------------------------------------------------
/**
 * Returns the date corresponding to the first day of the month this act
 * was performed in.
 * @return the first day of the month this act was performed in.
 */
//---------------------------------------------------------------------------

@Transient
public Date getFirstOfPerformedMonth ()
	{
	GregorianCalendar	l_PerformedMonth;
 	
	if (getPerformedCalendar() != null)
		{
//		l_PerformedMonth = new GregorianCalendar();
//		l_PerformedMonth.setTimeInMillis(m_PerformedDate.getTimeInMillis());
//		l_PerformedMonth.set(Calendar.DAY_OF_MONTH,1);
//		l_PerformedMonth.set (Calendar.HOUR_OF_DAY,0);
//		l_PerformedMonth.set (Calendar.MINUTE,0);
//		l_PerformedMonth.set (Calendar.SECOND,0);
//		l_PerformedMonth.set (Calendar.MILLISECOND,0);
		
		l_PerformedMonth = new GregorianCalendar(
				getPerformedCalendar().get(Calendar.YEAR),
				getPerformedCalendar().get(Calendar.MONTH),
				getPerformedCalendar().get(Calendar.DAY_OF_MONTH),
				getPerformedCalendar().get(Calendar.HOUR_OF_DAY),
				getPerformedCalendar().get(Calendar.MINUTE),
				getPerformedCalendar().get(Calendar.SECOND));
		return l_PerformedMonth.getTime();
		}
	else
		{
		return null;
		}
	}

//---------------------------------------------------------------------------
/**
 * Returns the hour of the day this act was performed on. If this act was
 * performed on 22:15, the method will return 22.
 * @return the hour of the day this act was performed on.
 */
//---------------------------------------------------------------------------

@Transient
public Integer getPerformedHour ()
	{
	Integer 			l_Hour;
//	GregorianCalendar	l_TimeOfPerformedDay;
 	
	if (getPerformedCalendar() != null)
		{
//		l_TimeOfPerformedDay = new GregorianCalendar();
//		l_TimeOfPerformedDay.setTime (m_PerformedDate);
//		l_Hour = Integer.valueOf (l_TimeOfPerformedDay.get (Calendar.HOUR_OF_DAY));
		l_Hour = Integer.valueOf(getPerformedCalendar().get(Calendar.HOUR_OF_DAY));
		}
	else
		{
		l_Hour = Integer.valueOf (0);
		}
	
	return l_Hour;
	}

//---------------------------------------------------------------------------
/**
 * Returns the date without time when act was performed
 * @return the performed date of this act stripped of its time
 */
//---------------------------------------------------------------------------

@Transient
public Date getPerformedDay ()
	{
//	return stripTime (this.getPerformedDate());
	return GECAMedUtils.stripTime(getPerformedDate());
	}


/**
 * ONLY USE THIS IN THE RULES.
 * This method needs a RulesObjectsHolder, which is only available in during the billing rules.
 * 
 * @return The result of getPerformedDate() or getPerformedDay(), depending on the RulesObjectsHolder settings.
 */
@Transient
public Date getSessionDate ()
{
	if (m_RulesObjectHolder == null)
	{
		logger.warn("Act.getSessionDate() should only be used in the billing rules.");
		return  GECAMedUtils.stripDate(getPerformedDate(), false, false, false, false, false, true, true);
	}
	else if (m_RulesObjectHolder.isSessionMode())
	{
		return  GECAMedUtils.stripDate(getPerformedDate(), false, false, false, false, false, true, true);
	}
	else
	{
		return getPerformedDay();
	}
}


//---------------------------------------------------------------------------
/**
 * This method allows to find out whether this act was performed on a saturday
 * or not.
 * @return <code>True</code> if this act was performed on a sunday, <code>False
 * </code> otherwise.
 */
//---------------------------------------------------------------------------

@Transient
public Boolean performedOnSaturday ()
	{
//	GregorianCalendar	l_PerformedDay;
 	
	if (getPerformedCalendar() != null)
		{
//		l_PerformedDay = new GregorianCalendar();
//		l_PerformedDay.setTime (m_PerformedDate);
//		if (l_PerformedDay.get (Calendar.DAY_OF_WEEK) == Calendar.SATURDAY)
		if (getPerformedCalendar().get (Calendar.DAY_OF_WEEK) == Calendar.SATURDAY)
			{
			return Boolean.TRUE;
			}
		}
	
	return Boolean.FALSE;
	}

//---------------------------------------------------------------------------
/**
 * This method allows to find out whether this act was performed on a sunday
 * or not.
 * @return <code>True</code> if this act was performed on a sunday, <code>False
 * </code> otherwise.
 */
//---------------------------------------------------------------------------

@Transient
public Boolean performedOnSunday ()
	{
//	GregorianCalendar	l_PerformedDay;
 	
	if (getPerformedCalendar() != null)
		{
//		l_PerformedDay = new GregorianCalendar();
//		l_PerformedDay.setTime (m_PerformedDate);
//		if (l_PerformedDay.get (Calendar.DAY_OF_WEEK) == Calendar.SUNDAY)
		if (getPerformedCalendar().get (Calendar.DAY_OF_WEEK) == Calendar.SUNDAY)
			{
			return Boolean.TRUE;
			}
		}
	
	return Boolean.FALSE;
	}

//---------------------------------------------------------------------------
/**
 * This method allows to find out whether this act was performed on a holiday
 * or not.
 * @return <code>True</code> if this act was performed on a holiday, <code>False
 * </code> otherwise.
 */
//---------------------------------------------------------------------------

@Transient
public Boolean performedOnHoliday ()
	{
 	boolean						l_PerformedOnHoliday = false;
	Date						l_PerformedDate;
 	
 	Iterator <NationalHoliday>	l_HolidayIterator;	
	NationalHoliday				l_Holiday;
 	
	if ( (getPerformedCalendar() != null) && (m_Holidays != null))
		{
		l_PerformedDate = getPerformedDay();
		
		l_HolidayIterator = m_Holidays.iterator();
		while (l_HolidayIterator.hasNext() && !l_PerformedOnHoliday)
			{
			l_Holiday = l_HolidayIterator.next();
			if ((l_PerformedDate.equals (l_Holiday.getDate())) && l_Holiday.getIsLegal())
				l_PerformedOnHoliday = true;
			}
		}
	
	return Boolean.valueOf (l_PerformedOnHoliday);
	}

//---------------------------------------------------------------------------
/**
 * This method checks whether this rate represents an equipment rental rate
  * @return <code>True</code> if rate is a rental rate, <code>False
 * </code> otherwise.
 */
//---------------------------------------------------------------------------

@Transient
public Boolean isRental ()
	{
	Boolean l_isRental;
	
	l_isRental = ((m_Code != null) && (m_Code.endsWith(c_RentalSuffix)))?Boolean.TRUE:Boolean.FALSE;
	
	return l_isRental;
	}

//---------------------------------------------------------------------------
/**
 * This method checks whether this rate represents an equipment rental rate
  * @return <code>True</code> if rate is a rental rate, <code>False
 * </code> otherwise.
 */
//---------------------------------------------------------------------------

@Transient
public Boolean isMaterial ()
	{
	Boolean l_isMaterial;
	
	l_isMaterial = ((m_Code != null) && (m_Code.endsWith(c_MaterialSuffix)))?Boolean.TRUE:Boolean.FALSE;
	
	return l_isMaterial;
	}

//---------------------------------------------------------------------------
/**
 * This method checks whether the nightly rate, i.e. whether suffix N is
 * already set or not.
 * @return <code>True</code> if nighlty rate is already applied, <code>False
 * </code> otherwise.
 */
//---------------------------------------------------------------------------

@Transient
public Boolean nightlyRateAlreadyApplied ()
	{
	Boolean l_AlreadyApplied;
	
	l_AlreadyApplied = Boolean.valueOf (this.suffixAlreadySet('N'));
	
	return l_AlreadyApplied;
	}

//---------------------------------------------------------------------------
/**
 * This method checks whether the sunday rate, i.e. whether suffix D is
 * already set or not.
 * @return <code>True</code> if sunday rate is already applied, <code>False
 * </code> otherwise.
 */
//---------------------------------------------------------------------------

@Transient
public Boolean sundayRateAlreadyApplied ()
	{
	Boolean l_AlreadyApplied;
	
	l_AlreadyApplied = Boolean.valueOf (this.suffixAlreadySet('D'));
	
	return l_AlreadyApplied;
	}

//---------------------------------------------------------------------------
/**
 * This method checks whether the holiday rate, i.e. whether suffix F is
 * already set or not.
 * @return <code>True</code> if holiday rate is already applied, <code>False
 * </code> otherwise.
 */
//---------------------------------------------------------------------------

@Transient
public Boolean holidayRateAlreadyApplied ()
	{
	Boolean l_AlreadyApplied;
	
	l_AlreadyApplied = Boolean.valueOf (this.suffixAlreadySet('F'));
	
	return l_AlreadyApplied;
	}

//---------------------------------------------------------------------------
/**
 * This method checks whether the holiday rate, i.e. whether suffix F is
 * already set or not.
 * @return <code>True</code> if holiday rate is already applied, <code>False
 * </code> otherwise.
 */
//---------------------------------------------------------------------------

@Transient
public Boolean reductionAlreadyApplied ()
	{
	Boolean l_AlreadyApplied;
	
	l_AlreadyApplied = Boolean.valueOf (this.suffixAlreadySet('R'));
	
	return l_AlreadyApplied;
	}

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

@Transient
public Boolean assistanceAlreadyApplied ()
{
	return Boolean.valueOf(suffixAlreadySet('P'));
}

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

@Transient
public Boolean anesthesieAlreadyApplied ()
{
	return Boolean.valueOf(suffixAlreadySet('A'));
}

//---------------------------------------------------------------------------
/**
 * Checks whether this act matches the specified Rate.
 * @param p_Rate specifies the rate to match this act against.
 * @return <code>True</code> if code of this act and of specified rate match
 * <code>false </code> otherwise.
 */
//---------------------------------------------------------------------------

@Transient
public Boolean isRate (Rate p_Rate)
	{
	Boolean l_ActIsRate;
	
	if (p_Rate == null) return Boolean.FALSE;
	
	l_ActIsRate = p_Rate.getCode().equals(this.getCode());
	
	return l_ActIsRate;
	}

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

@Transient
public Physician fetchPhysician ()
	{
	OfficeManagerInterface manager = (OfficeManagerInterface) ManagerFactory.getRemote(OfficeManagerBean.class);
	try
		{
		return manager.getPhysician(m_PhysicianId);
		}
	catch (Exception e)
		{
		logger.log(Level.ERROR, e.getMessage(), e);
		return null;
		}
	}

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

@Transient
public void resetOrgCoefficient ()
	{
	if (m_OrgCoefficient != null)
		{
		m_Coefficient		= m_OrgCoefficient;
		m_OrgCoefficient	= null;
		}
	}


@Transient
public boolean isCnsAct ()
	{
	// get all acts not belonging to the CNS, as this will normally be less
	if (m_NoneCnsCodes == null)
		{
		NomenclatureInterface manager = (NomenclatureInterface) ManagerFactory.getRemote(NomenclatureBean.class);
		m_NoneCnsCodes = manager.getAllPrivateCodes();
		}
	
	return !m_NoneCnsCodes.contains(m_Code);
	}

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

/**
 * @return The value of this act at a specific point in the rule system
 */
@Transient
public Double getRuleValue ()
{
	return m_RuleValue;
}

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

/**
 * @param m_RuleValue The value of this act at a specific point in the rule system
 */
@Transient
public void setRuleValue (Double p_RuleValue)
{
	m_RuleValue = p_RuleValue;
}

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

@Transient
public void clearAllSuffixes()
	{
	m_Suffixes = new String();
	}

@Transient
public String toString () 
{
	return new StringBuilder()
			.append(getQuantity())
			.append("x ")
			.append(getCode())
			.append(getSuffixes())
			.append(" = ")
			.append(getAmount())
//			// for testing:
//			.append(" (fix #")
//			.append(getOrderNo())
//			.append(" -> tmp #")
//			.append(m_TempOrderNo)
//			.append(")")
			.toString();
}


@Transient
public void addValueListener (PropertyChangeListener l)
{
	listeners.add(l);
}


@Transient
public void addValueListener (Collection<PropertyChangeListener> l)
{
	listeners.addAll(l);
}


@Transient
public void removeValueListener (PropertyChangeListener l)
{
	
	// remove all of this listeners
	while (listeners.remove(l))
		;
}


@Transient
public List<PropertyChangeListener> getValueListener ()
{
	return listeners;
}


@Transient
public void updateValueListener (PropertyChangeListener l)
{
	if (!listeners.contains(l))
		addValueListener(l);
}

@Transient
public void setChangedBy(String userName) {
	setUsername(userName + "[" + df.format(new Date()) + "]" + 
			((getUsername() != null)?"\n" + getUsername():""));
}

@Transient
public Integer getChanges() {
	return m_Changes;
}

@Transient
public void setChanges(Integer p_Changes) {
	this.m_Changes = p_Changes;
}

@Transient
public void setRulesObjectHolder (RulesObjectsHolder p_RulesObjectHolder)
{
	m_RulesObjectHolder	= p_RulesObjectHolder;
}

/**
 * The log form is: (<<fieldDescription>>:<<oldValue>>-><<newValue>>;)*
 * 
 * @param a2 The act to compare with
 * @param logLevel Defines the content of the output:<br>
 * <ul>
 * <li>0: Just 'yes', if something changed or <code>null</code> if nothing changed</li>
 * <li>1: The fields, that have changed</li>
 * <li>2: The fields, that have changed, including the new value</li>
 * <li>3: The fields, that have changed, including the old and new value</li>
 * </ul>
 * @return <code>null</code> if nothing has changed, otherwise, the changes as string
 */
@Transient
public static String compareActs (Act a1, Act a2, int logLevel, boolean singleLine)
{
	StringBuilder	changes	= new StringBuilder();
	
	
	// check all relevant fields
	compareLog(changes, logLevel, singleLine, "D",	a1.getPerformedCalendar(),		a2.getPerformedCalendar());
	compareLog(changes, logLevel, singleLine, "C",	a1.getCode(),					a2.getCode());
	compareLog(changes, logLevel, singleLine, "L",	a1.getLabel(),					a2.getLabel());
	compareLog(changes, logLevel, singleLine, "Q",	a1.getQuantity(),				a2.getQuantity());
	compareLog(changes, logLevel, singleLine, "C",	a1.getCoefficient(),			a2.getCoefficient());
	compareLog(changes, logLevel, singleLine, "LC",	a1.getKeyValue(),				a2.getKeyValue());
	compareLog(changes, logLevel, singleLine, "S",	a1.getSuffixes(),				a2.getSuffixes());
	compareLog(changes, logLevel, singleLine, "HC",	a1.getHospitalisationClass(),	a2.getHospitalisationClass());
	compareLog(changes, logLevel, singleLine, "M",	a1.getMajoration(),				a2.getMajoration());
	compareLog(changes, logLevel, singleLine, "A",	a1.getAdjustment(),				a2.getAdjustment());
	compareLog(changes, logLevel, singleLine, "FA",	a1.getFixAmount(),				a2.getFixAmount());
	compareLog(changes, logLevel, singleLine, "MP",	a1.getMedPrescCode(),			a2.getMedPrescCode());
	compareLog(changes, logLevel, singleLine, "P",	a1.getPhysicianId(),			a2.getPhysicianId());
	
	if (changes.length() == 0)
		return null;
	else 
	{
		changes.insert(0, a1.getCode() + (singleLine ? ": " : ": \n"));
		return changes.toString();
	}
}


@Transient
public void setFirstClassRequired (Boolean p_FirstClassRequired)
{
	if (p_FirstClassRequired == null)
		m_FirstClassRequired	= false;
	else
		m_FirstClassRequired	= p_FirstClassRequired.booleanValue();
}


@Transient
private static void compareLog (StringBuilder changes, int logLevel, boolean singleLine, String fieldDescription, Object oldValue, Object newValue)
{
	boolean				hasChanges	= changes.length() > 0;
	LinkedList<String>	logs;
	
	
	if (logLevel == 0 && hasChanges)
		// a change is already logged and this log level will not add anything further
		return;
	
	if (oldValue == null)
	{
		if (newValue == null)
			// nothing has changed
			return;
		// else: something changed - log it
	}
	else if (oldValue.equals(newValue))
	{
		// nothing changed
		return;
	}
	
	logs	= new LinkedList<String>();
	switch (logLevel)
	{
		case 3:
			logs.addLast("->");
			logs.addLast(parseLogValue(newValue));
			
		case 2:
			logs.addFirst(parseLogValue(oldValue));
			logs.addFirst(":");
			
		case 1:
			logs.addFirst(fieldDescription);
			if (hasChanges)
				logs.addFirst(singleLine ? ";" : "\n");
			break;
			
		case 0:
			if (!hasChanges)
				logs.addLast("yes");
			break;
	}
	
	for (String s : logs)
		changes.append(s);
}


@Transient
private static String parseLogValue (Object value)
{
	if (value == null)
		return "null";
	if (value instanceof String
			|| value instanceof Number)
		return value.toString();
	if (value instanceof Calendar)
		return GECAMedUtils.getDateFormatter("yyyy-MM-dd_HH:mm").format(((Calendar) value).getTime());
	
	return value.toString();
}

public String print ()
{
	return new StringBuilder()
		.append(getHospitalisationClass())
		.append(" ")
		.append(GECAMedUtils.getDateFormatter("yyyy-MM-dd HH:mm").format(getPerformedDate()))
		.append(" ")
		.append(getPerformedCalendar().getTimeZone().getDisplayName())
		.append(" ")
		.append(getCode())
		.append(" ")
		.append(getSuffixes())
		.append(": ")
		.append(getAmount())
		.toString();
}

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