Ejemplo n.º 1
0
/**
 * Calendar Period Model
 *
 * @author Jorg Janke
 * @version $Id: MPeriod.java,v 1.4 2006/07/30 00:51:05 jjanke Exp $
 */
public class MPeriod extends X_C_Period {
  /** Logger for class MPeriod */
  private static final org.compiere.util.CLogger log =
      org.compiere.util.CLogger.getCLogger(MPeriod.class);
  /** */
  private static final long serialVersionUID = 1L;

  /**
   * Get Period from Cache
   *
   * @param ctx context
   * @param C_Period_ID id
   * @return MPeriod
   */
  public static MPeriod get(Ctx ctx, int C_Period_ID) {
    Integer key = Integer.valueOf(C_Period_ID);
    MPeriod retValue = s_cache.get(ctx, key);
    if (retValue != null) return retValue;
    //
    retValue = new MPeriod(ctx, C_Period_ID, null);
    if (retValue.get_ID() != 0) s_cache.put(key, retValue);
    return retValue;
  } //	get

  /**
   * Find standard Period of DateAcct based on Client Calendar
   *
   * @param ctx context
   * @param DateAcct date
   * @return active Period or null
   */
  public static MPeriod getOfOrg(Ctx ctx, int AD_Org_ID, Timestamp DateAcct) {
    int C_Calendar_ID = 0;
    if (AD_Org_ID != 0) {
      MOrgInfo info = MOrgInfo.get(ctx, AD_Org_ID, null);
      C_Calendar_ID = info.getC_Calendar_ID();
    }
    if (C_Calendar_ID == 0) {
      MClientInfo cInfo = MClientInfo.get(ctx);
      C_Calendar_ID = cInfo.getC_Calendar_ID();
    }

    return getOfCalendar(ctx, C_Calendar_ID, DateAcct);
  } //	get

  /**
   * Find standard Period of DateAcct based on Client Calendar
   *
   * @param ctx context
   * @param C_Calendar_ID calendar
   * @param DateAcct date
   * @return active Period or null
   */
  public static MPeriod getOfCalendar(Ctx ctx, int C_Calendar_ID, Timestamp DateAcct) {
    if (DateAcct == null) {
      s_log.warning("No DateAcct");
      return null;
    }
    if (C_Calendar_ID == 0) {
      s_log.warning("No Calendar");
      return null;
    }
    //	Search in Cache first
    Iterator<MPeriod> it = s_cache.values().iterator();
    while (it.hasNext()) {
      MPeriod period = it.next();
      if (period.getC_Calendar_ID() == C_Calendar_ID
          && period.isStandardPeriod()
          && period.isInPeriod(DateAcct)) return period;
    }

    //	Get it from DB
    MPeriod retValue = null;
    String sql =
        "SELECT * FROM C_Period "
            + "WHERE C_Year_ID IN "
            + "(SELECT C_Year_ID FROM C_Year WHERE C_Calendar_ID=?)"
            + " AND ? BETWEEN TRUNC(StartDate,'DD') AND TRUNC(EndDate,'DD')"
            + " AND IsActive='Y' AND PeriodType='S'";
    PreparedStatement pstmt = null;
    ResultSet rs = null;
    try {
      pstmt = DB.prepareStatement(sql, (Trx) null);
      pstmt.setInt(1, C_Calendar_ID);
      pstmt.setTimestamp(2, TimeUtil.getDay(DateAcct));
      rs = pstmt.executeQuery();
      while (rs.next()) {
        MPeriod period = new MPeriod(ctx, rs, null);
        Integer key = Integer.valueOf(period.getC_Period_ID());
        s_cache.put(key, period);
        if (period.isStandardPeriod()) retValue = period;
      }
    } catch (SQLException e) {
      s_log.log(Level.SEVERE, "DateAcct=" + DateAcct, e);
    } finally {
      DB.closeStatement(pstmt);
      DB.closeResultSet(rs);
    }
    if (retValue == null)
      s_log.warning(
          "No Standard Period for " + DateAcct + " (C_Calendar_ID=" + C_Calendar_ID + ")");
    return retValue;
  } //	get

  /**
   * Find valid standard Period of DateAcct based on Client Calendar
   *
   * @param ctx context
   * @param DateAcct date
   * @return C_Period_ID or 0
   */
  public static int getC_Period_ID(Ctx ctx, int AD_Org_ID, Timestamp DateAcct) {
    MPeriod period = getOfOrg(ctx, AD_Org_ID, DateAcct);
    if (period == null) return 0;
    return period.getC_Period_ID();
  } //	getC_Period_ID

  /**
   * Is standard Period Open for Document Base Type - does not check Orgs
   *
   * @param ctx context
   * @param DateAcct date
   * @param DocBaseType base type
   * @return true if open
   * @deprecated use new isOpen
   */
  @Deprecated
  public static boolean isOpenOld(Ctx ctx, Timestamp DateAcct, String DocBaseType) {
    if (DateAcct == null) {
      s_log.warning("No DateAcct");
      return false;
    }
    if (DocBaseType == null) {
      s_log.warning("No DocBaseType");
      return false;
    }
    MPeriod period = MPeriod.getOfOrg(ctx, 0, DateAcct);
    if (period == null) {
      s_log.warning("No Period for " + DateAcct + " (" + DocBaseType + ")");
      return false;
    }
    String error = period.isOpen(DocBaseType, DateAcct);
    if (error != null) s_log.warning(error + " - " + period.getName());
    return error == null;
  } //	isOpen

  /**
   * Is standard Period Open for specified orgs for the client. For best performance, ensure that
   * the list of orgs does not contain duplicates.
   *
   * @param ctx
   * @param AD_Client_ID
   * @param orgs
   * @param DateAcct accounting date
   * @param DocBaseType document base type
   * @return error message or null
   */
  public static String isOpen(
      Ctx ctx, int AD_Client_ID, ArrayList<Integer> orgs, Timestamp DateAcct, String DocBaseType) {
    if (DateAcct == null) return "@NotFound@ @DateAcct@";
    if (DocBaseType == null) return "@NotFound@ @DocBaseType@";

    MAcctSchema as = MClient.get(ctx, AD_Client_ID).getAcctSchema();
    if (as == null) return "@NotFound@ @C_AcctSchema_ID@ for AD_Client_ID=" + AD_Client_ID;
    if (as.isAutoPeriodControl()) {
      if (as.isAutoPeriodControlOpen(DateAcct)) return null;
      else return "@PeriodClosed@ - @AutoPeriodControl@";
    }

    //	Get all Calendars in line with Organizations
    MClientInfo clientInfo = MClientInfo.get(ctx, AD_Client_ID, null);
    ArrayList<Integer> orgCalendars = new ArrayList<Integer>();
    ArrayList<Integer> calendars = new ArrayList<Integer>();
    for (int org : orgs) {
      MOrgInfo orgInfo = MOrgInfo.get(ctx, org, null);
      int C_Calendar_ID = orgInfo.getC_Calendar_ID();
      if (C_Calendar_ID == 0) C_Calendar_ID = clientInfo.getC_Calendar_ID();
      orgCalendars.add(C_Calendar_ID);
      if (!calendars.contains(C_Calendar_ID)) calendars.add(C_Calendar_ID);
    }
    //	Should not happen
    if (calendars.size() == 0) return "@NotFound@ @C_Calendar_ID@";

    //	For all Calendars get Periods
    for (int i = 0; i < calendars.size(); i++) {
      int C_Calendar_ID = calendars.get(i);
      MPeriod period = MPeriod.getOfCalendar(ctx, C_Calendar_ID, DateAcct);
      //	First Org for Calendar
      int AD_Org_ID = 0;
      for (int j = 0; j < orgCalendars.size(); j++) {
        if (orgCalendars.get(j) == C_Calendar_ID) {
          AD_Org_ID = orgs.get(j);
          break;
        }
      }
      if (period == null) {
        MCalendar cal = MCalendar.get(ctx, C_Calendar_ID);
        String date = DisplayType.getDateFormat(DisplayTypeConstants.Date).format(DateAcct);
        if (cal != null)
          return "@NotFound@ @C_Period_ID@: "
              + date
              + " - "
              + MOrg.get(ctx, AD_Org_ID).getName()
              + " -> "
              + cal.getName();
        else
          return "@NotFound@ @C_Period_ID@: "
              + date
              + " - "
              + MOrg.get(ctx, AD_Org_ID).getName()
              + " -> C_Calendar_ID="
              + C_Calendar_ID;
      }
      String error = period.isOpen(DocBaseType, DateAcct);
      if (error != null)
        return error
            + " - "
            + MOrg.get(ctx, AD_Org_ID).getName()
            + " -> "
            + MCalendar.get(ctx, C_Calendar_ID).getName();
    }
    return null; //	open
  } //	isOpen

  /**
   * Is standard Period Open for Document Base Type
   *
   * @param header header document record
   * @param lines document lines optional
   * @param DateAcct accounting date
   * @param DocBaseType document base type
   * @return error message or null
   */
  @Deprecated
  public static String isOpen(PO header, PO[] lines, Timestamp DateAcct, String DocBaseType) {

    // Get All Orgs
    ArrayList<Integer> orgs = new ArrayList<Integer>();
    orgs.add(header.getAD_Org_ID());
    if (lines != null) {
      for (PO line : lines) {
        int AD_Org_ID = line.getAD_Org_ID();
        if (!orgs.contains(AD_Org_ID)) orgs.add(AD_Org_ID);
      }
    }

    return isOpen(header.getCtx(), header.getAD_Client_ID(), orgs, DateAcct, DocBaseType);
  } // isOpen

  /**
   * Is standard Period closed for all Document Base Types
   *
   * @param ctx context for AD_Client
   * @param DateAcct accounting date
   * @return true if closed
   */
  public static boolean isClosed(Ctx ctx, Timestamp DateAcct) {
    if (DateAcct == null) return false;
    MAcctSchema as = MClient.get(ctx, ctx.getAD_Client_ID()).getAcctSchema();
    if (as.isAutoPeriodControl()) return !as.isAutoPeriodControlOpen(DateAcct);

    //	Get all Calendars in line with Organizations
    MClientInfo cInfo = MClientInfo.get(ctx, ctx.getAD_Client_ID(), null);
    ArrayList<Integer> calendars = new ArrayList<Integer>();
    MOrg[] orgs = MOrg.getOfClient(cInfo);
    for (MOrg org : orgs) {
      MOrgInfo info = MOrgInfo.get(ctx, org.getAD_Org_ID(), null);
      int C_Calendar_ID = info.getC_Calendar_ID();
      if (C_Calendar_ID == 0) C_Calendar_ID = cInfo.getC_Calendar_ID();
      if (!calendars.contains(C_Calendar_ID)) calendars.add(C_Calendar_ID);
    }
    //	Should not happen
    if (calendars.size() == 0) throw new IllegalArgumentException("@NotFound@ @C_Calendar_ID@");

    //	For all Calendars get Periods
    for (int i = 0; i < calendars.size(); i++) {
      int C_Calendar_ID = calendars.get(i);
      MPeriod period = MPeriod.getOfCalendar(ctx, C_Calendar_ID, DateAcct);
      //	Period not found
      if (period == null) return false;
      if (!period.isClosed()) return false;
    }
    return true; //	closed
  } //	isClosed

