diff --git a/README.md b/README.md index 09dfa90..c60b8d6 100644 --- a/README.md +++ b/README.md @@ -1,3 +1,3 @@ -# Java_Examples +# Java Examples Java code examples \ No newline at end of file diff --git a/pickMaint/AS400Interface.java b/pickMaint/AS400Interface.java new file mode 100644 index 0000000..bbe4ea1 --- /dev/null +++ b/pickMaint/AS400Interface.java @@ -0,0 +1,1061 @@ +package pickMaint; + +import java.sql.Connection; +import java.sql.DriverManager; +import java.sql.PreparedStatement; +import java.sql.ResultSet; +import java.sql.SQLException; +import java.text.SimpleDateFormat; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Calendar; +import java.util.Date; +import java.util.HashMap; +import java.util.Map; +import java.util.logging.Logger; + +/** + * AS400Interface + * + * @TODO Implement production/test data selection from log-in prompt + * + * This class is designed to abstract the database transactions required by the GUI components in Editor.java. + * + * A connection is established with the AS400 upon instantiating this class. + * There is only one constructor which takes two strings (User and Password). + * + * All multiple use SQL statements are pre-compiled into java.sql.PreparedStatement objects during initialization. + * + * The following are cached locally (into HashMaps) from the server during init: + * Down time reason codes/descriptions + * Weaver numbers/names + * Loom speeds (in 1000s of picks/hour) + * These items are used frequently for calculation/display purposes and don't often change so it is more than enough to + * cache them temporarily during application startup. + * + * @author Wesley Ray + */ +public class AS400Interface { + + private String LIB = "STMDATA"; + + private static final Logger LOGGER = Logger.getLogger(AS400Interface.class.getName()); + + private static final HashMap downTimeCodeLookup = new HashMap(); + private static final HashMap weaverNameLookup = new HashMap(); + private static final HashMap loomPPHLookup = new HashMap(); + + private Connection conn; //java.sql.Connection object + private PreparedStatement queryLoomds; + private PreparedStatement queryLoomds2; + private PreparedStatement adjPicksLoomds; + private PreparedStatement adjPicksLoomds2; + private PreparedStatement chgWeaverLoomds; + private PreparedStatement chgWeaverLoomds2; + private PreparedStatement chgDtCodeLoomds; + private PreparedStatement chgDtCodeLoomds2; + private PreparedStatement adjHoursLoomds; + private PreparedStatement adjHoursLoomds2; + private PreparedStatement adjHrsPksLoomds; + private PreparedStatement adjHrsPksLoomds2; + private PreparedStatement newLoomDSRecord; + private PreparedStatement newLoomDS2Record; + private PreparedStatement delLoomDSRecord; + private PreparedStatement delLoomDS2Record; + private PreparedStatement getProdLoomHours; + private PreparedStatement getProdPicks; + private PreparedStatement getNobLoomHours; + private PreparedStatement getNobPicks; + private PreparedStatement getSampLoomHours; + private PreparedStatement getSampPicks; + private PreparedStatement getShiftLength; + private PreparedStatement getEfficiencies; + + public AS400Interface(String usr, String psw, boolean production) throws SQLException{ + this.LIB = production ? "STMDATA" : "WES"; //Sets name of library to be compiled into SQL statements + + //Register AS400 DB2 driver and open a connection with the AS400 + LOGGER.info("Connecting to AS400 as user: " + usr); + DriverManager.registerDriver(new com.ibm.as400.access.AS400JDBCDriver()); + this.conn = DriverManager.getConnection("jdbc:as400://0.0.0.0", usr, psw); + + setUpDTcodeHashMap(); //Grabs down time codes from server + setUpWeaverHashMap(); //Grabs weaver names from server + setUpLoomPPHHashMap(); //Grabs loom speeds from server + preCompileStatements(); //Pre-compile SQL statements + } + + /** + * Called by the constructor to pre-compile all of the SQl statements used by the program. + * @throws SQLException - Shouldn't happen, usually only thrown when there are syntax errors in the queries. + */ + private final void preCompileStatements() throws SQLException{ + LOGGER.info("Pre-compiling multiple use SQL statements."); + queryLoomds = this.conn.prepareStatement("SELECT lddate, " + + " ldloom, " + + " ldshif, " + + " ldweav, " + + " wvrnam, " + + " ldsty1, " + + " ldhrs, " + + " ldpick, " + + " lmppm, " + + " lmppms, " + + " ( ( 100.0 * lmppm ) / 82.0 ) AS picksHR, " + + " ( ( 100.0 * lmppms ) / 82.0 ) AS picksHRsam, " + + " ldreas, " + + " ldcut1, " + + " ldcut2, " + + " bmtycd, " + + " lddlcd " + + "FROM " + LIB + ".loomds, " + + " stmdata.iewvms, " + + " stmdata.loomms " + + "WHERE ldweav = wvr# " + + " AND ldloom = lmlomn " + + " AND LDDATE = ? " + + " AND LDSHIF = ? " + + "ORDER BY ldloom"); + queryLoomds2 = this.conn.prepareStatement("SELECT ldpick, " + + " ldhrs " + + "FROM " + LIB + ".loomds2 " + + "WHERE lddate = ? " + + " AND ldloom = ? " + + " AND ldshif = ? " + + " AND ldweav = ? " + + "ORDER BY ldloom"); + adjPicksLoomds = this.conn.prepareStatement("UPDATE " + LIB + ".loomds " + + "SET ldpick = ? " + + "WHERE lddate = ? " + + " AND ldloom = ? " + + " AND ldshif = ? " + + " AND ldweav = ? " + + " AND ldsty1 = ? " + + " AND ldsty2 = ? "); + adjPicksLoomds2 = this.conn.prepareStatement("UPDATE " + LIB + ".loomds2 " + + "SET ldpick = ? " + + "WHERE lddate = ? " + + " AND ldloom = ? " + + " AND ldshif = ? " + + " AND ldweav = ? " + + " AND ldsty1 = ? " + + " AND ldsty2 = ? "); + chgWeaverLoomds = this.conn.prepareStatement("UPDATE " + LIB + ".loomds " + + "SET ldweav = ? " + + "WHERE lddate = ? " + + " AND ldloom = ? " + + " AND ldshif = ? " + + " AND ldweav = ? " + + " AND ldsty1 = ? " + + " AND ldsty2 = ? "); + chgWeaverLoomds2 = this.conn.prepareStatement("UPDATE " + LIB + ".loomds2 " + + "SET ldweav = ? " + + "WHERE lddate = ? " + + " AND ldloom = ? " + + " AND ldshif = ? " + + " AND ldweav = ? " + + " AND ldsty1 = ? " + + " AND ldsty2 = ? "); + chgDtCodeLoomds = this.conn.prepareStatement("UPDATE " + LIB + ".loomds " + + "SET ldreas = ? " + + "WHERE lddate = ? " + + " AND ldloom = ? " + + " AND ldshif = ? " + + " AND ldweav = ? " + + " AND ldsty1 = ? " + + " AND ldsty2 = ? "); + chgDtCodeLoomds2 = this.conn.prepareStatement("UPDATE " + LIB + ".loomds2 " + + "SET ldreas = ? " + + "WHERE lddate = ? " + + " AND ldloom = ? " + + " AND ldshif = ? " + + " AND ldweav = ? " + + " AND ldsty1 = ? " + + " AND ldsty2 = ? "); + adjHoursLoomds = this.conn.prepareStatement("UPDATE " + LIB + ".loomds " + + "SET ldhrs = ? " + + "WHERE lddate = ? " + + " AND ldloom = ? " + + " AND ldshif = ? " + + " AND ldweav = ? " + + " AND ldsty1 = ? " + + " AND ldsty2 = ? "); + adjHoursLoomds2 = this.conn.prepareStatement("UPDATE " + LIB + ".loomds2 " + + "SET ldhrs = ? " + + "WHERE lddate = ? " + + " AND ldloom = ? " + + " AND ldshif = ? " + + " AND ldweav = ? " + + " AND ldsty1 = ? " + + " AND ldsty2 = ? "); + adjHrsPksLoomds = this.conn.prepareStatement("UPDATE " + LIB + ".loomds " + + "SET ldhrs = ?, " + + " ldpick = ? " + + "WHERE lddate = ? " + + " AND ldloom = ? " + + " AND ldshif = ? " + + " AND ldweav = ? " + + " AND ldsty1 = ? " + + " AND ldsty2 = ? "); + adjHrsPksLoomds2 = this.conn.prepareStatement("UPDATE " + LIB + ".loomds2 " + + "SET ldhrs = ?, " + + " ldpick = ? " + + "WHERE lddate = ? " + + " AND ldloom = ? " + + " AND ldshif = ? " + + " AND ldweav = ? " + + " AND ldsty1 = ? " + + " AND ldsty2 = ? "); + newLoomDSRecord = this.conn.prepareStatement("insert into " + LIB + ".LOOMDS values(?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)"); + newLoomDS2Record = this.conn.prepareStatement("insert into " + LIB + ".LOOMDS2 values(?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)"); + delLoomDSRecord = this.conn.prepareStatement("DELETE FROM " + LIB + ".loomds " + + "WHERE lddate = ? " + + " AND ldloom = ? " + + " AND ldshif = ? " + + " AND ldweav = ? " + + " AND ldsty1 = ? " + + " AND ldsty2 = 0 "); + delLoomDS2Record = this.conn.prepareStatement("DELETE FROM " + LIB + ".loomds2 " + + "WHERE lddate = ? " + + " AND ldloom = ? " + + " AND ldshif = ? " + + " AND ldweav = ? " + + " AND ldsty1 = ? " + + " AND ldsty2 = 0 "); + getProdLoomHours = this.conn.prepareStatement("SELECT Sum(ldhrs) AS loom_hours " + + "FROM " + LIB + ".loomds " + + "WHERE lddate = ? " + + " AND ldshif = ? " + + "GROUP BY lddate, " + + " ldshif " + + "ORDER BY lddate "); + getProdPicks = this.conn.prepareStatement("SELECT Sum(ldpick) AS total_picks " + + "FROM " + LIB + ".loomds " + + "WHERE lddate = ? " + + " AND ldshif = ? " + + "GROUP BY lddate, " + + " ldshif " + + "ORDER BY lddate "); + getNobLoomHours = this.conn.prepareStatement("SELECT Sum(ldhrs) AS loom_hours " + + "FROM " + LIB + ".loomds2 " + + "WHERE lddate = ? " + + " AND ldshif = ? " + + "GROUP BY lddate, " + + " ldshif " + + "ORDER BY lddate "); + getNobPicks = this.conn.prepareStatement("SELECT Sum(ldpick) AS total_picks " + + "FROM " + LIB + ".loomds2 " + + "WHERE lddate = ? " + + " AND ldshif = ? " + + "GROUP BY lddate, " + + " ldshif " + + "ORDER BY lddate "); + getSampLoomHours = this.conn.prepareStatement("SELECT Sum(ldhrs) AS total_hours " + + "FROM " + LIB + ".loomds " + + "WHERE lddate = ? " + + " AND ldshif = ? " + + " AND ldsty1 = ' SAM' " + + "GROUP BY lddate, " + + " ldshif " + + "ORDER BY lddate "); + getSampPicks = this.conn.prepareStatement("SELECT Sum(ldpick) AS total_picks " + + "FROM " + LIB + ".loomds " + + "WHERE lddate = ? " + + " AND ldshif = ? " + + " AND ldsty1 = ' SAM' " + + "GROUP BY lddate, " + + " ldshif " + + "ORDER BY lddate "); + getShiftLength = this.conn.prepareStatement("SELECT Max(ldhrs) AS shift_length " + + "FROM " + LIB + ".loomds " + + "WHERE lddate = ? " + + " AND ldshif = ? " + + "GROUP BY lddate, " + + " ldshif " + + "ORDER BY lddate "); + getEfficiencies = this.conn.prepareStatement("SELECT ( ( 100.0 / 82.0 ) * lmppm ) AS target_pph, " + + " ( ldpick / ldhrs ) AS acutal_pph " + + "FROM " + LIB + ".loomds " + + " INNER JOIN stmdata.loomms " + + " ON ldloom = lmlomn " + + "WHERE lddate = ? " + + " AND ldshif = ? " + + " AND ldhrs > 0 "); + } + + /** + * Quietly close and null ALL PreparedStatement objects and the java.sql.Connection object. + * + * If an exception is raised while closing one of the PreparedStatement objects the Connection object + * will be closed in the finally block. + */ + public void close(){ + try { + queryLoomds.close(); + queryLoomds2.close(); + adjPicksLoomds.close(); + adjPicksLoomds2.close(); + chgWeaverLoomds.close(); + chgWeaverLoomds2.close(); + chgDtCodeLoomds.close(); + chgDtCodeLoomds2.close(); + adjHoursLoomds.close(); + adjHoursLoomds2.close(); + adjHrsPksLoomds.close(); + adjHrsPksLoomds2.close(); + newLoomDSRecord.close(); + newLoomDS2Record.close(); + delLoomDSRecord.close(); + delLoomDS2Record.close(); + getProdLoomHours.close(); + getProdPicks.close(); + getNobLoomHours.close(); + getNobPicks.close(); + getSampLoomHours.close(); + getSampPicks.close(); + getShiftLength.close(); + getEfficiencies.close(); + conn.close(); + queryLoomds = null; + queryLoomds2 = null; + adjPicksLoomds = null; + adjPicksLoomds2 = null; + chgWeaverLoomds = null; + chgWeaverLoomds2 = null; + chgDtCodeLoomds = null; + chgDtCodeLoomds2 = null; + adjHoursLoomds = null; + adjHoursLoomds2 = null; + adjHrsPksLoomds = null; + adjHrsPksLoomds2 = null; + newLoomDSRecord = null; + newLoomDS2Record = null; + delLoomDSRecord = null; + delLoomDS2Record = null; + getProdLoomHours = null; + getProdPicks = null; + getNobLoomHours = null; + getNobPicks = null; + getSampLoomHours = null; + getSampPicks = null; + getShiftLength = null; + getEfficiencies = null; + conn = null; + LOGGER.info("Open connection resources closed successfully!"); + } catch(SQLException e){ + } finally { + if(conn != null) + try { + conn.close(); + conn = null; + } catch (SQLException e) { + } + } + } + + /** + * Queries the AS400 for picks records for the given date/shift combination. Returns results as a LoomDSRecord[] + * @param date - Date to use in query + * @param shift - Shift to use in query + * @return Pick records for date/shift as LoomDSRecord[] + * @throws SQLException + */ + public LoomDSRecord[] getShiftDetail(int date, int shift) throws SQLException{ + LOGGER.info("Fetching picks for " + ((shift == 1) ? "1st" : (shift == 2) ? "2nd" : (shift == 3) ? "3rd" : "") + " shift on " + new SimpleDateFormat("MM/dd/yyyy").format(intToDate(date)) + " from server."); + ArrayList list = new ArrayList(); + queryLoomds.setInt(1, date); + queryLoomds.setInt(2, shift); + ResultSet rs = queryLoomds.executeQuery(); + while(rs.next()){ + list.add(new LoomDSRecord(this, rs)); + } + rs.close(); + if(list.size() < 1) + throw new SQLException("Empty result set for " + ((shift == 1) ? "1st" : (shift == 2) ? "2nd" : (shift == 3) ? "3rd" : "" + shift) + " Shift on " + intToDate(date)); + return list.toArray(new LoomDSRecord[list.size()]); //Return a fixed length array rather than an ArrayList, we are done adding elements + } + + /** + * The actual hours and picks for no bonus looms reside in LOOMDS2. If the LoomDSRecord constructor detects that a record is no bonus + * it will call this method to query LOOMDS2 for the hours and picks for that record. + * @param date - date to use in query + * @param loom - loom # to use in query + * @param shift - Shift to use in query + * @param weaver - weaver # to use in query + * @return - float[] return[0] contains the hours, float[1] contains the picks + * @throws SQLException + */ + public float[] getLoomDS2HoursPicks(int date, int loom, int shift, int weaver) throws SQLException{ + queryLoomds2.setInt(1, date); + queryLoomds2.setInt(2, loom); + queryLoomds2.setInt(3, shift); + queryLoomds2.setInt(4, weaver); + ResultSet rs = queryLoomds2.executeQuery(); + if(rs.next()) { + return new float[]{rs.getFloat(2) , (float) rs.getInt(1)}; + } else { + LOGGER.severe("No record found in LOOMDS2 for " + weaverLookup(weaver) + " (" + weaver + ") on loom " + loom + " for " + ((shift == 1) ? "1st" : (shift == 2) ? "2nd" : (shift == 3) ? "3rd" : "") + " shift on " + new SimpleDateFormat("MM/dd/yyyy").format(intToDate(date))); + return new float[]{-1, -1}; + } + } + + /** + * Returns the length of the given shift on the given date. + * This works by returning the largest value found for the HOURS column within the records selected based on the + * date and shift supplied. + * + * SELECT Max(ldhrs) AS shift_length + * FROM stmdata.loomds " + * WHERE lddate = ? + * AND ldshif = ? + * GROUP BY lddate, + * ldshif + * ORDER BY lddate + * + * @param date - The date of the shift you want the length of + * @param shift - The shift you want the length of + * @return The length of the shift in hours + * @throws SQLException + */ + public float getShiftLength(Date date, int shift) throws SQLException{ + int d = AS400Interface.dateToInt(date); + getShiftLength.setInt(1, d); + getShiftLength.setInt(2, shift); + ResultSet rs = getShiftLength.executeQuery(); + if(rs.next()) + return rs.getFloat(1); + else + throw new SQLException("Got an empty result set for shift length query. Your guess is as good as mine..."); + } + + /** + * Returns the shift's efficiency as a function of (TOTAL_PICKS / PICKS_POSSIBLE) + * + * This query is run: + * + * SELECT ( ( 100.0 / 82.0 ) * lmppm ) AS target_pph, + * ( ldpick / ldhrs ) AS acutal_pph + * FROM stmdata.loomds + * INNER JOIN stmdata.loomms + * ON ldloom = lmlomn + * WHERE lddate = ? + * AND ldshif = ? + * AND ldhrs > 0 + * + * The result set is looped over; summing up target and actual picks per hour for the entire shift. + * + * @param date - The date of the shift you want + * @param shift - The shift you want + * @return (TOTAL_PICKS / PICKS_POSSIBLE) for the entire shift. + * @throws SQLException + */ + public float getShiftEfficiency(Date date, int shift) throws SQLException{ + int d = AS400Interface.dateToInt(date); + getEfficiencies.setInt(1, d); + getEfficiencies.setInt(2, shift); + ResultSet rs = getEfficiencies.executeQuery(); + double target = 0; + double actual = 0; + while(rs.next()){ + target += rs.getDouble(1); + actual += rs.getDouble(2); + } + return (float) (actual / target); + } + + /** + * Totals and returns loom-hours for bonus looms only. For the given date and shift. + * + * SELECT Sum(ldhrs) AS loom_hours + * FROM stmdata.loomds + * WHERE lddate = ? + * AND ldshif = ? + * GROUP BY lddate, + * ldshif + * ORDER BY lddate + * + * @param date - The date of the shift you want + * @param shift - The shift you want + * @return Total production loom hours for the supplied shift + * @throws SQLException + */ + public float getProductionLoomHours(Date date, int shift) throws SQLException{ + int d = AS400Interface.dateToInt(date); + getProdLoomHours.setInt(1, d); + getProdLoomHours.setInt(2, shift); + ResultSet rs = getProdLoomHours.executeQuery(); + if(rs.next()) + return rs.getFloat(1); + else + throw new SQLException("Got an empty result set for aggregate production hours query. Your guess is as good as mine..."); + } + + /** + * Totals and returns all bonus picks for the given shift. + * + * SELECT Sum(ldpick) AS total_picks + * FROM stmdata.loomds + * WHERE lddate = ? + * AND ldshif = ? + * GROUP BY lddate, + * ldshif + * ORDER BY lddate + * + * @param date - The date of the shift you want + * @param shift - The shift you want + * @return Total production picks for the supplied shift + * @throws SQLException + */ + public int getProductionPicks(Date date, int shift) throws SQLException{ + int d = AS400Interface.dateToInt(date); + getProdPicks.setInt(1, d); + getProdPicks.setInt(2, shift); + ResultSet rs = getProdPicks.executeQuery(); + if(rs.next()) + return rs.getInt(1); + else + throw new SQLException("Got an empty result set for aggregate production picks query. Your guess is as good as mine..."); + } + + /** + * Totals and returns no-bonus loom-hours for the supplied shift. + * + * SELECT Sum(ldhrs) AS loom_hours + * FROM stmdata.loomds2 + * WHERE lddate = ? + * AND ldshif = ? + * GROUP BY lddate, + * ldshif + * ORDER BY lddate + * + * @param date - The date of the shift you want + * @param shift - The shift you want + * @return Total no-bonus loom-hours for the supplied shift + * @throws SQLException + */ + public float getNoBonusLoomHours(Date date, int shift) throws SQLException{ + int d = AS400Interface.dateToInt(date); + getNobLoomHours.setInt(1, d); + getNobLoomHours.setInt(2, shift); + ResultSet rs = getNobLoomHours.executeQuery(); + if(rs.next()) + return rs.getFloat(1); + else + throw new SQLException("Got an empty result set for aggregate no bonus hours query. Your guess is as good as mine..."); + } + + /** + * Totals and returns all no-bonus picks for the supplied shift. + * + * SELECT Sum(ldpick) AS total_picks + * FROM stmdata.loomds2 + * WHERE lddate = ? + * AND ldshif = ? + * GROUP BY lddate, + * ldshif + * ORDER BY lddate + * + * @param date - The date of the shift you want + * @param shift - The shift you want + * @return Total no-bonus picks for the supplied shift + * @throws SQLException + */ + public int getNoBonusPicks(Date date, int shift) throws SQLException{ + int d = AS400Interface.dateToInt(date); + getNobPicks.setInt(1, d); + getNobPicks.setInt(2, shift); + ResultSet rs = getNobPicks.executeQuery(); + if(rs.next()) + return rs.getInt(1); + else + throw new SQLException("Got an empty result set for aggregate no bonus picks query. Your guess is as good as mine..."); + } + + /** + * Totals and returns all loom-hours that can be attributed to sample cuts for the supplied shift. + * + * SELECT Sum(ldhrs) AS total_hours + * FROM stmdata.loomds + * WHERE lddate = ? + * AND ldshif = ? + * AND ldsty1 = ' SAM' + * GROUP BY lddate, + * ldshif + * ORDER BY lddate + * + * + * @param date - The date of the shift you want + * @param shift - The shift you want + * @return total loom-hours that ran samples for the shift + * @throws SQLException + */ + public float getSampleLoomHours(Date date, int shift) throws SQLException{ + int d = AS400Interface.dateToInt(date); + getSampLoomHours.setInt(1, d); + getSampLoomHours.setInt(2, shift); + ResultSet rs = getSampLoomHours.executeQuery(); + if(rs.next()) + return rs.getFloat(1); + else + throw new SQLException("Got an empty result set for aggregate no bonus hours query. Your guess is as good as mine..."); + } + + /** + * Totals and returns the amount of sample picks woven for the supplied shift + * + * SELECT Sum(ldpick) AS total_picks + * FROM stmdata.loomds + * WHERE lddate = ? + * AND ldshif = ? + * AND ldsty1 = ' SAM' + * GROUP BY lddate, + * ldshif + * ORDER BY lddate + * + * @param date - The date of the shift you want + * @param shift - The shift you want + * @return total sample picks woven during the supplied shift + * @throws SQLException + */ + public int getSamplePicks(Date date, int shift) throws SQLException{ + int d = AS400Interface.dateToInt(date); + getSampPicks.setInt(1, d); + getSampPicks.setInt(2, shift); + ResultSet rs = getSampPicks.executeQuery(); + if(rs.next()) + return rs.getInt(1); + else + throw new SQLException("Got an empty result set for aggregate no bonus picks query. Your guess is as good as mine..."); + } + + /** + * Deletes the record from the database represented by the information contained in the supplied LoomDSRecord object. + * Specifically, it uses the date, loom #, shift, weaver #, and style from the LoomDSRecord object for the where criteria + * in the delete statement. + * @param record - The record to be removed from the database + * @throws SQLException + */ + public void delRecord(LoomDSRecord record) throws SQLException{ + delLoomDSRecord.setInt(1, dateToInt(record.getDate())); + delLoomDSRecord.setInt(2, record.getLoomNumber()); + delLoomDSRecord.setInt(3, record.getShift()); + delLoomDSRecord.setInt(4, record.getWeaverNumber()); + delLoomDSRecord.setString(5, " " + record.getLdStyle().trim()); + LOGGER.info(delLoomDSRecord.executeUpdate() + " row deleted in LOOMDS"); + if(!record.isBonus()){ + delLoomDS2Record.setInt(1, dateToInt(record.getDate())); + delLoomDS2Record.setInt(2, record.getLoomNumber()); + delLoomDS2Record.setInt(3, record.getShift()); + delLoomDS2Record.setInt(4, record.getWeaverNumber()); + delLoomDS2Record.setString(5, " " + record.getLdStyle().trim()); + LOGGER.info(delLoomDS2Record.executeUpdate() + " row deleted in LOOMDS2"); + } + } + + /** + * Creates a new entry in the database. If the entry is to be "no bonus" a record must also be written into LOOMDS2. + * The actual hours and picks for no bonus entries are stored in LOOMDS2 and LOOMDS gets zeros instead. For normal entries + * a single record is written into LOOMDS. + * + * @param date - Date. + * @param loom - Loom Number. + * @param shift - Shift. + * @param weaver - Weaver # that worked. + * @param style1 - BLN, TRL, or SAM. Only required if noBonus. + * @param hours - How many hours worked. + * @param picks - How many picks woven. + * @param dtCode - Down time reason code, if there is one. + * @param cut - The cut number. For sample cuts only. + * @param cutSuffix - The character portion of the cut number, if there is one. For sample cuts only. + * @param typeCode - [B]lanket, [I]nitial, or [T]rial. Only required if noBonus. I is never used. + * @param noBonus - Did this loom run production for bonus or not? + * @throws SQLException + */ + public void addRecord(Date date, int loom, int shift, int weaver, String style1, Double hours, + int picks, String dtCode, int cut, String cutSuffix, String typeCode, + boolean noBonus) throws SQLException { + newLoomDSRecord.setObject(1, dateToInt(date)); //Date YYMMDD + newLoomDSRecord.setObject(2, loom); //Loom # + newLoomDSRecord.setObject(3, shift); //Shift + newLoomDSRecord.setObject(4, weaver); //Weaver # + newLoomDSRecord.setObject(5, style1); //Style 1 ϵ [BLN, TRL, SAM] + newLoomDSRecord.setObject(6, 0); //Style 2 - I've never seen this anything other than zero + newLoomDSRecord.setObject(7, (noBonus ? 0 : hours)); //Hours + newLoomDSRecord.setObject(8, (noBonus ? 0 : picks)); //Picks + newLoomDSRecord.setObject(9, dtCode); //Down time Reason Code + newLoomDSRecord.setObject(10, "D"); //Loom Type - Always "D" for Dornier + newLoomDSRecord.setObject(11, 0); //Loom Fixer - Never seems to be filled in + newLoomDSRecord.setInt(12, cut); //Cut 1 - For when samples are cut in on production looms + newLoomDSRecord.setObject(13, cutSuffix); //Cut 2 - For when samples are cut in on production looms + newLoomDSRecord.setObject(14, typeCode); //Type code ϵ [B, I, T] + newLoomDSRecord.setObject(15, " "); //Filler + newLoomDSRecord.setObject(16, (noBonus ? "N" : " ")); //Delete Code - In LOOMDS if it is no bonus picks write an "N" + LOGGER.info(newLoomDSRecord.executeUpdate() + " row added to LOOMDS"); + if(noBonus){ + newLoomDS2Record.setObject(1, dateToInt(date)); //Date YYMMDD + newLoomDS2Record.setObject(2, loom); //Loom # + newLoomDS2Record.setObject(3, shift); //Shift + newLoomDS2Record.setObject(4, weaver); //Weaver # + newLoomDS2Record.setObject(5, style1); //Style 1 ϵ [BLN, TRL, SAM] + newLoomDS2Record.setObject(6, 0); //Style 2 - I've never seen this anything other than zero + newLoomDS2Record.setObject(7, hours); //Hours + newLoomDS2Record.setObject(8, picks); //Picks + newLoomDS2Record.setObject(9, dtCode); //Down time Reason Code + newLoomDS2Record.setObject(10, "D"); //Loom Type - Always "D" for Dornier + newLoomDS2Record.setObject(11, 0); //Loom Fixer - Never seems to be filled in + newLoomDS2Record.setInt(12, cut); //Cut 1 - For when samples are cut in on production looms + newLoomDS2Record.setObject(13, cutSuffix); //Cut 2 - For when samples are cut in on production looms + newLoomDS2Record.setObject(14, typeCode); //Type code ϵ [B, I, T] + newLoomDS2Record.setObject(15, " "); //Filler + newLoomDS2Record.setObject(16, " "); //Delete Code + LOGGER.info(newLoomDS2Record.executeUpdate() + " row added to LOOMDS2"); + } + } + + + /** + * "Sample Cuts" are records with particular values for a few of the fields to indicate that they + * are indeed samples that were cut into the production schedule for that loom. + * + * Parameter 5 will always be: " SAM" + * Parameter 9 will always be: " " + * Parameter 14 will always be: "T" + * + * Additionally a cut number is required for sample cuts + * + * @param source + * @param cut + * @param cutSuffix + * @param hours + * @param picks + * @throws SQLException + */ + public void addSampleCut(LoomDSRecord source, int cut, String cutSuffix, Double hours, int picks) throws SQLException{ + newLoomDSRecord.setObject(1, dateToInt(source.getDate())); //Date YYMMDD + newLoomDSRecord.setObject(2, source.getLoomNumber()); //Loom # + newLoomDSRecord.setObject(3, source.getShift()); //Shift + newLoomDSRecord.setObject(4, source.getWeaverNumber()); //Weaver # + newLoomDSRecord.setObject(5, " SAM"); //Style 1 ϵ [BLN, TRL, SAM] + newLoomDSRecord.setObject(6, 0); //Style 2 - I've never seen this anything other than zero + newLoomDSRecord.setObject(7, hours); //Hours + newLoomDSRecord.setObject(8, picks); //Picks + newLoomDSRecord.setObject(9, " "); //Down time Reason Code + newLoomDSRecord.setObject(10, "D"); //Loom Type - Always "D" for Dornier + newLoomDSRecord.setObject(11, 0); //Loom Fixer - Never seems to be filled in + newLoomDSRecord.setInt(12, cut); //Cut 1 - For when samples are cut in on production looms + newLoomDSRecord.setObject(13, cutSuffix); //Cut 2 - For when samples are cut in on production looms + newLoomDSRecord.setObject(14, "T"); //Type code ϵ [B, I, T] + newLoomDSRecord.setObject(15, " "); //Filler + newLoomDSRecord.setObject(16, " "); //Delete Code - In LOOMDS if it is no bonus picks write an "N" + LOGGER.info(newLoomDSRecord.executeUpdate() + " row added to LOOMDS"); + } + + /** + * Adjusts hours and picks in a single transaction. + * See adjustHours() and adjustPicks() for detailed information. + * + * @param record - A LoomDSRecord representative of the record you wish to change on the server + * @throws SQLException + */ + public void adjustHoursPicks(LoomDSRecord record) throws SQLException{ + if(record.isBonus()){ + adjHrsPksLoomds.setObject(1, record.getHours()); + adjHrsPksLoomds.setObject(2, record.getPicks()); + adjHrsPksLoomds.setInt(3, dateToInt(record.getDate())); + adjHrsPksLoomds.setInt(4, record.getLoomNumber()); + adjHrsPksLoomds.setInt(5, record.getShift()); + adjHrsPksLoomds.setInt(6, record.getWeaverNumber()); + adjHrsPksLoomds.setObject(7, " " + record.getLdStyle()); + adjHrsPksLoomds.setInt(8, 0); + LOGGER.info(adjHrsPksLoomds.executeUpdate() + " row updated in LOOMDS"); + } else { + adjHrsPksLoomds2.setObject(1, record.getHours()); + adjHrsPksLoomds2.setObject(2, record.getPicks()); + adjHrsPksLoomds2.setInt(3, dateToInt(record.getDate())); + adjHrsPksLoomds2.setInt(4, record.getLoomNumber()); + adjHrsPksLoomds2.setInt(5, record.getShift()); + adjHrsPksLoomds2.setInt(6, record.getWeaverNumber()); + adjHrsPksLoomds2.setObject(7, " " + record.getLdStyle()); + adjHrsPksLoomds2.setInt(8, 0); + LOGGER.info(adjHrsPksLoomds2.executeUpdate() + " row updated in LOOMDS2"); + } + } + + /** + * Change the hours (on the AS400) for the supplied record object. + * The SQL statement needs a date, loom #, shift, weaver #, and style for the key to + * lookup the record to be changed; these are obtained from the LoomDSRecord parameter. + * + * The hours will be changed to whatever record.getHours() returns. + * + * So basically, change the object and then call this method to save those changes to the server: * + * LoomDSRecord derp; + * derp.setHours(4.5); + * as400.adjustHours(derp); + * + * @param record - A LoomDSRecord representative of the record you wish to change on the server + * @throws SQLException + */ + public void adjustHours(LoomDSRecord record) throws SQLException{ + if(record.isBonus()){ + adjHoursLoomds.setObject(1, record.getHours()); + adjHoursLoomds.setInt(2, dateToInt(record.getDate())); + adjHoursLoomds.setInt(3, record.getLoomNumber()); + adjHoursLoomds.setInt(4, record.getShift()); + adjHoursLoomds.setInt(5, record.getWeaverNumber()); + adjHoursLoomds.setObject(6, " " + record.getLdStyle()); + adjHoursLoomds.setInt(7, 0); + LOGGER.info(adjHoursLoomds.executeUpdate() + " row updated in LOOMDS"); + } else { + adjHoursLoomds2.setObject(1, record.getHours()); + adjHoursLoomds2.setInt(2, dateToInt(record.getDate())); + adjHoursLoomds2.setInt(3, record.getLoomNumber()); + adjHoursLoomds2.setInt(4, record.getShift()); + adjHoursLoomds2.setInt(5, record.getWeaverNumber()); + adjHoursLoomds2.setObject(6, " " + record.getLdStyle()); + adjHoursLoomds2.setInt(7, 0); + LOGGER.info(adjHoursLoomds2.executeUpdate() + " row updated in LOOMDS2"); + } + } + + /** + * Change the picks (on the AS400) for the supplied record object. + * The SQL statement needs a date, loom #, shift, weaver #, and style for the key to + * lookup the record to be changed; these are obtained from the LoomDSRecord parameter. + * + * The picks will be changed to whatever record.getPicks() returns. + * + * So basically, change the object and then call this method to save those changes to the server: * + * LoomDSRecord derp; + * derp.setPicks(154); + * as400.adjustPicks(derp); + * + * @param record - A LoomDSRecord representative of the record you wish to change on the server + * @throws SQLException + */ + public void adjustPicks(LoomDSRecord record) throws SQLException{ + if(record.isBonus()){ + adjPicksLoomds.setInt(1, record.getPicks()); + adjPicksLoomds.setInt(2, dateToInt(record.getDate())); + adjPicksLoomds.setInt(3, record.getLoomNumber()); + adjPicksLoomds.setInt(4, record.getShift()); + adjPicksLoomds.setInt(5, record.getWeaverNumber()); + adjPicksLoomds.setObject(6, " " + record.getLdStyle()); + adjPicksLoomds.setInt(7, 0); + LOGGER.info(adjPicksLoomds.executeUpdate() + " row updated in LOOMDS"); + } else { + adjPicksLoomds2.setInt(1, record.getPicks()); + adjPicksLoomds2.setInt(2, dateToInt(record.getDate())); + adjPicksLoomds2.setInt(3, record.getLoomNumber()); + adjPicksLoomds2.setInt(4, record.getShift()); + adjPicksLoomds2.setInt(5, record.getWeaverNumber()); + adjPicksLoomds2.setObject(6, " " + record.getLdStyle()); + adjPicksLoomds2.setInt(7, 0); + LOGGER.info(adjPicksLoomds2.executeUpdate() + " row updated in LOOMDS2"); + } + } + + /** + * Changes the weaver number (on the AS400) for the supplied record object. + * The SQL statement needs a date, loom #, shift, weaver #, and style for the key to + * lookup the record to be changed; these are obtained from the LoomDSRecord parameter. + * + * Since the weaver number is part of the key we need to lookup the appropriate record on + * the server we need an extra parameter (currentWeaver) to supply the currentWeaver number + * for building the record key. + * + * The new weaver number is obtained from the LoomDsRecord object. + * + * Example: + * LoomDsRecord derp; + * int oldWeaver = derp.getWeaver(); + * derp.setWeaver(888); + * as400.changeWeaver(derp, oldWeaver); + * + * @param record - A LoomDSRecord representative of the record you wish to change on the server + * @param currentWeaver - The weaver number currently on the server for the record you wish to change + * @throws SQLException + */ + public void changeWeaver(LoomDSRecord record, int currentWeaver) throws SQLException{ + chgWeaverLoomds.setInt(1, record.getWeaverNumber()); + chgWeaverLoomds.setInt(2, dateToInt(record.getDate())); + chgWeaverLoomds.setInt(3, record.getLoomNumber()); + chgWeaverLoomds.setInt(4, record.getShift()); + chgWeaverLoomds.setInt(5, currentWeaver); + chgWeaverLoomds.setObject(6, " " + record.getLdStyle()); + chgWeaverLoomds.setInt(7, 0); + LOGGER.info(chgWeaverLoomds.executeUpdate() + " row updated in LOOMDS"); + + chgWeaverLoomds2.setInt(1, record.getWeaverNumber()); + chgWeaverLoomds2.setInt(2, dateToInt(record.getDate())); + chgWeaverLoomds2.setInt(3, record.getLoomNumber()); + chgWeaverLoomds2.setInt(4, record.getShift()); + chgWeaverLoomds2.setInt(5, currentWeaver); + chgWeaverLoomds2.setObject(6, " " + record.getLdStyle()); + chgWeaverLoomds2.setInt(7, 0); + LOGGER.info(chgWeaverLoomds2.executeUpdate() + " row updated in LOOMDS2"); + } + + /** + * Change the down time code (on the AS400) for the supplied record object. + * The SQL statement needs a date, loom #, shift, weaver #, and style for the key to + * lookup the record to be changed; these are obtained from the LoomDSRecord parameter. + * + * The down time code will be change to whatever record.getDownTimeCode() returns. + * + * So basically, change the object and then call this method to save those changes to the server: * + * LoomDSRecord derp; + * derp.setDownTimeCode("IC"); + * as400.changeDTCode(derp); + * + * @param record - A LoomDSRecord representative of the record you wish to change on the server + * @throws SQLException + */ + public void changeDtCode(LoomDSRecord record) throws SQLException{ + chgDtCodeLoomds.setObject(1, record.getDownTimeCode()); + chgDtCodeLoomds.setInt(2, dateToInt(record.getDate())); + chgDtCodeLoomds.setInt(3, record.getLoomNumber()); + chgDtCodeLoomds.setInt(4, record.getShift()); + chgDtCodeLoomds.setInt(5, record.getWeaverNumber()); + chgDtCodeLoomds.setObject(6, " " + record.getLdStyle()); + chgDtCodeLoomds.setInt(7, 0); //This field is never used on the AS400; always zero + LOGGER.info(chgDtCodeLoomds.executeUpdate() + " row updated in LOOMDS"); + + chgDtCodeLoomds2.setObject(1, record.getDownTimeCode()); + chgDtCodeLoomds2.setInt(2, dateToInt(record.getDate())); + chgDtCodeLoomds2.setInt(3, record.getLoomNumber()); + chgDtCodeLoomds2.setInt(4, record.getShift()); + chgDtCodeLoomds2.setInt(5, record.getWeaverNumber()); + chgDtCodeLoomds2.setObject(6, " " + record.getLdStyle()); + chgDtCodeLoomds2.setInt(7, 0); //This field is never used on the AS400; always zero + LOGGER.info(chgDtCodeLoomds2.executeUpdate() + " row updated in LOOMDS2"); + } + + /** + * Down time codes will probably never change; despite that I decided against hard-coding them in and went with this approach instead. + * We are opening a connection with the database anyway so we might as well grab the DT codes quick on application start up. + * + * Grabs down time codes and their descriptions from the server and stores them in the downTimeCodeLookup class var for speedy lookups later. + * Since downTimeCodeLookup is a static class variable, after this class is instantiated at least once you will be able to get the description + * for any DT code by calling: + * + * String description = AS400Interface.downTimeCodeLookup.get("EW"); //Don't do this + * String description = AS400Interface.dtCodeLookup("EW"); //Same result; Encapsulation FTW + * + * This will work from any context since it's a class variable/method. + * @throws SQLException + */ + private final void setUpDTcodeHashMap() throws SQLException{ + LOGGER.info("Caching down time codes/decriptions locally from server."); + ResultSet rs = this.conn.createStatement().executeQuery("select IEDTCD, IEDTDS from stmdata.iedtfl order by IEDTCD"); + while(rs.next()) { + AS400Interface.downTimeCodeLookup.put(rs.getString(1).trim(), rs.getString(2).trim()); + } + rs.close(); + } + + /** + * Lookup a down time code description from the hash table created during init. + * @param code - The code you want the description for (1 or 2 characters only) + * @return A string (30 chars max) containing the description of the supplied down time code + */ + public static final String dtCodeLookup(String code){ + return AS400Interface.downTimeCodeLookup.get(code); + } + + /** + * Cache weaver names locally for speedy lookups later. + * See documentation for setUpDTcodeHashMap() for a more detailed explanation. + * @throws SQLException + */ + private final void setUpWeaverHashMap() throws SQLException{ + LOGGER.info("Caching weaver table locally from server."); + ResultSet rs = this.conn.createStatement().executeQuery("select WVR#, WVRNAM from stmdata.iewvms where shift > 0 order by WVR#"); + while(rs.next()) { + AS400Interface.weaverNameLookup.put(rs.getInt(1), rs.getString(2).trim()); + } + rs.close(); + } + + /** + * Lookup a weaver name from the hash table created during init + * @param weaver - The weaver number whos name you want + * @return A string (15 chars max) containing the name associated with the supplied weaver number + */ + public static final String weaverLookup(int weaver){ + return AS400Interface.weaverNameLookup.get(weaver); + } + + /** + * Cache all loom speeds locally in a hash table for speedy lookups later + * See documentation for setUpDTcodeHashMap() for a more detailed explanation. + * @throws SQLException + */ + private final void setUpLoomPPHHashMap() throws SQLException{ + LOGGER.info("Caching loom speeds locally from server."); + ResultSet rs = this.conn.createStatement().executeQuery("select lmlomn, (lmppm * (100.0 / 82.0)) as max_picks_per_hour from stmdata.loomms where lmlomn > 200"); + while(rs.next()) { + AS400Interface.loomPPHLookup.put(rs.getInt(1), rs.getFloat(2)); + } + rs.close(); + } + + /** + * Get a loom speed in 1000s Picks/hour from the hash table we created at init. + * @param loom - The loom number whos speed you want + * @return The speed in 1000s of Picks/hour + */ + public static final float loomSpeedLookup(int loom){ + return AS400Interface.loomPPHLookup.get(loom); + } + + /** + * Returns all down time codes and their descriptions in a string array. + * This is used for creating a JComboBox in the GUI. + * @return + */ + public static final String[] getDtCodeChoices(){ + String[] ret = new String[downTimeCodeLookup.size() + 1]; //+1 since we want to include "None" as a choice + ret[0] = ""; + int i = 1; + for (Map.Entry entry : downTimeCodeLookup.entrySet()) { + ret[i] = entry.getKey() + " - " + entry.getValue(); + i++; + } + Arrays.sort(ret); //Sort the array; ret[0] is blank at this point and will be placed at the beginning of the array + ret[0] = "None"; //Now stick "None" into ret[0] so it will be at the top of the JComboBox + return ret; + } + + /** + * Converts a java.util.Date into a integer date in yyMMdd format. + * + * Example: June 30th, 2013 --> 130630 + * + * In many files on the AS400 dates are stored in this format, so this is desirable when writing information + * back to the database. + * + * @param d a java.util.Date to convert + * @return + */ + public static final int dateToInt(Date d){ + return Integer.parseInt(new SimpleDateFormat("yyMMdd").format(d)); + } + + /** + * Many dates are stored in yyMMdd format as a single integer on the AS400; this method can parse that + * into a java.util.Date object which is handy when working with dates read from the server. + * + * Example: 140120 --> January 20th, 2014 + * 990315 --> March 15th, 2099 + * + * @param d an integer date (yyMMdd) to convert to a java.util.Date + * @return + */ + public static final Date intToDate(int d){ + Calendar ret = Calendar.getInstance(); + ret.set(((d / 10000) + 2000), (((d - ((d / 10000) * 10000)) / 100) - 1), (d % 100), 0, 0); //Hows this for a one-liner haha... + return ret.getTime(); + } + +} diff --git a/pickMaint/Analyze.java b/pickMaint/Analyze.java new file mode 100644 index 0000000..af2da63 --- /dev/null +++ b/pickMaint/Analyze.java @@ -0,0 +1,169 @@ +package pickMaint; + +import java.io.FileWriter; +import java.io.IOException; +import java.sql.Connection; +import java.sql.DriverManager; +import java.sql.PreparedStatement; +import java.sql.ResultSet; +import java.sql.SQLException; +import java.util.logging.Logger; + +/** + * This is a quick routine I wrote to automatically run some SQL queries I developed to judge how well the layout/tablet programs are + * performing. It works by figuring out what and how many rows were adjusted by Gloria/Val after the data was submitted from the tablets + * by Chelsea/Traci. + * + * Spits out an excel file for all 3 shifts for each date in the DATE[] constant. + * + * @author Wes + */ +public class Analyze { + + private static final int[] DATE = new int[]{ 141117 }; + + private static final Logger LOGGER = Logger.getLogger(Analyze.class.getName()); + private static final String[] COLUMN_NAMES = new String[]{ "DATE", "SHIFT", "LOOM #", "WEAVER #", "HOURS", "PICKS", "HOURS ENTERED", "PICKS ACTUAL", "PICKS ENTERED", "HOUR ADJUSTMENT", "PICK ADJUSTMENT", "DOWN TIME CODE"}; + private static final String ROWS_CHANGED_QUERY = "SELECT lddate, " + + " ldshif, " + + " ldloom, " + + " ldweav, " + + " ldhrs, " + + " ldpick, " + + " HOURS, " + + " pksact, " + + " pksent, " + + " ldhrs - HOURS HRS_ADJ, " + + " ldpick - pksact PKS_ADJ, " + + " '(' " + + " || stmdata.iedtfl.iedtcd " + + " || ') ' " + + " || stmdata.iedtfl.iedtds dtcode " + + "FROM stmdata.loomds " + + " LEFT JOIN stmdata.pksread ON lddate = DATE AND " + + " ldshif = shift AND " + + " ldloom = loom " + + " LEFT JOIN stmdata.IEDTFL ON stmdata.loomds.ldreas = stmdata.iedtfl.iedtcd " + + "WHERE lddate = ? AND " + + " ldshif = ? AND " + + " (ldhrs != HOURS OR " + + " ldpick != pksact) AND " + + " NOT (ldweav > 699) AND " + + " ldloom NOT IN (SELECT ldloom " + + " FROM stmdata.loomds " + + " WHERE lddate = ? AND " + + " ldshif = ? AND " + + " ldsty1 = ' SAM') " + + "ORDER BY ldloom "; + + private static final String TOTAL_ROWS = "SELECT COUNT(*) total_rows " + + "FROM stmdata.pksread " + + "WHERE date = ? AND " + + " shift = ? "; + + public static void main(String[] args) throws SQLException, IOException { + // Register AS400 DB2 driver and open a connection with the AS400 + LOGGER.info("Connecting to AS400 as user: WRAY\n"); + DriverManager.registerDriver(new com.ibm.as400.access.AS400JDBCDriver()); + Connection conn = DriverManager.getConnection("jdbc:as400://0.0.0.0", "USER", "PASS"); + + LOGGER.info("Pre-compiling SQL statements...\n"); + PreparedStatement rowsChanged = conn.prepareStatement(ROWS_CHANGED_QUERY, ResultSet.TYPE_SCROLL_INSENSITIVE, ResultSet.CONCUR_READ_ONLY); + PreparedStatement totalRows = conn.prepareStatement(TOTAL_ROWS); + + LOGGER.info("Downloading data for all shifts for " + DATE.length + " days...\n"); + for (int date : DATE) { + //System.out.println("Date: " + date); + for (int shift = 1; shift < 4; shift++) { + //System.out.println("Shift: " + shift); + rowsChanged.setInt(1, date); + rowsChanged.setInt(2, shift); + rowsChanged.setInt(3, date); + rowsChanged.setInt(4, shift); + + totalRows.setInt(1, date); + totalRows.setInt(2, shift); + + ResultSet changed = rowsChanged.executeQuery(); + ResultSet total = totalRows.executeQuery(); + + LOGGER.info(((shift == 1) ? "1st" : (shift == 2) ? "2nd" : (shift == 3) ? "3rd" : "") + " shift downloaded. Dumping to Excel sheet...\n"); + + int tot = 0; + if (total.next()) + tot = total.getInt(1); + + FileWriter out = new FileWriter(date + "_" + shift + ".xls"); + String buffer = ""; + + for (int i = 0; i < COLUMN_NAMES.length; i++) { + buffer += (COLUMN_NAMES[i] + "\t"); + } + + out.write(buffer + "\n"); + buffer = ""; + + while (changed.next()) { + for (int i = 0; i < COLUMN_NAMES.length; i++) { + if(i == 11) { + String dt = "" + changed.getObject(i + 1); + if(dt.equalsIgnoreCase("null")) + buffer += (" \t"); + else + buffer += (dt.trim() + "\t"); + } else { + buffer += (changed.getObject(i + 1) + "\t"); + } + } + out.write(buffer + "\n"); + buffer = ""; + } + + int c = getRowCount(changed); + + out.write("\nROWS CHANGED\tTOTAL ROWS\n"); + out.write(c + "\t" + tot + "\t \t" + "=1-(A" + (c + 4) + "/B" + (c + 4) + ")" + "\n"); + out.write(" \t \t \t=1-(A" + (c + 5) + "/B" + (c + 4) + ")" + "\n"); + + out.flush(); // Make sure everything gets written + out.close(); + out = null; + + changed.close(); + total.close(); + } + } + + LOGGER.info("Tidying up open resources and exiting..."); + rowsChanged.close(); + totalRows.close(); + rowsChanged = null; + totalRows = null; + conn.close(); + conn = null; + System.exit(0); + } + + /** + * The ResultSet you pass to this method must be from a PreparedStatement created with these args: + * prepareStatement("select * from ...", ResultSet.TYPE_SCROLL_INSENSITIVE, ResultSet.CONCUR_READ_ONLY); + * + * This will throw an exception with FORWARD_ONLY ResultSets (as in the default one you get with no extra arguments) + * + * ResultSet.CONCUR_READ_ONLY may actually be something else if you so desire. TYPE_SCROLL_INSENSITIVE is what is important. + * + * @param set - The ResultSet to count rows in + * @return The number of rows in the ResultSet + * @throws SQLException + */ + public static int getRowCount(ResultSet set) throws SQLException { + int rowCount; + int currentRow = set.getRow(); // Get current row + rowCount = set.last() ? set.getRow() : 0; // Determine number of rows + if (currentRow == 0) // If there was no current row + set.beforeFirst(); // We want next() to go to first row + else // If there WAS a current row + set.absolute(currentRow); // Restore it + return rowCount; + } +} \ No newline at end of file diff --git a/pickMaint/ButtonTabComponent.java b/pickMaint/ButtonTabComponent.java new file mode 100644 index 0000000..651b7e9 --- /dev/null +++ b/pickMaint/ButtonTabComponent.java @@ -0,0 +1,183 @@ +/* + * I grabbed this from the official Oracle JTabbedPane tutorial and slightly + * modified how it behaves when the close button is clicked to properly remove + * objects from the HashMaps in the Editor instance. + * + * Copyright (c) 1995, 2008, Oracle and/or its affiliates. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * - Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * - Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * - Neither the name of Oracle or the names of its + * contributors may be used to endorse or promote products derived + * from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS + * IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, + * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package pickMaint; + +import java.awt.BasicStroke; +import java.awt.Color; +import java.awt.Component; +import java.awt.Dimension; +import java.awt.FlowLayout; +import java.awt.Graphics; +import java.awt.Graphics2D; +import java.awt.event.ActionEvent; +import java.awt.event.ActionListener; +import java.awt.event.MouseAdapter; +import java.awt.event.MouseEvent; +import java.awt.event.MouseListener; +import java.text.DateFormat; +import java.text.ParseException; +import java.text.SimpleDateFormat; +import java.util.Date; + +import javax.swing.AbstractButton; +import javax.swing.BorderFactory; +import javax.swing.JButton; +import javax.swing.JLabel; +import javax.swing.JPanel; +import javax.swing.JTabbedPane; +import javax.swing.plaf.basic.BasicButtonUI; + +/** + * Component to be used as tabComponent; + * Contains a JLabel to show the text and + * a JButton to close the tab it belongs to + */ +public class ButtonTabComponent extends JPanel { + private static final long serialVersionUID = -5689913156783054837L; + private final JTabbedPane pane; + private final Editor editor; + + public ButtonTabComponent(final JTabbedPane pane, final Editor editor) { + //unset default FlowLayout' gaps + super(new FlowLayout(FlowLayout.LEFT, 0, 0)); + if (pane == null) { + throw new NullPointerException("TabbedPane is null"); + } + this.pane = pane; + this.editor = editor; + setOpaque(false); + + //make JLabel read titles from JTabbedPane + JLabel label = new JLabel() { + private static final long serialVersionUID = -3058269816351809172L; + + public String getText() { + int i = pane.indexOfTabComponent(ButtonTabComponent.this); + if (i != -1) { + return pane.getTitleAt(i); + } + return null; + } + }; + + add(label); + //add more space between the label and the button + label.setBorder(BorderFactory.createEmptyBorder(0, 0, 0, 5)); + //tab button + JButton button = new TabButton(); + add(button); + //add more space to the top of the component + setBorder(BorderFactory.createEmptyBorder(2, 0, 0, 0)); + } + + private class TabButton extends JButton implements ActionListener { + private static final long serialVersionUID = 369894927571691780L; + + public TabButton() { + int size = 17; + setPreferredSize(new Dimension(size, size)); + setToolTipText("close this tab"); + //Make the button looks the same for all Laf's + setUI(new BasicButtonUI()); + //Make it transparent + setContentAreaFilled(false); + //No need to be focusable + setFocusable(false); + setBorder(BorderFactory.createEtchedBorder()); + setBorderPainted(false); + //Making nice rollover effect + //we use the same listener for all buttons + addMouseListener(buttonMouseListener); + setRolloverEnabled(true); + //Close the proper tab by clicking the button + addActionListener(this); + } + + public void actionPerformed(ActionEvent e) { + int i = pane.indexOfTabComponent(ButtonTabComponent.this); + if (i != -1) { + String[] parse = pane.getTitleAt(i).split(" "); + int shift = Integer.parseInt(parse[2].substring(0, 1)); + DateFormat formatter = new SimpleDateFormat("MM/dd/yyyy"); + try { + Date date = formatter.parse(parse[0]); + editor.unloadShiftDetail(date, shift); + //pane.remove(i); + } catch (ParseException e1) { e1.printStackTrace(); } + } + } + + //we don't want to update UI for this button + public void updateUI() { + } + + //paint the cross + protected void paintComponent(Graphics g) { + super.paintComponent(g); + Graphics2D g2 = (Graphics2D) g.create(); + //shift the image for pressed buttons + if (getModel().isPressed()) { + g2.translate(1, 1); + } + g2.setStroke(new BasicStroke(2)); + g2.setColor(Color.BLACK); + if (getModel().isRollover()) { + g2.setColor(Color.MAGENTA); + } + int delta = 6; + g2.drawLine(delta, delta, getWidth() - delta - 1, getHeight() - delta - 1); + g2.drawLine(getWidth() - delta - 1, delta, delta, getHeight() - delta - 1); + g2.dispose(); + } + } + + private final static MouseListener buttonMouseListener = new MouseAdapter() { + public void mouseEntered(MouseEvent e) { + Component component = e.getComponent(); + if (component instanceof AbstractButton) { + AbstractButton button = (AbstractButton) component; + button.setBorderPainted(true); + } + } + + public void mouseExited(MouseEvent e) { + Component component = e.getComponent(); + if (component instanceof AbstractButton) { + AbstractButton button = (AbstractButton) component; + button.setBorderPainted(false); + } + } + }; +} \ No newline at end of file diff --git a/pickMaint/Editor.java b/pickMaint/Editor.java new file mode 100644 index 0000000..2ba82b7 --- /dev/null +++ b/pickMaint/Editor.java @@ -0,0 +1,1119 @@ +package pickMaint; + +import static pickMaint.ExcelExporter.exportJTableToFile; +import static pickMaint.ExcelExporter.openInExcel; + +import java.awt.BorderLayout; +import java.awt.Color; +import java.awt.Component; +import java.awt.event.ActionEvent; +import java.awt.event.ActionListener; +import java.awt.event.MouseAdapter; +import java.awt.event.MouseEvent; +import java.awt.event.WindowAdapter; +import java.awt.event.WindowEvent; +import java.io.IOException; +import java.math.BigDecimal; +import java.math.RoundingMode; +import java.sql.SQLException; +import java.text.DateFormat; +import java.text.NumberFormat; +import java.text.ParseException; +import java.text.SimpleDateFormat; +import java.util.Calendar; +import java.util.Date; +import java.util.HashMap; +import java.util.logging.Level; +import java.util.logging.Logger; + +import javax.swing.JCheckBox; +import javax.swing.JComboBox; +import javax.swing.JFrame; +import javax.swing.JLabel; +import javax.swing.JMenu; +import javax.swing.JMenuBar; +import javax.swing.JMenuItem; +import javax.swing.JOptionPane; +import javax.swing.JPanel; +import javax.swing.JPasswordField; +import javax.swing.JPopupMenu; +import javax.swing.JScrollPane; +import javax.swing.JTabbedPane; +import javax.swing.JTable; +import javax.swing.JTextField; +import javax.swing.KeyStroke; +import javax.swing.event.DocumentEvent; +import javax.swing.event.DocumentListener; +import javax.swing.table.TableCellRenderer; + +import com.toedter.calendar.JDateChooser; + +import net.miginfocom.swing.MigLayout; + +public class Editor extends JFrame { + + private static final long serialVersionUID = 1L; + + private final AS400Interface as400; + private HashMap loadedShifts = new HashMap(); //Holds data for all loaded tabs. Key format: MM/dd/yyy-shift ex: 02/03/14-2 + private HashMap loadedTables = new HashMap(); + private HashMap loadedTableModels = new HashMap(); + private HashMap loadedScrollPanes = new HashMap(); + + private final JTabbedPane tabbedPane; + private final Editor self; + + private JMenuBar topMenu = new JMenuBar(); + private JMenu fileMenu = new JMenu("File"); + private JMenuItem newTab = new JMenuItem("New Tab"); + private JMenu toolsMenu = new JMenu("Tools"); + private JMenuItem newRecord = new JMenuItem("Add New Record"); + private JMenuItem openInExcel = new JMenuItem("Export Current Tab to Excel"); + private JMenuItem shiftSummary = new JMenuItem("Shift Summary"); + + /** + * Dispatches a Runnable to the EDT that builds/displays a login prompt. + * The credentials entered in this prompt are used to create an AS400Interface object. + * This AS400Interface object is used to create/display a new Editor. + * + * @param args - Not used. + * @throws SQLException - Shouldn't happen :) + */ + public static void main(String[] args) { + java.awt.EventQueue.invokeLater(new Runnable() { + @Override + public void run() { + JPanel loginPrompt = new JPanel(new MigLayout("fill")); + + final JComboBox users = new JComboBox(new String[]{ "WRAY", "AARON", "CALEB"}); + JLabel userLabel = new JLabel("User:"); + loginPrompt.add(userLabel, "grow"); + loginPrompt.add(users, "grow, wrap"); + + final JPasswordField passwordField = new JPasswordField(10); + passwordField.addAncestorListener(new RequestFocusListener()); + JLabel passLabel = new JLabel("Password:"); + loginPrompt.add(passLabel, "grow"); + loginPrompt.add(passwordField, "grow, wrap"); + + final JComboBox dataSet = new JComboBox(new String[]{"PRODUCTION", "TEST"}); + loginPrompt.add(dataSet, "grow, span 2 1, wrap"); + + int option = JOptionPane.showOptionDialog(null, loginPrompt, "Pick Maintenance Login - " + (new SimpleDateFormat("MM/dd/yyyy - HH:mm").format(Calendar.getInstance().getTime())), + JOptionPane.NO_OPTION, JOptionPane.PLAIN_MESSAGE, null, new String[]{ "Login!", "Quit" }, "Login!"); + if(option == 0) + java.awt.EventQueue.invokeLater(new Runnable() { + @Override + public void run() { + AS400Interface connection = null; + boolean productionData = dataSet.getSelectedIndex() == 0; + try { + connection = new AS400Interface((String)users.getSelectedItem(), new String(passwordField.getPassword()), productionData); + new Editor(connection); + } catch (SQLException e) { + e.printStackTrace(); + System.exit(ERROR); //Not much else we can do here. Are you connected to the network? + } + } + }); + } + }); + } + + public Editor(AS400Interface as400) throws SQLException { + super("Pick Maintenance"); //Call super constructor, set title + this.as400 = as400; //Save a reference to the AS400Interface + self = this; //Store reference to this Editor object for accessing within anonymous inner classes + + setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); //Exit program gracefully when user closes JFrame + setSize(1440, 810); //Set size of the JFrame + + this.addWindowListener(new WindowAdapter(){ + public void windowClosing(WindowEvent e) { + self.as400.close(); + super.windowClosing(e); + } + }); + + //Create action for "New Tab" menu option + newTab.addActionListener(new ActionListener(){ + private final Logger LOGGER = Logger.getLogger(LoomDSRecord.class.getName()); + @Override + public void actionPerformed(ActionEvent e) { + try { + //Create prompt to get date and shift from user + JPanel newTabPrompt = new JPanel(); + JDateChooser dateChooser = new JDateChooser(new Date(), "MM/dd/yy"); + JTextField shiftInput = new JTextField(3); + shiftInput.addAncestorListener(new RequestFocusListener()); + shiftInput.addFocusListener(new SelectAllOnFocusListener()); + newTabPrompt.add(dateChooser); + newTabPrompt.add(new JLabel("Shift")); + newTabPrompt.add(shiftInput); + + //Display prompt + int option = JOptionPane.showOptionDialog(self, newTabPrompt, "New Tab", JOptionPane.NO_OPTION, JOptionPane.PLAIN_MESSAGE, null, new String[]{ "Add", "Cancel" }, "Add"); + if(option == 0){ + //If user clicked "Add" add a tab for the date and shift selected + addTab(dateChooser.getDate(), Integer.parseInt(shiftInput.getText())); + } + } catch (SQLException e1) { + JOptionPane.showMessageDialog(rootPane, e1.getMessage(), "Error Getting Data From AS400", JOptionPane.ERROR_MESSAGE); + } catch(NumberFormatException e2){ + LOGGER.log(Level.SEVERE, "Error parsing user input. Nothing loaded. See exception message for details.", e2); + JOptionPane.showMessageDialog(self, "Uh-oh..\n\rError parsing user input. Nothing loaded. See exception message for details.", "Whoops!", JOptionPane.ERROR_MESSAGE); + } + } + }); + + newRecord.addActionListener(new ActionListener() { + private final Logger LOGGER = Logger.getLogger(LoomDSRecord.class.getName()); + @Override + public void actionPerformed(ActionEvent e) { + String[] parse = tabbedPane.getTitleAt(tabbedPane.getSelectedIndex()).split(" "); + int shift = 0; + try { shift = Integer.parseInt(parse[2].substring(0, 1)); } catch(Exception e1) { LOGGER.log(Level.SEVERE, "Failed to build key for date/shift. Better luck next time.", e1); return; } + DateFormat formatter = new SimpleDateFormat("MM/dd/yyyy"); + Date date = null; + try { date = formatter.parse(parse[0]); } catch (ParseException e1) { LOGGER.log(Level.SEVERE, "Failed to build key for date/shift. Better luck next time.", e1); return; } + + JPanel prompt = new JPanel(new MigLayout("fill")); + + JDateChooser dateDisp = new JDateChooser(date, "MM/dd/yy"); //Date chooser just for displaying date, not editable + dateDisp.setEnabled(false); + JTextField shiftDisp = new JTextField("" + shift); //Text field for displaying shift, not editable + shiftDisp.setEnabled(false); + final JTextField loomInp = new JTextField(4); + loomInp.addAncestorListener(new RequestFocusListener()); //Yield focus to the loom input field when dialog displayed + loomInp.addFocusListener(new SelectAllOnFocusListener()); //Select any text currently in the field on focus + JTextField weaverInp = new JTextField(4); + weaverInp.addFocusListener(new SelectAllOnFocusListener()); //Select any text currently in the field on focus + JComboBox styleInp = new JComboBox(new String[]{" ", " BLN", " TRL", " SAM"}); + final JTextField hoursInp = new JTextField(4); + hoursInp.addFocusListener(new SelectAllOnFocusListener()); //Select any text currently in the field on focus + final JTextField picksInp = new JTextField(4); + picksInp.addFocusListener(new SelectAllOnFocusListener()); //Select any text currently in the field on focus + JComboBox dtInp = new JComboBox(AS400Interface.getDtCodeChoices()); + JTextField cutInp = new JTextField(4); + cutInp.addFocusListener(new SelectAllOnFocusListener()); //Select any text currently in the field on focus + JTextField cutSuffixInp = new JTextField(4); + cutSuffixInp.addFocusListener(new SelectAllOnFocusListener()); //Select any text currently in the field on focus + JComboBox typeInp = new JComboBox(new String[]{"B", "T"}); + JCheckBox bonusInp = new JCheckBox(); + + hoursInp.getDocument().addDocumentListener(new DocumentListener() { + @Override + public void removeUpdate(DocumentEvent e) { + update(); + } + @Override + public void insertUpdate(DocumentEvent e) { + update(); + } + @Override + public void changedUpdate(DocumentEvent e) { + update(); + } + private void update(){ + try { + float pph = AS400Interface.loomSpeedLookup(Integer.parseInt(loomInp.getText())); //Get max picks per hour for user entered loom + double hours = Double.parseDouble(hoursInp.getText()); //get user entered hours + picksInp.setText("" + (int) (pph * hours)); //Multiply, set pick input to product + } catch(Exception e){ } //If we don't have all the required information: do nothing. + } + }); + + loomInp.getDocument().addDocumentListener(new DocumentListener() { + @Override + public void removeUpdate(DocumentEvent e) { + update(); + } + @Override + public void insertUpdate(DocumentEvent e) { + update(); + } + @Override + public void changedUpdate(DocumentEvent e) { + update(); + } + private void update(){ + try { + float pph = AS400Interface.loomSpeedLookup(Integer.parseInt(loomInp.getText())); //Get max picks per hour for user entered loom + double hours = Double.parseDouble(hoursInp.getText()); //get user entered hours + picksInp.setText("" + (int) (pph * hours)); //Multiply, set pick input to product + } catch(Exception e){ } //If we don't have all the required information: do nothing. + } + }); + + prompt.add(new JLabel("Date"), "align right, grow"); + prompt.add(dateDisp, "align left, grow, wrap"); + prompt.add(new JLabel("Shift"), "align right, grow"); + prompt.add(shiftDisp, "align left, grow, wrap"); + prompt.add(new JLabel("Loom"), "align right, grow"); + prompt.add(loomInp, "align left, grow, wrap"); + prompt.add(new JLabel("Weaver"), "align right, grow"); + prompt.add(weaverInp, "align left, grow, wrap"); + prompt.add(new JLabel("Style"), "align right, grow"); + prompt.add(styleInp, "align left, grow, wrap"); + prompt.add(new JLabel("Hours"), "align right, grow"); + prompt.add(hoursInp, "align left, grow, wrap"); + prompt.add(new JLabel("Picks"), "align right, grow"); + prompt.add(picksInp, "align left, grow, wrap"); + prompt.add(new JLabel("Down Time Code"), "align right, grow"); + prompt.add(dtInp, "align left, grow, wrap"); + prompt.add(new JLabel("Cut Number"), "align right, grow"); + prompt.add(cutInp, "align left, grow, wrap"); + prompt.add(new JLabel("Cut Suffix"), "align right, grow"); + prompt.add(cutSuffixInp, "align left, grow, wrap"); + prompt.add(new JLabel("Type Code"), "align right, grow"); + prompt.add(typeInp, "align left, grow, wrap"); + prompt.add(new JLabel("No Bonus"), "align right, grow"); + prompt.add(bonusInp, "align left, grow, wrap"); + + int option = JOptionPane.showOptionDialog(self, prompt, "New Record", JOptionPane.NO_OPTION, JOptionPane.PLAIN_MESSAGE, null, new String[]{ "Add", "Cancel" }, "Add"); + if(option == 0){ + int loom = 0; + int weaver = 0; + String style1 = ""; + int picks = 0; + double hours = 0; + String dtCode = ""; + int cut1 = 0; + String cut2 = ""; + String typeCode = ""; + boolean noBonus = false; + try { + loom = Integer.parseInt(loomInp.getText()); + if(loom < 201 || loom > 317) + throw new NumberFormatException("Loom number out of range: " + loom); + weaver = Integer.parseInt(weaverInp.getText()); + if(AS400Interface.weaverLookup(weaver) == null) + throw new NumberFormatException("Weaver doesn't exist: " + weaver); + style1 = (String) styleInp.getSelectedItem(); + picks = Integer.parseInt(picksInp.getText()); + if(picks < 0 || picks > 250) + throw new NumberFormatException("Picks value not sane: " + picks); + hours = Double.parseDouble(hoursInp.getText()); + if(hours < 0 || hours > 12) + throw new NumberFormatException("Hours Value not sane: " + hours); + dtCode = ((String) dtInp.getSelectedItem()).split(" ")[0]; + if(dtCode.equalsIgnoreCase("NONE")) + dtCode = " "; + cut1 = Integer.parseInt(cutInp.getText()); + if(cut1 < 0 || cut1 > 999) + throw new NumberFormatException("Cut number is out of range: " + cut1); + if(cutSuffixInp.getText().length() > 2) + cut2 = cutSuffixInp.getText().substring(0, 2); + else + cut2 = cutSuffixInp.getText(); + typeCode = ((String) typeInp.getSelectedItem()); + noBonus = bonusInp.isSelected(); + } catch(NumberFormatException e2){ + LOGGER.log(Level.SEVERE, "Error parsing user input. Nothing added. See exception message for details.", e2); + JOptionPane.showMessageDialog(self, "Uh-oh..\n\rError parsing user input. Nothing added. See exception message for details.", "Whoops!", JOptionPane.ERROR_MESSAGE); + return; + } + try { + self.as400.addRecord(date, loom, shift, weaver, style1, hours, picks, dtCode, cut1, cut2, typeCode, noBonus); + reloadTab(makeKey(date, shift)); + } catch(SQLException e2) { + LOGGER.log(Level.SEVERE, "Error writing record to database. Better luck next time", e2); + JOptionPane.showMessageDialog(self, "Uh-oh..\n\rA databse transaction error occured. Nothing added. See exception message for details.", "Whoops!", JOptionPane.ERROR_MESSAGE); + } catch (ParseException e2) { + LOGGER.log(Level.SEVERE, "Error reloading tab. Add it manually. See exception message for details.", e2); + JOptionPane.showMessageDialog(self, "Uh-oh..\n\rError reloading tab. Add it manually. See exception message for details.", "Whoops!", JOptionPane.ERROR_MESSAGE); + } + } + } + }); + + openInExcel.addActionListener(new ActionListener() { + private final Logger LOGGER = Logger.getLogger(LoomDSRecord.class.getName()); + @Override + public void actionPerformed(ActionEvent e) { + String[] parse = tabbedPane.getTitleAt(tabbedPane.getSelectedIndex()).split(" "); + int shift = 0; + try { shift = Integer.parseInt(parse[2].substring(0, 1)); } catch(Exception e1) { LOGGER.log(Level.SEVERE, "Failed to build key for date/shift. Better luck next time.", e1); return; } + DateFormat formatter = new SimpleDateFormat("MM/dd/yyyy"); + Date date = null; + try { date = formatter.parse(parse[0]); } catch (ParseException e1) { LOGGER.log(Level.SEVERE, "Failed to build key for date/shift. Better luck next time.", e1); return; } + try { + exportJTableToFile(loadedTables.get(makeKey(date, shift)), "out.xls"); + openInExcel("out.xls"); + } catch (IOException e1) { + LOGGER.log(Level.SEVERE, "Some kind of file I/O took a dump while exporting table. Sorry.", e1); + return; + } + } + }); + + shiftSummary.addActionListener(new ActionListener(){ + private final Logger LOGGER = Logger.getLogger(LoomDSRecord.class.getName()); + @Override + public void actionPerformed(ActionEvent e) { + NumberFormat percentFormat = NumberFormat.getPercentInstance(); + percentFormat.setMaximumFractionDigits(3); + + String[] parse = tabbedPane.getTitleAt(tabbedPane.getSelectedIndex()).split(" "); + int shift = 0; + try { shift = Integer.parseInt(parse[2].substring(0, 1)); } catch(Exception e1) { LOGGER.log(Level.SEVERE, "Failed to build key for date/shift. Better luck next time.", e1); return; } + DateFormat formatter = new SimpleDateFormat("MM/dd/yyyy"); + Date date = null; + try { date = formatter.parse(parse[0]); } catch (ParseException e1) { LOGGER.log(Level.SEVERE, "Failed to build key for date/shift. Better luck next time.", e1); return; } + + float shiftLength = -1; + float prodLoomHrs = 0; + int prodLoomPicks = 0; + float noBonHours = 0; + int noBonPicks = 0; + float sampHours = 0; + int sampPicks = 0; + float shiftEff = 0; + try { + prodLoomHrs = self.as400.getProductionLoomHours(date, shift); + prodLoomPicks = self.as400.getProductionPicks(date, shift); + noBonHours = self.as400.getNoBonusLoomHours(date, shift); + noBonPicks = self.as400.getNoBonusPicks(date, shift); + sampHours = self.as400.getSampleLoomHours(date, shift); + sampPicks = self.as400.getSamplePicks(date, shift); + shiftLength = self.as400.getShiftLength(date, shift); + shiftEff = self.as400.getShiftEfficiency(date, shift); + } catch (SQLException e1) { + LOGGER.log(Level.SEVERE, "Something broke.", e1); + } + float prodLooms = prodLoomHrs / shiftLength; + float sampPicksPct = (float) sampPicks / prodLoomPicks; + float sampHrsPct = sampHours / prodLoomHrs; + + JPanel dialog = new JPanel(new MigLayout("fill")); + + dialog.add(new JLabel("Shift Efficiency:"), "grow, align right"); + dialog.add(new JLabel(percentFormat.format(shiftEff)), "grow, wrap"); + dialog.add(new JLabel("Shift Length:"), "grow, align right"); + dialog.add(new JLabel(shiftLength + " hours"), "grow, wrap"); + dialog.add(new JLabel("Production Loom Hours:"), "grow, align right"); + dialog.add(new JLabel(prodLoomHrs + " hours"), "grow, wrap"); + dialog.add(new JLabel("Number of Looms:"), "grow, align right"); + dialog.add(new JLabel("" + prodLooms), "grow, wrap"); + dialog.add(new JLabel("Production Picks:"), "grow, align right"); + dialog.add(new JLabel("" + prodLoomPicks), "grow, wrap"); + dialog.add(new JLabel("Sample Hours:"), "grow, align right"); + dialog.add(new JLabel(sampHours + " hours (" + percentFormat.format(sampHrsPct) + ")"), "grow, wrap"); + dialog.add(new JLabel("Sample Picks:"), "grow, align right"); + dialog.add(new JLabel(sampPicks + " (" + percentFormat.format(sampPicksPct) + ")"), "grow, wrap"); + dialog.add(new JLabel("No Bonus Looms:"), "grow, align right"); + dialog.add(new JLabel((noBonHours / 8.0) + ""), "grow, wrap"); + dialog.add(new JLabel("No Bonus Picks:"), "grow, align right"); + dialog.add(new JLabel(noBonPicks + ""), "grow, wrap"); + + JOptionPane.showOptionDialog(self, dialog, "Shift Summary", JOptionPane.NO_OPTION, JOptionPane.PLAIN_MESSAGE, null, new String[]{ "Ok" }, "Ok"); + } + }); + + JPanel contentPane = new JPanel(new BorderLayout()); //Main Panel - Everything gets added to this + + newTab.setAccelerator(KeyStroke.getKeyStroke('t')); + newRecord.setAccelerator(KeyStroke.getKeyStroke('n')); + shiftSummary.setAccelerator(KeyStroke.getKeyStroke('s')); + + //Set up menu bar + fileMenu.add(newTab); + toolsMenu.add(newRecord); + toolsMenu.add(openInExcel); + toolsMenu.addSeparator(); + toolsMenu.add(shiftSummary); + topMenu.add(fileMenu); + topMenu.add(toolsMenu); + contentPane.add(topMenu, BorderLayout.NORTH); //Add menu bar to the top of the UI + + //Set up tabbed pane + tabbedPane = new JTabbedPane(); + + //Add tabs for last three shifts that ran + loadMostRecentDataByShift(1); + loadMostRecentDataByShift(2); + loadMostRecentDataByShift(3); + + contentPane.add(tabbedPane, BorderLayout.CENTER); //Add tabbed pane to the center of the UI + + setContentPane(contentPane); //Set the content pane of the JFrame to our JPanel we've created + setLocationRelativeTo(null); + setVisible(true); //Display the UI + } + + private void loadMostRecentDataByShift(int shift) { + Calendar cal = Calendar.getInstance(); + boolean done = false; + int dayOfWeek = -1; + while (!done) { + try { + addTab(cal.getTime(), shift); //an SQL exception is thrown for an empty result set dropping us into the catch block below + done = true; //No exception thrown, tab must've loaded okay set done = true; to exit loop + } catch (SQLException e) { + /* + * In here we basically want to set the calendar to the previous day so we can try to load that. There some extra logic to + * avoid Saturday and Sunday dates to help minimize pointless database transactions. + */ + do { + cal.add(Calendar.DAY_OF_MONTH, -1); //Subtract a day from the current date + dayOfWeek = cal.get(Calendar.DAY_OF_WEEK); //Store day of the week + } while (dayOfWeek == Calendar.SATURDAY //if Saturday or Sunday: repeat. + || dayOfWeek == Calendar.SUNDAY); + } + } + } + + /** + * Create new tab for the specified date and shift + * @param date - integer date in yyMMdd format. Ex: 140203 = Feb. 3, 2014 + * @param shift - integer shift should be in the set [1, 2, 3] + * @throws SQLException + */ + @SuppressWarnings("unused") + private void addTab(int date, int shift) throws SQLException{ + loadShiftDetail(date, shift); //Query server for data + String key = makeKey(date, shift); //Form key to retrieve objects from HashMaps + JScrollPane c = loadedScrollPanes.get(key); //Grab the ScrollPane from HashMap + tabbedPane.add(formatKeyForDisplay(key), c); //Add ScrollPane to TabbedPane + tabbedPane.setSelectedComponent(c); //Set focus to newly added ScrollPane + tabbedPane.setTabComponentAt(tabbedPane.indexOfComponent(c), new ButtonTabComponent(tabbedPane, this)); //Add close button to tab + } + + /** + * Overloaded version to take a Java.util.Date for first arg instead of an integer. + * @param date - Java.util.Date object representing the desired date + * @param shift - integer shift should be in the set [1, 2, 3] + * @throws SQLException + */ + private void addTab(Date date, int shift) throws SQLException{ + loadShiftDetail(date, shift); //Query server for data + String key = makeKey(date, shift); //Form key to retrieve objects from HashMaps + JScrollPane c = loadedScrollPanes.get(key); //Grab the ScrollPane from HashMap + tabbedPane.add(formatKeyForDisplay(key), c); //Add ScrollPane to TabbedPane + tabbedPane.setSelectedComponent(c); //Set focus to newly added ScrollPane + tabbedPane.setTabComponentAt(tabbedPane.indexOfComponent(c), new ButtonTabComponent(tabbedPane, this)); //Add close button to tab + } + + /** + * Overloaded to accept a single integer date. Creates a Java.util.Date object via + * a static utility method and calls loadShiftDetail(Date, intDate, shift) + * @param date - integer date in yyMMdd format. Ex: 140203 = Feb. 3, 2014 + * @param shift - integer shift should be in the set [1, 2, 3] + * @throws SQLException + */ + private void loadShiftDetail(int date, int shift) throws SQLException{ + loadShiftDetail(AS400Interface.intToDate(date), date, shift); + } + + /** + * Overloaded to accept a single java.util.Date. Creates an integer representation of the + * date in yyMMdd format and calls loadShiftDetail(Date, intDate, shift) + * @param date - Java.util.Date object representing the desired date + * @param shift - integer shift should be in the set [1, 2, 3] + * @throws SQLException + */ + private void loadShiftDetail(Date date, int shift) throws SQLException{ + loadShiftDetail(date, AS400Interface.dateToInt(date), shift); + } + + /** + * Never called directly; use one of the overloaded helper methods instead. + * This method first creates the String key used by all hashmaps for this date/shift combo. + * The shift detail is then fetched from the server in the form of a LoomDSRecord[]. + * A reference to this array is stored in the loadedShifts HashMap + * A LoomDSTableModel is created and loaded with the LoomDSRecord[] + * A JTable is created using this TableModel + * A Mouse listener is added to the JTable to facilitate right clicking of table rows + * A ScrollPane is created using this JTable. This gets added to the new tab + * A reference to the TableModel, JTable, and ScrollPane is stored in the respective HashMaps. + * + * Key format for the hash maps is "MM/dd/yyyy-SHIFT" so 3rd shift on Feb. 3, 2014 would be "02/03/2014-3" + * @param date - A java.util.Date object for the desired date + * @param intDate - an integer representation of date in yyMMdd format + * @param shift - integer shift should be in the set [1, 2, 3] + * @throws SQLException + */ + private void loadShiftDetail(Date date, int intDate, int shift) throws SQLException{ + //Create Key + final String key = new SimpleDateFormat("MM/dd/yyyy-").format(date) + shift; + + //Check if tab is already loaded + if(loadedShifts.get(key) != null){ + JOptionPane.showMessageDialog(self, "Tab already loaded!", "Whoops!", JOptionPane.ERROR_MESSAGE); + return; + } + + //Get data from server, store in HashMap + loadedShifts.put(key, as400.getShiftDetail(intDate, shift)); + + LoomDSTableModel tableModel = new LoomDSTableModel(); //Create new TableModel + tableModel.loadTable(loadedShifts.get(key)); //Load with data from server + + final JTable table = new JTable(tableModel){ + private static final long serialVersionUID = 7479372430475297633L; + private final Color HILIGHTED_ROW = new Color(184,207,229); //Got this with a color picker, hopefully it doesn't change on me + private int lastRowDrawn = 0; + + public Component prepareRenderer(TableCellRenderer renderer, int row, int column) { + Component c = super.prepareRenderer(renderer, row, column); + + if(c.getBackground().equals(HILIGHTED_ROW)) //Check if current bg color is the blue for a highlighted row + return c; //Do nothing if it is + + LoomDSTableModel model = (LoomDSTableModel) getModel(); + if(model.highlightSample(row)){ + lastRowDrawn = row; + c.setBackground(new Color(255,248,220)); + } else if(model.highlightZero(row)) { + lastRowDrawn = row; + c.setBackground(new Color(255,202,202)); + } else if(!model.bonus(row) && row != lastRowDrawn){ + lastRowDrawn = row; + if(row % 2 == 0){ + c.setBackground(Color.LIGHT_GRAY); + } else { + c.setBackground(new Color(205,201,201)); + } + } + else if(row != lastRowDrawn){ + lastRowDrawn = row; + c.setBackground(new Color(255,255,255)); + } + + return c; + } + }; + table.addMouseListener(new MouseAdapter() { + /* + * This class enables row selection with the right mouse button. It also displays our + * JPopupMenu on right click. + */ + private String hashKey = key; + + @Override + public void mouseReleased(MouseEvent e) { + int r = table.rowAtPoint(e.getPoint()); + if (r >= 0 && r < table.getRowCount()) { + table.setRowSelectionInterval(r, r); + } else { + table.clearSelection(); + } + + int rowindex = table.getSelectedRow(); + if (rowindex < 0) + return; + if (e.isPopupTrigger() && e.getComponent() instanceof JTable ) { + JPopupMenu popup = createRightClickMenu(table, r, hashKey); + popup.show(e.getComponent(), e.getX(), e.getY()); + } + } + }); + + JScrollPane scrollPane = new JScrollPane(table); //Create ScrollPane for JTable + + loadedTables.put(key, table); //Store JTable in HashMap + loadedTableModels.put(key, tableModel); //Store TableModel in HashMap + loadedScrollPanes.put(key, scrollPane); //Store ScrollPane in HashMap + } + + /** + * Overloaded to accept a single integer date. + * + * First removes the tab associated with the key formed by the date and shift provided. + * Then, Removes all objects from all HashMaps associated with the key formed by the date and shift provided. + * + * @param date - integer date in yyMMdd format. Ex: 140203 = Feb. 3, 2014 + * @param shift - integer shift should be in the set [1, 2, 3] + * @throws SQLException + */ + public void unloadShiftDetail(int date, int shift){ + unloadShiftDetail(makeKey(date, shift)); + } + + /** + * Overloaded version to take a java.util.Date and an integer shift + * + * @param date - A java.util.Date object for the desired date + * @param shift - integer shift should be in the set [1, 2, 3] + */ + public void unloadShiftDetail(Date date, int shift){ + unloadShiftDetail(makeKey(date, shift)); + } + + /** + * First removes the tab associated with the key formed by the date and shift provided. + * Then, Removes all objects from all HashMaps associated with the key formed by the date and shift provided. + * + * @param key - The HashMap key for the objects to unload + */ + public void unloadShiftDetail(String key){ + tabbedPane.remove(loadedScrollPanes.get(key)); //Destroy Tab, if it exists + loadedShifts.remove(key); //Remove LoomDSRecord[] from HashMap + loadedTables.remove(key); //Remove JTable from HashMap + loadedTableModels.remove(key); //Remove TableModel from HashMap + loadedScrollPanes.remove(key); //Remove ScrollPane from HashMap + } + + /** + * Overloaded to take an integer date in yyMMdd format. Returns the array of LoomDSRecord objects + * associated with the provided date/shift combination. + * @param date - integer date in yyMMdd format. Ex: 140203 = Feb. 3, 2014 + * @param shift - integer shift should be in the set [1, 2, 3] + * @return An array of LoomDSRecord objects + */ + @SuppressWarnings("unused") + private LoomDSRecord[] getLoadedDetail(int date, int shift){ + return loadedShifts.get(makeKey(date, shift)); + } + + /** + * Returns the array of LoomDSRecord objects associated with the provided date/shift combination. + * @param date - A java.util.Date object for the desired date + * @param shift - integer shift should be in the set [1, 2, 3] + * @return An array of LoomDSRecord objects + */ + @SuppressWarnings("unused") + private LoomDSRecord[] getLoadedDetail(Date date, int shift){ + return loadedShifts.get(makeKey(date, shift)); + } + + /** + * Overloaded to take an integer date in yyMMdd format. + * Creates the string key needed to retrieve an objects from the hash maps out of the supplied + * date and shift. + * @param date - integer date in yyMMdd format. Ex: 140203 = Feb. 3, 2014 + * @param shift - integer shift should be in the set [1, 2, 3] + * @return String HashMap key + */ + private String makeKey(int date, int shift){ + return makeKey(AS400Interface.intToDate(date), shift); + } + + /** + * Creates the string key needed to retrieve an objects from the hash maps out of the supplied + * date and shift. + * @param date - A java.util.Date object for the desired date + * @param shift - integer shift should be in the set [1, 2, 3] + * @return String HashMap key + */ + private String makeKey(Date date, int shift){ + return new SimpleDateFormat("MM/dd/yyyy-").format(date) + shift; + } + + /** + * Totally destroys tab associated with the supplied key and then reloads it. + * + * @param key - The HashMap key for the tab to be destroyed/loaded + * @throws ParseException + * @throws SQLException + */ + private void reloadTab(String key) throws ParseException, SQLException{ + String[] parse = key.split("-"); + int shift = Integer.parseInt(parse[1]); + DateFormat formatter = new SimpleDateFormat("MM/dd/yyyy"); + Date date = formatter.parse(parse[0]); + unloadShiftDetail(key); + addTab(date, shift); + } + + /** + * Reloads the data into the table model for the given key. + * This is used to refresh the data in the table model to reflect changes made to any + * LoomDSRecord objects associated with it. + * @param key + */ + private void refreshTable(String key){ + loadedTableModels.get(key).loadTable(loadedShifts.get(key)); + } + + /** + * Takes the supplied HashMap key and formats it to be used as the title for a tab. + * @param key - String HashMap key + * @return Formatted key string + */ + private String formatKeyForDisplay(String key){ + String[] derp = key.split("-"); + int shift = Integer.parseInt(derp[1]); + return derp[0] + " - " + ((shift == 1) ? "1st" : (shift == 2) ? "2nd" : (shift == 3) ? "3rd" : "" + shift) + " Shift"; + } + + private static double round(double value, int places) { + if (places < 0) throw new IllegalArgumentException(); + + BigDecimal bd = new BigDecimal(value); + bd = bd.setScale(places, RoundingMode.HALF_UP); + return bd.doubleValue(); + } + + /** + * This method creates a JPopupMenu to display when a row of a table is right clicked. + * + * This is a long one... 6 anonymous inner classes for each of the menu options. + * + * @param table - The table object that was right-clicked + * @param row - the row of the table that was right clicked + * @param key - the key needed for getting objects from the HashMaps for this table. + * @return the JPopupMenu + */ + private JPopupMenu createRightClickMenu(JTable table, int row, final String key){ + final LoomDSRecord record = loadedShifts.get(key)[row]; + + JPopupMenu ret = new JPopupMenu("Loom " + table.getValueAt(row, 0)); + + JMenuItem adjPicks = new JMenuItem("Adjust Picks (Loom " + table.getValueAt(row, 0) + ")"); + JMenuItem adjHours = new JMenuItem("Adjust Hours (Loom " + table.getValueAt(row, 0) + ")"); + JMenuItem addSamp = new JMenuItem("Add Sample Cut (Loom " + table.getValueAt(row, 0) + ")"); + JMenuItem chgWeav = new JMenuItem("Change Weaver (Loom " + table.getValueAt(row, 0) + ")"); + JMenuItem setDT = new JMenuItem("Set Down Time Code (Loom " + table.getValueAt(row, 0) + ")"); + JMenuItem delete = new JMenuItem("Delete Record (Loom " + table.getValueAt(row, 0) + ")"); + + adjPicks.addActionListener(new ActionListener(){ + private final Logger LOGGER = Logger.getLogger(LoomDSRecord.class.getName()); + + @Override + public void actionPerformed(ActionEvent e) { + int max = record.getMaxPicks(); + double hours = record.getHours(); + JLabel msg = new JLabel("The maximum picks for " + hours + " hours is " + max); + JLabel label = new JLabel("Picks:"); + JTextField input = new JTextField("" + record.getPicks(), 4); + input.addAncestorListener(new RequestFocusListener()); + input.addFocusListener(new SelectAllOnFocusListener()); + JPanel pane = new JPanel(new MigLayout("fill")); + pane.add(msg, "span 2, align center, wrap"); + pane.add(label, "align right"); + pane.add(input, "align left, wrap"); + int option = JOptionPane.showOptionDialog(self, pane, "Adjust Picks - Loom " + record.getLoomNumber(), + JOptionPane.NO_OPTION, JOptionPane.PLAIN_MESSAGE, null, new String[]{ "Change", "Cancel" }, "Change"); + if(option == 0){ + int backup = record.getPicks(); + try { + int picks = Integer.parseInt(input.getText()); + + if(picks < 0 || picks > 250) + throw new NumberFormatException("Pick value is not sane."); //Insane amount of picks, exit + if(backup == picks) + return; //Might as well not hit the server with a transaction if we're not actually changing anything + + record.setPicks(picks); //Change the picks in the LoomDSRecord object + + } catch(NumberFormatException e2){ + JOptionPane.showMessageDialog(self, "Non-negative integer values <= 250 please!\n\rNo changes made.", "Bogus Input", JOptionPane.ERROR_MESSAGE); + LOGGER.log(Level.SEVERE, "User passed us bogus input. Ignoring..", e2); + return; + } + + try { + as400.adjustPicks(record); //This actually commits the changes to the server + refreshTable(key); //If the changes were committed successfully, reload the table + } catch (SQLException e1) { + LOGGER.log(Level.SEVERE, "Database transaction failed when attempting to update picks. Reverting Changes.", e1); + JOptionPane.showMessageDialog(self, "Uh-oh..\n\rSomething went wrong when applying this change to the AS400", "Update Failed", JOptionPane.ERROR_MESSAGE); + record.setPicks(backup); //Revert picks in LoomDSRecord object to previous value since DB transaction failed + } + } + } + }); + chgWeav.addActionListener(new ActionListener(){ + private final Logger LOGGER = Logger.getLogger(LoomDSRecord.class.getName()); + + @Override + public void actionPerformed(ActionEvent e) { + int backup = record.getWeaverNumber(); + JLabel msg = new JLabel("Current Weaver is: " + record.getWeaverNameDisplay()); + JLabel label = new JLabel("New Weaver #"); + JTextField input = new JTextField("" + record.getWeaverNumber(), 4); + input.addAncestorListener(new RequestFocusListener()); + input.addFocusListener(new SelectAllOnFocusListener()); + JPanel prompt = new JPanel(new MigLayout("fill")); + prompt.add(msg, "span 2, wrap"); + prompt.add(label, "align right"); + prompt.add(input, "align left, wrap"); + int option = JOptionPane.showOptionDialog(self, prompt, "Change Weaver - Loom " + record.getLoomNumber(), + JOptionPane.NO_OPTION, JOptionPane.PLAIN_MESSAGE, null, new String[]{ "Change", "Cancel" }, "Change"); + if(option == 0){ + try { + int newWeav = Integer.parseInt(input.getText()); + if(AS400Interface.weaverLookup(newWeav) == null) + throw new NumberFormatException("Weaver number doesn't exist. Did you add it after you launched this application?"); + record.setWeaverNumber(newWeav); + } catch(NumberFormatException e2){ + LOGGER.log(Level.WARNING, "User passed us bogus input. Ignoring.", e2); + JOptionPane.showMessageDialog(self, "Weaver Number Doesn't Exist!\n\rDid you add it after you launched this application?", "Bogus Input", JOptionPane.ERROR_MESSAGE); + return; + } + try { + as400.changeWeaver(record, backup); //This actually commits the changes to the server + refreshTable(key); //If the changes were committed successfully, reload the table + } catch (SQLException e1) { + LOGGER.log(Level.SEVERE, "Database transaction failed when attempting to update weaver. Reverting Changes.", e1); + JOptionPane.showMessageDialog(self, "Uh-oh..\n\rSomething went wrong when applying this change to the AS400", "Update Failed", JOptionPane.ERROR_MESSAGE); + record.setWeaverNumber(backup); //Revert weaver in LoomDSRecord object to previous value since DB transaction failed + } + } + } + }); + setDT.addActionListener(new ActionListener(){ + private final Logger LOGGER = Logger.getLogger(LoomDSRecord.class.getName()); + + @Override + public void actionPerformed(ActionEvent e) { + boolean updateHours = false; + String backup = record.getDownTimeCode(); + double hrsBackup = record.getHours(); + final int pksBackup = record.getPicks(); + JComboBox choice = new JComboBox(AS400Interface.getDtCodeChoices()); + JLabel msg = new JLabel(); + final JTextField hrsAdjustment = new JTextField(4); + String dialogTitle = ""; + String[] dialogOptions = new String[2]; + + dialogOptions[1] = "Cancel"; + if(backup.length() == 0) { + msg.setText("Add a down time reason code to loom " + record.getLoomNumber()); + dialogTitle = "Set Down Time Reason Code - Loom "; + dialogOptions[0] = "Set"; + } else { + msg.setText("Down time reason code is currently: " + record.getformattedDowntimeReason()); + dialogTitle = "Change Down Time Reason Code - Loom "; + dialogOptions[0] = "Change"; + } + JPanel prompt = new JPanel(new MigLayout("fill")); + prompt.add(msg, "span 2, align center, wrap"); + prompt.add(choice, "span 2, align center, wrap"); + prompt.add(new JLabel("Hours"), "align right, grow"); + prompt.add(hrsAdjustment, "align left"); + + int option = JOptionPane.showOptionDialog(self, prompt, dialogTitle + record.getLoomNumber(), + JOptionPane.NO_OPTION, JOptionPane.PLAIN_MESSAGE, null, dialogOptions, dialogOptions[0]); + if(option == 0){ + if(((String) choice.getSelectedItem()).equalsIgnoreCase("NONE") && backup.length() == 0) + return; + String newDtCode = ((String) choice.getSelectedItem()).split(" ")[0]; + if(backup.equalsIgnoreCase(newDtCode)) + return; + if(newDtCode.equalsIgnoreCase("NONE")) + newDtCode = ""; + + record.setDownTimeCode(newDtCode); + + try { + double adj = Double.parseDouble(hrsAdjustment.getText()); // Attempt to parse duration of downtime from input box + record.setHours(hrsBackup - adj); // Subtract length of downtime from loom's total hours + record.setPicks(Math.min(pksBackup, record.getMaxPicks())); // Adjust total picks to the lesser of: new maximum for the new amount of looms hours - or - what was already entered + updateHours = true; + } catch (NumberFormatException e3) { + updateHours = false; + } + try { + as400.changeDtCode(record); //This actually commits the changes to the server + if(updateHours) { + as400.adjustHours(record); + as400.adjustPicks(record); + } + refreshTable(key); //If the changes were committed successfully, reload the table + } catch (SQLException e1) { + LOGGER.log(Level.SEVERE, "Database transaction failed when attempting to update DT code. Reverting Changes.", e1); + JOptionPane.showMessageDialog(self, "Uh-oh..\n\rSomething went wrong when applying this change to the AS400", "Update Failed", JOptionPane.ERROR_MESSAGE); + record.setDownTimeCode(backup); //Revert dt code in LoomDSRecord object to previous value since DB transaction failed + record.setHours(hrsBackup); + record.setPicks(pksBackup); + } + } + } + }); + adjHours.addActionListener(new ActionListener() { + private final Logger LOGGER = Logger.getLogger(LoomDSRecord.class.getName()); + + @Override + public void actionPerformed(ActionEvent e) { + double backup = record.getHours(); + JLabel label = new JLabel("Hours"); + JTextField input = new JTextField("" + record.getHours(), 5); + input.addAncestorListener(new RequestFocusListener()); + input.addFocusListener(new SelectAllOnFocusListener()); + JPanel prompt = new JPanel(new MigLayout("fill")); + prompt.add(label, "align right, grow"); + prompt.add(input, "align left, grow, wrap"); + int option = JOptionPane.showOptionDialog(self, prompt, "Adjust Hours - Loom " + record.getLoomNumber(), + JOptionPane.NO_OPTION, JOptionPane.PLAIN_MESSAGE, null, new String[]{ "Change", "Cancel" }, "Change"); + if(option == 0){ + try{ + double hours = Double.parseDouble(input.getText()); + if(hours < 0 || hours > 12.0) + throw new NumberFormatException("Hours value not sane."); + if(hours == backup) + return; + record.setHours(hours); + record.setPicks(Math.min(record.getPicks(), record.getMaxPicks())); + } catch(NumberFormatException e2){ + JOptionPane.showMessageDialog(self, "Non-negative decimal values <= 12.0 please!\n\rNo changes made.", "Bogus Input", JOptionPane.ERROR_MESSAGE); + LOGGER.log(Level.SEVERE, "User passed us bogus input. Ignoring..", e2); + return; + } + try { + as400.adjustHours(record); //This actually commits the changes to the server + as400.adjustPicks(record); + refreshTable(key); //If the changes were committed successfully, reload the table + } catch (SQLException e1) { + LOGGER.log(Level.SEVERE, "Database transaction failed when attempting to update hours. Reverting Changes.", e1); + JOptionPane.showMessageDialog(self, "Uh-oh..\n\rSomething went wrong when applying this change to the AS400", "Update Failed", JOptionPane.ERROR_MESSAGE); + record.setHours(backup); //Revert hours in LoomDSRecord object to previous value since DB transaction failed + } + } + } + }); + addSamp.addActionListener(new ActionListener() { + private final Logger LOGGER = Logger.getLogger(LoomDSRecord.class.getName()); + @Override + public void actionPerformed(ActionEvent e) { + final double hrsBackup = record.getHours(); + final int pksBackup = record.getPicks(); + final int distanceFromMax = record.getMaxPicks() - record.getPicks(); + final JTextField pksInput = new JTextField(4); + pksInput.addFocusListener(new SelectAllOnFocusListener()); + final JTextField hrsInput = new JTextField("" + hrsBackup, 5); + int derp = ((int) (record.getPicksPerHourNoBonus() * Double.parseDouble(hrsInput.getText()))); + final JLabel msg = new JLabel("Enter hours and picks for sample cut.
Values cannot exceed " + hrsBackup + " hours, " + pksBackup + " picks

