net.cbtltd.server.ReservationService.java Source code

Java tutorial

Introduction

Here is the source code for net.cbtltd.server.ReservationService.java

Source

/**
 * @author   bookingnet
 * @see      License at http://razorpms.com/razor/License.html
 * @version   2.00
 */
package net.cbtltd.server;

import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Calendar;
import java.util.Collections;
import java.util.Date;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import net.cbtltd.json.SharedService;
import net.cbtltd.rest.response.QuoteDetail;
import net.cbtltd.server.api.AdjustmentMapper;
import net.cbtltd.server.api.AlertMapper;
import net.cbtltd.server.api.ContactMapper;
import net.cbtltd.server.api.ContractMapper;
import net.cbtltd.server.api.CountryMapper;
import net.cbtltd.server.api.EventMapper;
import net.cbtltd.server.api.IsService;
import net.cbtltd.server.api.LocationMapper;
import net.cbtltd.server.api.PartyMapper;
import net.cbtltd.server.api.PaymentTransactionMapper;
import net.cbtltd.server.api.PriceMapper;
import net.cbtltd.server.api.ProductMapper;
import net.cbtltd.server.api.PropertyManagerInfoMapper;
import net.cbtltd.server.api.ReservationExtMapper;
import net.cbtltd.server.api.ReservationMapper;
import net.cbtltd.server.api.SpecialMapper;
import net.cbtltd.server.api.TaxMapper;
import net.cbtltd.server.api.WorkflowMapper;
import net.cbtltd.server.api.YieldMapper;
import net.cbtltd.server.config.RazorHostsConfig;
import net.cbtltd.server.project.PartyIds;
import net.cbtltd.server.util.CommissionCalculationUtil;
import net.cbtltd.server.util.PaymentHelper;
import net.cbtltd.server.util.PriceUtil;
import net.cbtltd.server.util.ReservationUtil;
import net.cbtltd.server.util.price.FeeCalculationHelper;
import net.cbtltd.server.util.price.PriceComplexValue;
import net.cbtltd.server.util.price.TaxesCalculationHelper;
import net.cbtltd.shared.Adjustment;
import net.cbtltd.shared.Alert;
import net.cbtltd.shared.Contract;
import net.cbtltd.shared.Country;
import net.cbtltd.shared.Data;
import net.cbtltd.shared.DoubleResponse;
import net.cbtltd.shared.Error;
import net.cbtltd.shared.Event;
import net.cbtltd.shared.License;
import net.cbtltd.shared.Location;
import net.cbtltd.shared.ManagerToGateway;
import net.cbtltd.shared.Model;
import net.cbtltd.shared.NameId;
import net.cbtltd.shared.NameIdAction;
import net.cbtltd.shared.Party;
import net.cbtltd.shared.PaymentTransaction;
import net.cbtltd.shared.Price;
import net.cbtltd.shared.Product;
import net.cbtltd.shared.PropertyManagerCancellationRule;
import net.cbtltd.shared.PropertyManagerInfo;
import net.cbtltd.shared.Relation;
import net.cbtltd.shared.Reservation;
import net.cbtltd.shared.ReservationExt;
import net.cbtltd.shared.Serial;
import net.cbtltd.shared.Special;
import net.cbtltd.shared.Table;
import net.cbtltd.shared.Tax;
import net.cbtltd.shared.Time;
import net.cbtltd.shared.Unit;
import net.cbtltd.shared.Workflow;
import net.cbtltd.shared.Yield;
import net.cbtltd.shared.api.HasAlert;
import net.cbtltd.shared.api.HasCollision;
import net.cbtltd.shared.api.HasItem;
import net.cbtltd.shared.api.HasPrice;
import net.cbtltd.shared.finance.gateway.CreditCard;
import net.cbtltd.shared.journal.EventJournal;
import net.cbtltd.shared.party.Organization;
import net.cbtltd.shared.reservation.Available;
import net.cbtltd.shared.reservation.AvailableItem;
import net.cbtltd.shared.reservation.AvailableWidget;
import net.cbtltd.shared.reservation.Brochure;
import net.cbtltd.shared.reservation.BrochurePrice;
import net.cbtltd.shared.reservation.BrochureProduct;
import net.cbtltd.shared.reservation.BrochureRead;
import net.cbtltd.shared.reservation.BrochureUpdate;
import net.cbtltd.shared.reservation.LookBook;
import net.cbtltd.shared.reservation.LookBookSpecial;
import net.cbtltd.shared.reservation.OfflineAccept;
import net.cbtltd.shared.reservation.OfflineRead;
import net.cbtltd.shared.reservation.OfflineReject;
import net.cbtltd.shared.reservation.PriceResponse;
import net.cbtltd.shared.reservation.ReservationCreate;
import net.cbtltd.shared.reservation.ReservationDelete;
import net.cbtltd.shared.reservation.ReservationEntities;
import net.cbtltd.shared.reservation.ReservationEventJournalBalance;
import net.cbtltd.shared.reservation.ReservationEventJournalTable;
import net.cbtltd.shared.reservation.ReservationHistory;
import net.cbtltd.shared.reservation.ReservationPrice;
import net.cbtltd.shared.reservation.ReservationPriceAdjust;
import net.cbtltd.shared.reservation.ReservationRead;
import net.cbtltd.shared.reservation.ReservationTable;
import net.cbtltd.shared.reservation.ReservationUndo;
import net.cbtltd.shared.reservation.ReservationUpdate;
import net.cbtltd.shared.reservation.ReservationWidget;
import net.cbtltd.shared.reservation.WidgetQuote;

import org.apache.commons.lang.StringUtils;
import org.apache.ibatis.session.SqlSession;
import org.apache.log4j.Logger;
import org.joda.time.format.DateTimeFormat;
import org.joda.time.format.DateTimeFormatter;

import com.bookingnet.utils.BPThreadLocal;

/** The Class ReservationService responds to reservation requests. */

public class ReservationService implements IsService {

    public static final Logger LOG = Logger.getLogger(ReservationService.class.getName());
    private static final SimpleDateFormat DF = new SimpleDateFormat("yyyy-MM-dd");
    private static ReservationService service;
    private static DateTimeFormatter dateTimeFormatter = DateTimeFormat.forPattern("yyy-MM-dd");

    /**
     * Gets the single instance of ReservationService to manage Reservation instances.
     * @see net.cbtltd.shared.Reservation
     *
     * @return single instance of ReservationService
     */
    public static synchronized ReservationService getInstance() {
        if (service == null) {
            service = new ReservationService();
        }
        return service;
    }

    /**
     * Executes the OfflineRead action to read an off line Reservation instance.
     * Reads the reservation for its name, state, arrival and departure dates, prices and currency.
     *
     * @param sqlSession the current SQL session.
     * @param action the action to be executed.
     * @return the response.
     */
    public final Reservation execute(SqlSession sqlSession, OfflineRead action) {
        Date timestamp = new Date();
        try {
            return sqlSession.getMapper(ReservationMapper.class).offlineread(action.getId());
        } catch (Throwable x) {
            sqlSession.rollback();
            MonitorService.log(x);
        }
        MonitorService.monitor("OfflineRead", timestamp);
        return action;
    }

    /**
     * Executes the OfflineAccept action to accept an off line reservation.
     * Checks if the reservation has already been accepted or rejected.
     * Cancels the reservation and email the agent or guest if it collides with another.
     *
     * @param sqlSession the current SQL session.
     * @param action the action to be executed.
     * @return the response.
     */
    public final Reservation execute(SqlSession sqlSession, OfflineAccept action) {
        return offline(sqlSession, action, true);
    }

    /**
     * Executes the OfflineReject action to reject an off line reservation.
     * Email the agent or guest accordingly.
     *
     * @param sqlSession the current SQL session.
     * @param action the action to be executed.
     * @return the response.
     */
    public final Reservation execute(SqlSession sqlSession, OfflineReject action) {
        return offline(sqlSession, action, false);
    }

    public static final Reservation offline(SqlSession sqlSession, Reservation action, boolean accept) {
        Date timestamp = new Date();
        try {
            //LOG.debug("OfflineAccept " + action);
            Reservation reservation = sqlSession.getMapper(ReservationMapper.class).read(action.getId());
            if (reservation.hasState(Reservation.State.Initial.name())) {
                reservation.setState(accept ? Reservation.State.Confirmed.name() : Reservation.State.Final.name());
                //            reservationUpdate(sqlSession, reservation);
                if (reservation.hasCollisions()) {
                    reservation.setState(Reservation.State.Final.name());
                }
                sqlSession.getMapper(ReservationMapper.class).update(reservation);
                if (reservation.hasState(Reservation.State.Final.name())) {
                    EmailService.rejectedReservation(sqlSession, reservation);
                } else {
                    EmailService.acceptedReservation(sqlSession, reservation);
                }
                MonitorService.update(sqlSession, Data.Origin.CONSOLE, NameId.Type.Reservation, reservation);
            }
        } catch (Throwable x) {
            sqlSession.rollback();
            MonitorService.log(x);
        }
        MonitorService.monitor("Offline", timestamp);
        return action;
    }

    /**
     * Executes the ReservationWidget action to create a Reservation instance if the manager is not known.
     *
     * @param sqlSession the current SQL session.
     * @param action the action to be executed.
     * @return the response.
     */
    public final Reservation execute(SqlSession sqlSession, ReservationWidget action) {
        Date timestamp = new Date();
        try {
            Product product = sqlSession.getMapper(ProductMapper.class).read(action.getProductid());
            action.setOrganizationid(product.getSupplierid());
            action.setName(SessionService.pop(sqlSession, action.getOrganizationid(), Serial.RESERVATION));
            sqlSession.getMapper(ReservationMapper.class).create(action);
            MonitorService.update(sqlSession, Data.Origin.CONSOLE, NameId.Type.Reservation, action);
        } catch (Throwable x) {
            sqlSession.rollback();
            MonitorService.log(x);
        }
        MonitorService.monitor("ReservationWidget", timestamp);
        return action;
    }

    /**
     * Executes the ReservationCreate action to create a Reservation instance.
     *
     * @param sqlSession the current SQL session.
     * @param action the action to be executed.
     * @return the response.
     */
    public final Reservation execute(SqlSession sqlSession, ReservationCreate action) {
        Date timestamp = new Date();
        //LOG.debug("ReservationCreate in " + action);
        try {
            action.setName(SessionService.pop(sqlSession, action.getOrganizationid(), Serial.RESERVATION));
            sqlSession.getMapper(ReservationMapper.class).create(action);
            MonitorService.update(sqlSession, Data.Origin.CONSOLE, NameId.Type.Reservation, action);
        } catch (Throwable x) {
            sqlSession.rollback();
            MonitorService.log(x);
        }
        //LOG.debug("ReservationCreate out " + action);
        MonitorService.monitor("ReservationCreate", timestamp);
        return action;
    }

    /**
     * Executes the ReservationRead action to read a Reservation instance.
     *
     * @param sqlSession the current SQL session.
     * @param action the action to be executed.
     * @return the response.
     */
    public final Reservation execute(SqlSession sqlSession, ReservationRead action) {
        Date timestamp = new Date();
        //LOG.debug("ReservationRead in " + action);
        Reservation reservation = null;
        try {
            reservation = sqlSession.getMapper(ReservationMapper.class).read(action.getId());
            reservation.setQuotedetail(sqlSession.getMapper(PriceMapper.class).quotedetail(action.getId()));
            reservation.setTasks(TaskService.readbyparentid(sqlSession, action.getId()));
            reservation.setOldstate(reservation.getState());
            getWorkflow(sqlSession, reservation);
            //LOG.debug("ReservationRead out " + reservation);
        } catch (Throwable x) {
            sqlSession.rollback();
            MonitorService.log(x);
        }
        MonitorService.monitor("ReservationRead", timestamp);
        return reservation;
    }

    /* 
     * Sets the previous and next states of the specified reservation based on its work flow rules and current state.
     * 
     * @param sqlSession the current SQL session.
     * @param reservation the specified reservation.
     * @return the workflow
     */
    private static final void getWorkflow(SqlSession sqlSession, Reservation reservation) {
        ArrayList<String> donestates = sqlSession.getMapper(ReservationMapper.class)
                .donestates(reservation.getId());
        ArrayList<Workflow> workflows = sqlSession.getMapper(WorkflowMapper.class)
                .read(reservation.getOrganizationid());
        if (workflows == null || workflows.isEmpty()) {
            return;
        }
        String oldstate = Reservation.State.Provisional.name();
        String duestate = Reservation.State.Departed.name();
        Date now = new Date();
        Date olddate = new Date(0);
        Date duedate = Time.addDuration(now, 1500, Time.DAY); //Archive in 5 years time

        for (Workflow workflow : workflows) {
            if (workflow.notEnabled() || reservation.isAfterState(workflow.getState())) {
                continue;
            }
            //            if (workflow.getEnabled()) {
            //            if (reservation.hasState(workflow.getState())) {continue;}
            Date workflowdate = Time.addDuration(reservation.getWorkflowDate(workflow.getDatename()),
                    workflow.getSignedDuration(), Time.DAY);
            if (donestates != null && donestates.contains(workflow.getState())) {
                if (workflowdate.after(olddate)) {
                    olddate = workflowdate;
                    oldstate = workflow.getState();
                }
            } else {
                if (workflowdate.before(duedate)) {
                    duedate = workflowdate;
                    duestate = workflow.getState();
                }
            }
            //         }
        }
        if (!reservation.hasState(reservation.getOldstate())) {
            reservation.setDuedate(duedate.before(now) ? now : duedate);
        }
        reservation.setOldstate(oldstate);
        reservation.setDuestate(duestate);
    }