  /**
   * Find first Year Period of DateAcct based on Client Calendar
   *
   * @param ctx context
   * @param C_Calendar_ID calendar
   * @param DateAcct date
   * @return active first Period
   */
  public static MPeriod getFirstInYear(Ctx ctx, int C_Calendar_ID, Timestamp DateAcct) {
    MPeriod retValue = null;
    String sql =
        "SELECT * "
            + "FROM C_Period "
            + "WHERE C_Year_ID IN "
            + "(SELECT p.C_Year_ID "
            + "FROM C_Year y"
            + " INNER JOIN C_Period p ON (y.C_Year_ID=p.C_Year_ID) "
            + "WHERE y.C_Calendar_ID=?"
            + "	AND ? BETWEEN StartDate AND EndDate)"
            + " AND IsActive='Y' AND PeriodType='S' "
            + "ORDER BY StartDate";
    PreparedStatement pstmt = null;
    ResultSet rs = null;
    try {
      pstmt = DB.prepareStatement(sql, (Trx) null);
      pstmt.setInt(1, C_Calendar_ID);
      pstmt.setTimestamp(2, DateAcct);
      rs = pstmt.executeQuery();
      if (rs.next()) // 	first only
      retValue = new MPeriod(ctx, rs, null);
    } catch (SQLException e) {
      s_log.log(Level.SEVERE, sql, e);
    } finally {
      DB.closeStatement(pstmt);
      DB.closeResultSet(rs);
    }
    return retValue;
  } //	getFirstInYear

  /** Cache */
  private static final CCache<Integer, MPeriod> s_cache =
      new CCache<Integer, MPeriod>("C_Period", 10);

  /** Logger */
  private static final CLogger s_log = CLogger.getCLogger(MPeriod.class);

  /**
   * ************************************************************************ Standard Constructor
   *
   * @param ctx context
   * @param C_Period_ID id
   * @param trx transaction
   */
  public MPeriod(Ctx ctx, int C_Period_ID, Trx trx) {
    super(ctx, C_Period_ID, trx);
    if (C_Period_ID == 0) {
      //	setC_Period_ID (0);		//	PK
      //  setC_Year_ID (0);		//	Parent
      //  setName (null);
      //  setPeriodNo (0);
      //  setStartDate (new Timestamp(System.currentTimeMillis()));
      setPeriodType(PERIODTYPE_StandardCalendarPeriod);
    }
  } //	MPeriod

  /**
   * Load Constructor
   *
   * @param ctx context
   * @param rs result set
   * @param trx transaction
   */
  public MPeriod(Ctx ctx, ResultSet rs, Trx trx) {
    super(ctx, rs, trx);
  } //	MPeriod

  /**
   * Parent constructor
   *
   * @param year year
   * @param PeriodNo no
   * @param name name
   * @param startDate start
   * @param endDate end
   */
  public MPeriod(MYear year, int PeriodNo, String name, Timestamp startDate, Timestamp endDate) {
    this(year.getCtx(), 0, year.get_Trx());
    setClientOrg(year);
    setC_Year_ID(year.getC_Year_ID());
    setPeriodNo(PeriodNo);
    setName(name);
    setStartDate(startDate);
    setEndDate(endDate);
  } //	MPeriod

  /** Period Controls */
  private MPeriodControl[] m_controls = null;
  /** Calendar */
  private int m_C_Calendar_ID = 0;

  /**
   * Get Period Control
   *
   * @param requery requery
   * @return period controls
   */
  public MPeriodControl[] getPeriodControls(boolean requery) {
    if (m_controls != null && !requery) return m_controls;
    //
    ArrayList<MPeriodControl> list = new ArrayList<MPeriodControl>();
    String sql = "SELECT * FROM C_PeriodControl " + "WHERE C_Period_ID=?";
    PreparedStatement pstmt = null;
    ResultSet rs = null;
    try {
      pstmt = DB.prepareStatement(sql, get_Trx());
      pstmt.setInt(1, getC_Period_ID());
      rs = pstmt.executeQuery();
      while (rs.next()) list.add(new MPeriodControl(getCtx(), rs, get_Trx()));
    } catch (Exception e) {
      log.log(Level.SEVERE, sql, e);
    } finally {
      DB.closeResultSet(rs);
      DB.closeStatement(pstmt);
    }
    m_controls = new MPeriodControl[list.size()];
    list.toArray(m_controls);
    return m_controls;
  } //	getPeriodControls

  /**
   * Get Period Control
   *
   * @param DocBaseType Document Base Type
   * @return period control or null
   */
  public MPeriodControl getPeriodControl(String DocBaseType) {
    if (DocBaseType == null) return null;
    getPeriodControls(false);
    for (MPeriodControl element : m_controls) {
      //	log.fine("getPeriodControl - " + 1 + " - " + m_controls[i]);
      if (DocBaseType.equals(element.getDocBaseType())) return element;
    }
    return null;
  } //	getPeriodControl

  /**
   * Date In Period
   *
   * @param date date
   * @return true if in period
   */
  public boolean isInPeriod(Timestamp date) {
    if (date == null) return false;
    Timestamp dateOnly = TimeUtil.getDay(date);
    Timestamp from = TimeUtil.getDay(getStartDate());
    if (dateOnly.before(from)) return false;
    Timestamp to = TimeUtil.getDay(getEndDate());
    if (dateOnly.after(to)) return false;
    return true;
  } //	isInPeriod

  /**
   * Is Period Open for Doc Base Type
   *
   * @param DocBaseType document base type
   * @param dateAcct accounting date
   * @return error message or null
   */
  public String isOpen(String DocBaseType, Timestamp dateAcct) {
    if (!isActive()) {
      s_log.warning("Period not active: " + getName());
      return "@C_Period_ID@ <> @IsActive@";
    }

    MAcctSchema as = MClient.get(getCtx(), getAD_Client_ID()).getAcctSchema();
    if (as != null && as.isAutoPeriodControl()) {
      if (!as.isAutoPeriodControlOpen(dateAcct)) return "@PeriodClosed@ - @AutoPeriodControl@";
      //	We are OK
      Timestamp today = new Timestamp(System.currentTimeMillis());
      if (isInPeriod(today) && as.getC_Period_ID() != getC_Period_ID()) {
        as.setC_Period_ID(getC_Period_ID());
        as.save();
      }
      return null;
    }

    //	Standard Period Control
    if (DocBaseType == null) {
      log.warning(getName() + " - No DocBaseType");
      return "@NotFound@ @DocBaseType@";
    }
    MPeriodControl pc = getPeriodControl(DocBaseType);
    if (pc == null) {
      log.warning(getName() + " - Period Control not found for " + DocBaseType);
      return "@NotFound@ @C_PeriodControl_ID@: " + DocBaseType;
    }
    log.fine(getName() + ": " + DocBaseType);
    if (pc.isOpen()) return null;
    return "@PeriodClosed@ - @C_PeriodControl_ID@ (" + DocBaseType + ", " + dateAcct + ")";
  } //	isOpen

  /**
   * Return true if all PC are closed
   *
   * @return true if closed
   */
  public boolean isClosed() {
    MPeriodControl[] pcs = getPeriodControls(false);
    for (MPeriodControl pc : pcs) {
      if (!pc.isClosed()) return false;
    }
    return true;
  } //	isClosed

  /**
   * Standard Period
   *
   * @return true if standard calendar period
   */
  public boolean isStandardPeriod() {
    return PERIODTYPE_StandardCalendarPeriod.equals(getPeriodType());
  } //	isStandardPeriod

  /**
   * Get Calendar of Period
   *
   * @return calendar
   */
  public int getC_Calendar_ID() {
    if (m_C_Calendar_ID == 0) {
      MYear year = MYear.get(getCtx(), getC_Year_ID());
      if (year != null) m_C_Calendar_ID = year.getC_Calendar_ID();
      else log.severe("@NotFound@ C_Year_ID=" + getC_Year_ID());
    }
    return m_C_Calendar_ID;
  } //	getC_Calendar_ID

  /**
   * Before Save. Truncate Dates
   *
   * @param newRecord new
   * @return true
   */
  @Override
  protected boolean beforeSave(boolean newRecord) {
    Timestamp startdate = getStartDate();
    Timestamp enddate = getEndDate();

    if (enddate != null && startdate.after(enddate)) {

      s_log.saveError("Error", Msg.getMsg(getCtx(), "CalPeriodInvalidDate"));
      return false;
    }
    //	Truncate Dates
    startdate = TimeUtil.getDay(startdate);
    setStartDate(startdate);

    if (enddate != null) enddate = TimeUtil.getDay(enddate);
    else enddate = TimeUtil.getMonthLastDay(getStartDate());

    //		Adding the time component of 23:59:59 to the end date
    enddate = new Timestamp(enddate.getTime() + 86399000);
    setEndDate(enddate);

    MPeriod[] periods = getAllPeriodsInYear(getC_Year_ID(), "S", getCtx(), get_Trx());
    MPeriod[] allperiods = getAllPeriodsInCalendar(getC_Calendar_ID(), "S", getCtx(), get_Trx());
    //		Check for non-negative period number
    if (getPeriodNo() < 0) {
      s_log.saveError("Error", Msg.getMsg(getCtx(), "CalNegPeriodNo"));
      return false;
    }

    //		Check for standard period
    if (isStandardPeriod() == true) {
      // Check Period number is in ascending order

      Timestamp nextPeriodStartDate = null;
      Timestamp prevPeriodStartDate = null;

      // Get the next standard period number Start Date in this year
      String sql =
          "SELECT StartDate FROM C_Period WHERE "
              + "C_Period.IsActive='Y' AND PeriodType='S' "
              + "AND C_Period.C_Year_ID =? "
              + "AND C_Period.C_Period_ID <> ?"
              + "AND  C_Period.PeriodNo "
              + " >  ?  ORDER BY  C_Period.PeriodNo ASC";
      Object[][] result = null;
      result =
          QueryUtil.executeQuery(get_Trx(), sql, getC_Year_ID(), getC_Period_ID(), getPeriodNo());

      if (result.length != 0) nextPeriodStartDate = (Timestamp) result[0][0];

      // Get the previous standard period number Start Date in this year
      sql =
          "SELECT StartDate FROM C_Period WHERE "
              + "C_Period.IsActive='Y' AND PeriodType='S'  "
              + "AND C_Period.C_Year_ID =? "
              + "AND C_Period.C_Period_ID <> ?"
              + "AND C_Period.PeriodNo "
              + "< ?  ORDER BY  C_Period.PeriodNo DESC";

      result =
          QueryUtil.executeQuery(get_Trx(), sql, getC_Year_ID(), getC_Period_ID(), getPeriodNo());
      if (result.length != 0) prevPeriodStartDate = (Timestamp) result[0][0];

      if ((prevPeriodStartDate != null
          && TimeUtil.max(prevPeriodStartDate, startdate) == prevPeriodStartDate)) {
        s_log.saveError("Error", Msg.getMsg(getCtx(), "CalPeriodAsc"));
        return false;
      }

      if ((nextPeriodStartDate != null
          && TimeUtil.max(nextPeriodStartDate, startdate) == startdate)) {
        s_log.saveError("Error", Msg.getMsg(getCtx(), "CalPeriodAsc"));
        return false;
      }

      //  Check if the Standard Period is overlapping other periods.

      for (MPeriod period : allperiods) {
        if ((TimeUtil.isValid(period.getStartDate(), period.getEndDate(), startdate) == true
                || TimeUtil.isValid(period.getStartDate(), period.getEndDate(), enddate) == true)
            && period.getC_Period_ID() != getC_Period_ID()) {
          s_log.saveError("Error", Msg.getMsg(getCtx(), "CalPeriodOverlap"));
          return false;
        }
      }

    }
    //		Check for adjusting period
    else {
      boolean startflag = false;
      boolean endflag = false;
      for (MPeriod period : periods) {
        if (TimeUtil.isValid(period.getStartDate(), period.getEndDate(), startdate) == true)
          startflag = true;
        if (TimeUtil.isValid(period.getStartDate(), period.getEndDate(), enddate) == true)
          endflag = true;
        if (startflag == true && endflag == true) break;
      }
      if (startflag == false || endflag == false) {
        s_log.saveError("Error", Msg.getMsg(getCtx(), "CalAdjPeriod"));
        return false;
      }
    }
    return true;
  } //	beforeSave