Range that will not affect total picks: " + Math.max((derp - distanceFromMax), 0) + " - " + derp); + pksInput.setText("" + ((int) (record.getPicksPerHourNoBonus() * Double.parseDouble(hrsInput.getText())))); + hrsInput.addAncestorListener(new RequestFocusListener()); + hrsInput.addFocusListener(new SelectAllOnFocusListener()); + hrsInput.getDocument().addDocumentListener(new DocumentListener() { + public void changedUpdate(DocumentEvent e) { + update(); + } + public void removeUpdate(DocumentEvent e) { + update(); + } + public void insertUpdate(DocumentEvent e) { + update(); + } + private void update(){ + try { + int maxSampPicks; + if(hrsInput.getText().startsWith("m")) { + double minutes = (double) Integer.parseInt(hrsInput.getText().substring(1)); + maxSampPicks = (int) (record.getPicksPerHourNoBonus() * round(minutes / 60.0, 2)); + } else { + maxSampPicks = (int) (record.getPicksPerHourNoBonus() * Double.parseDouble(hrsInput.getText())); + } + pksInput.setText("" + maxSampPicks); + msg.setText("Enter hours and picks for sample cut.
Values cannot exceed " + hrsBackup + " hours, " + pksBackup + " picks

Range that will not affect total picks: " + Math.max((maxSampPicks - distanceFromMax), 0) + " - " + maxSampPicks); + } catch(Exception e){ + + } + } + }); + JTextField cut1Input = new JTextField(3); + cut1Input.addFocusListener(new SelectAllOnFocusListener()); + JTextField cut2Input = new JTextField(3); + cut2Input.addFocusListener(new SelectAllOnFocusListener()); + JPanel prompt = new JPanel(new MigLayout("fill")); + prompt.add(msg, "span 4, align center, wrap"); + prompt.add(new JLabel("Hours"), "align right, grow"); + prompt.add(hrsInput, "align left, grow"); + prompt.add(new JLabel("Picks"), "align left, grow"); + prompt.add(pksInput, "align right, grow, wrap"); + prompt.add(new JLabel("Cut #"), "align right, grow"); + prompt.add(cut1Input, "align left, grow"); + prompt.add(new JLabel("Cut Suffix"), "align right, grow"); + prompt.add(cut2Input, "align left, grow, wrap"); + int option = JOptionPane.showOptionDialog(self, prompt, "Add Sample Cut - Loom " + record.getLoomNumber(), + JOptionPane.NO_OPTION, JOptionPane.PLAIN_MESSAGE, null, new String[]{ "Add", "Cancel" }, "Add"); + if(option == 0){ + int cutNumber = 0; + double sampHours = 0; + int sampPicks = 0; + String cutSuffix = ""; + if(cut2Input.getText().length() > 2) + cutSuffix = cut2Input.getText().substring(0, 2).toUpperCase(); + else + cutSuffix = cut2Input.getText().toUpperCase(); + try{ + if(hrsInput.getText().startsWith("m")) { + double minutes = (double) Integer.parseInt(hrsInput.getText().substring(1)); + sampHours = round(minutes / 60.0, 2); + } else { + sampHours = Double.parseDouble(hrsInput.getText()); + } + sampPicks = Integer.parseInt(pksInput.getText()); + cutNumber = Integer.parseInt(cut1Input.getText()); + + if(sampHours <= 0) + throw new NumberFormatException("User passed a negative/zero hours value. Aint nobody got time fo dat."); + else if(sampHours > hrsBackup) + throw new NumberFormatException("Not enough hours to cut in sample! Orig Record: " + hrsBackup + " User Entered: " + sampHours); + if(sampPicks <= 0) + throw new NumberFormatException("User passed a negative/zero picks value. Aint nobody got time fo dat."); + else if(sampPicks > pksBackup) + throw new NumberFormatException("Not enough picks to cut in sample! Orig Record: " + pksBackup + " User Entered: " + sampPicks); + if(cutNumber < 1 || cutNumber > 999) + throw new NumberFormatException("Invalid cut number: " + cutNumber + ". Valid range [1 - 999]"); + + record.setHours(hrsBackup - sampHours); + record.setPicks(Math.min((pksBackup - sampPicks), record.getMaxPicks())); //By adjusting the hours first on the previous line; getMaxPicks() will return the new maximum picks that we do not wish to exceed + //record.setPicks(pksBackup - sampPicks); + //record.setHours(hrsBackup - sampHours); + } catch(NumberFormatException e2){ + JOptionPane.showMessageDialog(self, "Non-negative decimal values please!\n\rNo changes made.", "Bogus Input", JOptionPane.ERROR_MESSAGE); + LOGGER.log(Level.SEVERE, "User passed us bogus input. Ignoring..", e2); + return; + } + try { + as400.adjustHoursPicks(record); //Decrease hours and picks of target record + as400.addSampleCut(record, cutNumber, cutSuffix, sampHours, sampPicks); //Add new SAM record to LOOMDS with desired hours/picks + reloadTab(key); + } catch (SQLException e1) { + LOGGER.log(Level.SEVERE, "Database transaction failed when attempting to cut in sample. Reverting Changes.", e1); + JOptionPane.showMessageDialog(self, "Uh-oh..\n\rSomething went wrong when applying this change to the AS400", "Update Failed", JOptionPane.ERROR_MESSAGE); + record.setHours(hrsBackup); //Revert hours in LoomDSRecord object to previous value since DB transaction failed + record.setPicks(pksBackup); //Revert picks in LoomDSRecord object to previous value since DB transaction failed + } catch (ParseException e1) { + LOGGER.log(Level.WARNING, "Failed to reload shift detail after record insert. Re-open the tab manually", e1); + JOptionPane.showMessageDialog(self, "Uh-oh..\n\rFailed to reload shift detail after record insert\n\rRe-open tab manually", "Whoops!", JOptionPane.WARNING_MESSAGE); + } + } + } + }); + delete.addActionListener(new ActionListener() { + private final Logger LOGGER = Logger.getLogger(LoomDSRecord.class.getName()); + @Override + public void actionPerformed(ActionEvent e) { + String msg = "Are you absolutely sure you want to DELETE this record?
This _CANNOT_ be un-done!
"; + msg = msg + "Loom: " + record.getLoomNumber() + "
"; + msg = msg + "Weaver: " + record.getWeaverNameDisplay() + "
"; + msg = msg + "Style: " + record.getLdStyle() + "
"; + msg = msg + "Hours: " + record.getHours() + "
"; + msg = msg + "Picks: " + record.getPicks() + "
"; + msg = msg + "Down Time: " + record.getformattedDowntimeReason() + "
"; + msg = msg + "Cut: " + record.getFormattedCutNumber() + "
"; + msg = msg + "Type Code: " + record.getTypeCode(); + int proceed = JOptionPane.showConfirmDialog(self, msg, "Delete Record", JOptionPane.OK_CANCEL_OPTION); + if(proceed == 0){ + try { + as400.delRecord(record); + reloadTab(key); + } catch (SQLException e1) { + LOGGER.log(Level.SEVERE, "Database transaction failed when attempting to delete record..", e1); + JOptionPane.showMessageDialog(self, "Uh-oh..\n\rSomething went wrong when applying this change to the AS400", "Update Failed", JOptionPane.ERROR_MESSAGE); + } catch (ParseException e1) { + LOGGER.log(Level.WARNING, "Failed to reload shift detail after record deletion. Re-open the tab manually", e1); + JOptionPane.showMessageDialog(self, "Uh-oh..\n\rFailed to reload shift detail after record deletion\n\rRe-open tab manually", "Whoops!", JOptionPane.WARNING_MESSAGE); + } + } + } + }); + + ret.add(adjPicks); + ret.add(adjHours); + ret.addSeparator(); + ret.add(addSamp); + ret.addSeparator(); + ret.add(chgWeav); + ret.add(setDT); + ret.addSeparator(); + ret.add(delete); + + return ret; + } +} \ No newline at end of file diff --git a/pickMaint/EfficiencyBonusCalc.java b/pickMaint/EfficiencyBonusCalc.java new file mode 100644 index 0000000..563c482 --- /dev/null +++ b/pickMaint/EfficiencyBonusCalc.java @@ -0,0 +1,132 @@ +package pickMaint; + +import java.io.IOException; +import java.sql.Connection; +import java.sql.DriverManager; +import java.sql.PreparedStatement; +import java.sql.ResultSet; +import java.sql.SQLException; +import java.util.logging.Logger; + +/** + * This represents my attempts to recreate the incentive pay calculations done on the AS400 + * This is for research purposes. Magic numbers are constants pulled from the DB. + * @author Wes + */ +public class EfficiencyBonusCalc { + + private static final int[] DATE = new int[]{ 180219, 180220, 180221, 180222 }; + private static final int WEAVER = 183; + + private static final double[] MAX_BONUS = new double[]{ 0.0, 0.0, 0.0, 0.0, 2.5, 3.0, 3.75, 4.0, 4.25, 4.75, 7.0, 10.25, 33.0, 33.0 }; + + private static final Logger LOGGER = Logger.getLogger(EfficiencyBonusCalc.class.getName()); + private static final String EFF_BONUS_BY_DATE = "SELECT y.lddate, " + + " y.ldshif, " + + " y.looms, " + + " y.picks, " + + " y.pickspossible, " + + " ((((CAST(y.picks AS FLOAT) / y.pickspossible) * (y.loomhours * LMRTLH)) - (y.shiftlen * LMRATE)) * lmpct) EFF_BON " + + "FROM (SELECT x.lddate, " + + " x.ldshif, " + + " CAST(ROUND(SUM(x.ldhrs) / max(x.ldhrs), 0) AS INT) looms, " + + " SUM(x.ldhrs) loomhours, " + + " max(x.ldhrs) shiftlen, " + + " sum(x.ldpick) picks, " + + " sum(x.maxpicks) pickspossible " + + " FROM (SELECT lddate, " + + " ldshif, " + + " ldloom, " + + " ldweav, " + + " ldhrs, " + + " ldpick, " + + " CAST(ROUND((lmppm * ldhrs), 0) AS INT) maxpicks " + + " FROM stmdata.loomds " + + " INNER JOIN stmdata.loomms ON stmdata.loomds.ldloom = stmdata.loomms.lmlomn " + + " WHERE lddate = ? AND " + + " ldshif = ? AND " + + " ldweav = ?) x " + + " GROUP BY x.lddate,x.ldshif) y " + + " LEFT JOIN stmdata.lmprir ON stmdata.lmprir.lmloom = 'D' AND " + + " stmdata.lmprir.lmnolm = y.looms "; + + private static final String WEAVER_INFO = "select wvrnam, shift from stmdata.iewvms where WVR# = " + WEAVER; + + + public static void main(String[] args) throws SQLException, IOException { + System.out.println(EFF_BONUS_BY_DATE); + // Register AS400 DB2 driver and open a connection with the AS400 + LOGGER.info("Connecting to AS400 as user: WRAY\n"); + DriverManager.registerDriver(new com.ibm.as400.access.AS400JDBCDriver()); + Connection conn = DriverManager.getConnection("jdbc:as400://0.0.0.0", "USER", "PASSWORD"); + + LOGGER.info("Pre-compiling SQL statements...\n"); + PreparedStatement effBonusGet = conn.prepareStatement(EFF_BONUS_BY_DATE); + + LOGGER.info("Looking up weaver name and shift for number " + WEAVER +"...\n"); + int shift = 0; + String name = ""; + double total = 0; + ResultSet weavInfo = conn.createStatement().executeQuery(WEAVER_INFO); + if(weavInfo.next()) { + shift = weavInfo.getInt(2); + name = weavInfo.getString(1).trim(); + } else { + throw new RuntimeException("Weaver doesn't exist!"); + } + + System.out.println("Efficiency Bonus Calculations for " + name + " (" + WEAVER + ") " + ((shift == 1) ? "1st" : (shift == 2) ? "2nd" : (shift == 3) ? "3rd" : "") + " shift.\n"); + + for(int d : DATE){ + effBonusGet.setInt(1, d); + effBonusGet.setInt(2, shift); + effBonusGet.setInt(3, WEAVER); + + ResultSet rs = effBonusGet.executeQuery(); + if(rs.next()){ + int looms = rs.getInt(3); + if(looms > 13) + looms = 13; + double bonus = rs.getDouble(6); + bonus = Math.min(bonus, MAX_BONUS[looms]); + + System.out.println(d + "_" + shift + " - " + looms + " looms. $" + bonus); + total += bonus; + rs.close(); + } else { + System.out.println(d + "_" + shift + " - NO DATA"); + } + } + + System.out.println("\nTotal Efficiency Bonus: $" + total); + + effBonusGet.close(); + effBonusGet = null; + conn.close(); + conn = null; + System.exit(0); + } + + /** + * The ResultSet you pass to this method must be from a PreparedStatement created with these args: + * prepareStatement("select * from ...", ResultSet.TYPE_SCROLL_INSENSITIVE, ResultSet.CONCUR_READ_ONLY); + * + * This will throw an exception with FORWARD_ONLY ResultSets (as in the default one you get with no extra arguments) + * + * ResultSet.CONCUR_READ_ONLY may actually be something else if you so desire. TYPE_SCROLL_INSENSITIVE is what is important. + * + * @param set - The ResultSet to count rows in + * @return The number of rows in the ResultSet + * @throws SQLException + */ + public static int getRowCount(ResultSet set) throws SQLException { + int rowCount; + int currentRow = set.getRow(); // Get current row + rowCount = set.last() ? set.getRow() : 0; // Determine number of rows + if (currentRow == 0) // If there was no current row + set.beforeFirst(); // We want next() to go to first row + else // If there WAS a current row + set.absolute(currentRow); // Restore it + return rowCount; + } +} \ No newline at end of file diff --git a/pickMaint/ExcelExporter.java b/pickMaint/ExcelExporter.java new file mode 100644 index 0000000..9175b88 --- /dev/null +++ b/pickMaint/ExcelExporter.java @@ -0,0 +1,62 @@ +package pickMaint; + +import java.io.FileWriter; +import java.io.IOException; +import java.util.logging.Logger; + +import javax.swing.JTable; +import javax.swing.table.TableModel; + +/** + * This class provides means to dump the contents of a JTable into a tab delimited file and then open it in Excel. + * + * @author Wesley Ray + */ +public class ExcelExporter { + private static final Logger LOGGER = Logger.getLogger(LoomDSRecord.class.getName()); + + /** + * Dumps the data in a JTable out to a tab delimited file. + * @param table + * @param outFile + * @throws IOException + */ + public static void exportJTableToFile(JTable table, String outFile) throws IOException{ + TableModel m = table.getModel(); + FileWriter out = new FileWriter(outFile); + String buffer = ""; + + LOGGER.info("Exporting " + m.getRowCount() + " rows to tab delmited file: " + outFile); + + for (int i = 0; i < m.getColumnCount(); i++) { + buffer += (m.getColumnName(i) + "\t"); + } + + out.write(buffer + "\n"); + buffer = ""; + + for (int i = 0; i < m.getRowCount(); i++) { + for (int j = 0; j < m.getColumnCount(); j++) { + buffer += (m.getValueAt(i, j) + "\t"); + } + out.write(buffer + "\n"); + buffer = ""; + } + + out.flush(); //Make sure everything gets written + out.close(); //Close the file + out = null; //Make sure this gets garbage collected + } + + /** + * This only works on Windows. This will work with any file that you can double click to open really. + * XLS files happen to open in excel automatically. + * + * @param path + * @throws IOException + */ + public static void openInExcel(String path) throws IOException{ + LOGGER.info("Invoking \"cmd.exe /c start " + path + "\""); + Runtime.getRuntime().exec("cmd.exe /c start " + path); + } +} diff --git a/pickMaint/LoomDSKey.java b/pickMaint/LoomDSKey.java new file mode 100644 index 0000000..34c1364 --- /dev/null +++ b/pickMaint/LoomDSKey.java @@ -0,0 +1,49 @@ +package pickMaint; + +public class LoomDSKey { + + private final int DATE; + private final int SHIFT; + private final int LOOM; + private final int WEAVER; + private final String TYPE; + + public LoomDSKey(int date, int shift, int loom, int weaver, String type){ + this.DATE = date; + this.SHIFT = shift; + this.LOOM = loom; + this.WEAVER = weaver; + this.TYPE = type; + } + + public String toString(){ + return DATE + "-" + SHIFT + "-" + LOOM + "-" + WEAVER + "-" + TYPE; + } + + /** + * All 5 components of the composite key must be equal + */ + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (!(o instanceof LoomDSKey)) return false; + LoomDSKey key = (LoomDSKey) o; + return DATE == key.DATE && + SHIFT == key.SHIFT && + LOOM == key.LOOM && + WEAVER == key.WEAVER && + TYPE.equalsIgnoreCase(key.TYPE); + } + + /** + * Obviously not a perfect hash; although I suspect collisions will be rare in our use case. + */ + @Override + public int hashCode() { + int hash = DATE * SHIFT; + hash = hash + LOOM; + hash = hash * (WEAVER / 2); + hash = hash + TYPE.hashCode(); + return hash; + } +} diff --git a/pickMaint/LoomDSRecord.java b/pickMaint/LoomDSRecord.java new file mode 100644 index 0000000..c95d88f --- /dev/null +++ b/pickMaint/LoomDSRecord.java @@ -0,0 +1,293 @@ +package pickMaint; + +import java.sql.ResultSet; +import java.sql.SQLException; +import java.text.SimpleDateFormat; +import java.util.Date; +import java.util.logging.Logger; + +/** + * This class is essentially just an Object representation of a single record in LOOMDS/LOOMDS2. + * + * The constructor takes a reference to an AS400Interface only in case we need to pull "no bonus" hours and picks from LOOMDS2. + * See below: if(deleteCode.equalsIgnoreCase("N")) { ... } + * + * The bulk of the object is created using the java.sql.ResultSet that is passed to the constructor. This ResultSet is expected to be + * generated by a particular query stored in AS400Interface.java + * + * The rest of the class is just getters/setters. + * + * @author Wesley Ray + */ +public class LoomDSRecord implements Comparable { + + private static final Logger LOGGER = Logger.getLogger(LoomDSRecord.class.getName()); + + private Date date; + private int shift; + private int loomNumber; + private int weaverNumber; + private String weaverName; + private String ldStyle; //TRL, BLN, or SAM + private double hours; + private int picks; + private float picksPerHourBonus; + private float picksPerHourNoBonus; + private String downTimeCode; + private String downTimeCodeDescription; + private int cut; + private String cutSuffix; + private String typeCode; + private String deleteCode; + + private boolean bonus; + private boolean changed = false; //Never ended up using this, not a bad idea though. leaving it here just in case + + public LoomDSRecord(AS400Interface as400, ResultSet row) throws SQLException { + date = AS400Interface.intToDate(row.getInt(1)); + loomNumber = row.getInt(2); + shift = row.getInt(3); + weaverNumber = row.getInt(4); + weaverName = row.getString(5).trim(); + ldStyle = row.getString(6).trim(); + hours = row.getDouble(7); + picks = row.getInt(8); + picksPerHourBonus = row.getFloat(11); + picksPerHourNoBonus = row.getFloat(12); + downTimeCode = row.getString(13).trim(); + downTimeCodeDescription = AS400Interface.dtCodeLookup(downTimeCode); + cut = row.getInt(14); + cutSuffix = row.getString(15).trim(); + typeCode = row.getString(16).trim(); + deleteCode = row.getString(17); + + bonus = !((weaverNumber > 699 && weaverNumber < 703) || (ldStyle.equalsIgnoreCase("TRL") || ldStyle.equalsIgnoreCase("BLN") || deleteCode.equalsIgnoreCase("N"))); + + if(deleteCode.equalsIgnoreCase("N")){ + LOGGER.fine(logPrefix() + "is flagged NO_BONUS need to fetch hours and picks from LOOMDS2."); + float[] values = as400.getLoomDS2HoursPicks(AS400Interface.dateToInt(date), loomNumber, shift, weaverNumber); + hours = values[0]; + picks = (int) values[1]; + } + } + + /** + * @return the weaverNumber + */ + public final int getWeaverNumber() { + return weaverNumber; + } + + /** + * @param weaverNumber the weaverNumber to set + */ + public final void setWeaverNumber(int weaverNumber) { + LOGGER.info("Loom: " + loomNumber + " - " + ((shift == 1) ? "1st" : (shift == 2) ? "2nd" : (shift == 3) ? "3rd" : "") + " shift - " + new SimpleDateFormat("MM/dd/yyyy").format(date) + + " - Changing weaver from: " + weaverName + " (" + this.weaverNumber + ") to: " + AS400Interface.weaverLookup(weaverNumber) + " (" + weaverNumber + ")"); + this.weaverNumber = weaverNumber; + this.weaverName = AS400Interface.weaverLookup(weaverNumber); + bonus = !((weaverNumber > 699 && weaverNumber < 703) || (ldStyle.equalsIgnoreCase("TRL") || ldStyle.equalsIgnoreCase("BLN"))); + changed = true; + } + + /** + * @return the ldStyle + */ + public final String getLdStyle() { + return ldStyle; + } + + /** + * @param ldStyle the ldStyle to set + */ + public final void setLdStyle(String ldStyle) { + LOGGER.info(logPrefix() + "- Changing LDSTYL from: " + this.ldStyle + " to: " + ldStyle); + this.ldStyle = ldStyle; + bonus = !((weaverNumber > 699 && weaverNumber < 703) || (ldStyle.equalsIgnoreCase("TRL") || ldStyle.equalsIgnoreCase("BLN"))); + changed = true; + } + + /** + * @return the hours + */ + public final double getHours() { + return hours; + } + + /** + * @param hours the hours to set + */ + public final void setHours(double hours) { + LOGGER.info(logPrefix() + "- Changing hours from: " + this.hours + " to: " + hours); + this.hours = hours; + changed = true; + } + + /** + * @return the picks + */ + public final int getPicks() { + return picks; + } + + /** + * @param picks the picks to set + */ + public final void setPicks(int picks) { + LOGGER.info(logPrefix() + "- Changing picks from: " + this.picks + " to: " + picks); + this.picks = picks; + changed = true; + } + + public final int getMaxPicks(){ + return (int) ((hours * (bonus ? picksPerHourBonus : picksPerHourNoBonus)) + 0.5); //The +0.5 combined with the (int) cast creates the effect of rounding up to nearest whole number + } + + /** + * @return the downTimeCode + */ + public final String getDownTimeCode() { + return downTimeCode; + } + + /** + * @param downTimeCode the downTimeCode to set + */ + public final void setDownTimeCode(String downTimeCode) { + LOGGER.info(logPrefix() + "- Changing down time code from: " + this.downTimeCode + " to: " + downTimeCode); + this.downTimeCode = downTimeCode; + this.downTimeCodeDescription = AS400Interface.dtCodeLookup(downTimeCode); + changed = true; + } + + /** + * @return the typeCode + */ + public final String getTypeCode() { + return typeCode; + } + + /** + * @param typeCode the typeCode to set + */ + public final void setTypeCode(String typeCode) { + LOGGER.info(logPrefix() + "- Changing type code from: " + this.typeCode + " to: " + typeCode); + this.typeCode = typeCode; + changed = true; + } + + /** + * @return the date + */ + public final Date getDate() { + return date; + } + + /** + * @return the shift + */ + public final int getShift() { + return shift; + } + + /** + * @return the loomNumber + */ + public final int getLoomNumber() { + return loomNumber; + } + + /** + * @return the weaverName + */ + public final String getWeaverName() { + return weaverName; + } + + public final String getWeaverNameDisplay(){ + return weaverName + " (" + weaverNumber + ")"; + } + + /** + * @return the picksPerHourBonus + */ + public final float getPicksPerHourBonus() { + return picksPerHourBonus; + } + + /** + * @return the picksPerHourNoBonus + */ + public final float getPicksPerHourNoBonus() { + return picksPerHourNoBonus; + } + + /** + * @return the downTimeCodeDescription + */ + public final String getDownTimeCodeDescription() { + return downTimeCodeDescription; + } + + public final String getformattedDowntimeReason(){ + if(downTimeCodeDescription != null) + return "(" + downTimeCode + ") " + downTimeCodeDescription; + else + return ""; + } + + /** + * @return the cut + */ + public final int getCut() { + return cut; + } + + /** + * @return the cutSuffix + */ + public final String getCutSuffix() { + return cutSuffix; + } + + public final String getFormattedCutNumber(){ + if(cut < 1) + return ""; + if(cutSuffix.length() == 0) + return "" + cut; + return cut + "-" + cutSuffix; + } + + /** + * @return the deleteCode + */ + public final String getDeleteCode() { + return deleteCode; + } + + /** + * @return the bonus + */ + public final boolean isBonus() { + return bonus; + } + + public final boolean changed(){ + return changed; + } + + private String logPrefix(){ + return "Loom: " + loomNumber + " - " + ((shift == 1) ? "1st" : (shift == 2) ? "2nd" : (shift == 3) ? "3rd" : "") + " shift - " + + new SimpleDateFormat("MM/dd/yyyy").format(date) + " Weaver: " + weaverName + " (" + weaverNumber + ") "; + } + + @Override + public int compareTo(Object o) { + if(o instanceof LoomDSRecord){ + return loomNumber - ((LoomDSRecord) o).getLoomNumber(); + } else { + throw new RuntimeException("Something screwed when sorting LoomDSRecord array"); + } + } + +} diff --git a/pickMaint/LoomDSTableModel.java b/pickMaint/LoomDSTableModel.java new file mode 100644 index 0000000..59c17ef --- /dev/null +++ b/pickMaint/LoomDSTableModel.java @@ -0,0 +1,122 @@ +package pickMaint; + +import java.util.ArrayList; + +import javax.swing.table.AbstractTableModel; + +/** + * This class implements all abstract methods in AbstractTableModel as needed to display LoomDSRecords properly. + * + * @author Wesley Ray + */ +public class LoomDSTableModel extends AbstractTableModel { + + private static final long serialVersionUID = 1L; + private static final String [] COL_HEADERS = {"Loom", "Weaver", "Style", "Hours", "Picks", "Picks Possible", "Down Time", "Sample Cut"}; + + private LoomDSRecord[] data; + private Integer[] sampleHighlight; + private Integer[] zeroHighlight; + + /** + * Updates the data to be displayed by the table model. + * Also builds an array of specific rows to highlight in a different color due to them being sample cut-ins + * @param data The LoomDSRecord[] to display + */ + public void loadTable(LoomDSRecord[] data){ + this.data = data; + + ArrayList q = new ArrayList(); + for(LoomDSRecord r : data){ + if(r.getLdStyle().trim().equalsIgnoreCase("SAM")) + q.add(r.getLoomNumber()); + } + sampleHighlight = q.toArray(new Integer[q.size()]); + + q = new ArrayList(); + for(LoomDSRecord r : data){ + float derp = (float) r.getPicks() / (float) r.getMaxPicks(); + if(derp <= 0.75 && r.getWeaverNumber() != 700 && r.getWeaverNumber() != 701 && r.getWeaverNumber() != 702) + q.add(r.getLoomNumber()); + } + zeroHighlight = q.toArray(new Integer[q.size()]); + + this.fireTableDataChanged(); + } + + /** + * Returns true if the given row number is to be highlighted for being a sample cut + * @param row - the row number to check + * @return true/false if row contains sample cut + */ + public boolean highlightSample(int row){ + int target = data[row].getLoomNumber(); + for(int i : sampleHighlight){ + if(i == target) + return true; + } + return false; + } + + /** + * Returns true if the given row number is to be highlighted for having zero picks + * @param row - the row number to check + * @return true/false + */ + public boolean highlightZero(int row){ + int target = data[row].getLoomNumber(); + for(int i : zeroHighlight){ + if(i == target) + return true; + } + return false; + } + + /** + * Returns bonus status for a given row + * @param row - the row to check + * @return Bonus or not? + */ + public boolean bonus(int row){ + return data[row].isBonus(); + } + + @Override + public int getRowCount() { + return data.length; + } + + @Override + public int getColumnCount() { + return COL_HEADERS.length; + } + + @Override + public String getColumnName(int column) { + return COL_HEADERS[column]; + } + + @Override + public Object getValueAt(int rowIndex, int columnIndex) { + switch(columnIndex){ + case 0: + return data[rowIndex].getLoomNumber(); + case 1: + return data[rowIndex].getWeaverNameDisplay(); + case 2: + return data[rowIndex].getLdStyle(); + case 3: + return data[rowIndex].getHours(); + case 4: + return data[rowIndex].getPicks(); + case 5: + return data[rowIndex].getMaxPicks() + " (+" + (data[rowIndex].getMaxPicks() - data[rowIndex].getPicks()) + ")"; + case 6: + return data[rowIndex].getformattedDowntimeReason(); + case 7: + return data[rowIndex].getFormattedCutNumber(); + } + return null; //required to compile + } + +} diff --git a/pickMaint/RequestFocusListener.java b/pickMaint/RequestFocusListener.java new file mode 100644 index 0000000..d65270b --- /dev/null +++ b/pickMaint/RequestFocusListener.java @@ -0,0 +1,62 @@ +package pickMaint; + +import javax.swing.*; +import javax.swing.event.*; + +/** + * Convenience class to request focus on a component. + * + * When the component is added to a realized Window then component will request + * focus immediately, since the ancestorAdded event is fired immediately. + * + * When the component is added to a non realized Window, then the focus request + * will be made once the window is realized, since the ancestorAdded event will + * not be fired until then. + * + * Using the default constructor will cause the listener to be removed from the + * component once the AncestorEvent is generated. A second constructor allows + * you to specify a boolean value of false to prevent the AncestorListener from + * being removed when the event is generated. This will allow you to reuse the + * listener each time the event is generated. + */ +public class RequestFocusListener implements AncestorListener { + private boolean removeListener; + + /* + * Convenience constructor. The listener is only used once and then it is + * removed from the component. + */ + public RequestFocusListener() { + this(true); + } + + /* + * Constructor that controls whether this listen can be used once or + * multiple times. + * + * @param removeListener when true this listener is only invoked once + * otherwise it can be invoked multiple times. + */ + public RequestFocusListener(boolean removeListener) { + this.removeListener = removeListener; + } + + @Override + public void ancestorAdded(AncestorEvent e) { + JComponent component = e.getComponent(); + component.requestFocusInWindow(); + + if (removeListener) + component.removeAncestorListener(this); + } + + @Override + public void ancestorMoved(AncestorEvent e) { + } + + @Override + public void ancestorRemoved(AncestorEvent e) { + } + +} + diff --git a/pickMaint/SelectAllOnFocusListener.java b/pickMaint/SelectAllOnFocusListener.java new file mode 100644 index 0000000..d26c9b1 --- /dev/null +++ b/pickMaint/SelectAllOnFocusListener.java @@ -0,0 +1,39 @@ +package pickMaint; + +import java.awt.event.FocusEvent; +import java.awt.event.FocusListener; + +import javax.swing.JTextField; +import javax.swing.SwingUtilities; + +/** + * Convenience class. + * When this FocusListener is added to a text field the contents of the text field will be highlighted + * automagicaly when the cursor enters the field. + * @author Wesley Ray + */ +public class SelectAllOnFocusListener implements FocusListener { + + @Override + public void focusGained(FocusEvent e) { + /* + * The following causes any existing text to be selected on focusGained. Using invokeLater to ensure + * this happens after any other events pertaining to the text field in order to avoid any unexpected + * behavior + */ + final JTextField source = (JTextField) e.getSource(); + SwingUtilities.invokeLater( new Runnable() { + + @Override + public void run() { + source.selectAll(); + } + }); + } + + @Override + public void focusLost(FocusEvent e) { + //Required to compile + } + +}