    /**
     * Records changes to reservation state.
     * 
     * @param sqlSession the current SQL session.
     * @param reservation the reservation whose state change is to be recorded.
     */
    private static void onStateChange(SqlSession sqlSession, Reservation reservation, Product product) {
        if (reservation.noState() || reservation.hasState(Reservation.State.Initial.name())
        //            || reservation.hadState(reservation.getState())
        ) {
            return;
        }
        Event<HasItem> event = new Event<HasItem>();
        event.setActivity(NameId.Type.Reservation.name());
        event.setActorid(reservation.getActorid());
        event.setAmount(reservation.getQuote());
        event.setDate(new Date());
        event.setName(reservation.getName());
        event.setNotes("Property " + product.getName() + " from  " + DF.format(reservation.getFromdate()) + " to "
                + DF.format(reservation.getTodate()));
        event.setOrganizationid(reservation.getOrganizationid());
        event.setParentid(reservation.getId());
        event.setProcess("StateChange");
        event.setState(reservation.getState());
        event.setType(Event.LOGGING);
        sqlSession.getMapper(EventMapper.class).create(event);
    }

    /**
     * Executes the ReservationUpdate action to update a Reservation instance.
     *
     * @param sqlSession the current SQL session.
     * @param action the action to be executed.
     * @return the response.
     */
    public final Reservation execute(SqlSession sqlSession, ReservationUpdate action) {
        return reservationUpdate(sqlSession, action);
    }

    /**
     * Executes the ReservationUndo action to return a Reservation instance to its previous state.
     *
     * @param sqlSession the current SQL session.
     * @param action the action to be executed.
     * @return the response.
     */
    public final Reservation execute(SqlSession sqlSession, ReservationUndo action) {
        try {
            sqlSession.getMapper(ReservationMapper.class).deleteoldstate(action);
        } catch (Throwable x) {
            sqlSession.rollback();
            MonitorService.log(x);
        }
        return reservationUpdate(sqlSession, action);
    }

    /**
     * Updates the specified Reservation instance if it does not collide with any other reservations.
     * The collision test is done for the product itself and for parent and child properties if the 
     * reserved product is part of a composite property. Text is updated and the next and previous
     * states for the new state are set using he work flow rules of the organization.
     *
     * @param sqlSession the current SQL session.
     * @param reservation the reservation to be updated.
     * @return the updated reservation.
     */
    public static final Reservation reservationUpdate(SqlSession sqlSession, Reservation reservation) {
        Date timestamp = new Date();
        LOG.debug("reservationUpdate in " + reservation);
        try {
            reservation.setCollisions(getCollisions(sqlSession, reservation));

            if (reservation.noCollisions()) {
                Product product = sqlSession.getMapper(ProductMapper.class).read(reservation.getProductid());
                if (product == null) {
                    throw new ServiceException(Error.product_id, reservation.getProductid());
                }
                reservation.setAltpartyid(product.getAltpartyid());
                reservation.setOrganizationid(product.getSupplierid());
                if (reservation.noActorid()) {
                    reservation.setActorid(Party.NO_ACTOR);
                }

                //TODO: extend for other APIs
                if (product.hasAltpartyid(PartyIds.PARTY_INTERHOME_ID)) {
                    Party agent = sqlSession.getMapper(PartyMapper.class).read(reservation.getAgentid());
                    if (agent == null) {
                        throw new ServiceException(Error.party_id, reservation.getAgentid());
                    }
                    agent.setValues(
                            RelationService.read(sqlSession, Relation.PARTY_VALUE, reservation.getAgentid(), null));
                    String interhome = agent.getValue(Party.Value.Interhome.name());
                    if (interhome == null || interhome.isEmpty()) {
                        throw new ServiceException(Error.reservation_api,
                                "You need an Interhome retailer code to book this property - request for a code for RAZOR at partners@interhome.com");
                    }
                    reservation.setAgentname(interhome);
                }

                TextService.update(sqlSession, reservation.getTexts());

                if (reservation.hasState(Reservation.State.Initial.name())) {
                    sqlSession.getMapper(ReservationMapper.class).update(reservation);
                    EmailService.offlineReservation(sqlSession, reservation, null,
                            RazorHostsConfig.getOfflineUrl());
                } else {
                    TaskService.update(sqlSession, reservation.getId(), reservation.getTasks());
                    getWorkflow(sqlSession, reservation);
                    sqlSession.getMapper(ReservationMapper.class).update(reservation);
                    createQuotedetail(sqlSession, reservation);
                    sqlSession.getMapper(SpecialMapper.class).deletequotecollision(reservation);
                    onStateChange(sqlSession, reservation, product);
                    if (reservation.hasState(Reservation.State.Provisional.name())) {
                        if (reservation.hasAltpartyid()) {
                            PartnerService.createReservation(sqlSession, reservation);
                        }
                        if (reservation.isActive() && product.noAltpartyid()) {
                            EmailService.provisionalReservation(sqlSession, reservation);
                        }
                    } else if (reservation.hasState(Reservation.State.Departed.name())) {
                        EmailService.departedReservation(sqlSession, reservation);
                    } else if (reservation.hasAltpartyid()
                            && reservation.hasState(Reservation.State.Confirmed.name())) {
                        PartnerService.confirmReservation(sqlSession, reservation);
                    } else if (reservation.hasAltpartyid()
                            && reservation.hasState(Reservation.State.Cancelled.name())) {
                        PartnerService.cancelReservation(sqlSession, reservation);
                    }
                }
                sqlSession.getMapper(ReservationMapper.class).update(reservation);
                MonitorService.update(sqlSession, Data.Origin.CONSOLE, NameId.Type.Reservation, reservation);
            }
        } catch (Throwable x) {
            sqlSession.rollback();
            reservation.setMessage(x.getMessage());
            MonitorService.log(x);
        }
        //LOG.debug("reservationUpdate out " + reservation);
        MonitorService.monitor("ReservationUpdate", timestamp);
        return reservation;
    }

    /**
     * Gets the reservation collisions.
     *
     * @param sqlSession the current SQL session.
     * @param hascollision the reservation to be checked for collisions.
     * @return the list of collisions with the reservation, if any.
     */
    public static ArrayList<NameId> getCollisions(SqlSession sqlSession, HasCollision hascollision) {
        ArrayList<NameId> collisions = sqlSession.getMapper(ReservationMapper.class).collisions(hascollision);
        Integer minavailable = sqlSession.getMapper(ReservationMapper.class).minavailable(hascollision);
        if (minavailable == null || minavailable <= 0) {
            collisions = new ArrayList<NameId>();
            collisions.add(new NameId("No Available Units", "0"));
            hascollision.setCollisions(collisions);
        }
        //      else if (collisions == null || collisions.size() < minavailable) {reservation.setCollisions(null);}
        //      else {reservation.setCollisions(collisions);}
        else if (collisions == null) {
            hascollision.setCollisions(null);
        } else {
            Integer size = hascollision.getQuantity();
            for (NameId collision : collisions) {
                Reservation reservation = sqlSession.getMapper(ReservationMapper.class).read(collision.getId());
                size += reservation.getQuantity();
            }
            //         LOG.error("getCollisions " + hascollision.getQuantity() + " " + size + " " + minavailable + " " + (size <= minavailable));
            if (size <= minavailable) {
                hascollision.setCollisions(null);
            } else {
                hascollision.setCollisions(collisions);
            }
        }
        hascollision.addCollisions(sqlSession.getMapper(ReservationMapper.class).parentcollisions(hascollision));
        hascollision.addCollisions(sqlSession.getMapper(ReservationMapper.class).childcollisions(hascollision));
        return hascollision.getCollisions();
    }

    /**
     * Gets the number of available units.
     *
     * @param sqlSession the current SQL session.
     * @param hascollision the reservation to be checked for available units.
     * @return the number of available properties.
     */
    public static Integer getAvailable(SqlSession sqlSession, HasCollision hascollision) {
        ArrayList<NameId> collisions = sqlSession.getMapper(ReservationMapper.class).collisions(hascollision);
        Integer minavailable = sqlSession.getMapper(ReservationMapper.class).minavailable(hascollision);
        if (minavailable == null || minavailable <= 0) {
            return 0;
        } else if (collisions == null) {
            return minavailable;
        } else {
            Integer size = 0; //hascollision.getQuantity();
            for (NameId collision : collisions) {
                Reservation reservation = sqlSession.getMapper(ReservationMapper.class).read(collision.getId());
                size += reservation.getQuantity();
            }
            //LOG.error("getAvailable " + hascollision.getQuantity() + " " + size + " " + minavailable + " " + (minavailable - size) + " " + collisions);
            return minavailable - size;
        }
    }

    /**
     * Creates the quote detail items.
     *
     * @param sqlSession the current SQL session.
     * @param hasprice the object that has quoted price.
     */
    private static void createQuotedetail(SqlSession sqlSession, HasPrice hasprice) {
        LOG.debug("getQuotedetail " + hasprice.getQuotedetail());
        Price price = new Price();
        price.setEntitytype(NameId.Type.Reservation.name());
        price.setEntityid(hasprice.getReservationid());
        sqlSession.getMapper(PriceMapper.class).deletebyexample(price);
        price.setState(Price.CREATED);
        price.setMinimum(0.0);
        price.setUnit(Unit.EA);
        if (hasprice.getQuotedetail() != null) {
            for (Price quotedetail : hasprice.getQuotedetail()) {
                price.setQuantity(quotedetail.getQuantity());
                price.setValue(quotedetail.getValue());
                price.setCurrency(quotedetail.getCurrency());
                price.setType(quotedetail.getType());
                price.setName(quotedetail.getName());
                sqlSession.getMapper(PriceMapper.class).create(price);
            }
        }
    }

    /**
     * Executes the ReservationDelete action to delete a Reservation instance.
     * This sets its state to final but does not delete the instance.
     *
     * @param sqlSession the current SQL session.
     * @param action the action to be executed.
     * @return the response.
     */
    public final Reservation execute(SqlSession sqlSession, ReservationDelete action) {
        Date timestamp = new Date();
        try {
            action.setState(Reservation.State.Final.name());
            sqlSession.getMapper(ReservationMapper.class).update(action);
            if (action.hasAltpartyid()) {
                PartnerService.cancelReservation(sqlSession, action);
            }
            MonitorService.update(sqlSession, Data.Origin.CONSOLE, NameId.Type.Reservation, action);
        } catch (Throwable x) {
            sqlSession.rollback();
            MonitorService.log(x);
        }
        MonitorService.monitor("ReservationDelete", timestamp);
        return null;
    }

    /**
     * Executes the NameIdAction action to read a list of reservation NameId instances.
     *
     * @param sqlSession the current SQL session.
     * @param action the action to be executed.
     * @return the response.
     */
    public final Table<NameId> execute(SqlSession sqlSession, NameIdAction action) {
        Date timestamp = new Date();
        Table<NameId> table = new Table<NameId>();
        try {
            if (action.isSuggested()) {
                table.setValue(sqlSession.getMapper(ReservationMapper.class).nameidbyid(action));
            } else {
                table.setValue(sqlSession.getMapper(ReservationMapper.class).nameidbyname(action));
            }
        } catch (Throwable x) {
            sqlSession.rollback();
            MonitorService.log(x);
        }
        MonitorService.monitor("Reservation NameIdAction", timestamp);
        return table;
    }

    /**
     * Executes the LookBookSpecial action to read a list of special offer AvailableItem instances.
     *
     * @param sqlSession the current SQL session.
     * @param action the action to be executed.
     * @return the response.
     */
    public final Table<AvailableItem> execute(SqlSession sqlSession, LookBookSpecial action) {
        Date timestamp = new Date();
        //TODO: test      action.setProductids(sqlSession.getMapper(ReservationMapper.class).productsatposition(action));
        ArrayList<String> productids = sqlSession.getMapper(ReservationMapper.class).productsatposition(action);

        action.setProductids(LicenseService.getLicensed(sqlSession, action.getOrganizationid(), action.getAgentid(),
                productids, License.Type.Console, License.DEFAULT_WAIT));

        if (action.hasAttributes()) {
            action.setProductids(sqlSession.getMapper(ReservationMapper.class).productswithattributes(action));
        }

        ArrayList<AvailableItem> availableitems = sqlSession.getMapper(SpecialMapper.class).speciallist(action);
        //LOG.debug("ReservationService LookBookSpecial availableitems " + availableitems);
        Table<AvailableItem> table = new Table<AvailableItem>();
        if (availableitems == null) {
            return table;
        }
        ArrayList<AvailableItem> result = new ArrayList<AvailableItem>();
        int count = 0;
        for (AvailableItem availableitem : availableitems) {

            availableitem.setPerson(action.getCount());
            resetPrice(sqlSession, availableitem, action.getPriceunit());
            resetCurrency(sqlSession, availableitem, action.getCurrency());
            availableitem.setCost((availableitem.getQuote() - availableitem.getExtra())
                    * getDiscountfactor(sqlSession, availableitem));

            if (availableitem.noPrice() || availableitem.getQuote() < action.getQuotemin()
                    || availableitem.getQuote() > action.getQuotemax() || availableitem.getCost() < 0.0
                    || ((availableitem.getQuote() - availableitem.getCost()) < (availableitem.getQuote()
                            * action.getDiscount() / 100.0))) {
                continue;
            }
            setAlert(sqlSession, availableitem, availableitem.getFromdate(), availableitem.getTodate());
            if (count++ >= action.getStartrow() && result.size() < action.getNumrows()) {
                result.add(availableitem);
            }
        }
        Collections.sort(result);
        table.setDatasize(count);
        table.setValue(result);
        MonitorService.monitor("LookBookSpecial", timestamp);
        return table;
    }