  /**
   * After Save
   *
   * @param newRecord new
   * @param success success
   * @return success
   */
  @Override
  protected boolean afterSave(boolean newRecord, boolean success) {
    if (newRecord) {
      //	SELECT Value FROM AD_Ref_List WHERE AD_Reference_ID=183
      MDocType[] types = MDocType.getOfClient(getCtx());
      int count = 0;
      ArrayList<String> baseTypes = new ArrayList<String>();
      for (MDocType type : types) {
        String DocBaseType = type.getDocBaseType();
        if (baseTypes.contains(DocBaseType)) continue;
        MPeriodControl pc = new MPeriodControl(this, DocBaseType);
        if (pc.save()) count++;
        baseTypes.add(DocBaseType);
      }
      log.fine("PeriodControl #" + count);
    }
    return success;
  } //	afterSave

  /**
   * String Representation
   *
   * @return info
   */
  @Override
  public String toString() {
    StringBuffer sb = new StringBuffer("MPeriod[");
    sb.append(get_ID())
        .append("-")
        .append(getName())
        .append(", ")
        .append(getStartDate())
        .append("-")
        .append(getEndDate())
        .append("]");
    return sb.toString();
  } //	toString

  /**
   * Returns the next period forward
   *
   * @param period MPeriod
   * @param trx trx
   * @param ctx Ctx
   * @return MPeriod
   */
  public static MPeriod getNextPeriod(MPeriod period, Ctx ctx, Trx trx) {

    MPeriod newPeriod = null;
    String sql =
        "SELECT * FROM C_Period WHERE "
            + "C_Period.IsActive='Y' AND PeriodType='S' "
            + "AND C_Period.C_Year_ID IN "
            + "(SELECT C_Year_ID FROM C_Year WHERE C_Year.C_Calendar_ID = ? ) "
            + "AND ((C_Period.C_Year_ID * 1000) + C_Period.PeriodNo) "
            + " > ((? * 1000) + ?) ORDER BY C_Period.C_Year_ID ASC, C_Period.PeriodNo ASC";

    PreparedStatement pstmt = null;
    ResultSet rs = null;
    try {
      pstmt = DB.prepareStatement(sql, trx);
      pstmt.setInt(1, period.getC_Calendar_ID());
      pstmt.setInt(2, period.getC_Year_ID());
      pstmt.setInt(3, period.getPeriodNo());
      rs = pstmt.executeQuery();
      if (rs.next()) newPeriod = new MPeriod(ctx, rs, trx);
    } catch (Exception e) {
      s_log.log(Level.SEVERE, sql, e);
    } finally {
      DB.closeResultSet(rs);
      DB.closeStatement(pstmt);
    }
    return newPeriod;
  }

  /**
   * Returns the previous period
   *
   * @param period MPeriod
   * @param periodCount Count
   * @param trx trx
   * @param ctx Ctx
   * @return MPeriod
   */
  public static MPeriod getPreviousPeriod(MPeriod period, Ctx ctx, Trx trx) {

    MPeriod newPeriod = null;
    String sql =
        "SELECT * FROM C_Period WHERE "
            + "C_Period.IsActive='Y' AND PeriodType='S' "
            + "AND C_Period.C_Year_ID IN "
            + "(SELECT C_Year_ID FROM C_Year WHERE C_Year.C_Calendar_ID = ? ) "
            + "AND ((C_Period.C_Year_ID * 1000) + C_Period.PeriodNo) "
            + " < ((? * 1000) + ?) ORDER BY C_Period.C_Year_ID DESC, C_Period.PeriodNo DESC";

    PreparedStatement pstmt = null;
    ResultSet rs = null;
    try {
      pstmt = DB.prepareStatement(sql, trx);
      pstmt.setInt(1, period.getC_Calendar_ID());
      pstmt.setInt(2, period.getC_Year_ID());
      pstmt.setInt(3, period.getPeriodNo());
      rs = pstmt.executeQuery();
      if (rs.next()) newPeriod = new MPeriod(ctx, rs, trx);
    } catch (Exception e) {
      s_log.log(Level.SEVERE, sql, e);
    } finally {
      DB.closeResultSet(rs);
      DB.closeStatement(pstmt);
    }
    return newPeriod;
  }

  /**
   * gets all Periods in the Range
   *
   * @param startPeriod
   * @param endPeriod
   * @param calendar_ID
   * @return MPeriod[]
   */
  public static MPeriod[] getAllPeriodsInRange(
      MPeriod startPeriod, MPeriod endPeriod, int calendar_ID, Ctx ctx, Trx trx) {
    if ((startPeriod.getC_Calendar_ID() != calendar_ID)
        || (endPeriod.getC_Calendar_ID() != calendar_ID)) {
      log.saveError("Error", "Periods do not belong to the calendar");
      return null;
    }

    ArrayList<MPeriod> periods = new ArrayList<MPeriod>();
    String sql =
        "SELECT * FROM C_Period WHERE "
            + "C_Period.IsActive='Y' AND PeriodType='S' "
            + "AND C_Period.C_Year_ID IN "
            + "(SELECT C_Year_ID FROM C_Year WHERE C_Year.C_Calendar_ID = ? ) "
            + // calendar_ID
            "AND ((C_Period.C_Year_ID * 1000) + C_Period.PeriodNo) BETWEEN"
            + " (? * 1000 + ?) AND (? * 1000 + ? )"
            + // start Period year ID, Period Number , End Period Year ID, Period Number
            " ORDER BY C_Period.C_Year_ID ASC, C_Period.PeriodNo ASC";

    PreparedStatement pstmt = null;
    ResultSet rs = null;
    try {
      pstmt = DB.prepareStatement(sql, trx);
      pstmt.setInt(1, calendar_ID);
      pstmt.setInt(2, startPeriod.getC_Year_ID());
      pstmt.setInt(3, startPeriod.getPeriodNo());
      pstmt.setInt(4, endPeriod.getC_Year_ID());
      pstmt.setInt(5, endPeriod.getPeriodNo());
      rs = pstmt.executeQuery();
      while (rs.next()) periods.add(new MPeriod(ctx, rs, trx));
    } catch (Exception e) {
      s_log.log(Level.SEVERE, sql, e);
    } finally {
      DB.closeResultSet(rs);
      DB.closeStatement(pstmt);
    }
    MPeriod[] retValue = new MPeriod[periods.size()];
    periods.toArray(retValue);
    return retValue;
  }

  /**
   * Find Period of Date based on Client Calendar, it need not be a standard period (used in MRP)
   *
   * @param ctx context
   * @param C_Calendar_ID calendar
   * @param Date date
   * @param trx trx
   * @return active Period or null
   */
  public static MPeriod getPeriod(Ctx ctx, int C_Calendar_ID, Timestamp Date, Trx trx) {
    if (Date == null) {
      s_log.warning("No Date");
      return null;
    }
    if (C_Calendar_ID == 0) {
      s_log.warning("No Calendar");
      return null;
    }

    //	Get it from DB
    PreparedStatement pstmt = null;
    ResultSet rs = null;
    MPeriod retValue = null;
    String sql =
        "SELECT * FROM C_Period "
            + "WHERE C_Year_ID IN "
            + "(SELECT C_Year_ID FROM C_Year WHERE C_Calendar_ID=?)"
            + " AND ? BETWEEN TRUNC(StartDate,'DD') AND TRUNC(EndDate,'DD')"
            + " AND IsActive='Y' ";
    try {
      pstmt = DB.prepareStatement(sql, trx);
      pstmt.setInt(1, C_Calendar_ID);
      pstmt.setTimestamp(2, TimeUtil.getDay(Date));
      rs = pstmt.executeQuery();
      if (rs.next()) {
        retValue = new MPeriod(ctx, rs, trx);
      }
    } catch (SQLException e) {
      s_log.log(Level.SEVERE, "DateAcct=" + Date, e);
    } finally {
      DB.closeResultSet(rs);
      DB.closeStatement(pstmt);
    }
    if (retValue == null)
      s_log.warning("No Period for " + Date + " (C_Calendar_ID=" + C_Calendar_ID + ")");
    return retValue;
  } //	getPeriod

