Copyright 1999-2009 - American Coders, Ltd. Raleigh NC USA


Author: Joe McVerry


WORK IN PROGRESS
The document may contain inaccuracies and missing information.


Last updated 2007-05-15.


This document serves two purposes - how to work the OBOE API and how to write Java programs to interact with the EDI objects OBOE generates.

I start the process by first creating a X12-997 parsing program using the OBOECodeGenenerator. Then I discuss the changes to the program I made. Using a standard text editor a final program is created that shows you how objects are used to get to data and create a different type of EDI document.

  1. Using the OBOECodeGenerator class create a source program that builds 997 transactions. java com.americancoders.util.OBOECodeGenerator x12.envelope.xml bJ 997 build997.java The Generated Program
    Another alternative to generating a program from the command line is to use the Swing application RulesAndCodeBuilder that is also part of the util package.
  2. Using a text editor, edit the newly created source program build997.java
  3. The source program class will create a 997 document but it needs data. The data will come from an already OBOE parsed EDI document that may or may not be valid. So we change the constructor to accept two parameters, the parsed document, in this case an X12Envelope object, and the DocumentErrors object created when the envelope object was created. public build997(X12Envelope inEnv, DocumentErrors inDErr)
  4. Don't forget to keep the source program documentation up to date. So change /** constructor for class build997 *@throws OBOEException - most likely transactionset not found */ to read as /** constructor for class build997 * @param inEnv an OBOE X12Document object that we are responding to with this 997 * @param inDErr DocumentErrors object created when inEnv was created. *@throws OBOEException - most likely transactionset not found */
  5. So the constructor now looks like /** constructor for class build997 * @param inEnv an OBOE X12Document object that we are responding to with this 997 * @param inDErr DocumentErrors object created when inEnv was created. *@throws OBOEException - most likely transactionset not found */ public build997(X12Envelope inEnv, DocumentErrors inDErr)
  6. Now we need to populate the ISA segment data
    If we were using the Extended Edition we could set up default functionality in the OBOE rules file. But since we're not, let's go through the steps for giving values to each data element object.
    1. remove the lineinterchange_Control_Header.useDefault();
    2. add the following: Segment incomingISA = inEnv.getSegment("ISA"); // get the inEnv ISA so we can use its dataelements // and then set our ISA dataelements interchange_Control_Header.getDataElement("I01").set(incomingISA.getDataElement("I01").get()); interchange_Control_Header.getDataElement("I02").set(incomingISA.getDataElement("I02").get()); interchange_Control_Header.getDataElement("I03").set(incomingISA.getDataElement("I03").get()); interchange_Control_Header.getDataElement("I04").set(incomingISA.getDataElement("I04").get()); interchange_Control_Header.getDataElement(5).set(incomingISA.getDataElement(7).get()); // flip sender/receiver interchange_Control_Header.getDataElement("I06").set(incomingISA.getDataElement("I07").get()); // flip sender/receiver interchange_Control_Header.getDataElement(7).set(incomingISA.getDataElement(5).get()); // flip receiver/sender interchange_Control_Header.getDataElement("I07").set(incomingISA.getDataElement("I06").get()); // flip receiver/sender interchange_Control_Header.getDataElement("I08").set(com.americancoders.util.Util.currentDate()); interchange_Control_Header.getDataElement("I09").set(com.americancoders.util.Util.currentTime()); interchange_Control_Header.getDataElement("I10").set(incomingISA.getDataElement("I10").get()); interchange_Control_Header.getDataElement("I11").set(incomingISA.getDataElement("I11").get()); //we need to write a method called getNextControlNumber(String)... interchange_Control_Header.getDataElement("I12").set(getNextControlNumber("100")); interchange_Control_Header.getDataElement("I13").set("0"); // we don't want acknowledgment, otherwise set to "1" interchange_Control_Header.getDataElement("I14").set(incomingISA.getDataElement("I14").get()); interchange_Control_Header.getDataElement("I15").set(incomingISA.getDataElement("I15").get());
    3. we could have added an import com.americancoders.util; to simplify some of the previous statements.
  7. We are not using the Grade of Service or Deferred Delivery request segments. So we remove the following code // Grade of Service Request not required Segment grade_of_Service_Request = Grade_of_Service_Request.getInstance(); grade_of_Service_Request.useDefault(); // Deferred Delivery Request not required Segment deferred_Delivery_Request = Deferred_Delivery_Request.getInstance(); deferred_Delivery_Request.useDefault();
  8. Now we need to populate one functional group for the outbound document. So we work the GS segment and its dataelements. The segment is available to us through the variable fgHeader. Using data from the incoming Envelope, using some static methods in the Util class, and hard coding other values we can fill all the GS dataelements. So after the lines of code where the program sets the fg header segment we add code to populate its dataelements.

    Replace the statement fgHeader.useDefault(); with

    Segment incomingGS = inEnv.getFunctionalGroup(0).getSegment("GS"); // to get data out of the GS segment fgHeader.getDataElement("479").set("FA"); // 997's fall into this group fgHeader.getDataElement("142").set(incomingGS.getDataElement(3).get());// flip sender/receiver fgHeader.getDataElement("124").set(incomingGS.getDataElement(2).get());// flip sender/receiver fgHeader.getDataElement("373").set(com.americancoders.util.Util.currentDate()); fgHeader.getDataElement("337").set(com.americancoders.util.Util.currentTime()); fgHeader.getDataElement("28").set(getNextControlNumber("200")); fgHeader.getDataElement("455").set("X"); // could also be a "T" for TDCC, but we're doing X12. fgHeader.getDataElement("480").set("004010"); // ouch; it's hard coded
  9. Now we need to populate our documents ST segment data. So go to the method named buildSegmentTransactionSetHeaderforTableHeader(Table... and change the last set statement to fill in the transaction set control number. Again we using the class method getNextControlNumber. The call goes from de = (DataElement) segment.buildDE(2); // 329 Transaction Set Control Number de.set(""); to de = (DataElement) segment.buildDE(2); // 329 Transaction Set Control Number de.set(getNextControlNumber("200"));
  10. For each functional group in the incoming Envelope object we generate an AK1 segment and later an AK9 segment. The code generator doesn't do everything for us so we need to pass the functional group we are currently working on to the method. We change the method buildSegmentFunctionalGroupResponseHeaderforTableHeader, which builds the AK1 response segment. There is one AK1 for each functional group in the incoming document. * @param inTable table containing this segment * @throws OBOEException - most likely segment not found */ public Segment buildSegmentFunctionalGroupResponseHeaderforTableHeader(Table inTable) to accept the functionalgroup object as * @param inTable table containing this segment * @param inFG functional group we are responding to * @throws OBOEException - most likely segment not found */ public Segment buildSegmentFunctionalGroupResponseHeaderforTableHeader(Table inTable, FunctionalGroup inFG)
    Notice the documentation change.
  11. Next we replace the ubiquitous de.set("") statements de = (DataElement) segment.buildDE(1); // 479 Functional Identifier Code de.set(""); de = (DataElement) segment.buildDE(2); // 28 Group Control Number de.set(""); to use the parsed inFG dataelements. de = (DataElement) segment.buildDE(1); // 479 Functional Identifier Code de.set(inFG.getHeader().getDataElement("479").get()); de = (DataElement) segment.buildDE(2); // 28 Group Control Number de.set(inFG.getHeader().getDataElement("28").get());
  12. For each transaction set in a functional group the outgoing document needs an AK2. And hanging off of the AK2 segment is a AK3 loop and a AK5 segment. The AK3 loop contains a AK3 and AK4 segment The AK3 loop and AK5 segment report on each transaction set, AK3 for segment notes and AK5 for response indicators for the transaction set. And with the AK3 segment, the AK4s are for reporting problems with specific data elements of the segment reported by the AK3 segment.
  13. As with the other methods generated by the OBOECodeGenerator utility, we change the buildLoopTransactionSetResponseHeaderforTableHeader to accept two additional parameters, the transactionset we responding to and the DocumentErrors object that contains the error information. * @throws OBOEException - most likely segment not found */ public Loop buildLoopTransactionSetResponseHeaderforTableHeader(Table inTable) is replaced with * @param inTable table containing this segment * @param inFG functional group with transaction sets we're responding to * @param inDErr possible errors are contained in this. * @throws OBOEException - most likely segment not found */ public Loop buildLoopTransactionSetResponseHeaderforTableHeader(Table inTable, FunctionalGroup inFG, DocumentErrors inDErr) Likewise up in the constructor we need to change the method call buildLoopTransactionSetResponseHeaderforTableHeader(table); to buildLoopTransactionSetResponseHeaderforTableHeader(table, inEnv.getFunctionalGroup(fgcnt).getTransactionSet(tscnt), inDErr);
  14. Since the generator built logic for the segment within the AK2 loop we update the buildSegmentAK2... call and method. But this time we will be using the incoming transaction set object and the document errors object.

    We replace the call

    buildSegmentTransactionSetResponseHeaderforLoopAK2(loop);

    with

    buildSegmentTransactionSetResponseHeaderforLoopAK2(loop, inTS, inDErr);

    Similarly the method itself is changed from

    * @param inLoop loop containing this segment * @returns segment object AK2 * @throws OBOEException - most likely segment not found */ public Segment buildSegmentTransactionSetResponseHeaderforLoopAK2(Loop inLoop) throws OBOEException

    to

    * @param inLoop loop containing this segment * @param inTS transactionset we're responding to * @param inDErr possible errors are contained in this. * @returns segment object AK2 * @throws OBOEException - most likely segment not found */ public Segment buildSegmentTransactionSetResponseHeaderforLoopAK2(Loop inLoop, TransactionSet inTS, DocumentErrors inDErr) throws OBOEException

  15. We change the dummy set statements with useful information from the inTS object. de = (DataElement) segment.buildDE(1); // 143 Transaction Set Identifier Code de.set(inTS.getID()); de = (DataElement) segment.buildDE(2); // 329 Transaction Set Control Number de.set(""); de = (DataElement) segment.buildDE(1); // 143 Transaction Set Identifier Code de.set(inTS.getID()); // not quite the same but just as effective de = (DataElement) segment.buildDE(2); // 329 Transaction Set Control Number de.set(inTS.getHeaderTable().getSegment("ST").getDataElement("329").get()); // get the header table then the ST segment
  16. Now to report on the individual segment errors we need to drill down through the incoming OBOE transaction set object to find the segments referenced by the DocumentErrors object. We can do this recursively since the DocumentErrors objects reference SegmentContainers. SegmentContainers are implemented by several different OBOE objects which can contain other implemented SegmentContainers hence allowing for recursion.

    We will work primarily with the generated method buildSegmentDataSegmentNoteforLoopAK3. And we will need two similar methods one to work with a AK3 segment to report specific segment errors and the other to create AK3 that will also contain AK4s to report data element errors within a segment.

    First, the drill done logic is written. We simply go through looking for each of the three possible Table object. If one is found we drill down through each of its segments until the last table has been processed.

    While we check the transaction set we add a boolean to track if the transaction set has any errors, likewise the functional group. And we track the number of transaction sets that are acceptable. So at the very top of the program we add three class attributes to do this for us.

    boolean fgInErr=false; boolean tsInErr=false; int acceptedTSs = 0; Obviously we need to set these booleans at the appropriate places. So we set the fgInErr boolean as we loop trough each functional group in the incoming object. So back in the constructor we add the following code. And we will edit the call to build the AK1 segment for each functional group and AK2 for each transaction set within a functional group.

    We replace

    buildSegmentFunctionalGroupResponseHeaderforTableHeader(table); // for (i = 0; i < multipletimes; i++) buildLoopTransactionSetResponseHeaderforTableHeader(table, inEnv.getFunctionalGroup(fgcnt).getTransactionSet(tscnt), inDErr); with for (int fgcnt = 0; fgcnt < inEnv.getFunctionalGroupCount(); fgcnt++) { acceptedTSs = 0; fgInErr = false; buildSegmentFunctionalGroupResponseHeaderforTableHeader(table, inEnv.getFunctionalGroup(fgcnt)); }

    Work must be done to populate the AK2 segment. So in the buildLoopTransactionSetResponseHeaderforTableHeader method we replace the call to build the AK2 segment

    buildLoopTransactionSetResponseHeaderforTableHeader(loop); with if (inEnv.getFunctionalGroup(fgcnt).getTransactionSetCount() > 0) buildLoopTransactionSetResponseHeaderforTableHeader(table, inEnv.getFunctionalGroup(fgcnt), inDErr); And we update buildLoopTransactionSetResponseHeaderforTableHeader by replacing buildSegmentTransactionSetResponseHeaderforLoopAK2(loop, inTS); with for (int tscnt = 0; tscnt < inFG.getTransactionSetCount(); tscnt++) { tsInErr = false; buildSegmentTransactionSetResponseHeaderforLoopAK2(loop, inFG.getTransactionSet(tscnt), inDErr); }

    Then down in method buildSegmentTransactionSetResponseHeaderforLoopAK2 we add the code that goes through each table object. As a segment container object and it matches the DocumentErrors container object, found with the getContainer method call, we report the segment in error by building an AK3.

    Table inTbl = null; while (true) { if (inTbl == inTS.getSummaryTable()) break; else if (inTbl == inTS.getDetailTable()) inTbl = inTS.getSummaryTable(); else if (inTbl == inTS.getHeaderTable()) inTbl = inTS.getDetailTable(); else if (inTbl == null) inTbl = inTS.getHeaderTable(); if (inTbl == null) break; for (int i = 0; i < inDErr.getErrorCount(); i++) { if (inDErr.getContainer(i) == inTbl) { buildLoopDataSegmentNoteforLoopAK2(inLoop, inDErr.getErrorID(i), inDErr.getErrorPosition(i), inDErr.getErrorCode(i)); } }
  17. If you notice, the while (true) loop is not closed. That is to show that we also need to check the loops and segments within the table. First we need to write two new methods. I call them checkLoop and checkSegment. These methods will take four arguments: So inside the while (true) loop we add the call for (int i = 0; i < inTbl.getContainerSize(); i++) if (inTbl.isSegment(i)) checkSegment(inLoop, inTbl.getSegment(i), inDErr); else if (inTbl.isLoop(i)) checkSegment(inLoop, inTbl.getLoop(i), inDErr); else if (inTbl.isVector(i)) { for (int j = 0; j < inTbl.getSegmentCount(i); j++) { if (inTbl.isSegment(i,j)) checkSegment(inLoop, inTbl.getSegment(i,j), inDErr); else if (inTbl.isLoop(i,j)) checkSegment(inLoop, inTbl.getLoop(i,j), inDErr); } } Notice the check for vectors using the isVector call. This is because Loops and Segments can occur multiple times within its container. For these multiply occurring objects OBOE contains them in a Vector object.
  18. Again, the code checks to see if the position parameter points to a vector object. If what is held in that position is a vector then the code gets the element type at each vector position and calls the appropriate method. And finally the method checkSegment, the one that does the individual segment checking, is called if the position points to an individual segment. Both methods are presented below. It is in the checkSegment method where the application determines if the error is at the segment level, thereby generating only an AK3 segment or at the data level and so the method needs to generate both an AK3 and the AK3's AK4 subsegment.

    If the reported container is a Loop object the error is reported. Note: This error can only be an unknown segment.

    Since loops implement the loopContainer interface, like tables in the previous method discussion, we can now recursively call the checkLoop method for loops that are contained by other loops

    /** works with loops looking to see if DocumentError object is pointing at any of its elements * @param loopAK2forAK3 loop to contain the reporting AK3 * @param loop * @param inDErr DocumentErrors object */ public void checkLoop(Loop loopAK2forAK3, Loop inLoop, DocumentErrors inDErr) { for (int i = 0; i < inDErr.getErrorCount(); i++) { if (inDErr.getContainer(i) == inLoop) { tsInErr = true; fgInErr = true; buildLoopAndSegmenAK3forTableHeaderLoopAK2(loopAK2forAK3, inDErr.getErrorID(i), inDErr.getErrorPosition(i), "1"); return; } } for (int i = 0; i < inLoop.getContainerSize(); i++) if (inLoop.isSegment(i)) checkSegment(inLoop, inLoop.getSegment(i), inDErr); else if (inLoop.isLoop(i)) checkLoop(inLoop, inLoop.getLoop(i), inDErr); else if (inLoop.isVector(i)) { for (int j = 0; j < inLoop.getSegmentCount(i); j++) { if (inLoop.isSegment(i,j)) checkSegment(inLoop, inLoop.getSegment(i,j), inDErr); else if (inLoop.isLoop(i,j)) checkLoop(inLoop, inLoop.getLoop(i,j), inDErr); } } } /** works with individual segments looking to see if DocumentError object is pointing at it * @param loopAK2forAK3 loop to contain the reporting AK3 * @param testSeg segment we are testing * @param inDErr DocumentErrors object */ public void checkSegment(Loop loopAK2forAK3, Segment testSeg, DocumentErrors inDErr) { for (int i = 0; i < inDErr.getErrorCount(); i++) { if (inDErr.getContainer(i) == testSeg) { tsInErr = true; fgInErr = true; if ( (inDErr.getErrorObject(i) instanceof Segment) || (inDErr.getErrorObject(i) instanceof TemplateSegment) ) { buildLoopAndSegmenAK3forTableHeaderLoopAK2(loopAK2forAK3, inDErr.getErrorID(i), inDErr.getErrorPosition(i), inDErr.getErrorCode(i)); } else if ( (inDErr.getErrorObject(i) instanceof TemplateDE) || (inDErr.getErrorObject(i) instanceof TemplateComposite) || (inDErr.getErrorObject(i) instanceof DataElement) || (inDErr.getErrorObject(i) instanceof CompositeDE) ) { Loop ak3Loop = buildLoopAndSegmenAK3forTableHeaderLoopAK2(loopAK2forAK3, inDErr.getErrorID(i), inDErr.getErrorPosition(i)); buildSegmentAK4forTableHeaderLoopAK2LoopAK3(ak3Loop, inDErr.getErrorObject(i), inDErr.getErrorCode(i), inDErr.getErrorID(i)); } else if ( inDErr.getErrorDescription(i).startsWith("Unknown or out of place segment")) buildLoopAndSegmenAK3forTableHeaderLoopAK2(loopAK2forAK3, inDErr.getErrorID(i), inDErr.getErrorPosition(i), "1"); } }

  19. We now alter the call to AK3 loops and segments Instead of simply passing it the outbound SegmentContainer object we pass error information and other codes. Also since we are building the AK4 outbound segment we need to reference its parent AK3 loop. So we alter one of the buildLoopAK3forTableHeaderLoopAK2 methods and create a new second method, in which one of its new functions is to return the outbound AK3 loop that the method creates for use with the AK4

    To simply things will combine the process of building the AK3 loop and AK3 segment within one method.

    /** builds loop and segment AK3 that is part of the TableHeader LoopAK2 *<br>Data Segment Note used *<br>To report errors in a data segment and identify the location of the data segment * @param inLoop loop containing this subsegment, the AK2 loop * @param errID segment ID in error * @param errPos int * @throws OBOEException - most likely segment not found * @returns Loop the AK3 loop that was built so the AK4 can be attached later */ public Loop buildLoopAndSegmenAK3forTableHeaderLoopAK2(Loop inLoop, String errID, int errPos) throws OBOEException { tsInErr = true; fgInErr = true; Loop ak3Loop = inLoop.createLoop("AK3"); inLoop.addLoop(ak3Loop)l Segment segment = ak3Loop.createSegment("AK3"); ak3Loop.addSegment(segment); DataElement de; /* segment.useDefault(); */ de = (DataElement) segment.buildDE(1); // 721 Segment ID Code de.set(errID); de = (DataElement) segment.buildDE(2); // 719 Segment Position in Transaction Set de.set(errPos+""); // change the int to a String return ak3Loop; } /** builds loop and segment AK3 that is part of the HeaderAK2 *<br>Data Segment Note used *<br>To report errors in a data segment and identify the location of the data segment * @param inLoop loop containing this subsegment, the AK2 loop * @param errID segment ID containing errors * @param errPos int * @param errCode String value as reported by parser * @throws OBOEException - most likely segment not found */ public Loop buildLoopAndSegmenAK3forTableHeaderLoopAK2(Loop inLoop, String errID, int errPos, String errCode) throws OBOEException { tsInErr = true; fgInErr = true; Loop ak3Loop = inLoop.createLoop("AK3"); inLoop.addLoop(ak3Loop); Segment segment = ak3Loop.createSegment("AK3"); ak3Loop.addSegment(segment); DataElement de; /* segment.useDefault(); */ de = (DataElement) segment.buildDE(1); // 721 Segment ID Code de.set(errID); de = (DataElement) segment.buildDE(2); // 719 Segment Position in Transaction Set de.set(errPos+""); // change the int to a String de = (DataElement) segment.buildDE(3); // 447 Loop Identifier Code de.set(""); // loop ID not used de = (DataElement) segment.buildDE(4); // 720 Segment Syntax Error Code de.set(errCode); return ak3Loop; }
  20. We are almost done drilling down to the lowest levels of the OBOE object we will report data element errors. As mentioned earlier the AK4 segment is used to do report dataelement errors. Like all of the other generated methods from the OBOECodeGenerator application there is not enough information available to the method. So we change the buildSegmentAK4forTableHeaderLoopAK2LoopAK3 method to accept more information. The information is to populate the outbound AK4 segment.

    Also the method needs to determine if it is reporting errors at the composite level or the individual data element. The new code reads as follows:

    /** builds segment AK4 that is part of the HeaderAK2AK3 *<br>Data Element Note used *<br>To report errors in a data element or composite data structure and identify the location of the data element * @param inLoop containing this segment * @param inErrObject causing the error * @param errCode 723 error code * @param errID field id in error * @throws OBOEException - most likely segment not found */ public Segment buildSegmentAK4forTableHeaderLoopAK2LoopAK3(Loop inLoop, Object inErrObject, String errCode, String errID) throws OBOEException { Segment segment = inLoop.createSegment("AK4"); inLoop.addSegment(segment); DataElement de; /* segment.useDefault(); */ if (inErrObject instanceof CompositeDE) { CompositeDE composite = (CompositeDE) segment.buildDE(1); // C030 Position in Segment CompositeDE errcde = (CompositeDE) inErrObject; de = (DataElement) composite.buildDE(1); // composite element 722 Element Position in Segment de.set(errcde.getSequence()+""); de = (DataElement) composite.buildDE(2); // composite element 1528 Component Data Element Position in Composite de.set(""); } else if (inErrObject instanceof TemplateComposite) { CompositeDE composite = (CompositeDE) segment.buildDE(1); // C030 Position in Segment TemplateComposite errcde = (TemplateComposite) inErrObject; de = (DataElement) composite.buildDE(1); // composite element 722 Element Position in Segment de.set(errcde.getSequence()+""); de = (DataElement) composite.buildDE(2); // composite element 1528 Component Data Element Position in Composite de.set(""); } de = (DataElement) segment.buildDE(2); // 725 Data Element Reference Number de.set(errID); de = (DataElement) segment.buildDE(3); // 723 Data Element Syntax Error Code de.set(errCode); if (inErrObject instanceof DataElement) { DataElement derr = (DataElement) inErrObject; de = (DataElement) segment.buildDE(4); // 724 Copy of Bad Data Element de.set(derr.get()); } return segment; }
  21. We are done drilling down through the transaction set object. Notice that in the new code that if an error is found we set our class attribute variable tsInErr to true. This attribute assists us in two ways. One, to send an Accept or Reject indicator in the AK5 outbound segment And second, to keep a track of the accepted transactions sets for the AK9 segment.

    So we edit the way the AK5 segment is built in the buildSegmentTransactionSetResponseTrailerforLoopAK2 method. Two new parameters are added, the accept/reject value for the transactions set and the 718 syntax error code. If the transaction set is being accepted then there is no reason to build the 718 data element. Here's the updated method.

    /** builds segment AK5 that is part of the HeaderAK2 *<br>Transaction Set Response Trailer used *<br>To acknowledge acceptance or rejection and report errors in a transaction set * @param inLoop Loop that will hold the AK5 segment to be built, an AK2. * @param accRej - "A" or "R" * @param code - 718 code indicating type of error * @throws OBOEException - most likely segment not found */ public void buildSegmentTransactionSetResponseTrailerforLoopAK2(Loop inLoop, String accRej, String code) throws OBOEException { Segment segment = inLoop.createSegment("AK5"); inLoop.addSegment(segment); DataElement de; /* segment.useDefault(); */ de = (DataElement) segment.buildDE(1); // 717 Transaction Set Acknowledgment Code de.set(accRej); if (code != null) { de = (DataElement) segment.buildDE(2); // 718 Transaction Set Syntax Error Code de.set(code); } }
  22. Back up in the method buildLoopTransactionSetResponseHeaderforTableHeader where the code loops through the transaction sets we update the call to the above method. if (tsInErr) buildSegmentTransactionSetResponseTrailerforLoopAK2(loop, "R", "5"); else { buildSegmentTransactionSetResponseTrailerforLoopAK2(loop, "A", null); acceptedTSs++; }
  23. And as we reported each transaction set in the incoming document with an AK2 AK5 segment combination we report each functional group with a AK1 AK9 combo.

    We update the code in the constructor method where we loop through each functional group. Here the method call to buildSegmentFunctionalGroupResponseTrailerforTableHeader will pass the functional group tested, the fgInErr attribute and the number of accepted transactions sets in the functional group.

    for (int fgcnt = 0; fgcnt < inEnv.getFunctionalGroupCount(); fgcnt++) buildSegmentFunctionalGroupResponseTrailerforTableHeader(table, inEnv.getFunctionalGroup(fgcnt), fgInErr, acceptedTSs);
  24. And the updated buildSegmentFunctionalGroupResponseTrailerforTableHeader now reads as: /** builds segment AK9 that is part of the Header *<br>Functional Group Response Trailer used *<br>To acknowledge acceptance or rejection of a functional group and report the number of included transaction sets from the original trailer, the accepted sets, and the received sets in this functional group * @param inTable table containing the AK9 segment to be built * @param inFG functional group reporting on * @param boolean error or no error * @param int - number of accepted TSs. * @throws OBOEException - most likely segment not found */ public Segment buildSegmentFunctionalGroupResponseTrailerforTableHeader(Table inTable, FunctionalGroup inFG, boolean inErr, int acceptCnt) throws OBOEException { Segment segment = inTable.createSegment("AK9"); inTable.addSegment(segment); DataElement de; /* segment.useDefault(); */ de = (DataElement) segment.buildDE(1); // 715 Functional Group Acknowledge Code if (inErr) de.set("R"); else de.set("A"); de = (DataElement) segment.buildDE(2); // 97 Number of Transaction Sets Included de.set(inFG.getTrailer().getDataElement("28").get()); de = (DataElement) segment.buildDE(3); // 123 Number of Received Transaction Sets de.set(inFG.getTransactionSetCount()+""); de = (DataElement) segment.buildDE(4); // 2 Number of Accepted Transaction Sets de.set(acceptCnt+""); /* de = (DataElement) segment.buildDE(5); // 716 Functional Group Syntax Error Code de.set(""); */ return segment; }
  25. We are almost done. To close our 997 transaction the code sets the SE segment. Remembering that the OBOECodeGenerator class build very generic code. We need to clean some of the SE logic up. In the method buildSegmentSEforTableHeader remove the de.set("") statements and add code in the constructor that instructs the transactionset object populate the two dataelements.
    from de = (DataElement) segment.buildDE(1); // 96 Number of Included Segments de.set(""); de = (DataElement) segment.buildDE(2); // 329 Transaction Set Control Number de.set(""); to de = (DataElement) segment.buildDE(1); // 96 Number of Included Segments de = (DataElement) segment.buildDE(2); // 329 Transaction Set Control Number In the constructor add the following by changing buildSegmentTransactionSetTrailerforTableHeader(table); to buildSegmentTransactionSetTrailerforTableHeader(table); ts.setTrailerFields();
  26. We are now done. So close our functional group. Since the generator adds the following code inside a simple loop. env.getFunctionalGroup(i).setCountInTrailer(); , which sets the count and the control code, there's nothing really to do.
  27. Finish up our document with an IEA segment. The generator adds the following code env.setFGCountInTrailer(); which sets the FG count and the ISA control code in the IEA segment, so, again, there's nothing really to do.
  28. Things added.
    1. We needed a method to generate control numbers. Here's mine but it's not too effective. /** routine to return a control number - not very useful. here to satisfy the compiler */ static int num = 0; public String getNextControlNumber(String what) { return what + "0000" + num++; }
    2. And for testing I usually add a main method to the class to test from the command line. public static void main(String args[]) { DocumentErrors de = new DocumentErrors(); //create a empty document errors in case there aren't any X12DocumentHandler dh = new X12DocumentHandler(); try { dh.startParsing(new java.io.FileReader(args[0])); } catch (OBOEException oe) // the application assumes an error exists { de = oe.getDocumentErrors(); for (int i = 0; i < de.getErrorCount(); i++) { System.out.println(i + " " + de.getErrorCode(i) + " " + de.getErrorID(i) + " " + de.getErrorDescription(i)); } } catch (java.io.IOException ioe) { ioe.printStackTrace(); } finally { build997 b = new build997((X12Envelope) dh.getEnvelope(), de); System.out.println(b.env.getFormattedText(Envelope.X12_FORMAT)); } }
  29. The Finished Product