    /**
     * Executes the LookBook action to read a list of normal AvailableItem instances.
     *
     * @param sqlSession the current SQL session.
     * @param action the action to be executed.
     * @return the response.
     */
    public final Table<AvailableItem> execute(SqlSession sqlSession, LookBook action) {
        Date timestamp = new Date();
        Table<AvailableItem> table = new Table<AvailableItem>();
        try {
            //TODO: test         action.setProductids(sqlSession.getMapper(ReservationMapper.class).productsatposition(action));
            ArrayList<String> productids = sqlSession.getMapper(ReservationMapper.class).productsatposition(action);
            action.setProductids(LicenseService.getLicensed(sqlSession, action.getOrganizationid(),
                    action.getAgentid(), productids, License.Type.Console, License.DEFAULT_WAIT));

            if (action.hasAttributes()) {
                action.setProductids(sqlSession.getMapper(ReservationMapper.class).productswithattributes(action));
            } // properties meeting criteria

            if (action.hasDuration()) {
                action.setCollisions(sqlSession.getMapper(ReservationMapper.class).productcollisions(action)); //remove properties that are not available between the dates
                action.removeCollisions(sqlSession.getMapper(ReservationMapper.class).productnocollisions(action)); //add back multiple unit collisions where product.quantity > count(reservations)
            }

            if (action.noProductids()) {
                return table;
            }

            ArrayList<AvailableItem> availableitems = sqlSession.getMapper(ReservationMapper.class)
                    .lookbookitems(action);
            if (availableitems == null) {
                return table;
            }
            ArrayList<AvailableItem> priceditems = new ArrayList<AvailableItem>();
            int row = 0;
            for (AvailableItem availableitem : availableitems) {
                if (priceditems.size() >= action.getNumrows()) {
                    break;
                }
                if (row++ >= action.getStartrow()) {
                    availableitem.setPerson(action.getCount());
                    resetPrice(sqlSession, availableitem, action.getPriceunit());
                    resetCurrency(sqlSession, availableitem, action.getCurrency());
                    availableitem.setCost((availableitem.getQuote() - availableitem.getExtra())
                            * getDiscountfactor(sqlSession, availableitem));

                    if (availableitem.noPrice() || availableitem.noQuote()
                            || availableitem.getQuote() < action.getQuotemin()
                            || availableitem.getQuote() > action.getQuotemax() || availableitem.getCost() < 0.0
                            || ((availableitem.getQuote() - availableitem.getCost()) < (availableitem.getQuote()
                                    * action.getDiscount() / 100.0))) {
                        continue;
                    }

                    setAlert(sqlSession, availableitem, availableitem.getFromdate(), availableitem.getTodate());
                    priceditems.add(availableitem);
                }
            }

            Collections.sort(priceditems);

            table.setDatasize(priceditems.size());
            table.setValue(priceditems);
        } catch (Throwable x) {
            sqlSession.rollback();
            MonitorService.log(x);
        }
        MonitorService.monitor("LookBook", timestamp);
        return table;
    }

    /*
     * Sets the alert string if one or more applies to the date range.
     * 
     * @param sqlSession the current SQL session.
     * @param availableitem the available item for the alert.
     */
    private static void setAlert(SqlSession sqlSession, AvailableItem availableitem, Date fromdate, Date todate) {
        if (availableitem == null) {
            return;
        }
        Alert action = new Alert();
        action.setEntitytype(NameId.Type.Product.name());
        action.setEntityid(availableitem.getProductid());
        action.setFromdate(fromdate);
        action.setTodate(todate);
        ArrayList<Alert> alerts = sqlSession.getMapper(AlertMapper.class).exists(action);
        if (alerts == null || alerts.isEmpty()) {
            availableitem.setAlert(null);
        } else {
            StringBuilder sb = new StringBuilder();
            for (Alert alert : alerts) {
                sb.append(DF.format(alert.getFromdate())).append(" - ").append(DF.format(alert.getTodate()))
                        .append(" ").append(alert.getName()).append("\n");
            }
            availableitem.setAlert(sb.toString());
        }
    }

    /*
     * Sets the alert string if one or more applies to the date range.
     * 
     * @param sqlSession the current SQL session.
     * @param availableitem the available item for the alert.
     */
    private static ArrayList<Alert> getAlerts(SqlSession sqlSession, HasAlert hasalert) {
        Alert alert = new Alert();
        alert.setEntitytype(hasalert.getEntitytype());
        alert.setEntityid(hasalert.getEntityid());
        alert.setFromdate(hasalert.getFromdate());
        alert.setTodate(hasalert.getTodate());
        return sqlSession.getMapper(AlertMapper.class).exists(alert);
    }

    /*
     * Resets the prices of the specified HasPrice (reservation or available item) instance for a night or the whole stay.
     * 
     * @param sqlSession the current SQL session.
     * @param hasprice the reservation or available item to be priced.
     * @param priceunit the price unit is true if prices are for the whole stay, or false if prices are per night. 
     */
    /**
     * Reset price.
     *
     * @param sqlSession the sql session
     * @param hasprice the hasprice
     * @param priceunit the priceunit
     */
    private void resetPrice(SqlSession sqlSession, HasPrice hasprice, Boolean priceunit) {
        Date timestamp = new Date();
        if (hasprice.getDuration(Time.DAY) <= 0) {
            return;
        }

        computePrice(sqlSession, hasprice, null);
        Double discountfactor = getDiscountfactor(sqlSession, hasprice);
        hasprice.setCost((hasprice.getQuote() - hasprice.getExtra()) * discountfactor);

        if (!hasprice.noPrice() && !priceunit) { //false = night true = stay
            Double unitfactor = 1.0 / hasprice.getDuration(Time.DAY);
            hasprice.setPrice(hasprice.getPrice() * unitfactor);
            hasprice.setQuote(hasprice.getQuote() * unitfactor);
            hasprice.setExtra(hasprice.getExtra() * unitfactor);
            hasprice.setCost(hasprice.getCost() * unitfactor);
        }
        hasprice.setPriceunit(priceunit);

        MonitorService.monitor("ResetPrice", timestamp);
    }

    /*
     * Resets the prices of the specified HasPrice (reservation or available item) instance to the specified currency.
     * 
     * @param sqlSession the current SQL session.
     * @param hasprice the reservation or available item to be priced.
     * @param tocurrency the code of the currency of the prices. 
     */
    /**
     * Reset currency.
     *
     * @param sqlSession the sql session
     * @param hasprice the hasprice
     * @param tocurrency the tocurrency
     */
    private void resetCurrency(SqlSession sqlSession, HasPrice hasprice, String tocurrency) {
        Date timestamp = new Date();
        if (hasprice == null || hasprice.hasCurrency(tocurrency)) {
            return;
        }
        Double exchangerate = WebService.getRate(sqlSession, hasprice.getCurrency(), tocurrency, new Date());
        hasprice.setPrice(hasprice.getPrice() * exchangerate);
        hasprice.setQuote(hasprice.getQuote() * exchangerate);
        hasprice.setCurrency(tocurrency);
        MonitorService.monitor("ResetCurrency", timestamp);
    }

    /**
     * Executes the WidgetQuote action to get a PriceResponse instance of a reservation.
     * 
     * @param sqlSession the current SQL session.
     * @param action the action to be executed.
     * @return the price response.
     */
    public final PriceResponse execute(SqlSession sqlSession, WidgetQuote action) {
        Date timestamp = new Date();
        Product product = sqlSession.getMapper(ProductMapper.class).read(action.getProductid());
        action.setOrganizationid(product.getSupplierid());
        PriceResponse result = getPriceResponse(sqlSession, action);
        MonitorService.monitor("WidgetQuote", timestamp);
        return result;
    }

    /**
     * Executes the ReservationPrice action to get a PriceResponse instance of a reservation.
     * 
     * @param sqlSession the current SQL session.
     * @param action the action to be executed.
     * @return the price response.
     */
    public final PriceResponse execute(SqlSession sqlSession, ReservationPrice action) {
        Date timestamp = new Date();
        PriceResponse result = getPriceResponse(sqlSession, action);
        MonitorService.monitor("ReservationPrice", timestamp);
        return result;
    }

    /**
     * Executes the ReservationEventJournalTable action to read a table of EventJournal (journal event) instances.
     * @see net.cbtltd.shared.journal.EventJournal
     *
     * @param sqlSession the current SQL session.
     * @param action the action to be executed.
     * @return the response.
     */
    public final Table<EventJournal> execute(SqlSession sqlSession, ReservationEventJournalTable action) {
        Date timestamp = new Date();
        Table<EventJournal> table = new Table<EventJournal>();
        try {
            table.setDatasize(sqlSession.getMapper(EventMapper.class).countbyreservation(action.getId()));
            table.setValue(sqlSession.getMapper(EventMapper.class).listbyreservation(action.getId()));
        } catch (Throwable x) {
            sqlSession.rollback();
            MonitorService.log(x);
        }
        MonitorService.monitor("ReservationEventJournalTable", timestamp);
        return table;
    }

    /**
     * Executes the ReservationActionBalance action to get the financial balance of a reservation.
     *
     * @param sqlSession the current SQL session.
     * @param action the action to be executed.
     * @return the double financial balance of a reservation.
     */
    public final DoubleResponse execute(SqlSession sqlSession, ReservationEventJournalBalance action) {
        Date timestamp = new Date();
        DoubleResponse response = new DoubleResponse();
        response.setValue(sqlSession.getMapper(EventMapper.class).balancebyreservation(action.getId()));
        MonitorService.monitor("ReservationEventJournalBalance", timestamp);
        return response;
    }

    /**
     * Gets the price response.
     *
     * @param sqlSession the sql session
     * @param action the action
     * @return the price response
     */
    private PriceResponse getPriceResponse(SqlSession sqlSession, ReservationPrice action) {
        Date timestamp = new Date();
        PriceResponse response = new PriceResponse();
        try {
            computePrice(sqlSession, action, null);
            response.setValue(action.getPrice());
            response.setQuote(action.getQuote()); //yielded
            response.setQuotedetail(action.getQuotedetail());
            response.setExtra(action.getExtra());
            response.setCurrency(action.getCurrency());
            Double discountfactor = getDiscountfactor(sqlSession, action);
            response.setDiscountfactor(discountfactor);
            response.setCost((response.getQuote() - response.getExtra()) * discountfactor);
            response.setDeposit(getDeposit(sqlSession, action)); //.getOrganizationid(), action.getFromdate()));
            response.setCollisions(getCollisions(sqlSession, action));
            response.setAlerts(getAlerts(sqlSession, action));
        } catch (Throwable x) {
            sqlSession.rollback();
            MonitorService.log(x);
        }
        MonitorService.monitor("ReservationPrice", timestamp);
        return response;
    }

    /**
     * Gets the deposit percentage required by a property manager (organization) on the specified date
     *
     * @param sqlSession the current SQL session.
     * @param reservation the reservation for which the deposit percentage is to be calculated.
     * @return the deposit percentage.
     */
    public static final Double getDeposit(SqlSession sqlSession, Reservation reservation) {
        String temp = RelationService.value(sqlSession, Relation.PARTY_VALUE, reservation.getOrganizationid(),
                Party.Value.Payfull.name());
        Integer payfull = temp == null ? 30 : Integer.valueOf(temp);
        temp = RelationService.value(sqlSession, Relation.PARTY_VALUE, reservation.getOrganizationid(),
                Party.Value.Payunit.name());
        String unit = temp == null ? Unit.DAY : temp;
        if (Time.WEEK.name().equalsIgnoreCase(unit)) {
            payfull *= 7;
        } else if (Time.MONTH.name().equalsIgnoreCase(unit)) {
            payfull *= 30;
        }
        if (Time.getDuration(new Date(), reservation.getFromdate(), Time.DAY) <= payfull) {
            return 100.;
        }

        String type = RelationService.value(sqlSession, Relation.PARTY_VALUE, reservation.getOrganizationid(),
                Party.Value.DepositType.name());
        if (type == null) {
            type = Party.DEPOSITS[1];
        }
        temp = RelationService.value(sqlSession, Relation.PARTY_VALUE, reservation.getOrganizationid(),
                Party.Value.Deposit.name());
        Double value = temp == null ? 0.0 : Double.valueOf(temp);
        //      if (Party.DEPOSITS[0].equalsIgnoreCase(type)) {value = (value / 100.0) * reservation.getQuote();} // % Booking Value
        if (Party.DEPOSITS[1].equalsIgnoreCase(type)) {
            value = (value / 100.0) * reservation.getQuote() / reservation.getDuration(Time.DAY);
        } // % Daily Rate
        else if (Party.DEPOSITS[2].equalsIgnoreCase(type)) {
            ;
        } // Amount per Booking
        else if (Party.DEPOSITS[3].equalsIgnoreCase(type)) {
            value = value * reservation.getDuration(Time.DAY);
        } // Amount per Day
        else {
            value = (value / 100.0) * reservation.getQuote();
        } // % Booking Value
        LOG.debug("" + type + " " + temp + " " + value + " " + reservation.getQuote() + " "
                + (value * 100.0 / reservation.getQuote()) + " " + reservation.getDuration(Time.DAY));
        value = value * 100.0 / reservation.getQuote();
        return value.doubleValue();
    }