  /**
   * Find the periods in a calendar it need not be a standard period (used in MRP)
   *
   * @param C_Calendar_ID calendar
   * @param periodType Period Type
   * @param ctx context
   * @param trx trx
   * @return MPeriod[]
   */
  public static MPeriod[] getAllPeriodsInCalendar(
      int C_Calendar_ID, String periodType, Ctx ctx, Trx trx) {

    List<MPeriod> periods = new ArrayList<MPeriod>();
    String sql = "SELECT * FROM C_Period WHERE IsActive='Y'";

    sql = sql + " AND C_Year_ID IN ( SELECT C_Year_ID FROM C_Year WHERE C_Calendar_ID=?)";

    if (periodType != null) sql = sql + " AND PeriodType = ? ";

    sql = sql + " ORDER BY StartDate ";

    PreparedStatement pstmt = null;
    ResultSet rs = null;
    try {
      pstmt = DB.prepareStatement(sql, trx);
      pstmt.setInt(1, C_Calendar_ID);

      if (periodType != null) pstmt.setString(2, periodType);

      rs = pstmt.executeQuery();
      while (rs.next()) periods.add(new MPeriod(ctx, rs, trx));
    } catch (Exception e) {
      s_log.log(Level.SEVERE, sql, e);
    } finally {
      DB.closeResultSet(rs);
      DB.closeStatement(pstmt);
    }
    MPeriod[] retValue = new MPeriod[periods.size()];
    periods.toArray(retValue);
    return retValue;
  }

  /**
   * Find the periods in a calendar year it need not be a standard period (used in MRP)
   *
   * @param C_Year_ID Year
   * @param periodType Period Type
   * @param ctx context
   * @param trx trx
   * @return MPeriod[]
   */
  public static MPeriod[] getAllPeriodsInYear(int C_Year_ID, String periodType, Ctx ctx, Trx trx) {

    List<MPeriod> periods = new ArrayList<MPeriod>();
    String sql = "SELECT * FROM C_Period WHERE IsActive='Y'";

    sql = sql + " AND C_Year_ID = ?";

    if (periodType != null) sql = sql + " AND PeriodType = ? ";

    sql = sql + " order by StartDate ";

    PreparedStatement pstmt = null;
    ResultSet rs = null;
    try {
      pstmt = DB.prepareStatement(sql, trx);
      pstmt.setInt(1, C_Year_ID);

      if (periodType != null) pstmt.setString(2, periodType);

      rs = pstmt.executeQuery();
      while (rs.next()) periods.add(new MPeriod(ctx, rs, trx));
    } catch (Exception e) {
      s_log.log(Level.SEVERE, sql, e);
    } finally {
      DB.closeResultSet(rs);
      DB.closeStatement(pstmt);
    }
    MPeriod[] retValue = new MPeriod[periods.size()];
    periods.toArray(retValue);
    return retValue;
  }

  /**
   * Find all the year records in a Calendar, it need not be a standard period (used in MRP)
   *
   * @param C_Calendar_ID calendar
   * @param ctx context
   * @param trx trx
   * @return MYear[]
   */
  public static MYear[] getAllYearsInCalendar(int C_Calendar_ID, Ctx ctx, Trx trx) {

    List<MYear> years = new ArrayList<MYear>();
    String sql = "SELECT * FROM C_Year WHERE " + "IsActive='Y' AND C_Calendar_ID = ? ";

    PreparedStatement pstmt = null;
    ResultSet rs = null;
    try {
      pstmt = DB.prepareStatement(sql, trx);
      pstmt.setInt(1, C_Calendar_ID);
      rs = pstmt.executeQuery();
      while (rs.next()) years.add(new MYear(ctx, rs, trx));

    } catch (Exception e) {
      s_log.log(Level.SEVERE, sql, e);
    } finally {

      DB.closeResultSet(rs);
      DB.closeStatement(pstmt);
    }

    MYear[] retValue = new MYear[years.size()];
    years.toArray(retValue);
    return retValue;
  }
} //	MPeriod
Ejemplo n.º 2
0
/**
 * GWT Server Implementation. You maintain one instance per User
 *
 * @author Jorg Janke, dzhao
 * @version $Id$
 */
public class GwtServer {

  public static void resetWinDefCache() {
    Userdef_Winids.reset();
    UIWindows.reset();
  }

  private static final CCache<WindowVOCacheKey, Integer> Userdef_Winids =
      new CCache<WindowVOCacheKey, Integer>("AD_Global_WindowVO", 200, 120);

  // use only one window cache
  private static final CCache<WindowCacheKey, UIWindow> UIWindows =
      new CCache<WindowCacheKey, UIWindow>("AD_Global_Window", 2000, 120);

  /** Logger */
  private static final CLogger log = CLogger.getCLogger(GwtServer.class);
  /** Server ID */
  private static AtomicInteger s_gwtServer_ID = new AtomicInteger(1);

  /** ************************************************************************* Gwt Server */
  public GwtServer() {
    m_context = new GWTServerContext();
    m_context.setContext(MRole.GWTSERVERID, s_gwtServer_ID.getAndIncrement());
  } // GwtServer

  /** Context */
  private final GWTServerContext m_context;

  /** Login */
  private Login m_login = null;

  /** Locale */
  private Locale m_loc = null;

  /** Role for User */
  private MRole m_role = null;

  /** Window Cache */
  // private final HashMap<Integer, UIWindow> m_windows
  // = new HashMap<Integer, UIWindow>(20);
  /** Tab Cache */
  private final HashMap<Integer, UITab> m_tabs = new HashMap<Integer, UITab>(20);

  private final HashMap<Integer, UITab> m_referencetabs = new HashMap<Integer, UITab>(20);

  /** Field Cache */
  private final HashMap<Integer, UIField> m_fields = new HashMap<Integer, UIField>(200);

  /** Tab Results */
  private final HashMap<Integer, ArrayList<String[]>> m_results =
      new HashMap<Integer, ArrayList<String[]>>();

  /** Dashboard Drilldowns */
  private final HashMap<String, NodeVO> m_nodes = new HashMap<String, NodeVO>();

  /**
   * Get Login
   *
   * @return login
   */
  public Login getLogin() {
    if (m_login == null) m_login = new Login(m_context);
    return m_login;
  } // getLogin

  /**
   * Returns the context associated with this GwtServer
   *
   * @return context
   */
  public CContext getContext() {
    return m_context;
  } // getContext

  /**
   * Get Role for User
   *
   * @return role
   */
  public MRole getRole() {
    if (m_role == null) {
      if (m_login == null
          || m_login.getRole() == null
          || m_login.getAD_Role_ID() == -1
          || m_login.getAD_User_ID() == -1) throw new IllegalArgumentException("Not logged in yet");
      m_role = m_login.getRole();
    }
    return m_role;
  } // getRole

  /**
   * Set Locale
   *
   * @param loc locale (from login)
   */
  public void setLocale(Locale loc) {
    m_loc = loc;
  } // setLocale

  /**
   * Logout
   *
   * @param expired expire
   */
  public void logout(boolean expired) {

    // End Session
    MSession session = MSession.get(m_context); // finish
    if (session != null) {
      if (expired) {
        if (session.getDescription() == null) session.setDescription("Expired");
        else session.setDescription(session.getDescription() + " Expired");
      }
      session.logout(); // saves
    }
    if (m_context != null) {
      int gwtServerID = m_context.getContextAsInt(MRole.GWTSERVERID);
      if (gwtServerID > 0) MRole.resetGwt(gwtServerID);
    }
    // Clear Cache
    m_tabs.clear();
    m_fields.clear();
    // m_windows.clear();
    m_context.clear();
    m_results.clear();
    //

  } // logout

  public boolean isLogout() {
    return m_context.size() == 0;
  }

  /** Finalize. Remove Role */
  @Override
  protected void finalize() throws Throwable {
    if (m_context != null) {
      int gwtServerID = m_context.getContextAsInt(MRole.GWTSERVERID);
      if (gwtServerID > 0) MRole.resetGwt(gwtServerID);
    }
    super.finalize();
  } // finalize

  /**
   * Get Locale
   *
   * @return Locale
   */
  public Locale getLocale() {
    if (m_loc == null) return Locale.US;
    return m_loc;
  } // getLocale

  /**
   * Get Menu
   *
   * @return menu as array list
   */
  public ArrayList<CTreeNode> getMenuTree() {
    int AD_Tree_ID = getTreeID();
    log.fine("AD_Tree_ID=" + AD_Tree_ID + " - " + Env.getAD_Language(m_context));
    return getMenuTree(AD_Tree_ID, false);
  } // getMenuTree

  /**
   * Get Tree ID for role
   *
   * @return AD_Tree_ID as int
   */
  private int getTreeID() {
    int AD_Role_ID = m_context.getAD_Role_ID();
    // Load Menu Structure ----------------------
    int AD_Tree_ID =
        QueryUtil.getSQLValue(
            null,
            "SELECT COALESCE(r.AD_Tree_Menu_ID, ci.AD_Tree_Menu_ID)"
                + "FROM AD_ClientInfo ci"
                + " INNER JOIN AD_Role r ON (ci.AD_Client_ID=r.AD_Client_ID) "
                + "WHERE AD_Role_ID=?",
            AD_Role_ID);
    if (AD_Tree_ID <= 0) AD_Tree_ID = 10; // Menu
    return AD_Tree_ID;
  } // getTreeID

  /**
   * Get Menu favorites for a user
   *
   * @return menu as array list
   */
  public ArrayList<CTreeNode> getMenuFavorites() {
    MUser user = MUser.get(getContext());
    int AD_Tree_ID = user.getAD_Tree_MenuFavorite_ID();
    if (AD_Tree_ID == 0) // favorites has not yet been created
    return new ArrayList<CTreeNode>();
    return getMenuTree(AD_Tree_ID, false);
  } // get favorites menu

  /**
   * Get Menu tree that directly enter "create new" mode
   *
   * @return menu as array list
   */
  public ArrayList<CTreeNode> getMenuCreateNew() {
    MUser user = MUser.get(getContext());
    int AD_Tree_ID = user.getAD_Tree_MenuNew_ID();
    if (AD_Tree_ID == 0) // create new has not yet been created
    return new ArrayList<CTreeNode>();
    return getMenuTree(AD_Tree_ID, false);
  } // getMenuCreateNew

  /**
   * Get a menu tree representation based on a AD_Tree_ID
   *
   * @param AD_Tree_ID A tree based on AD_Menu
   * @return menu as array list
   */
  private ArrayList<CTreeNode> getMenuTree(int AD_Tree_ID, boolean edit) {
    MTree tree = new MTree(m_context, AD_Tree_ID, edit, true, true, null); // Language
    // set
    // in
    // WLogin
    // Trim tree
    CTreeNode root = tree.getRoot();
    Enumeration<?> en = root.preorderEnumeration();
    while (en.hasMoreElements()) {
      CTreeNode nd = (CTreeNode) en.nextElement();
      if (nd.isTask() || nd.isWorkbench() // || nd.isWorkFlow()
      // server
      ) {
        CTreeNode parent = (CTreeNode) nd.getParent();
        parent.remove(nd);
      }
    }
    tree.trimTree();
    en = root.preorderEnumeration();
    ArrayList<CTreeNode> retValue = new ArrayList<CTreeNode>();
    while (en.hasMoreElements()) {
      CTreeNode nd = (CTreeNode) en.nextElement();
      // Issue #420: removed menu entries for un-implemented forms
      if (nd.getAD_Form_ID() == 119 || nd.getAD_Form_ID() == 102
      //					|| nd.getAD_Workflow_ID() == 106
      //					|| nd.getAD_Workflow_ID() == 104
      //					// Review
      //					|| nd.getAD_Workflow_ID() == 112
      //					// Setup
      //					|| nd.getAD_Workflow_ID() == 113
      //					|| nd.getAD_Workflow_ID() == 110
      //					|| nd.getAD_Workflow_ID() == 111
      // || nd.getAD_Process_ID() == 205
      ) {
      } else retValue.add(nd);
    }
    return retValue;
  }

  /**
   * Make Favorites add/remove persistent ("bar" in swing client)
   *
   * @param add true if add - otherwise remove
   * @param Node_ID Node ID
   * @return true if updated
   */
  public boolean updateFavorites(boolean add, int Node_ID) {
    /*
     * Code logic now uses MUser to store favorites. TODO:
     * VTreePanel.barDBupdate should be similarly updated or deprecated for
     * Swing client.
     */
    MUser user = MUser.get(getContext());
    return user.addUserMenuFavorite(Node_ID, 0);
  } // updateFavorites

  /**
   * Update of user favorites for a user with specified ordering for favorites
   *
   * @param menuIDs List<Integer> ordered list of menuIDs to put in the tree
   * @return true if updated
   */
  public boolean updateFavorites(List<Integer> menuIDs) {
    MUser user = MUser.get(getContext());
    MTree menuTree = null;
    if ((menuTree = user.getUserFavoriteTree()) == null) return false;
    return updateUserTree(menuIDs, menuTree);
  } // updateFavorites

  /**
   * Make create new add/remove persistent ("bar" in swing client)
   *
   * @param add true if add - otherwise remove
   * @param Node_ID Node ID
   * @return true if updated
   */
  public boolean updateCreateNew(boolean add, int Node_ID) {
    /*
     * Code logic now uses MUser to store favorites. TODO:
     * VTreePanel.barDBupdate should be similarly updated or deprecated for
     * Swing client.
     */
    MUser user = MUser.get(getContext());
    return user.addUserMenuNewFavorite(Node_ID, 0);
  } // updateCreateNew

  /**
   * Update of user favorites for a user with specified ordering for favorites
   *
   * @param menuIDs List<Integer> ordered list of menuIDs to put in the tree
   * @return true if updated
   */
  public boolean updateCreateNew(List<Integer> menuIDs) {
    MUser user = MUser.get(getContext());
    MTree menuTree = null;
    if ((menuTree = user.getUserNewFavoriteTree()) == null) return false;
    return updateUserTree(menuIDs, menuTree);
  } // updateCreateNew

  /*
   * Update of user tree for ordered menu nodes (favorites, create new list)
   * favorites @param menuIDs List<Integer> ordered list of menuIDs to put in
   * the tree @param menuTree MTree the tree to be reordered @return true if
   * updated
   */
  private boolean updateUserTree(List<Integer> menuIDs, MTree menuTree) {
    CTreeNode root = menuTree.getRoot();
    if (root != null) {
      Enumeration<?> nodes = root.preorderEnumeration();
      while (nodes.hasMoreElements()) {
        CTreeNode nd = (CTreeNode) nodes.nextElement();
        if (!menuIDs.contains(nd.getNode_ID())) {
          MTreeNodeMM node = null;
          if ((node = MTreeNodeMM.get(menuTree, nd.getNode_ID())) != null) {
            if (!node.delete(true)) return false;
          }
        }
      }
    }
    int seq = 0;
    for (int id : menuIDs) {
      MTreeNodeMM node = null;
      if ((node = MTreeNodeMM.get(menuTree, id)) == null) {
        node = new MTreeNodeMM(menuTree, id);
      }
      node.setSeqNo(++seq);
      if (!node.save()) return false;
    }
    return true;
  }

  /**
   * Get Number of open Requests
   *
   * @return number of requests
   */
  public int getRequests() {
    return GwtServerUtil.getRequests(m_context);
  } // getRequests

  /**
   * Get number of open Notes
   *
   * @return Number of notes
   */
  public int getNotes() {
    return GwtServerUtil.getNotes(m_context);
  } // getNotes

  /**
   * ************************************************************************* Get Window in default
   * context based on Role
   *
   * @param windowNO relative window
   * @param AD_Window_ID window
   * @param AD_Menu_ID menu
   * @return WindowVO or null
   */
  public UIWindow getWindow(int windowNO, int AD_Window_ID, int AD_Menu_ID) {
    UIWindow win = null;
    // win = m_windows.get(AD_Window_ID);
    // if (win != null)
    // {
    // win.clearLookupCache();
    // return win;
    // }
    UIWindowVOFactory winFactory = new UIWindowVOFactory();
    UIWindowVO winVO = null;
    int AD_UserDef_Win_ID = -1;
    WindowVOCacheKey vokey =
        new WindowVOCacheKey(
            AD_Window_ID, m_context.getAD_Role_ID(), AD_Menu_ID, Env.getAD_Language(m_context));

    // note, the usage of m_context below in constructing window is only for
    // language, menu, role,
    // and those are already included in the cache key, so we can safely
    // assume the win is correctly cached
    if (Userdef_Winids.get(null, vokey) == null) {
      winVO = winFactory.get(m_context, AD_Window_ID, AD_Menu_ID);
      if (winVO == null) {
        log.config("No Window - AD_Window_ID=" + AD_Window_ID + ",AD_Menu_ID=" + AD_Menu_ID);
        return null;
      }
      int theAD_UserDef_Win_ID = winVO.getAD_UserDef_Win_ID();

      if (Userdef_Winids.putIfAbsent(vokey, theAD_UserDef_Win_ID) == null)
        AD_UserDef_Win_ID = theAD_UserDef_Win_ID;
    } else AD_UserDef_Win_ID = Userdef_Winids.get(m_context, vokey);

    WindowCacheKey key =
        new WindowCacheKey(
            AD_Window_ID,
            AD_UserDef_Win_ID,
            m_context.getAD_Role_ID(),
            AD_Menu_ID,
            Env.getAD_Language(m_context));
    win = UIWindows.get(null, key);
    if (win == null) {
      // log.warning("key:" + key + " not found, create");
      if (winVO == null) winVO = winFactory.get(m_context, AD_Window_ID, AD_Menu_ID);
      if (winVO == null) {
        log.config("No Window - AD_Window_ID=" + AD_Window_ID + ",AD_Menu_ID=" + AD_Menu_ID);
        return null;
      }
      UIWindow newWin = new UIWindow(winVO);
      AD_Window_ID = newWin.getAD_Window_ID();
      //
      UIFieldVOFactory fieldFactory = new UIFieldVOFactory();
      newWin.setFields(fieldFactory.getAll(m_context, AD_Window_ID, AD_UserDef_Win_ID));
      //
      UITabVOFactory tabFactory = new UITabVOFactory();
      // setTabVOs initrlize tabs but not fields, 'cuz fields needs to be
      // copied over and initialized later
      newWin.setTabVOsWithFieldsUninitialized(
          m_context, tabFactory.getAll(m_context, AD_Window_ID, AD_UserDef_Win_ID), windowNO);
      win = UIWindows.putIfAbsent(key, newWin);
      if (win == null) win = newWin;
    }
    // deep copy the window object so we hold a separate window object for
    // each user session
    UIWindow duplicatedWin = (UIWindow) DeepCopy.copy(win);
    log.fine(duplicatedWin.toString());
    fillTabsFieldsAndInitFieldsAndCreateDependencyRelations(duplicatedWin, windowNO);

    MSession session = MSession.get(m_context);
    if (session != null)
      session.windowLog(
          m_context.getAD_Client_ID(),
          m_context.getAD_Org_ID(),
          duplicatedWin.getAD_Window_ID(),
          0);

    return duplicatedWin;
  } // getWindowVO

  /**
   * Get Tab with ID
   *
   * @param AD_Tab_ID
   * @return tab or null
   */
  public UITab getTab(int AD_Tab_ID) {
    Integer tabKey = Integer.valueOf(AD_Tab_ID);
    UITab tab = m_tabs.get(tabKey);
    if (tab == null) {
      // Check added for referenced tabs
      if (m_referencetabs.get(tabKey) != null) return m_referencetabs.get(tabKey);
      throw new CompiereStateException("No such tab:" + AD_Tab_ID);
    } // find in window
    return tab;
  } // getTab

  /**
   * Get Field
   *
   * @param AD_Field_ID id
   * @param windowNo relative windowNo
   * @return field or null
   */
  public UIField getField(int AD_Field_ID, int windowNo) {
    Integer key = Integer.valueOf(AD_Field_ID);
    UIField field = m_fields.get(key);
    if (field == null) {
      UIFieldVOFactory fieldFactory = new UIFieldVOFactory();
      UIFieldVO vo = fieldFactory.get(m_context, AD_Field_ID);
      // m_context.setSOTrx(windowNo, isSOTrx);
      if (vo != null) {
        field = new UIField(vo);
        field.initialize(m_context, windowNo);
        log.warning("Loaded directly: " + field); // SOTrx may not
        // be correct
        m_fields.put(key, field); // save in cache
      }
    } // create new
    return field;
  } // getField

  /** Fill Tab and Field arrays */
  private void fillTabsFieldsAndInitFieldsAndCreateDependencyRelations(UIWindow win, int windowNO) {
    ArrayList<UITab> tabs = win.getTabs();
    for (int j = 0; j < tabs.size(); j++) {
      UITab winTab = tabs.get(j);
      Integer tabKey = Integer.valueOf(winTab.getAD_Tab_ID());
      Integer ReferencetabKey = Integer.valueOf(winTab.getReferenced_Tab_ID());
      m_tabs.put(tabKey, winTab);
      m_referencetabs.put(ReferencetabKey, winTab);
      //
      ArrayList<UIField> fields = winTab.getFields();
      for (int k = 0; k < fields.size(); k++) {
        UIField field = fields.get(k);
        field.initialize(m_context, windowNO);
        Integer fieldKey = Integer.valueOf(field.getAD_Field_ID());
        // set the correct value
        if (field.isLookup()) field.getLookup().setContext(m_context, windowNO);
        m_fields.put(fieldKey, field);
      }
      winTab.createDependencyRelations();
    }
  } // fillTabsFields