    /**
     * Gets the deposit percentage required by a property manager (organization) on the specified date.
     * 
     * @param reservation the reservation for which the deposit percentage is to be calculated.
     * @param propertyManagerInfo property manager that is responsible for the reservation.
     * @return the deposit percentage.
     */
    public static final Double getDeposit(Reservation reservation, PropertyManagerInfo propertyManagerInfo) {
        if (propertyManagerInfo.getNumberOfPayments() == null) {
            throw new ServiceException(Error.database_cannot_find, "numberOfPayments in property manager info");
        }
        // 1. Check number_of_payments, return 100 if 1 payment should be processed
        if (propertyManagerInfo.getNumberOfPayments() == 1) {
            return 100.;
        }

        // 2. Return 100 if current date is between payment remainder date and arrival date 
        Integer secondPaymentRemainder = propertyManagerInfo.getRemainderPaymentDate();
        if (Time.getDuration(new Date(), reservation.getFromdate(), Time.DAY) <= secondPaymentRemainder) {
            return 100.;
        }

        // 3. Calculate deposit percentage regarding the payment rules
        Integer paymentType = propertyManagerInfo.getPaymentType();
        switch (paymentType) {
        case 1:
            return propertyManagerInfo.getPaymentAmount().doubleValue();
        case 2:
            Double percentage = propertyManagerInfo.getPaymentAmount() / reservation.getQuote() * 100.;
            return percentage.doubleValue();
        default:
            throw new ServiceException(Error.payment_type_unsupported);
        }
    }

    /**
     * Gets the payment type required by a property manager for deposits and full payment.
     *
     * @param sqlSession the current SQL session.
     * @param organizationid the ID of the property manager.
     * @return the payment policy.
     */
    public static final String getPaytype(SqlSession sqlSession, String organizationid) {
        String value = RelationService.value(sqlSession, Relation.PARTY_VALUE, organizationid,
                Party.Value.Paytype.name());
        return value == null ? Organization.Paytype.None.name() : value;
    }

    /**
     * Gets the discount factor available to an agency (agent) from a property manager (organization).
     *
     * @param sqlSession the current SQL session.
     * @param hasprice the reservation.
     * @return the discount factor modified by a contracted discount if it exists.
     */
    public static final Double getDiscountfactor(SqlSession sqlSession, HasPrice hasprice) {
        Double discountfactor = 1.0;
        if (hasprice.noAgentid()) {
            return discountfactor;
        }
        Contract contract = new Contract(NameId.Type.Reservation.name(), hasprice.getSupplierid(),
                hasprice.getAgentid());
        contract = sqlSession.getMapper(ContractMapper.class).readbyexample(contract);
        if (contract == null) {
            Product product = sqlSession.getMapper(ProductMapper.class).read(hasprice.getProductid());
            discountfactor = (product == null || product.getDiscount() == null) ? 1.0
                    : (100.0 - product.getDiscount()) / 100.0;
        } else if (contract.hasState(Contract.SUSPENDED)) {
            discountfactor = -1.0;
        } else
            discountfactor = (100.0 - contract.getDiscount()) / 100.0;
        return discountfactor;
    }

    /**
     * Adjusts the quoted price for the specified property, agent, date range and currency.
     *
     * @param sqlSession the current SQL session.
     * @param action an item that can be priced.
     */
    public static final PriceResponse execute(SqlSession sqlSession, ReservationPriceAdjust action) {
        Date timestamp = new Date();
        PriceResponse response = new PriceResponse();
        try {
            Double newquote = 0.0;
            ArrayList<Price> newquotedetail = new ArrayList<Price>();
            if (action.getQuote() >= 0.01) {
                Double oldquote = 0.0;
                Double oldtax = 0.0;
                for (Price price : action.getQuotedetail()) {
                    if (price.hasType(Price.TAX_INCLUDED) || price.hasType(Price.TAX_EXCLUDED)
                            || price.hasType(Price.TAX_ON_TAX)) {
                        oldtax += price.getTotalvalue();
                    }
                    oldquote += price.getTotalvalue();
                }
                Double taxratio = action.getQuote() / oldquote;

                for (Price price : action.getQuotedetail()) {
                    if (price.hasType(Price.MANDATORY) || price.hasType(Price.OPTIONAL)) {
                        newquotedetail.add(price);
                        newquote += price.getTotalvalue();
                    } else if (price.hasType(Price.TAX_INCLUDED) || price.hasType(Price.TAX_EXCLUDED)
                            || price.hasType(Price.TAX_ON_TAX)) {
                        price.setValue(NameId.round(price.getValue() * taxratio));
                        newquotedetail.add(price);
                        newquote += price.getTotalvalue();
                    }
                }
                Double value = action.getQuote() - newquote;
                Double quantity = action.getDuration(Time.DAY);
                Price price = new Price();
                price.setId(Model.ZERO);
                price.setEntitytype(NameId.Type.Reservation.name());
                price.setEntityid(action.getReservationid());
                price.setName(Price.ADJUSTED);
                price.setType(Price.RATE);
                price.setPartyname("");
                price.setQuantity(quantity);
                price.setUnit(Time.DAY.name());
                price.setValue(value / quantity);
                price.setCurrency(action.getCurrency());
                newquotedetail.add(price);
                newquote += price.getTotalvalue();
            }
            response.setValue(action.getPrice());
            response.setQuote(newquote); //adjusted
            response.setQuotedetail(newquotedetail);
            response.setExtra(action.getExtra());
            response.setCurrency(action.getCurrency());
            Double discountfactor = getDiscountfactor(sqlSession, action);
            response.setDiscountfactor(discountfactor);
            response.setCost((response.getQuote() - response.getExtra()) * discountfactor);
            response.setAlerts(getAlerts(sqlSession, action));
        } catch (Throwable x) {
            sqlSession.rollback();
            MonitorService.log(x);
        }
        MonitorService.monitor("ReservationPriceAdjust", timestamp);
        return response;
    }

    private static boolean hasMinimum = true;

    /**
     * Gets the quoted or yielded price for the specified property, agent, date range and currency.
     * Applies yield management rules if yielded is true. 
     *
     * @param sqlSession the current SQL session.
     * @param hasPrice the item that can be priced.
     */
    public static final net.cbtltd.rest.payment.ReservationPrice computePrice(SqlSession sqlSession,
            HasPrice hasPrice, String currency) {
        net.cbtltd.rest.payment.ReservationPrice reservationPrice = new net.cbtltd.rest.payment.ReservationPrice();
        List<QuoteDetail> quoteDetails = new ArrayList<QuoteDetail>();
        reservationPrice.setTotal(0.0);

        Date timestamp = new Date();
        PriceComplexValue priceValue = new PriceComplexValue();
        hasMinimum = true;

        Product product = sqlSession.getMapper(ProductMapper.class).read(hasPrice.getProductid());
        PropertyManagerInfo propertyManagerInfo = sqlSession.getMapper(PropertyManagerInfoMapper.class)
                .readbypmid(Integer.valueOf(product.getSupplierid()));
        hasPrice.setSupplierid(product.getSupplierid());

        hasPrice.setQuotedetail(new ArrayList<Price>());
        priceValue = addPriceAdjustments(sqlSession, hasPrice, priceValue);
        priceValue = addTotalPrice(sqlSession, hasPrice, priceValue);

        if (priceValue == null) {
            return reservationPrice;
        }

        priceValue.setTotPrice(priceValue.getTotPrice() * hasPrice.getQuantity());
        priceValue.setTotQuote(priceValue.getTotQuote() * hasPrice.getQuantity());

        // Util class for commission calculation (NetRate, CreditCardFee)
        CommissionCalculationUtil commissionUtil = new CommissionCalculationUtil(sqlSession, hasPrice,
                priceValue.getTotPrice(), 0.0);
        Double newPublishedPrice = 0.0;

        if (commissionUtil.isNetRate()) {

            // Save nightly rate quote for future calculation
            hasPrice.setNightlyrate(priceValue.getTotPrice());

            // Calculate new NetRates prices
            newPublishedPrice = commissionUtil.getNewPublishedNightlyRate();
            Double newPublichedPriceCCFee = commissionUtil.getCreditCardFeeValue(newPublishedPrice);
            priceValue.setTotPrice(newPublishedPrice + newPublichedPriceCCFee);

            commissionUtil.setPriceAmount(priceValue.getTotQuote()); // Price + Yields
            Double newQuoteAmount = commissionUtil.getNewPublishedNightlyRate();

            if (commissionUtil.isPMFundsHolder()) {
                newQuoteAmount += newPublichedPriceCCFee;
            }

            priceValue.setTotQuote(newQuoteAmount);
            /*Double newPublishedPriceAmount = commissionUtil.getNewPublishedNightlyRate();
            Double newPublichedPriceCCFee = commissionUtil.getCreditCardFeeValue(newPublishedPriceAmount);
                
            priceValue.setTotPrice(newPublishedPriceAmount + newPublichedPriceCCFee);
            priceValue.setTotQuote(newPublishedPriceAmount);*/
        }

        priceValue = addPriceFeatures(sqlSession, hasPrice, priceValue);
        // new version of Fees from Fee table (Streamline)
        priceValue = FeeCalculationHelper.addTaxableFees(sqlSession, hasPrice, priceValue);

        //  taxes with relations (will be deprecated)
        priceValue = addPriceTaxes(sqlSession, hasPrice, priceValue);
        // new type of taxes (Streamline)
        priceValue = TaxesCalculationHelper.addTaxes(sqlSession, hasPrice, priceValue);

        priceValue = addNotTaxedPrices(sqlSession, hasPrice, priceValue);
        // new version of Fees from Fee table (Streamline)
        priceValue = FeeCalculationHelper.addNotTaxableFees(sqlSession, hasPrice, priceValue);

        // Damage insurance and cleaning fee adding
        if (product.getSecuritydeposit() != null) {
            Double depositValue = product.getSecuritydeposit();
            priceValue.addTotalQuote(depositValue);
            priceValue.addTotalExtra(depositValue);
        }
        if (product.getCleaningfee() != null) {
            Double feeValue = product.getCleaningfee();
            priceValue.addTotalQuote(feeValue);
            priceValue.addTotalExtra(feeValue);
        }

        // add special mandatory Fees from Fee table (Streamline, ...)
        priceValue = FeeCalculationHelper.addSpecialFees(sqlSession, hasPrice, priceValue);

        if (commissionUtil.isNetRate() && commissionUtil.isBPFundsHolder()) {

            commissionUtil.setExtraAmount(priceValue.getTotExtra());

            //Double newPublishedQuoteAmount = priceValue.getTotQuote() - priceValue.getTotExtra();
            Double newPublishedQuoteCCFee = commissionUtil.getCreditCardFeeValue(newPublishedPrice);

            priceValue.addTotalQuote(newPublishedQuoteCCFee);
        }

        LOG.debug("computePrice getQuotedetail " + hasPrice.getQuotedetail());
        hasPrice.setPrice(priceValue.getTotPrice()); /*hasprice.setPrice(totPrice);*/
        hasPrice.setQuote(priceValue.getTotQuote()); /*hasprice.setQuote(totQuote);*/
        hasPrice.setExtra(priceValue.getTotExtra()); /*hasprice.setExtra(totExtra);*/

        quoteDetails = ReservationUtil.getReservationPriceQuoteDetails(hasPrice);
        quoteDetails.add(
                new QuoteDetail(String.valueOf(priceValue.getTotQuote()), currency, "Total quote", "", "", false));

        if (propertyManagerInfo != null && propertyManagerInfo.getAdditionalCommission() != null
                && propertyManagerInfo.getFundsHolder() == ManagerToGateway.PROPERTY_MANAGER_HOLDER) {
            PriceUtil.addCommission(hasPrice, propertyManagerInfo.getAdditionalCommission() / 100.);
        }

        reservationPrice.setPrice(priceValue.getTotPrice());
        reservationPrice.setTotal(priceValue.getTotQuote());
        reservationPrice.setCurrency(hasPrice.getCurrency());
        reservationPrice.setQuoteDetails(quoteDetails);

        MonitorService.monitor("computePrice", timestamp);

        if (currency != null) {
            hasPrice = PriceUtil.convertCurrency(sqlSession, hasPrice, currency);
            reservationPrice = PriceUtil.convertCurrency(sqlSession, reservationPrice, currency);
        }

        return reservationPrice;
    }

    /**
     * Gets the quoted or yielded price for the specified property, agent, date range and currency during search process.
     * Applies yield management rules if yielded is true. 
     *
     * @param sqlSession the current SQL session.
     * @param hasPrice the item that can be priced.
     */
    public static final net.cbtltd.rest.payment.ReservationPrice computeLivePrice(SqlSession sqlSession,
            HasPrice hasPrice, net.cbtltd.rest.payment.ReservationPrice reservationPrice, String currency) {

        if (hasPrice == null || hasPrice.getPrice() == null || hasPrice.getPrice() <= 0) {
            return reservationPrice;
        }

        Date timestamp = new Date();
        PriceComplexValue priceValue = new PriceComplexValue();

        priceValue.setTotPrice(hasPrice.getPrice());
        priceValue.setTotQuote(hasPrice.getPrice());

        Product product = sqlSession.getMapper(ProductMapper.class).read(hasPrice.getProductid());
        PropertyManagerInfo propertyManagerInfo = sqlSession.getMapper(PropertyManagerInfoMapper.class)
                .readbypmid(Integer.valueOf(product.getSupplierid()));
        hasPrice.setSupplierid(product.getSupplierid());

        if (hasPrice.getQuantity() != null) {
            priceValue.setTotPrice(priceValue.getTotPrice() * hasPrice.getQuantity());
            priceValue.setTotQuote(priceValue.getTotQuote() * hasPrice.getQuantity());
        }

        // Util class for commission calculation (NetRate, CreditCardFee)
        CommissionCalculationUtil commissionUtil = new CommissionCalculationUtil(sqlSession, hasPrice,
                priceValue.getTotPrice(), 0.0);

        if (commissionUtil.isNetRate()) {
            //return reservationPrice;

            reservationPrice = new net.cbtltd.rest.payment.ReservationPrice();
            List<QuoteDetail> quoteDetails = new ArrayList<QuoteDetail>();
            reservationPrice.setTotal(0.0);

            hasPrice.setQuotedetail(new ArrayList<Price>());

            // Save nightly rate quote for future calculation
            hasPrice.setNightlyrate(priceValue.getTotPrice());

            // Calculate new NetRates prices
            Double newPublishedPrice = commissionUtil.getNewPublishedNightlyRate();
            Double newPublichedPriceCCFee = commissionUtil.getCreditCardFeeValue(newPublishedPrice);
            priceValue.setTotPrice(newPublishedPrice + newPublichedPriceCCFee);

            if (commissionUtil.isPMFundsHolder()) {
                priceValue.setTotQuote(newPublishedPrice + newPublichedPriceCCFee);
            } else {
                priceValue.setTotQuote(newPublishedPrice);
            }

            priceValue = addPriceFeatures(sqlSession, hasPrice, priceValue);
            // new version of Fees from Fee table (Streamline)
            priceValue = FeeCalculationHelper.addTaxableFees(sqlSession, hasPrice, priceValue);

            //  taxes with relations (will be deprecated)
            priceValue = addPriceTaxes(sqlSession, hasPrice, priceValue);
            // new type of taxes (Streamline)
            priceValue = TaxesCalculationHelper.addTaxes(sqlSession, hasPrice, priceValue);

            priceValue = addNotTaxedPrices(sqlSession, hasPrice, priceValue);
            // new version of Fees from Fee table (Streamline)
            priceValue = FeeCalculationHelper.addNotTaxableFees(sqlSession, hasPrice, priceValue);

            // Damage insurance and cleaning fee adding
            if (product.getSecuritydeposit() != null) {
                Double depositValue = product.getSecuritydeposit();
                priceValue.addTotalQuote(depositValue);
                priceValue.addTotalExtra(depositValue);
            }
            if (product.getCleaningfee() != null) {
                Double feeValue = product.getCleaningfee();
                priceValue.addTotalQuote(feeValue);
                priceValue.addTotalExtra(feeValue);
            }

            // add special mandatory Fees from Fee table (Streamline, ...)
            priceValue = FeeCalculationHelper.addSpecialFees(sqlSession, hasPrice, priceValue);

            if (commissionUtil.isBPFundsHolder()) {

                commissionUtil.setExtraAmount(priceValue.getTotExtra());
                Double newPublishedQuoteCCFee = commissionUtil.getCreditCardFeeValue(newPublishedPrice);
                priceValue.addTotalQuote(newPublishedQuoteCCFee);
            }

            LOG.debug("computePrice getQuotedetail " + hasPrice.getQuotedetail());
            hasPrice.setPrice(priceValue.getTotPrice()); /*hasprice.setPrice(totPrice);*/
            hasPrice.setQuote(priceValue.getTotQuote()); /*hasprice.setQuote(totQuote);*/
            hasPrice.setExtra(priceValue.getTotExtra()); /*hasprice.setExtra(totExtra);*/

            quoteDetails = ReservationUtil.getReservationPriceQuoteDetails(hasPrice);
            quoteDetails.add(new QuoteDetail(String.valueOf(priceValue.getTotQuote()), currency, "Total quote", "",
                    "", false));
            reservationPrice.setQuoteDetails(quoteDetails);

            reservationPrice.setCurrency(hasPrice.getCurrency());

        }

        if (propertyManagerInfo != null && propertyManagerInfo.getAdditionalCommission() != null
                && propertyManagerInfo.getFundsHolder() == ManagerToGateway.PROPERTY_MANAGER_HOLDER) {
            PriceUtil.addCommission(hasPrice, propertyManagerInfo.getAdditionalCommission() / 100.);
        }

        if (reservationPrice != null) {
            reservationPrice.setPrice(hasPrice.getPrice());
            reservationPrice.setTotal(hasPrice.getQuote());
        }

        MonitorService.monitor("computeLivePrice", timestamp);

        if (currency != null) {
            hasPrice = PriceUtil.convertCurrency(sqlSession, hasPrice, currency);
            if (reservationPrice != null) {
                reservationPrice = PriceUtil.convertCurrency(sqlSession, reservationPrice, currency);
            }
        }

        return reservationPrice;

    }

    /**
     * Add adjustments to the TotalPrice.
     * @param sqlSession
     * @param hasPrice
     * @param priceValue
     * @return
     */
    public static PriceComplexValue addPriceAdjustments(SqlSession sqlSession, HasPrice hasPrice,
            PriceComplexValue priceValue) {

        byte b = 0x0;
        if (hasPrice != null && priceValue != null) {

            if (hasPrice.getFromdate() == null || hasPrice.getTodate() == null
                    || !StringUtils.isNotEmpty(hasPrice.getProductid())
                    || !StringUtils.isNotEmpty(hasPrice.getSupplierid())) {
                return priceValue;
            }
            Calendar calendar = Calendar.getInstance();
            calendar.setTime(hasPrice.getFromdate());
            int checkInDay = calendar.get(Calendar.DAY_OF_WEEK);
            // create mask for service days         
            b |= 1 << checkInDay - 1;

            // select adjustment by reservation example
            Adjustment example = new Adjustment();
            example.setProductID(hasPrice.getProductid());
            example.setPartyID(hasPrice.getSupplierid());
            example.setFromDate(hasPrice.getFromdate());
            example.setState(Adjustment.Created);
            example.setToDate(Time.addDuration(hasPrice.getTodate(), -1.0, Time.DAY));
            example.setMinStay(hasPrice.getDuration(Time.DAY).intValue());
            example.setMaxStay(hasPrice.getDuration(Time.DAY).intValue());

            Adjustment adj = sqlSession.getMapper(AdjustmentMapper.class).readbyexample(example);

            // check adj for null and if checkin day in service days
            if (adj == null || (b & adj.getServicedays()) == 0) {
                // select adjustment with fix price value.
                Adjustment action = new Adjustment();
                action.setProductID(hasPrice.getProductid());
                action.setPartyID(hasPrice.getSupplierid());
                action.setMaxStay(Adjustment.MAX_STAY_VALUE);
                action.setFromDate(hasPrice.getFromdate());
                action.setState(Adjustment.Created);
                Double fixPriceValue = sqlSession.getMapper(AdjustmentMapper.class).getfixprice(action);

                if (fixPriceValue != null) {
                    priceValue.addTotalPrice(fixPriceValue);
                    priceValue.addTotalQuote(fixPriceValue);
                }
            } else {
                //get reservation duration in days
                Double duration = Time.getDuration(hasPrice.getFromdate(), hasPrice.getTodate(), Time.DAY);
                //if adjustment value is not null
                if (adj.getExtra() != null) {
                    // get total adjustment value
                    Double adjustmentValue = duration * adj.getExtra();
                    priceValue.addTotalPrice(adjustmentValue);
                    priceValue.addTotalQuote(adjustmentValue);
                }
                if (adj.getFixPrice() != null) {
                    priceValue.addTotalPrice(adj.getFixPrice());
                    priceValue.addTotalQuote(adj.getFixPrice());
                }
            }
        }

        return priceValue;
    }

    public static final void computeOneRowPrice(SqlSession sqlSession, HasPrice hasPrice, Product product,
            String currency) {

        Date timestamp = new Date();
        PriceComplexValue priceValue = new PriceComplexValue();

        PropertyManagerInfo propertyManagerInfo = sqlSession.getMapper(PropertyManagerInfoMapper.class)
                .readbypmid(Integer.valueOf(product.getSupplierid()));

        hasPrice.setSupplierid(product.getSupplierid());
        hasPrice.setQuotedetail(new ArrayList<Price>());

        if (!product.IsUseonepricerow()) {
            return;
        }

        final Price example = new Price();
        example.setEntitytype(NameId.Type.Product.name());
        example.setEntityid(hasPrice.getProductid());
        example.setPartyid(hasPrice.getSupplierid());
        example.setDate(hasPrice.getFromdate());
        example.setTodate(hasPrice.getTodate());
        example.setQuantity(hasPrice.getDuration(Time.DAY));
        example.setUnit(hasPrice.getUnit());

        Price price = sqlSession.getMapper(PriceMapper.class).readexactmatch(example);
        if (price == null) {
            hasPrice.setPrice(0.0);
            hasPrice.setQuote(0.0);
            hasPrice.setExtra(0.0);
            return;
        }
        hasPrice.setCurrency(price.getCurrency());

        double totMinimum = 0.0;
        totMinimum = (totMinimum < price.getMinimum()) ? price.getMinimum() : totMinimum;
        Double duration = Time.getDuration(hasPrice.getFromdate(), hasPrice.getTodate(), Time.DAY);
        double value = price.getValue() * duration;
        priceValue.addTotalPrice(value);

        ReservationUtil.addQuotedetail(hasPrice, price.getId(), price.getName(), Price.RATE, price.getSupplierid(),
                duration, price.getUnit(), (hasMinimum && value < totMinimum) ? totMinimum : value, price.getDate(),
                price.getTodate());

        Yield yield = new Yield(NameId.Type.Product.name(), hasPrice.getProductid(), hasPrice.getFromdate(),
                hasPrice.getTodate());
        ArrayList<Yield> rules = sqlSession.getMapper(YieldMapper.class).listbyentity(yield);

        // apply yield management rules for each day!
        Integer occupancy = 0;
        if (hasRule(rules, Yield.OCCUPANCY_ABOVE) || hasRule(rules, Yield.OCCUPANCY_BELOW)) {
            occupancy = sqlSession.getMapper(ReservationMapper.class).occupancy(yield);
            occupancy = (occupancy == null) ? 0 : occupancy;
        }

        boolean gap = false;
        if (hasRule(rules, Yield.GAP_FILLER)) {
            Integer previous = sqlSession.getMapper(ReservationMapper.class).previous(yield);
            Integer next = sqlSession.getMapper(ReservationMapper.class).next(yield);
            gap = (previous != null && previous == 1 && next != null && next == 1);
        }

        for (int day = Time.getDay(hasPrice.getFromdate()); day < Time.getDay(hasPrice.getTodate()); day++) {
            Double yieldValue = getYieldValue(rules, Time.getDate(day), gap,
                    hasPrice.getDuration(Time.DAY).intValue(), occupancy, price.getValue(), hasPrice);
            priceValue.addTotalQuote(yieldValue);
        }

        priceValue.setTotPrice(
                (hasMinimum && priceValue.getTotPrice() < totMinimum) ? totMinimum : priceValue.getTotPrice());

        if (hasMinimum && priceValue.getTotQuote() < totMinimum) {
            priceValue.setTotQuote(totMinimum);
            ArrayList<Price> prices = new ArrayList<Price>(hasPrice.getQuotedetail());
            for (Price p : prices) {
                if (p.hasType(Price.YIELD)) {
                    hasPrice.getQuotedetail().remove(p);
                }
            }
        }

        /*addTotalPrice(sqlSession, hasprice, priceValue);*/

        priceValue.setTotPrice(
                priceValue.getTotPrice() * hasPrice.getQuantity()); /*totPrice *= hasprice.getQuantity();*/
        priceValue.setTotQuote(
                priceValue.getTotQuote() * hasPrice.getQuantity()); /*totQuote *= hasprice.getQuantity();*/

        // Util class for commission calculation (NetRate, CreditCardFee)
        CommissionCalculationUtil commissionUtil = new CommissionCalculationUtil(sqlSession, hasPrice,
                priceValue.getTotPrice(), 0.0);
        Double newPublishedPrice = 0.0;

        if (commissionUtil.isNetRate()) {

            // Save nightly rate quote for future calculation
            hasPrice.setNightlyrate(priceValue.getTotPrice());

            // Calculate new NetRates prices
            newPublishedPrice = commissionUtil.getNewPublishedNightlyRate();
            Double newPublichedPriceCCFee = commissionUtil.getCreditCardFeeValue(newPublishedPrice);
            priceValue.setTotPrice(newPublishedPrice + newPublichedPriceCCFee);

            commissionUtil.setPriceAmount(priceValue.getTotQuote()); // Price + Yields
            Double newQuoteAmount = commissionUtil.getNewPublishedNightlyRate();

            if (commissionUtil.isPMFundsHolder()) {
                newQuoteAmount += newPublichedPriceCCFee;
            }

            priceValue.setTotQuote(newQuoteAmount);
            /*Double newPublishedPriceAmount = commissionUtil.getNewPublishedNightlyRate();
            Double newPublichedPriceCCFee = commissionUtil.getCreditCardFeeValue(newPublishedPriceAmount);
                
            priceValue.setTotPrice(newPublishedPriceAmount + newPublichedPriceCCFee);
            priceValue.setTotQuote(newPublishedPriceAmount);*/
        }

        priceValue = addPriceFeatures(sqlSession, hasPrice, priceValue);
        // new version of Fees from Fee table (Streamline)
        priceValue = FeeCalculationHelper.addTaxableFees(sqlSession, hasPrice, priceValue);

        //  taxes with relations (will be deprecated)
        priceValue = addPriceTaxes(sqlSession, hasPrice, priceValue);
        // new type of taxes (Streamline)
        priceValue = TaxesCalculationHelper.addTaxes(sqlSession, hasPrice, priceValue);

        priceValue = addNotTaxedPrices(sqlSession, hasPrice, priceValue);
        // new version of Fees from Fee table (Streamline)
        priceValue = FeeCalculationHelper.addNotTaxableFees(sqlSession, hasPrice, priceValue);

        // Damage insurance and cleaning fee adding
        if (product.getSecuritydeposit() != null) {
            Double depositValue = product.getSecuritydeposit();
            priceValue.addTotalQuote(depositValue);
            priceValue.addTotalExtra(depositValue);
        }
        if (product.getCleaningfee() != null) {
            Double feeValue = product.getCleaningfee();
            priceValue.addTotalQuote(feeValue);
            priceValue.addTotalExtra(feeValue);
        }

        // add special mandatory Fees from Fee table (Streamline, ...)
        priceValue = FeeCalculationHelper.addSpecialFees(sqlSession, hasPrice, priceValue);

        if (commissionUtil.isNetRate() && commissionUtil.isBPFundsHolder()) {

            commissionUtil.setExtraAmount(priceValue.getTotExtra());

            //Double newPublishedQuoteAmount = priceValue.getTotQuote() - priceValue.getTotExtra();
            Double newPublishedQuoteCCFee = commissionUtil.getCreditCardFeeValue(newPublishedPrice);

            priceValue.addTotalQuote(newPublishedQuoteCCFee);

        }

        LOG.debug("computePrice getQuotedetail " + hasPrice.getQuotedetail());
        hasPrice.setPrice(priceValue.getTotPrice());
        hasPrice.setQuote(priceValue.getTotQuote());
        hasPrice.setExtra(priceValue.getTotExtra());

        if (propertyManagerInfo != null && propertyManagerInfo.getAdditionalCommission() != null
                && propertyManagerInfo.getFundsHolder() == ManagerToGateway.PROPERTY_MANAGER_HOLDER) {
            PriceUtil.addCommission(hasPrice, propertyManagerInfo.getAdditionalCommission() / 100.);
        }

        MonitorService.monitor("computePrice", timestamp);

        if (currency != null) {
            hasPrice = PriceUtil.convertCurrency(sqlSession, hasPrice, currency);
        }
    }