  /**
   * Execute Query for Tab
   *
   * @param AD_Tab_ID tab
   * @param queryVO optional query
   * @param context record context for link columns and other variables
   * @param queryResultID stored query identifier provided by client
   * @return number of records or -1 if error
   */
  public int executeQuery(
      int AD_Tab_ID, QueryVO queryVO, HashMap<String, String> context, int queryResultID) {
    UITab tab = getTab(AD_Tab_ID);
    if (tab == null) {
      log.config("Not found AD_Tab_ID=" + AD_Tab_ID);
      return -1;
    }
    ArrayList<String[]> result = tab.executeQueryString(queryVO, context, m_context);
    if (result == null) {
      log.config("Not Result for AD_Tab_ID=" + AD_Tab_ID);
      return -1;
    }
    MRole role = getRole();
    // return -1 to indicate query exceeds
    if (role.isQueryMax(result.size())) {
      m_results.put(queryResultID, new ArrayList<String[]>());
      return -1;
    }
    m_results.put(queryResultID, result);
    return result.size();
  } // executeQuery

  public Query createQuery(int AD_Tab_ID, QueryVO queryVO, WindowCtx ctx, String tableName) {
    UITab tab = getTab(AD_Tab_ID);
    String whereClause = tab.getWhereClause();
    if (tab == null) {
      log.config("Not found AD_Tab_ID=" + AD_Tab_ID);
      return null;
    }
    Query result = tab.createQueryForReport(m_context, queryVO);
    if (result == null) {
      result = new Query(tableName);
    }
    if (whereClause != null && whereClause.length() != 0) {
      QueryRestriction restriction = new QueryRestriction(whereClause);
      result.addRestriction(restriction);
    }
    return result;
  } // executeQuery

  /**
   * Retrieve results for Tab. If the from/to range does not exist, it returns existing rows
   *
   * @param queryResultID stored query identifier provided by client
   * @param fromRow from row first is 0
   * @param noRows number of rows
   * @return array of rows of array of field values or null if error. You get the columnNames via
   *     String[] columns = uiTab.getColumnNames();
   */
  public String[][] getResults(int queryResultID, int fromRow, int noRows) {
    if (noRows < 0) {
      log.config("Invalid: fromRow=" + fromRow + ",noRows" + noRows);
    } else if (noRows == 0) return new String[][] {};
    //
    ArrayList<String[]> resultAll = m_results.get(queryResultID);
    if (resultAll == null) {
      log.config("No Results for queryResultID=" + queryResultID);
      return null;
    }
    if (resultAll.size() < fromRow) {
      log.config(
          "Insufficient Results for queryResultID="
              + queryResultID
              + ", Length="
              + resultAll.size()
              + ", fromRow="
              + fromRow);
      return null;
    }
    // copy
    if (resultAll.size() < noRows) {
      log.config(
          "Insufficient Rows for queryResultID="
              + queryResultID
              + ", Length="
              + resultAll.size()
              + ", fromRow="
              + fromRow
              + ", noRows="
              + noRows);
      noRows = resultAll.size();
    }
    String[][] result = new String[noRows][];
    for (int i = 0; i < noRows; i++) {
      int index = i + fromRow;
      if (index >= resultAll.size()) break;
      result[i] = resultAll.get(index);
    }
    return result;
  } // getResult

  public void sortResults(
      int WindowNo, int AD_Tab_ID, int AD_Field_ID, int queryResultID, final boolean ascending) {
    class SortCell {

      String[] row;

      String sort;
    }
    ArrayList<String[]> results = m_results.get(queryResultID);
    if (results == null)
      log.severe("cannot sort. results non-existent for queryResultID:" + queryResultID);
    UITab tab = getTab(AD_Tab_ID);
    final UIField field = getField(AD_Field_ID, WindowNo);
    final int displayType = field.getAD_Reference_ID();
    final int idx = tab.getFieldIndex(AD_Field_ID);
    // if not a lookup, directly sort
    if (!field.isLookup()) {
      if (FieldType.isNumeric(displayType)) {
        Collections.sort(
            results,
            new Comparator<String[]>() {

              @Override
              public int compare(String[] o1, String[] o2) {
                if (o1[idx] == null) o1[idx] = "";
                if (o2[idx] == null) o2[idx] = "";
                BigDecimal s1 = new BigDecimal(o1[idx].equals("") ? "-1e-10" : o1[idx]);
                BigDecimal s2 = new BigDecimal(o2[idx].equals("") ? "-1e-10" : o2[idx]);
                return ascending ? s1.compareTo(s2) : s2.compareTo(s1);
              }
            });
      } else if (FieldType.isDate(displayType)) {
        Collections.sort(
            results,
            new Comparator<String[]>() {

              @Override
              public int compare(String[] o1, String[] o2) {
                if (o1[idx] == null) o1[idx] = "";
                if (o2[idx] == null) o2[idx] = "";
                Long s1 = new Long(o1[idx].equals("") ? "-1000000" : o1[idx]);
                Long s2 = new Long(o2[idx].equals("") ? "-1000000" : o2[idx]);
                return ascending ? s1.compareTo(s2) : s2.compareTo(s1);
              }
            });
      } else {
        Collections.sort(
            results,
            new Comparator<String[]>() {

              @Override
              public int compare(String[] o1, String[] o2) {
                if (o1[idx] == null) o1[idx] = "";
                if (o2[idx] == null) o2[idx] = "";
                String s1 = o1[idx];
                String s2 = o2[idx];
                return ascending ? s1.compareTo(s2) : s2.compareTo(s1);
              }
            });
      }
      return;
    }
    Comparator<SortCell> c =
        new Comparator<SortCell>() {

          public int compare(SortCell o1, SortCell o2) {
            if (ascending) return o1.sort.compareTo(o2.sort);
            else return o2.sort.compareTo(o1.sort);
          }
        };
    // for look up, first get id values
    ArrayList<String> fieldValues = new ArrayList<String>(results.size());
    for (String[] row : results) {
      fieldValues.add(row[idx]);
    }
    // then translate into real values
    ArrayList<String> sorts = getLookupValueOnlyDirect(AD_Field_ID, fieldValues, true);
    ArrayList<SortCell> toBeSorteds = new ArrayList<SortCell>(sorts.size());
    for (int i = 0; i < sorts.size(); i++) {
      SortCell toBeSorted = new SortCell();
      toBeSorted.row = results.get(i);
      toBeSorted.sort = sorts.get(i);
      toBeSorteds.add(toBeSorted);
    }
    // sort
    Collections.sort(toBeSorteds, c);
    // after sorting, replace col with original values
    int i = 0;
    for (SortCell toBeSorted : toBeSorteds) {
      results.set(i, toBeSorted.row);
      i++;
    }
  }

  public void copyQueryResults(int sourceID, int destID) {
    ArrayList<String[]> results = m_results.get(sourceID);
    m_results.put(destID, results);
  }

  // Method to return a list of matches according to fields for a tab, using
  // the cached results
  // this does not store the result in the cache - this behavior is deferred
  // to the caller
  private int searchTabResults(
      int WindowNo,
      UITab tab,
      List<Integer> fieldIds,
      int queryResultID,
      int searchResultID,
      String query,
      int rowCount) {
    ArrayList<String[]> results = m_results.get(queryResultID);
    if (query.trim().equals("")) {
      m_results.put(searchResultID, results);
      return results.size();
    }
    ScoreStrategy scorer = new ScoreStrategy(query);
    ScoreCell[] scores = new ScoreCell[results.size()];
    // first initialize score cells
    int j = 0;
    for (String[] result : results) {
      // initialize score cells
      ScoreCell score = new ScoreCell();
      score.row = result;
      score.score = scorer.createScore();
      scores[j++] = score;
    }
    for (int id : fieldIds) {
      UIField field = getField(id, WindowNo);
      final int idx = tab.getFieldIndex(id);
      if (field.isLookup()) {
        ArrayList<String> fieldValues = new ArrayList<String>(results.size());
        for (String[] row : results) {
          fieldValues.add(row[idx]);
        }
        ArrayList<String> sorts = getLookupValueOnlyDirect(id, fieldValues, true);
        int i = 0;
        for (String value : sorts) {
          scorer.getScore(value, scores[i].score);
          i++;
        }
      } else {
        int i = 0;
        for (String[] row : results) {
          String value = row[idx];
          scorer.getScore(value, scores[i].score);
          i++;
        }
      }
    }
    ArrayList<ScoreCell> matchingScores = new ArrayList<ScoreCell>();
    for (ScoreCell cell : scores) {
      if (cell.score.isMatch) {
        matchingScores.add(cell);
      }
    }
    Collections.sort(matchingScores, scorer);
    ArrayList<String[]> matches = new ArrayList<String[]>();
    for (ScoreCell score : matchingScores) {
      matches.add(score.row);
    }
    m_results.put(searchResultID, matches);
    return matches.size();
  }

  public String[][] getTabSearchResults(int searchResultID, int rowCount) {
    ArrayList<String[]> matches = m_results.get(searchResultID);
    if (matches != null) {
      if (matches.size() < rowCount) {
        rowCount = matches.size();
      }
      String[][] result = new String[rowCount][];
      int i = 0;
      for (String[] row : matches) {
        result[i++] = row;
        if (i == rowCount) break;
      }
      return result;
    } else {
      return new String[0][];
    }
  }

  /**
   * Execute Query for Tab. If the from/to range does not exist, it returns existing rows
   *
   * @param queryResultID stored query identifier provided by client
   * @param row row number first is 0
   * @return array of rows of array of field values or null if error. You get the columnNames via
   *     String[] columns = uiTab.getColumnNames();
   */
  public String[] requery(int queryResultID, int row) {
    // TODO requery
    String[][] results = getResults(queryResultID, row, 1);
    return results[0];
  } // requery

  /**
   * Release Results
   *
   * @param resultIDs stored query identifier provided by client
   */
  public void disposeWindow(ArrayList<Integer> resultIDs) {
    // System.out.println("before cached id:" + m_results.keySet());
    for (Integer queryResultID : resultIDs) m_results.remove(queryResultID);
    // System.out.println("after cached id:" + m_results.keySet());
    m_context.removeAllWindows();
  } // releaseResults

  /**
   * Create new Row with Default values. The new Row is not saved in Results
   *
   * @param windowNo relative window
   * @param AD_Tab_ID tab
   * @param context record context for parent columns and other variables
   * @return array of field values or null if error. You get the columnNames via String[] columns =
   *     uiTab.getColumnNames();
   */
  public ChangeVO newRow(int windowNo, int AD_Tab_ID, Map<String, String> context) {
    UITab tab = getTab(AD_Tab_ID);
    if (tab == null) {
      log.config("Not found AD_Tab_ID=" + AD_Tab_ID);
      return null;
    }
    CContext ctx = new CContext(m_context.entrySet());
    ctx.addWindow(windowNo, context);
    ctx.setIsSOTrx(windowNo, tab.isSOTrx());
    ChangeVO change = tab.newRow(ctx, windowNo);
    /**
     * Very likely not needed if (change.changedDropDowns == null) change.changedDropDowns = new
     * HashMap<String,ArrayList<NamePair>>(); for(UIField f:tab.getFields()) { if
     * (f.isDependentValue()) change.changedDropDowns.put(f.getColumnName(),
     * getLookupValues(windowNo, f.getAD_Field_ID(), change.changedFields)); }
     */
    tab.canUpdate(ctx, windowNo, change);
    return change;
  } // newRow