    private static PriceComplexValue addTotalPrice(SqlSession sqlSession, HasPrice hasprice,
            PriceComplexValue priceValue) {
        double totMinimum = 0.0;
        hasMinimum = true;

        final Price example = new Price();
        example.setPartyid(hasprice.getSupplierid());
        example.setEntitytype(NameId.Type.Product.name());
        example.setEntityid(hasprice.getProductid());
        example.setDate(Time.addDuration(hasprice.getTodate(), -1.0, Time.DAY));
        example.setQuantity(hasprice.getDuration(Time.DAY));
        example.setUnit(hasprice.getUnit());

        int startDay = Time.getDay(hasprice.getFromdate());
        int endDay = Time.getDay(hasprice.getTodate());
        int i = 0;
        while (i++ < 1000) {
            example.setDate(Time.getDate(endDay - 1));
            example.setDateStr(DF.format(example.getDate()));

            Price price = sqlSession.getMapper(PriceMapper.class).readbydate(example);

            if (hasprice == null || price == null || price.getDate().after(price.getTodate())) {
                hasprice.setPrice(0.0);
                hasprice.setQuote(0.0);
                hasprice.setExtra(0.0);
                hasprice.setQuotedetail(new ArrayList<Price>());
                return null;
            }
            hasprice.setCurrency(price.getCurrency());
            int priceDay = Time.getDay(price.getDate());
            totMinimum = (totMinimum < price.getMinimum()) ? price.getMinimum() : totMinimum;

            Yield yield = new Yield(NameId.Type.Product.name(), hasprice.getProductid(), hasprice.getFromdate(),
                    hasprice.getTodate());
            ArrayList<Yield> rules = sqlSession.getMapper(YieldMapper.class).listbyentity(yield);
            int duration = (startDay > priceDay) ? endDay - startDay : endDay - priceDay;
            double value = (endDay <= startDay) ? price.getValue() : price.getValue() * duration;
            priceValue.addTotalPrice(value);

            // skip the rules for booking.com like channels
            if (BPThreadLocal.get() != null && BPThreadLocal.get()) {
                // do not apply the rules
                priceValue.setTotQuote(priceValue.getTotPrice());
            } else {
                // apply yield management rules for each day!
                Integer occupancy = 0;
                if (hasRule(rules, Yield.OCCUPANCY_ABOVE) || hasRule(rules, Yield.OCCUPANCY_BELOW)) {
                    occupancy = sqlSession.getMapper(ReservationMapper.class).occupancy(yield);
                    occupancy = (occupancy == null) ? 0 : occupancy;
                }

                boolean gap = false;
                if (hasRule(rules, Yield.GAP_FILLER)) {
                    Integer previous = sqlSession.getMapper(ReservationMapper.class).previous(yield);
                    Integer next = sqlSession.getMapper(ReservationMapper.class).next(yield);
                    gap = (previous != null && previous == 1 && next != null && next == 1);
                }

                for (int day = (startDay > priceDay) ? startDay : priceDay; day < endDay; day++) {
                    Double yieldValue = getYieldValue(rules, Time.getDate(day), gap,
                            hasprice.getDuration(Time.DAY).intValue(), occupancy, price.getValue(), hasprice);
                    priceValue.addTotalQuote(yieldValue);
                }
            }

            ReservationUtil.addQuotedetail(hasprice, price.getId(), price.getName(), Price.RATE,
                    price.getSupplierid(), Double.valueOf(duration), price.getUnit(),
                    (hasMinimum && value < totMinimum) ? totMinimum : value, price.getDate(), price.getTodate());

            if (priceDay <= startDay) {
                break;
            } else {
                endDay = priceDay;
            }
        }

        priceValue.setTotPrice(
                (hasMinimum && priceValue.getTotPrice() < totMinimum) ? totMinimum : priceValue.getTotPrice());

        if (hasMinimum && priceValue.getTotQuote() < totMinimum) {
            priceValue.setTotQuote(totMinimum);
            ArrayList<Price> prices = new ArrayList<Price>(hasprice.getQuotedetail());
            for (Price price : prices) {
                if (price.hasType(Price.YIELD)) {
                    hasprice.getQuotedetail().remove(price);
                }
            }
        }
        return priceValue;
    }

    /**
     *  Add feature prices to total and extra price.
     * @param sqlSession
     * @param hasprice
     * @param priceValue
     * @param quoteDetails
     * @return
     */
    private static PriceComplexValue addPriceFeatures(SqlSession sqlSession, HasPrice hasprice,
            PriceComplexValue priceValue) {

        // Mandatory per stay prices
        Price action = new Price();
        action.setEntitytype(NameId.Type.Mandatory.name());
        action.setEntityid(hasprice.getProductid());
        action.setDate(hasprice.getFromdate());
        action.setOrderby(Price.ID);

        ArrayList<Price> features = sqlSession.getMapper(PriceMapper.class).entityfeature(action);
        if (features != null && !features.isEmpty()) {
            for (Price feature : features) {
                if (feature.isTaxable()) {
                    Double value = feature.getValue();
                    priceValue.addTotalQuote(value);
                    priceValue.addTotalExtra(value);
                    ReservationUtil.addQuotedetail(hasprice, feature.getId(), feature.getName(), Price.MANDATORY,
                            feature.getSupplierid(), 1.0, feature.getUnit(), value, feature.getDate(),
                            feature.getTodate());
                }
            }
        }

        // Mandatory per day prices
        action.setEntitytype(NameId.Type.MandatoryPerDay.name());

        features = sqlSession.getMapper(PriceMapper.class).entityfeature(action);
        if (features != null && !features.isEmpty()) {
            for (Price feature : features) {
                Double quantity = hasprice.getDuration(Time.DAY);
                Double value = feature.getValue() * quantity;
                priceValue.addTotalQuote(value);
                priceValue.addTotalExtra(value);
                ReservationUtil.addQuotedetail(hasprice, feature.getId(), feature.getName(),
                        NameId.Type.MandatoryPerDay.name(), feature.getSupplierid(), quantity, feature.getUnit(),
                        value, feature.getDate(), feature.getTodate());
            }
        }

        // Optional features
        features = sqlSession.getMapper(PriceMapper.class).quotedetail(hasprice.getReservationid());
        if (features != null && !features.isEmpty()) {
            for (Price feature : features) {
                if (feature.isOptional()) {
                    Double value = feature.getValue();
                    priceValue.addTotalQuote(value);
                    priceValue.addTotalExtra(value);
                    ReservationUtil.addQuotedetail(hasprice, feature.getId(), feature.getName(), Price.OPTIONAL,
                            feature.getPartyname(), 1.0, feature.getUnit(), value, feature.getDate(),
                            feature.getTodate());
                }
            }
        }

        return priceValue;
    }

    /**
     * Add price values which are not taxable.
     * @param sqlSession
     * @param hasprice
     * @param priceValue
     * @param quoteDetails
     * @return
     */
    private static PriceComplexValue addNotTaxedPrices(SqlSession sqlSession, HasPrice hasprice,
            PriceComplexValue priceValue) {

        // Example price to get not taxable prices
        final Price example = new Price();
        example.setPartyid(hasprice.getSupplierid());
        example.setEntitytype(NameId.Type.Mandatory.name());
        example.setEntityid(hasprice.getProductid());
        example.setType(Price.NOT_TAXABLE);
        example.setDate(hasprice.getFromdate());
        example.setDateStr(DF.format(example.getDate()));
        example.setTodate(hasprice.getTodate());
        /*example.setQuantity(hasprice.getDuration(Time.DAY));
        example.setUnit(hasprice.getUnit());*/

        ArrayList<Price> prices = sqlSession.getMapper(PriceMapper.class).readbytype(example);

        if (prices != null) {
            for (Price price : prices) {
                if (price.getValue() != null) {
                    priceValue.addTotalQuote(price.getValue());
                    priceValue.addTotalExtra(price.getValue());
                    // Add price to reservation price history.
                    ReservationUtil.addQuotedetail(hasprice, price.getId(), price.getName(), Price.NOT_TAXABLE,
                            price.getPartyname(), 1.0, Unit.EA, price.getValue(), price.getDate(),
                            price.getTodate());
                }
            }
        }

        return priceValue;
    }

    private static PriceComplexValue addPriceTaxes(SqlSession sqlSession, HasPrice hasprice,
            PriceComplexValue priceValue) {

        Price action = new Price();
        action.setEntitytype(NameId.Type.Mandatory.name());
        action.setEntityid(hasprice.getProductid());
        action.setDate(hasprice.getFromdate());
        action.setOrderby(Price.ID);

        //Tax included in price
        action.setType(Tax.Type.SalesTaxIncluded.name());
        ArrayList<Tax> taxes = sqlSession.getMapper(TaxMapper.class).taxdetail(action);
        if (taxes != null && !taxes.isEmpty()) {
            Double tottaxValue = 0.0;
            for (Tax tax : taxes) {
                ReservationUtil.addQuotedetail(hasprice, tax.getId(), tax.getName(), Price.TAX_INCLUDED,
                        tax.getPartyname(), 1.0, Unit.EA, tax.getTaxIncluded(priceValue.getTotQuote()), null, null);
                tottaxValue -= tax.getTaxIncluded(priceValue.getTotQuote());
            }
            ReservationUtil.addQuotedetail(hasprice, Model.ZERO, Price.INCLUDED, Price.TAX_INCLUDED, "", 1.0,
                    Unit.EA, tottaxValue, null, null);
        }

        //Tax excluded from price
        action.setType(Tax.Type.SalesTaxExcluded.name());
        taxes = sqlSession.getMapper(TaxMapper.class).taxdetail(action);
        if (taxes != null && !taxes.isEmpty()) {
            double totRate = priceValue.getTotQuote();
            for (Tax tax : taxes) {
                Double value = tax.getTaxExcluded(totRate);
                priceValue.addTotalQuote(value);
                priceValue.addTotalExtra(value);
                ReservationUtil.addQuotedetail(hasprice, tax.getId(), tax.getName(), Price.TAX_EXCLUDED,
                        tax.getPartyname(), 1.0, Unit.EA, value, null, null);
            }
        }

        //Tax on price and previous taxes
        action.setType(Tax.Type.SalesTaxOnTax.name());
        taxes = sqlSession.getMapper(TaxMapper.class).taxdetail(action);
        if (taxes != null && !taxes.isEmpty()) {
            for (Tax tax : taxes) {
                Double value = tax.getTaxExcluded(priceValue.getTotQuote());
                priceValue.addTotalQuote(value);
                priceValue.addTotalExtra(value);
                ReservationUtil.addQuotedetail(hasprice, tax.getId(), tax.getName(), Price.TAX_ON_TAX,
                        tax.getPartyname(), 1.0, Unit.EA, value, null, null);
            }
        }

        return priceValue;
    }

    /**
     * Returns negative value in case of it is needed to make a refund and positive in case of surcharge
     * 
     * @param sqlSession current SQL session
     * @param reservation reservation to cancel
     * @param propertyManagerInfo property manager that obtains the booking to be cancelled
     * @return value to charge or refund
     */
    public static double calculateCancellationAmount(SqlSession sqlSession, Reservation reservation,
            PropertyManagerInfo propertyManagerInfo) {

        List<PaymentTransaction> paymentTransactions = sqlSession.getMapper(PaymentTransactionMapper.class)
                .readByReservationId(Integer.valueOf(reservation.getId()));
        if (paymentTransactions == null || paymentTransactions.isEmpty()) {
            throw new ServiceException(Error.database_cannot_find, "payment transactions for reservation");
        }

        double chargedAmountToManager = getChargedAmountToManager(paymentTransactions);
        String transactionCurrency = paymentTransactions.get(0).getCurrency();
        Double reservationQuoteTransactionCurrency = PaymentService.convertCurrency(sqlSession,
                reservation.getCurrency(), transactionCurrency, reservation.getQuote());
        double amountToManager = 0;

        PropertyManagerCancellationRule cancellationRule = PaymentHelper.getRuleForCurrentDate(sqlSession,
                reservation, propertyManagerInfo.getPropertyManagerId());

        if (cancellationRule != null) {
            double cancellationTransactionFee = cancellationRule.getCancellationTransactionFee() == null ? 0
                    : cancellationRule.getCancellationTransactionFee();
            double amountToRenter = PaymentHelper.getCancellationAmountWithoutFee(reservation, cancellationRule,
                    transactionCurrency);
            amountToManager = cancellationTransactionFee + reservationQuoteTransactionCurrency - amountToRenter
                    - chargedAmountToManager;
        } else if (chargedAmountToManager != reservationQuoteTransactionCurrency) {
            amountToManager = reservationQuoteTransactionCurrency - chargedAmountToManager;
        }
        return amountToManager;
    }