  /**
   * Refresh current row of Tab
   *
   * @param windowNo relative window
   * @param AD_Tab_ID tab
   * @param relRowNo relative row number in results
   * @param context current (relevant) context of new row
   * @return error message or null
   */
  public ChangeVO refreshRow(
      int windowNo, int AD_Tab_ID, int queryResultID, int relRowNo, Map<String, String> context) {
    if (context == null || context.size() == 0) return new ChangeVO(true, "No Context");
    UITab tab = getTab(AD_Tab_ID);
    if (tab == null) {
      log.config("Not found AD_Tab_ID=" + AD_Tab_ID);
      return new ChangeVO(true, "@NotFound@ @AD_Tab_ID@=" + AD_Tab_ID);
    }
    CContext ctx = new CContext(m_context.entrySet());
    ctx.addWindow(windowNo, context);
    ChangeVO retValue = tab.refreshRow(ctx, windowNo);
    if (retValue.hasError()) return retValue;
    // Update Results
    ArrayList<String[]> data = m_results.get(queryResultID);
    if (data == null) retValue.addError("Data Not Found");
    else {
      String[] dataRow = retValue.rowData.clone();
      data.set(relRowNo, dataRow);
      postProcessChangeVO(retValue, windowNo, context, dataRow, tab);
      retValue.trxInfo = GridTab.getTrxInfo(tab.getTableName(), ctx, windowNo, tab.getTabNo());
    }
    return retValue;
  } // refreshRow

  public ChangeVO updateRow(
      int windowNo,
      int AD_Tab_ID,
      int queryResultID,
      int relRowNo,
      Map<String, String> context,
      boolean force) {
    if (context == null || context.size() == 0)
      return new ChangeVO(true, Msg.translate(m_context, "NoContext"));
    ArrayList<String[]> data = m_results.get(queryResultID);
    if (data == null || data.size() == 0)
      return new ChangeVO(true, Msg.translate(m_context, "CachedDataNotFound"));
    UITab tab = getTab(AD_Tab_ID);
    if (tab == null) {
      log.config("Not found AD_Tab_ID=" + AD_Tab_ID);
      return new ChangeVO(true, Msg.translate(m_context, "@NotFound@ @AD_Tab_ID@=" + AD_Tab_ID));
    }
    CContext ctx = new CContext(m_context.entrySet());
    ctx.addWindow(windowNo, context);
    ChangeVO retValue;
    if (force) retValue = tab.saveRow(ctx, windowNo, false, null);
    else retValue = tab.saveRow(ctx, windowNo, false, data.get(relRowNo));
    if (retValue.hasError()) return retValue;
    // Update Results
    String[] dataRow = retValue.rowData.clone();
    data.set(relRowNo, dataRow);
    postProcessChangeVO(retValue, windowNo, context, dataRow, tab);
    retValue.trxInfo = GridTab.getTrxInfo(tab.getTableName(), ctx, windowNo, tab.getTabNo());
    if (retValue.isRefreshAll()) {}
    return retValue;
  }

  private void postProcessChangeVO(
      ChangeVO change, int windowNo, Map<String, String> context, String[] dataRow, UITab tab) {
    // make an updated context to get the necessary listboxvos
    Map<String, String> contextAfterUpdate = new HashMap<String, String>(context);
    int j = 0;
    for (UIField field : tab.getFields()) {
      contextAfterUpdate.put(field.getColumnName(), dataRow[j]);
      j++;
    }
    // now change rowData to remove password, and reload the changed
    // listboxes
    j = 0;
    for (UIField field : tab.getFields()) {
      // return an empty string for passwords etc
      if (field.isEncryptedField()
          || field.isEncryptedColumn()
          || "Password".equals(field.getColumnName())) change.rowData[j] = "";
      if (FieldType.isClientLookup(field.getAD_Reference_ID()) && field.isDependentValue()) {
        if (change.changedDropDowns == null)
          change.changedDropDowns = new HashMap<String, ArrayList<NamePair>>();
        ArrayList<NamePair> values;
        if (field.getAD_Reference_ID() == DisplayTypeConstants.Search) {
          ArrayList<String> t = new ArrayList<String>(1);
          t.add(context.get(field.getColumnName()));
          values = getLookupValueDirect(field.getAD_Field_ID(), t, true);
        } else values = getLookupData(windowNo, field.getAD_Field_ID(), context, true);
        change.changedDropDowns.put(field.getColumnName(), values);
      }
      j++;
    }
  }

  /**
   * Save (Update existing) Row of Tab
   *
   * @param windowNo relative window
   * @param AD_Tab_ID tab
   * @param relRowNo relative row number in results
   * @param context current (relevant) context of new row
   * @return error message or null
   */
  public ChangeVO updateRow(
      int windowNo, int AD_Tab_ID, int queryResultID, int relRowNo, Map<String, String> context) {
    return updateRow(windowNo, AD_Tab_ID, queryResultID, relRowNo, context, false);
  } // updateRow

  /**
   * Save (Insert new) Row of Tab
   *
   * @param windowNo relative window
   * @param AD_Tab_ID tab
   * @param curRow insert after relative row number in results
   * @param context current (relevant) context of new row
   * @return error message or null
   */
  public ChangeVO insertRow(
      int windowNo, int AD_Tab_ID, int queryResultID, int curRow, Map<String, String> context) {
    if (context == null || context.size() == 0) return new ChangeVO(true, "No Context");
    UITab tab = getTab(AD_Tab_ID);
    if (tab == null) {
      log.config("Not found AD_Tab_ID=" + AD_Tab_ID);
      return new ChangeVO(true, "@NotFound@ @AD_Tab_ID@=" + AD_Tab_ID);
    }

    log.info("Line Amt:" + context.get("LineNetAmt"));
    CContext ctx = new CContext(m_context.entrySet());
    ctx.addWindow(windowNo, context);
    ChangeVO retValue = tab.saveRow(ctx, windowNo, true);
    if (retValue.hasError()) return retValue;
    // Update Results
    ArrayList<String[]> data = m_results.get(queryResultID);
    if (data == null) retValue.addError("Data Not Found");
    else {
      String[] dataRow = retValue.rowData;
      if (curRow >= data.size()) data.add(dataRow);
      else data.add(curRow, dataRow);
      retValue.trxInfo = GridTab.getTrxInfo(tab.getTableName(), ctx, windowNo, tab.getTabNo());
    }
    return retValue;
  } // insertRow

  /**
   * Delete existing Row
   *
   * @param windowNo relative window
   * @param AD_Tab_ID tab
   * @param relRowNo relative row number in results
   * @return error message or null
   */
  public ChangeVO deleteRow(int windowNo, int AD_Tab_ID, int queryResultID, int relRowNo) {
    UITab tab = getTab(AD_Tab_ID);
    if (tab == null) {
      log.config("Not found AD_Tab_ID=" + AD_Tab_ID);
      return new ChangeVO(true, "@NotFound@ @AD_Tab_ID@=" + AD_Tab_ID);
    }
    ArrayList<String[]> data = m_results.get(queryResultID);
    if (data == null) return new ChangeVO(true, "Data Not Found");
    String[] rowData = data.get(relRowNo);
    // Copy Data into Context
    Map<String, String> context = new HashMap<String, String>();
    String[] columns = tab.getColumnNames();
    for (int i = 0; i < columns.length; i++) {
      String column = columns[i];
      context.put(column, rowData[i]);
    }
    CContext ctx = new CContext(m_context.entrySet());
    ctx.addWindow(windowNo, context);
    ChangeVO retValue = tab.deleteRow(ctx, windowNo);
    if (retValue.hasError()) return retValue;
    // Update Results
    data.remove(relRowNo);
    return retValue;
  } // deleteRow

  /**
   * Field Changed
   *
   * @param windowNo relative window
   * @param AD_Field_ID field
   * @param AD_Tab_ID tab
   * @param oldValue old field value
   * @param newValue new field value
   * @param context record context
   * @return Field Change VO
   */
  public ChangeVO fieldChanged(
      int windowNo,
      int AD_Field_ID,
      int AD_Tab_ID,
      String oldValue,
      String newValue,
      Map<String, String> context) {
    // Same Values
    if (oldValue == null || oldValue.equals(Null.NULLString)) oldValue = "";
    if (newValue == null || newValue.equals(Null.NULLString)) newValue = "";
    if (oldValue.equals(newValue)) return null;
    //
    UITab tab = getTab(AD_Tab_ID);
    if (tab == null) {
      log.config("Not found AD_Tab_ID=" + AD_Tab_ID);
      return null;
    }
    UIField field = getField(AD_Field_ID, windowNo);
    if (field == null) {
      log.warning("Cannot find AD_Field_ID=" + AD_Field_ID);
      return null;
    }

    CContext ctx = new CContext(m_context.entrySet());
    ctx.addWindow(windowNo, context);
    CContext origCtx = new CContext(m_context.entrySet());
    origCtx.addWindow(windowNo, context);
    ChangeVO change = null;
    try {
      // reset the thread active flag, in case the thread is reused later on
      CThreadUtil.setCalloutActive(false);
      change =
          tab.fieldChanged(
              origCtx, ctx, new ArrayList<UIField>(5), windowNo, field, oldValue, newValue);
      CThreadUtil.setCalloutActive(false);
      ctx.setContext(windowNo, field.getColumnName(), change.newConfirmedFieldValue);
    } catch (Exception e) {
      log.severe("fieldChange error:" + field.getColumnName() + e.getMessage());
    } finally {
      CThreadUtil.setCalloutActive(false);
    }

    return change;
  } // fieldChanged

  /**
   * Get Field Lookup Value Direct
   *
   * @param windowNo Window
   * @param AD_Field_ID
   * @param keyValues array of id values
   * @param cache
   * @return list of display values
   */
  public ArrayList<NamePair> getLookupValueDirect(
      int AD_Field_ID, ArrayList<String> keyValues, boolean cache) {
    int windowNo = 0; // No Context
    ArrayList<NamePair> displayValues = new ArrayList<NamePair>();
    UIField field = getField(AD_Field_ID, windowNo);

    // if (cache && field.isLookup())
    // field.getLookup().removeAllElements();
    if (field == null) log.warning("Cannot find AD_Field_ID=" + AD_Field_ID);
    //
    for (int i = 0; i < keyValues.size(); i++) {
      String key = keyValues.get(i);
      String value = null;
      if (field != null) value = field.getLookupDisplay(m_context, windowNo, key, cache);
      if (value == null) {
        /*
         * if(key == null) value = ""; else value = "<" + key + ">";
         */
        value = "";
      }
      NamePair pp = new ValueNamePair(key, value);
      displayValues.add(pp);
    }
    return displayValues;
  } // getLookupValueDirect

  /**
   * Get Field Lookup Value Direct
   *
   * @param windowNo Window
   * @param AD_Field_ID
   * @param keyValues array of id values
   * @param cache
   * @return list of display values
   */
  public ArrayList<String> getLookupValueOnlyDirect(
      int AD_Field_ID, ArrayList<String> keyValues, boolean cache) {
    int windowNo = 0; // No Context
    ArrayList<String> displayValues = new ArrayList<String>();
    UIField field = getField(AD_Field_ID, windowNo);
    if (field == null) log.warning("Cannot find AD_Field_ID=" + AD_Field_ID);
    //
    for (int i = 0; i < keyValues.size(); i++) {
      String key = keyValues.get(i);
      String value = null;
      if (field != null) value = field.getLookupDisplay(m_context, windowNo, key, cache);
      if (value == null) {
        /*
         * if(key == null) value = ""; else value = "<" + key + ">";
         */
        value = "";
      }
      displayValues.add(value);
    }
    return displayValues;
  } // getLookupValueDirect

  /**
   * Get Lookup Data for Field in context
   *
   * @param AD_Field_ID field
   * @param context context
   * @param refresh requery
   * @return lookup pair array
   */
  public ArrayList<NamePair> getLookupData(
      int windowNo, int AD_Field_ID, Map<String, String> context, boolean refresh) {
    UIField field = getField(AD_Field_ID, windowNo);
    if (field == null) {
      log.warning("Cannot find AD_Field_ID=" + AD_Field_ID);
      return null;
    }
    CContext ctx = new CContext(m_context.entrySet());
    ctx.addWindow(windowNo, context);
    if (field.isLookup() || field.isButtonLookup()) return field.getAllLookupData(ctx, windowNo);
    else log.warning("No Lookup: " + field.getColumnName());
    return null;
  } // getLookupData

  /**
   * ************************************************************************* Get All Lookup Data
   * for fields w/o context dependency
   *
   * @param AD_Tab_ID tab
   * @return map if FiledName and lookup pair array public Map<String,NamePair[]> getLookupDataAll
   *     (int AD_Tab_ID) { return null; } // getLookuupDataAll /**
   *     ************************************************************************
   */
  private static int curZoomWindowNO = 0;

  public int getZoomWindowNO() {
    curZoomWindowNO += 100;
    return curZoomWindowNO;
  }

  public Boolean savePreferences(Map<String, String> ctx) {
    CContext cContext = getContext();
    MUser user = MUser.get(cContext);
    MUserPreference preference = user.getPreference();
    String printerName = ctx.get("PrinterName");
    if (printerName != null && printerName.trim().equalsIgnoreCase("")) {
      cContext.setPrinterName(printerName);
      preference.setPrinterName(printerName);
    }
    String autoCommit = ctx.get("AutoCommit");
    if (autoCommit != null) {
      cContext.setAutoCommit(autoCommit.trim().equalsIgnoreCase("Y"));
      preference.setIsAutoCommit(autoCommit.trim().equalsIgnoreCase("Y"));
    }
    String showAdvanced = ctx.get("#ShowAdvanced");
    if (showAdvanced != null) {
      cContext.setContext("#ShowAdvanced", showAdvanced);
      preference.setIsShowAdvanced(showAdvanced.trim().equalsIgnoreCase("Y"));
    }
    String showAccounting = ctx.get("#ShowAcct");
    if (showAccounting != null) {
      cContext.setContext("#ShowAcct", showAccounting);
      preference.setIsShowAcct(showAccounting.trim().equalsIgnoreCase("Y"));
    }
    String showTranslation = ctx.get("#ShowTrl");
    if (showTranslation != null) {
      cContext.setContext("#ShowTrl", showTranslation);
      preference.setIsShowTrl(showTranslation.trim().equalsIgnoreCase("Y"));
    }
    String uiTheme = ctx.get("#UITheme");
    if (uiTheme != null && !uiTheme.trim().equalsIgnoreCase("")) {
      cContext.setContext("#UITheme", uiTheme);
      preference.setUITheme(uiTheme);
    }

    String printPreview = ctx.get("#PrintPreview");
    if (printPreview != null) {
      cContext.setPrintPreview(printPreview.equalsIgnoreCase("Y"));
      Ini.setProperty(Ini.P_PRINTPREVIEW, printPreview.equalsIgnoreCase("Y"));
      Ini.saveProperties(Ini.isClient());
    }

    String date = ctx.get("#Date");
    cContext.setContext("#Date", date);

    return preference.save();
  }

  public Boolean deleteSavedSearch(int tab_ID, String savedSearchName) {
    CContext cContext = getContext();
    MUserQuery query = MUserQuery.getForUser(cContext, tab_ID, savedSearchName);
    if (query != null)
      if (query.deleteLines()) {
        if (query.delete(true)) {
          return true;
        }
      }
    return false;
  } // deleteSavedsearch

  private static class WindowCacheKey extends WindowVOCacheKey {

    WindowCacheKey(
        int AD_Window_ID,
        int AD_UserDef_Win_ID,
        int AD_Role_ID,
        int AD_Menu_ID,
        String AD_Language) {
      super(AD_Window_ID, AD_Role_ID, AD_Menu_ID, AD_Language);
      this.AD_UserDef_Win_ID = AD_UserDef_Win_ID;
    }

    @Override
    public boolean equals(Object obj) {
      if (!(obj instanceof WindowCacheKey)) return false;
      WindowCacheKey key = (WindowCacheKey) obj;
      return super.equals(obj) && AD_UserDef_Win_ID == key.AD_UserDef_Win_ID;
    }

    @Override
    public int hashCode() {
      return toString().hashCode();
    }

    @Override
    public String toString() {
      return super.toString() + AD_UserDef_Win_ID;
    }

    int AD_UserDef_Win_ID;
  }

  private static class WindowVOCacheKey {

    WindowVOCacheKey(int AD_Window_ID, int AD_Role_ID, int AD_Menu_ID, String AD_Language) {
      this.AD_Window_ID = AD_Window_ID;
      this.AD_Role_ID = AD_Role_ID;
      this.AD_Menu_ID = AD_Menu_ID;
      this.AD_Language = AD_Language;
    }

    @Override
    public boolean equals(Object obj) {
      if (!(obj instanceof WindowVOCacheKey)) return false;
      WindowVOCacheKey key = (WindowVOCacheKey) obj;
      return AD_Window_ID == key.AD_Window_ID
          // && AD_UserDef_Win_ID == key.AD_UserDef_Win_ID
          && AD_Role_ID == key.AD_Role_ID
          && AD_Menu_ID == key.AD_Menu_ID
          && AD_Language.equals(key.AD_Language);
    }

    @Override
    public int hashCode() {
      return toString().hashCode();
    }

    @Override
    public String toString() {
      return AD_Language + AD_Window_ID + AD_Role_ID + AD_Menu_ID;
    }

    int AD_Window_ID;

    int AD_Role_ID;

    int AD_Menu_ID;

    String AD_Language;
  }

  private static class ScoreStrategy implements Comparator<ScoreCell> {

    // TODO: populate from locale to strip out the/a/and etc...
    private String[] terms;

    private String query;

    public ScoreStrategy(String queryString) {
      HashSet<String> unique = new HashSet<String>();
      this.query = queryString.trim().toLowerCase();
      for (String term : this.query.split("\\s+")) {
        if (!unique.contains(term)) {
          unique.add(term);
        }
      }
      this.terms = new String[unique.size()];
      int i = 0;
      for (String term : unique) {
        this.terms[i++] = term;
      }
    }

    public Score createScore() {
      Score score = new Score();
      score.terms = new int[terms.length];
      return score;
    }

    public void getScore(String value3, Score score) {
      if (value3 == null || value3.trim().equals("")) return;
      String value2 = value3.toLowerCase();
      if (query.equals(value2)) {
        score.equalsMatches++;
        score.isMatch = true;
        return;
      }
      try {
        int occurrences = value2.length() - value2.replaceAll(query, "").length();
        if (occurrences > 0) {
          score.completeMatches += occurrences;
          score.isMatch = true;
        }
        if (terms.length > 1) {
          int termNo = 0;
          for (String term : terms) {
            int count = value2.length() - value2.replaceAll(term, "").length();
            if (count > 0) {
              score.terms[termNo++] += count;
              score.isMatch = true;
            }
          }
        }
      } catch (PatternSyntaxException pse) {
        // assume no match on pattern syntax error
      }
    }

    public int compare(ScoreCell o1, ScoreCell o2) {
      Score s1 = o1.score;
      Score s2 = o2.score;
      int difference = 0;
      if ((difference = s2.equalsMatches - s1.equalsMatches) != 0) {
        return difference;
      }
      if ((difference = s2.completeMatches - s1.completeMatches) != 0) {
        return difference;
      }
      int total1 = 0;
      int total2 = 0;
      int terms1 = 0;
      int terms2 = 0;
      // otherwise check if both terms are matched and tabulate total
      for (int score : s1.terms) {
        if (score > 0) {
          total1 += score;
          terms1++;
        }
      }
      for (int score : s2.terms) {
        if (score > 0) {
          total2 += score;
          terms2++;
        }
      }
      if ((difference = terms2 - terms1) != 0) {
        return difference;
      } else {
        return total2 - total1;
      }
    }
  }

  private static class ScoreCell {

    String[] row;

    Score score;
  }

  private static class Score {

    public int completeMatches = 0;

    public int equalsMatches = 0;

    public int[] terms;

    public boolean isMatch = false;
  }

  public int searchTabResults(
      int WindowNo,
      int AD_Tab_ID,
      int queryResultID,
      int searchResultID,
      String query,
      int rowCount) {
    UITab tab = getTab(AD_Tab_ID);
    ArrayList<Integer> ids = new ArrayList<Integer>();
    for (UIField field : tab.getFields()) {
      if (field.isSelectionColumn()
          || field.isIdentifier() && FieldType.isText(field.getAD_Reference_ID())) {
        ids.add(field.getAD_Field_ID());
      }
    }
    return searchTabResults(WindowNo, tab, ids, queryResultID, searchResultID, query, rowCount);
  }

  public NodeVO getNode(String key) {
    return m_nodes.get(key);
  }

  public void putNode(String key, NodeVO node) {
    m_nodes.put(key, node);
  }
} // GwtServer