    public static void createEvent(SqlSession sqlSession, PaymentTransaction paymentTransaction,
            Reservation reservation, CreditCard creditCard) {
        Product product = sqlSession.getMapper(ProductMapper.class).read(reservation.getProductid());
        //      Reservation oldReservation = sqlSession.getMapper(ReservationMapper.class).read(reservation.getId());
        //      if(reservation.getState().equals(Reservation.State.Provisional.name()) ||  !oldReservation.getState().equals(reservation.getState())) { // FIXME : hardcode
        SharedService.onReservationStateChange(sqlSession, reservation, product);
        //      }
        if (paymentTransaction != null && (reservation.getState().equals(Reservation.State.Confirmed.name())
                || reservation.getState().equals(Reservation.State.FullyPaid.name()))) {
            Double amount = paymentTransaction.getTotalAmount();
            String cardholder = creditCard.getLastName() + ", " + creditCard.getFirstName();
            String notes = getNotes(paymentTransaction);
            SharedService.cardReceipt(sqlSession, reservation, notes, cardholder, amount);
        }
    }

    public static void createEvent(SqlSession sqlSession, PaymentTransaction paymentTransaction,
            Reservation reservation, String cardholderName) {
        Product product = sqlSession.getMapper(ProductMapper.class).read(reservation.getProductid());
        //      Reservation oldReservation = sqlSession.getMapper(ReservationMapper.class).read(reservation.getId());
        //      if(reservation.getState().equals(Reservation.State.Provisional.name()) ||  !oldReservation.getState().equals(reservation.getState())) { // FIXME : hardcode
        SharedService.onReservationStateChange(sqlSession, reservation, product);
        //      }
        if (paymentTransaction != null && (reservation.getState().equals(Reservation.State.Confirmed.name())
                || reservation.getState().equals(Reservation.State.FullyPaid.name()))) {
            Double amount = paymentTransaction.getTotalAmount();
            String cardholder = cardholderName;
            String notes = getNotes(paymentTransaction);
            SharedService.cardReceipt(sqlSession, reservation, notes, cardholder, amount);
        }
    }

    private static String getNotes(PaymentTransaction paymentTransaction) {
        StringBuilder string = new StringBuilder();
        Integer gatewayId = paymentTransaction.getGatewayId();
        String message = paymentTransaction.getMessage();
        Double finalAmount = paymentTransaction.getFinalAmount();
        Integer partialIin = paymentTransaction.getPartialIin();
        String chargeType = paymentTransaction.getChargeType();
        Integer paymentMethod = paymentTransaction.getPaymentMethod();

        if (gatewayId != null && !gatewayId.equals("")) {
            string.append("Gateway ID: " + gatewayId);
        }
        if (message != null && !message.equals("")) {
            string.append("Message: " + message);
        }
        if (finalAmount != null) {
            string.append("Final amount: " + finalAmount);
        }
        if (partialIin != null) {
            string.append("Last four credit card digits: " + partialIin);
        }
        if (chargeType != null && !chargeType.equals("")) {
            string.append("Charge type: " + chargeType);
        }
        if (paymentMethod != null && !paymentMethod.equals("")) {
            string.append("Payment method: " + paymentMethod);
        }
        return string.toString();
    }

    private static double getChargedAmountToManager(List<PaymentTransaction> paymentTransactions) {
        double chargedAmountToManager = 0;
        for (PaymentTransaction paymentTransaction : paymentTransactions) {
            chargedAmountToManager += paymentTransaction.getTotalAmount();
        }
        return chargedAmountToManager;
    }

    /**
     * Get the property address. Return address from product table in case it is exists and get the address from location if not.
     * 
     * @param sqlSession current SQL session
     * @param product product to get the location for
     * @return string of property address
     */
    public static String getPropertyLocation(SqlSession sqlSession, Product product) {
        String address = Arrays.toString(product.getAddress()).replace("[", "").replace("]", "").trim();
        if (StringUtils.isEmpty(address) || address.equals("null")) {
            Location location = sqlSession.getMapper(LocationMapper.class).read(product.getLocationid());
            if (location == null) {
                LOG.error("There is no location set for property " + product.getId());
                return "";
            }
            Country country = sqlSession.getMapper(CountryMapper.class).read(location.getCountry());
            address = location.getName() + ", " + country.getName();
        }
        return address;
    }

    /**
     * Checks if the rule name is in the list of yield management rules.
     *
     * @param rules the list of rules. 
     * @param name the rule name.
     * @return true, if the rule name is in the list of yield management rules.
     */
    private static boolean hasRule(ArrayList<Yield> rules, String name) {
        for (Yield yield : rules) {
            if (yield.hasName(name)) {
                return true;
            }
        }
        return false;
    }

    /**
     * Gets the price after yield management rules, if any, have been applied to the specified value.
     * 
     * @param rules the yield management rules to be applied.
     * @param date the date for which rules are to be applied.
     * @param fillsGap is true if the reservation fills a gap precisely.
     * @param stay the length of stay in nights.
     * @param occupancy the occupancy percentage.
     * @param value the price value to be adjusted by the yield management rules.
     * @return the price after yield management rules have been applied.
     */
    private static final Double getYieldValue(ArrayList<Yield> rules, Date date, boolean fillsGap, Integer stay,
            Integer occupancy, Double value, HasPrice hasprice) {
        if (rules == null || rules.isEmpty()) {
            return value;
        }
        Date timestamp = new Date();
        Map<String, Yield> yields = new HashMap<String, Yield>();
        for (Yield yield : rules) {
            //TODO: CJM         if (yield.getFromdate().after(date) || yield.getTodate().before(date)) {continue;}
            if (yield.isInDateRange(date)) {
                Yield oldyield = null;
                //TODO: CJM            if (yield.hasName(Yield.DATE_RANGE) && yield.isInDateRange(date)) {yields.put(Yield.DATE_RANGE, yield);}
                if (yield.hasName(Yield.DATE_RANGE)) {
                    yields.put(Yield.DATE_RANGE, yield);
                }
                if (yield.hasName(Yield.DAY_OF_WEEK) && yield.isDayOfWeek(date)) {
                    yields.put(Yield.DATE_RANGE, yield);
                } else if (yield.hasName(Yield.EARLY_BIRD) && yield.isEarlyBird(date)) {
                    oldyield = yields.get(Yield.EARLY_BIRD);
                    if (oldyield == null || oldyield.getParam() > yield.getParam()) {
                        yields.put(Yield.EARLY_BIRD, yield);
                    }
                } else if (yield.hasName(Yield.GAP_FILLER) && yield.isGapFiller(fillsGap, stay)) {
                    oldyield = yields.get(Yield.GAP_FILLER);
                    if (oldyield == null || oldyield.getParam() < yield.getParam()) {
                        yields.put(Yield.GAP_FILLER, yield);
                    }
                } else if (yield.hasName(Yield.LAST_MINUTE) && yield.isLastMinute(date)) {
                    oldyield = yields.get(Yield.LAST_MINUTE);
                    if (oldyield == null || oldyield.getParam() > yield.getParam()) {
                        yields.put(Yield.LAST_MINUTE, yield);
                    }
                } else if (yield.hasName(Yield.LENGTH_OF_STAY) && yield.isLengthOfStay(stay)) {
                    oldyield = yields.get(Yield.LENGTH_OF_STAY);
                    if (oldyield == null || oldyield.getParam() < yield.getParam()) {
                        yields.put(Yield.LENGTH_OF_STAY, yield);
                    }
                } else if (yield.hasName(Yield.OCCUPANCY_ABOVE) && yield.isOccupancyAbove(occupancy)) {
                    oldyield = yields.get(Yield.OCCUPANCY_ABOVE);
                    if (oldyield == null || oldyield.getParam() < yield.getParam()) {
                        yields.put(Yield.OCCUPANCY_ABOVE, yield);
                    }
                } else if (yield.hasName(Yield.OCCUPANCY_BELOW) && yield.isOccupancyBelow(occupancy)) {
                    oldyield = yields.get(Yield.OCCUPANCY_BELOW);
                    if (oldyield == null || oldyield.getParam() > yield.getParam()) {
                        yields.put(Yield.OCCUPANCY_BELOW, yield);
                    }
                } else if (yield.hasName(Yield.WEEKEND) && yield.isWeekend(date, yield.getParam())) {
                    oldyield = yields.get(Yield.WEEKEND);
                    if (oldyield == null || oldyield.getParam() > yield.getParam()) {
                        yields.put(Yield.WEEKEND, yield);
                    }
                }
                //         else if (yield.hasName(Yield.RETURNING_GUEST) && yield.isOccupancyBelow(occupancy)) {
                //            oldyield = yields.get(Yield.RETURNING_GUEST);
                //            if (oldyield == null || oldyield.getParam() > yield.getParam()) {yields.put(Yield.OCCUPANCY_BELOW, yield);}
                //         }
            }
        }
        for (Yield yield : yields.values()) {
            if (yield.isGapFiller()) {
                hasMinimum = false;
            }
            Double oldvalue = value;
            value = yield.getValue(value);
            ReservationUtil.addQuotedetail(hasprice, yield.getId(), yield.getName(), Price.YIELD, "", 1.0, Unit.EA,
                    value - oldvalue, null, null);
        }
        MonitorService.monitor("GetYieldValue", timestamp);
        return value;
    }

    /**
     * Executes the ReservationHistory action to read a Reservation table of previous stays.
     *
     * @param sqlSession the current SQL session.
     * @param action the action to be executed.
     * @return the response.
     */
    public final Table<Reservation> execute(SqlSession sqlSession, ReservationHistory action) {
        Date timestamp = new Date();
        Table<Reservation> table = new Table<Reservation>();
        //TODO: CJM SLOW QUERY 
        try {
            table.setValue(sqlSession.getMapper(ReservationMapper.class).listbycustomerid(action));
        } catch (Throwable x) {
            MonitorService.log(x);
        }
        MonitorService.monitor("ReservationHistory", timestamp);
        return table;
    }

    /**
     * Refreshes the special offers, which is invoked periodically by the RazorServer scheduler.
     * Deletes the existing special list, creates new special list, calculates new special prices.
     */
    public static final void specialrefresh() {
        Date timestamp = new Date();
        SqlSession sqlSession = RazorServer.openSession();
        try {
            sqlSession.getMapper(SpecialMapper.class).deleteall();
            ArrayList<Yield> maximumgapfillers = sqlSession.getMapper(YieldMapper.class).maximumgapfillers();
            for (Yield yield : maximumgapfillers) {
                sqlSession.getMapper(SpecialMapper.class).refresh(yield);
            }
            ArrayList<Special> specials = sqlSession.getMapper(SpecialMapper.class).readall();
            if (specials == null || specials.isEmpty()) {
                return;
            }
            Reservation reservation = new Reservation();
            for (Special special : specials) {

                reservation.setOrganizationid(special.getSupplierid());
                reservation.setActorid(Party.NO_ACTOR);
                reservation.setFromdate(special.getStartdate());
                reservation.setTodate(special.getEnddate());
                reservation.setUnit(Unit.DAY);
                reservation.setCurrency(special.getCurrency());
                reservation.setProductid(special.getProductid());

                computePrice(sqlSession, reservation, null);
                Double price = reservation.getPrice();
                Double quote = reservation.getQuote();
                Double extra = reservation.getExtra();
                if (price == null || quote == null || price <= 0.01 || quote <= 0.01 || price <= quote
                        || special.getStartdate().after(special.getEnddate())) {
                    sqlSession.getMapper(SpecialMapper.class).delete(special.getId());
                } else {
                    special.setPrice(price);
                    special.setQuote(quote);
                    sqlSession.getMapper(SpecialMapper.class).update(special);
                    createQuotedetail(sqlSession, reservation);
                }
            }
            sqlSession.commit();
        } catch (Throwable x) {
            sqlSession.rollback();
            LOG.error(x.getMessage());
        } finally {
            sqlSession.close();
        }
        MonitorService.monitor("specialrefresh", timestamp);
    }

    /**
     * Executes the Available action to get a table of AvailableItem instances from which to create a schedule of availability.
     *
     * @param sqlSession the current SQL session.
     * @param action the action to be executed.
     * @return the response.
     */
    public final Table<AvailableItem> execute(SqlSession sqlSession, Available action) {
        Date timestamp = new Date();
        ArrayList<String> productids = sqlSession.getMapper(ReservationMapper.class).availableatposition(action);
        action.setProductids(LicenseService.getLicensed(sqlSession, action.getOrganizationid(), action.getAgentid(),
                productids, License.Type.Console, License.DEFAULT_WAIT));
        Table<AvailableItem> table = available(sqlSession, action);
        MonitorService.monitor("Available", timestamp);
        return table;
    }

    /**
     * Executes the AvailableWidget action to get a table of AvailableItem instances from which to create a calendar widget.
     *
     * @param sqlSession the current SQL session.
     * @param action the action to be executed.
     * @return the response.
     */
    public final Table<AvailableItem> execute(SqlSession sqlSession, AvailableWidget action) {
        Date timestamp = new Date();
        Table<AvailableItem> table = available(sqlSession, action);
        MonitorService.monitor("AvailableWidget", timestamp);
        return table;
    }

    /*
     * Gets a table of AvailableItem instances from which to create a schedule of availability.
     * @see net.cbtltd.client.form.AvailableForm
     * 
     * @param sqlSession the current SQL session.
     * @param action the action that specifies the criteria by which instances are selected.
     * @return the table of available items.
     */
    private static final Table<AvailableItem> available(SqlSession sqlSession, Available action) {
        Table<AvailableItem> table = new Table<AvailableItem>();
        try {
            if (action.noProductids()) {
                return table;
            }
            ArrayList<AvailableItem> availableitems = sqlSession.getMapper(ReservationMapper.class)
                    .availableitems(action);
            childitems(sqlSession, action, availableitems);
            parentitems(sqlSession, action, availableitems);
            Collections.sort(availableitems);
            if (availableitems != null) {
                for (AvailableItem availableitem : availableitems) {
                    setAlert(sqlSession, availableitem, action.getFromdate(),
                            Time.addDuration(action.getFromdate(), 60, Time.DAY));
                }
            }
            table.setValue(availableitems);
            table.setDatasize(sqlSession.getMapper(ReservationMapper.class).countatposition(action));
        } catch (Throwable x) {
            sqlSession.rollback();
            MonitorService.log(x);
        }
        return table;
    }

    /*
     * Resets the specified table of AvailableItem instances to include the effect of the availability of child properties.
     * @see net.cbtltd.client.form.AvailableForm
     * 
     * @param sqlSession the current SQL session.
     * @param action the action that specifies the criteria by which instances are selected.
     * @param the specified table of available items.
     */
    private static void childitems(SqlSession sqlSession, Available action,
            ArrayList<AvailableItem> availableitems) {
        ArrayList<String> productids = action.getProductids();
        for (String productid : action.getProductids()) {
            action.setId(productid);
            ArrayList<String> childids = new ArrayList<String>();
            childids.add(productid);
            action.setProductids(childids);
            childids = sqlSession.getMapper(ReservationMapper.class).childids(action);
            if (childids == null || childids.isEmpty()) {
                continue;
            }
            action.setProductids(childids);
            ArrayList<AvailableItem> childavailableitems = sqlSession.getMapper(ReservationMapper.class)
                    .availableitem(action);
            //LOG.debug("\n\nchilditems " + productid + ", " + childids + " " + action + "\n" + childavailableitems);
            if (childavailableitems != null) {
                availableitems.addAll(childavailableitems);
            }
        }
        action.setProductids(productids);
    }

    /*
     * Resets the specified table of AvailableItem instances to include the effect of the availability of parent properties.
     * @see net.cbtltd.client.form.AvailableForm
     * 
     * @param sqlSession the current SQL session.
     * @param action the action that specifies the criteria by which instances are selected.
     * @param the specified table of available items.
     */
    private static void parentitems(SqlSession sqlSession, Available action,
            ArrayList<AvailableItem> availableitems) {
        ArrayList<String> productids = action.getProductids();
        for (String productid : productids) {
            ArrayList<String> parentids = new ArrayList<String>();
            parentids.add(productid);
            action.setProductids(parentids);
            action.setId(productid);
            parentids = sqlSession.getMapper(ReservationMapper.class).parentids(action);
            if (parentids == null || parentids.isEmpty()) {
                continue;
            }
            action.setProductids(parentids);
            ArrayList<AvailableItem> parentavailableitems = sqlSession.getMapper(ReservationMapper.class)
                    .availableitem(action);
            //LOG.debug("\n\nparentitems " + productid + ", " + parentids + " " + action + "\n" + parentavailableitems);
            if (parentavailableitems != null) {
                availableitems.addAll(parentavailableitems);
            }
        }
        action.setProductids(productids);
    }

    /**
     * Executes the ReservationEntities action to read a ReservationEntities instance.
     * The instance has details of the entities (guest, agent, property) related to the reservation.
     *
     * @param sqlSession the current SQL session.
     * @param action the action to be executed.
     * @return the response. entities
     */
    public final ReservationEntities execute(SqlSession sqlSession, ReservationEntities action) {
        Date timestamp = new Date();
        try {
            action.setReservation(
                    sqlSession.getMapper(ReservationMapper.class).read(action.getReservation().getId()));
            if (action.getReservation() == null) {
                throw new ServiceException(Error.reservation_id, action.getReservation().getId());
            }
            action.getReservation().setDiscountfactor(getDiscountfactor(sqlSession, action.getReservation()));
            if (action.getReservation().hasProductid()) {
                action.setProduct(
                        sqlSession.getMapper(ProductMapper.class).read(action.getReservation().getProductid()));
            }
            if (action.getProduct() != null && action.getProduct().getOwnerid() != null) {
                action.setOwner(sqlSession.getMapper(PartyMapper.class).read(action.getProduct().getOwnerid()));
            }
            if (action.getProduct() != null && action.getProduct().getSupplierid() != null) {
                action.setManager(
                        sqlSession.getMapper(PartyMapper.class).read(action.getProduct().getSupplierid()));
            }
            if (action.getReservation().hasAgentid()) {
                action.setAgent(sqlSession.getMapper(PartyMapper.class).read(action.getReservation().getAgentid()));
            }
            if (action.getReservation().hasCustomerid()) {
                action.setCustomer(
                        sqlSession.getMapper(PartyMapper.class).read(action.getReservation().getCustomerid()));
            }
            if (action.getReservation().hasServiceid()) {
                action.setService(
                        sqlSession.getMapper(PartyMapper.class).read(action.getReservation().getServiceid()));
            }
        } catch (Throwable x) {
            sqlSession.rollback();
            MonitorService.log(x);
        }
        MonitorService.monitor("ReservationEntities", timestamp);
        return action;
    }

    /**
     * Executes the BrochureProduct action to read a Brochure instance for a property (product).
     *
     * @param sqlSession the current SQL session.
     * @param action the action to be executed.
     * @return the response.
     */
    public final Brochure execute(SqlSession sqlSession, BrochureProduct action) {
        Date timestamp = new Date();
        try {
            String productid = action.getId();
            sqlSession.getMapper(ContactMapper.class).create(action);
            String[] productids = productid.split(",");
            action.setAvailableitems(sqlSession.getMapper(ReservationMapper.class).brochureproduct(productids));
        } catch (Throwable x) {
            sqlSession.rollback();
            MonitorService.log(x);
        }
        MonitorService.monitor("BrochureProduct", timestamp);
        return action;
    }

    /**
     * Executes the BrochureRead action to read a Brochure instance given its ID.
     *
     * @param sqlSession the current SQL session.
     * @param action the action to be executed.
     * @return the response.
     */
    public final Brochure execute(SqlSession sqlSession, BrochureRead action) {
        Date timestamp = new Date();
        Brochure brochure = action;
        try {
            brochure = sqlSession.getMapper(ContactMapper.class).brochure(action.getId());
            if (brochure != null) {
                brochure.setAvailableitems(
                        sqlSession.getMapper(ReservationMapper.class).brochureitems(action.getId()));
            }
        } catch (Throwable x) {
            sqlSession.rollback();
            MonitorService.log(x);
        }
        MonitorService.monitor("BrochureRead", timestamp);
        return brochure;
    }

    /**
     * Executes the BrochureUpdate action to update a Brochure instance and email its URL to the prospective guest.
     *
     * @param sqlSession the current SQL session.
     * @param action the action to be executed.
     * @return the response.
     */
    public final Brochure execute(SqlSession sqlSession, BrochureUpdate action) {
        Date timestamp = new Date();
        //LOG.debug("BrochureUpdate " + action);
        try {
            sqlSession.getMapper(ContactMapper.class).create(action);
            RelationService.create(sqlSession, Relation.CONTACT_PARTY, action.getId(), action.getCustomerid());

            for (AvailableItem availableitem : action.getAvailableitems()) {
                Reservation reservation = availableitem.getReservation(action.getActorid(), action.getCustomerid(),
                        action.getNotes());
                reservation.setCustomerid(action.getCustomerid());
                reservation.setDuedate(reservation.getDate());
                reservation.setMarket(action.getParentid());
                reservation.setParentid(action.getId());
                reservation.setState(Reservation.State.Initial.name());
                reservation.setName(
                        SessionService.pop(sqlSession, reservation.getOrganizationid(), Serial.RESERVATION));
                sqlSession.getMapper(ReservationMapper.class).create(reservation);
            }

            Party customer = sqlSession.getMapper(PartyMapper.class).read(action.getCustomerid());
            Party organization = sqlSession.getMapper(PartyMapper.class).read(action.getOrganizationid());
            action.setActorname(organization.getName());
            EmailService.brochure(sqlSession, action, customer.getEmailaddress());
        } catch (Throwable x) {
            sqlSession.rollback();
            MonitorService.log(x);
        }
        MonitorService.monitor("BrochureUpdate", timestamp);
        return action;
    }

    /**
     * Executes the BrochurePrice action to refresh its price.
     * This occurs when the currency or duration of the prospective stay is changed.
     *
     * @param sqlSession the current SQL session.
     * @param action the action to be executed.
     * @return the response.
     */
    public final PriceResponse execute(SqlSession sqlSession, BrochurePrice action) {
        Date timestamp = new Date();
        PriceResponse response = new PriceResponse();
        try {
            resetPrice(sqlSession, action, action.getPriceunit());
            resetCurrency(sqlSession, action, action.getTocurrency());
            response.setValue(action.getPrice());
            response.setQuote(action.getQuote());
            response.setQuotedetail(action.getQuotedetail());
            response.setCost((action.getQuote() - action.getExtra()) * getDiscountfactor(sqlSession, action));
            response.setCurrency(action.getCurrency());
            //         response.setCollisions(sqlSession.getMapper(ReservationMapper.class).collisions(action));
            //         response.addCollisions(sqlSession.getMapper(ReservationMapper.class).parentcollisions(action));
            //         response.addCollisions(sqlSession.getMapper(ReservationMapper.class).childcollisions(action));
            response.setCollisions(getCollisions(sqlSession, action));
            response.setAlerts(getAlerts(sqlSession, action));
        } catch (Throwable x) {
            sqlSession.rollback();
            MonitorService.log(x);
        }
        MonitorService.monitor("BrochurePrice", timestamp);
        return response;
    }

    /**
     * Executes the ReservationTable action to read a list of Reservation instances.
     *
     * @param sqlSession the current SQL session.
     * @param action action the action to be executed.
     * @return the response.
     */
    public final Table<Reservation> execute(SqlSession sqlSession, ReservationTable action) {
        Date timestamp = new Date();
        Table<Reservation> table = new Table<Reservation>();
        try {
            table.setDatasize(sqlSession.getMapper(ReservationMapper.class).count(action));
            table.setValue(sqlSession.getMapper(ReservationMapper.class).list(action)); //CJM ACTION!
        } catch (Throwable x) {
            sqlSession.rollback();
            MonitorService.log(x);
        }
        MonitorService.monitor("ReservationTable", timestamp);
        return table;
    }

    public final List<ReservationEntities> fetchReservation(SqlSession sqlSession, String lastFetch) {
        Date timestamp = new Date();
        List<ReservationEntities> listReservationEntities = new ArrayList<ReservationEntities>();
        try {
            List<Reservation> listReservation = sqlSession.getMapper(ReservationMapper.class)
                    .readBasedOnTime(lastFetch);

            for (Reservation reservation : listReservation) {
                //get ReservationExt
                ReservationExt ext = new ReservationExt();
                ext.setReservationId(reservation.getAltid());
                List<ReservationExt> listReservationExt = sqlSession.getMapper(ReservationExtMapper.class)
                        .readReservationExt(ext);
                if (listReservationExt != null) {
                    reservation.setListReservationExt(listReservationExt);
                }

                //get Product mapped to reservation.
                Product product = sqlSession.getMapper(ProductMapper.class).read(reservation.getProductid());

                reservation.setProduct(product);
                Party customer = sqlSession.getMapper(PartyMapper.class).read(reservation.getCustomerid());
                reservation.setCustomer(customer);

                ReservationEntities action = new ReservationEntities();
                action.setReservation(reservation);

                if (action.getReservation() == null) {
                    throw new ServiceException(Error.reservation_id, action.getReservation().getId());
                }
                action.getReservation().setDiscountfactor(getDiscountfactor(sqlSession, action.getReservation()));
                if (action.getReservation().hasProductid()) {
                    action.setProduct(
                            sqlSession.getMapper(ProductMapper.class).read(action.getReservation().getProductid()));
                }
                if (action.getProduct() != null && action.getProduct().getOwnerid() != null) {
                    action.setOwner(sqlSession.getMapper(PartyMapper.class).read(action.getProduct().getOwnerid()));
                }
                if (action.getProduct() != null && action.getProduct().getSupplierid() != null) {
                    action.setManager(
                            sqlSession.getMapper(PartyMapper.class).read(action.getProduct().getSupplierid()));
                }
                if (action.getReservation().hasAgentid()) {
                    action.setAgent(
                            sqlSession.getMapper(PartyMapper.class).read(action.getReservation().getAgentid()));
                }
                if (action.getReservation().hasCustomerid()) {
                    action.setCustomer(
                            sqlSession.getMapper(PartyMapper.class).read(action.getReservation().getCustomerid()));
                }
                if (action.getReservation().hasServiceid()) {
                    action.setService(
                            sqlSession.getMapper(PartyMapper.class).read(action.getReservation().getServiceid()));
                }
                listReservationEntities.add(action);
            }
        } catch (Throwable x) {
            sqlSession.rollback();
            MonitorService.log(x);
        }
        MonitorService.monitor("ReservationEntities", timestamp);
        return listReservationEntities;
    }
}