EDI application in electronic logistics

background

EDI stands for Electronic Data Interchange. In traditional enterprises, a lot of process operations or communications are usually done by paper media, such as purchase orders, invoices, order synchronization and so on. However, because all communication of paper media depends on human hands, it will bring many inevitable disadvantages, such as slow operation and synchronization of information, high consumption of human and material resources and so on. EDI emerged to solve the disadvantages of paper interaction. It can greatly improve business efficiency, synchronize various states and information more quickly, reduce error-correcting processes, access information in near real time, and most importantly, save money. Studies have shown that on average the cost of using EDI is only one third of the cost of paper compared to paper exchange. It also saves more time (61% in some studies), shortens order times, automates processes, and so on.

It is estimated that most of the people who can see this article are Internet practitioners, and it may be difficult to imagine that there are so many places that have not been “informationized” in this “informationized” era. But the amazing thing is, in the absence of information technology, people have achieved everything by manpower even up to this point there are so many traditional industries that have completely different daily processes.

When you look at this first paragraph, you might be thinking: Isn’t that what apis do? When EDI was founded, it actually did something similar to what we do with apis today. It’s just a different era. EDI first appeared in the 1960s. The more familiar ones, like XML, were introduced in 1996 and JSON in 2013. That’s how young the modern Internet really is.

However, the difference between EDI and API is that, in my opinion, the concept of EDI is actually more like a general API than our current API. The “standard” of our API is more that everyone uses the same technology, but everyone has their own definition. EDI is everybody uses the same technology, and everybody uses the same structure. Because EDI documents do not contain explanations. Therefore, the fields used in EDI in each copy express the same content. You can also think of it as an API that does not contain field names but only individual field values.

EDI originally had its roots in military logistics. However, with the development of time, EDI has gradually been used by all walks of life. However, due to the different needs of different industries, there are multiple circulating standards for EDI today. For example, EDI 856 is used for shipping manifest, EDI 810 is used for invoice, ANSI X12 is most used in The United States, UN/EDIFACT is widely used in other parts of the world and so on. Different industries, different situations will have a corresponding standard can be used.

The principle of

So with all that said, how do you use EDI? It’s simple, in three steps:

  1. Prepare the data to be transmitted
  2. Convert data to EDI format
  3. Close the refrigerator door (Bushi

EDI Is a text file according to different application scenarios. Inside, the corresponding information is converted into EDI format for storage according to certain standards. As for transferring it, you can use FTP/SFTP/FTPS, AS1/AS2/AS4, OFTP/OFTP2 or even email this EDI file. Anything that can transfer a text file from one place to another will do. There are even people who have made apis and have (EDI for formatting). In short, EDI is direct end-to-end transmission in most cases. But there is also a small amount of VAN (value-added network).

After the other end gets the file, if it is automated, you can use the program to carry out the next process. Using EDI translation equipment or a parser can be used to open them.

For example, EDI 315,315, used in a project I did before, is mainly used to track the status of logistics information and details of transport/container events in shipping. It reads as follows:

ISA*00*          *00*          *ZZ*OECGROUP       *ZZ*AAA            *201120*1304*U*00401*000259937*0*P*>
GS*QO*OECGROUP*AAA*20201120*1304*259937*X*004010
ST*315*0001
B4***I*20201030*0000*CNYTN*DFSU*773057*L*4500*CNYTN*UN*6
N9*BM*OERT210702J01222
N9*BN*MEDUZ7825111
N9*EQ*DFSU7730576
N9*SN*FJ10356721
N9*SCA*OERT
Q2*NONE********045W***L*MAERSK ALGOL
R4*5*UN*CNYTN*YANTIAN PT*CN
DTM*140*20201030*0000*LT
R4*R*UN*CNYTN*YANTIAN PT*CN
DTM*140*20201030*0000*LT
R4*L*UN*CNYTN*YANTIAN PT*CN
DTM*140*20201110*1040*LT
R4*D*UN*USSAV*SAVANNAH*US
DTM*139*20201213*0000*LT
R4*E*UN*USSAV*SAVANNAH*US
DTM*139*20201215*1500*LT
SE*19*0001
GE*1*259937
IEA*1*000259937
Copy the code

This is a complete EDI file. It might look like gibberish, but it’s just that there’s no field name to explain it.

All EDI files are made up of three blocks:

  • Element: The contents of a row are different elements.
  • Segments: Segments are elements of the same type, similar to groups. Classify elements. In the example above, a row is a group.
  • Transaction Sets: A set of transactions, also called EDI messages or EDI transactions. When information is collected in segments, it forms a set.

The idea behind these standards is to have a mutually agreed upon standard for transmitting information that can say more with less.

I’m just going to explain one of the examples above, just to get a sense of what it means and how it works. For more details on what each paragraph means, see this document: The above document is just what I found on the Internet and uses the same standard, but the Details may be different from my example. Under the same standard, different companies may use different fields. For example, some fields may be omitted if they are not needed by their companies. But it means the same thing.

R4*5*UN*CNYTN*YANTIAN PT*CN
Copy the code
  • “R4” – is the beginning of this section, indicating that this is a line describing “Port or Terminal”.

  • “*” – the asterisk is just a separator in this document and has no real meaning. So this is essentially R4, 5, UN, CNYTN, YANTIAN PT, CN.

  • “5” – This is also a convention value. When it is “5”, it means that this is describing the “Active Location”.

  • “UN” – indicates that the next value is the UNLOCODE used, which is a table describing ports around the world.

  • “CNYTN” – This is the current port code of UNLOCODE. You can look it up here. Yantian Port in Shenzhen.

  • “YANTIAN PT. “- That’s the name of the port. PT is Port.

  • “CN” – This is the ISO country code of two, which refers to China 🇨🇳

It’s actually quite understandable if there are instructions. But no explanation would be a mystery.

The implementation of

I’ve actually been wondering if I should write this verse. Because after the principle is clear, in fact, there is no great need to write, a development can be the whole analysis out. There are plenty of existing libraries on Github, but because there are so many standards, chances are you’ll have to implement one yourself.

In terms of transmission, if it’s something like E-mail, you can use the mail service directly to intercept attachments and send the content to the parser. If it is a file service such as FTP, you may need to set up a file monitoring program or set up a timer for periodic scanning.

About parsing content, below I attach the simplest parsing code to parse the above example. Parsing this stuff to see what the usage scenario needs can be very complicated or very simple. Everything follows demand.

const UNUSED = undefined;

const InterchangeControlHeader = [
  'AuthorizationInformationQualifier'.'AuthorizationInformation'.'SecurityInformationQualifier'.'SecurityInformation'.'InterchangeSenderIDQualifier'.'InterchangeSenderID'.'InterchangeReceiverIDQualifier'.'InterchangeReceiverID'.'InterchangeDate'.'InterchangeTime'.'InterchangeControlStandardsIdentifier'.'InterchangeControlVersionNumber'.'InterchangeControlNumber'.'AcknowledgmentRequested'.'UsageIndicator'.'ComponentElementSeparator',];const FunctionalGroupHeader = [
  'FunctionalIdentifierCode'.'ApplicationSendersCode'.'ApplicationReceiversCode'.'Date'.'Time'.'GroupControlNumber'.'ResponsibleAgencyCode'.'VersionReleaseIndustryIdentifierCode',];const TransactionSetHeader = [
  'TransactionSetIdentifierCode'.'TransactionSetControlNumber',];const BeginningSegmentForInquiryOrReply = [
  UNUSED,
  UNUSED,
  'ShipmentStatusCode'.'Date'.'StatusTime'.'StatusLocation'.'EquipmentInitial'.'EquipmentNumber'.'EquipmentStatusCode'.'EquipmentType'.'LocationIdentifier'.'LocationQualifier'.'EquipmentNumberCheckDigit',];const ReferenceIdentification = [
  'ReferenceIdentificationQualifier'.'ReferenceIdentification',];const StatusDetailsOcean = [
  'VesselCode',
  UNUSED,
  UNUSED,
  UNUSED,
  UNUSED,
  UNUSED,
  UNUSED,
  'CountryCode'.'VoyageNumber',
  UNUSED,
  UNUSED,
  'VesselCodeQualifier'.'VesselName',];const PortOrTerminal = [
  'PortOrTerminalFunctionCode'.'LocationQualifier'.'LocationIdentifier'.'PortName'.'CountryCode',];const DateTimeReference = ['DateTimeQualifier'.'Date'.'Time'.'TimeCode'];

const TransactionSetTrailer = [
  'NumberOfIncludedSegments'.'TransactionSetControlNumber',];const FunctionalGroupTrailer = [
  'NumberOfTransactionSetsIncluded'.'GroupControlNumber',];const InterchangeControlTrailer = [
  'NumberOfIncludedFunctionalGroups'.'InterchangeControlNumber',];const segments = {
  ISA: 'InterchangeControlHeader'.GS: 'FunctionalGroupHeader'.ST: 'TransactionSetHeader'.B4: 'BeginningSegmentForInquiryOrReply'.N9: 'ReferenceIdentification'.Q2: 'StatusDetailsOcean'.R4: 'PortOrTerminal'.DTM: 'DateTimeReference'.SE: 'TransactionSetTrailer'.GE: 'FunctionalGroupTrailer'.IEA: 'InterchangeControlTrailer'};const segmentFields = {
  [segments.ISA]: InterchangeControlHeader,
  [segments.GS]: FunctionalGroupHeader,
  [segments.ST]: TransactionSetHeader,
  [segments.B4]: BeginningSegmentForInquiryOrReply,
  [segments.N9]: ReferenceIdentification,
  [segments.Q2]: StatusDetailsOcean,
  [segments.R4]: PortOrTerminal,
  [segments.DTM]: DateTimeReference,
  [segments.SE]: TransactionSetTrailer,
  [segments.GE]: FunctionalGroupTrailer,
  [segments.IEA]: InterchangeControlTrailer,
};

type Edi315 = {
  [key in keyof typeofsegments]? : | Record<Partial<keyoftypeof segmentFields>, string>
    | Record<Partial<keyof typeof segmentFields>, string> []; };const parse = function(
  data: string | string[],
  segmentSeparator = '\n',
  valueSeparator = The '*'.) :Edi315 {
  const result = {};
  const availableSegments = Object.keys(segments);
  const _data = Array.isArray(data) ? data : data.split(segmentSeparator);

  _data.map((line, index) = > {
    if(! line.replace(valueSeparator,' ').trim()) return;

    const lineData = line.split(valueSeparator);
    const segmentName = lineData[0];

    if(! availableSegments.includes(segmentName)) {console.error('Unknown segment:', line);
      return;
    }

    lineData.slice(1).map((item, idx, array) = > {
      const fieldName = segmentFields[segments[segmentName]][idx];

      if (result[segmentName] === undefined) {
        if (['N9'.'R4'.'DTM'].includes(segmentName)) {
          result[segmentName] = [];
        } else{ result[segmentName] = {}; }}if(segmentFields[segments[segmentName]].length ! = array.length) {if (idx < 1) {
          console.error('Mismatch segment length:', line);
        }
        return;
      }
      if(fieldName ! == UNUSED) {if (Array.isArray(result[segmentName])) {
          if (result[segmentName][index] === undefined) {
            result[segmentName][index] = {};
          }
          result[segmentName][index][fieldName] = item;
        } else{ result[segmentName][fieldName] = item; }}}); });Object.keys(segments).map(segmentName= > {
    if (Array.isArray(result[segmentName])) {
      result[segmentName] = result[segmentName].filter(
        x= > x as Record<string.string>,); }});return result;
};

export default parse;
Copy the code

So when you pass in the above example you get something like this:

{
  ISA: {
    AuthorizationInformationQualifier: '00'.AuthorizationInformation: ' '.SecurityInformationQualifier: '00'.SecurityInformation: ' '.InterchangeSenderIDQualifier: 'ZZ'.InterchangeSenderID: 'OECGROUP '.InterchangeReceiverIDQualifier: 'ZZ'.InterchangeReceiverID: 'AAA '.InterchangeDate: '201120'.InterchangeTime: '1304'.InterchangeControlStandardsIdentifier: 'U'.InterchangeControlVersionNumber: '00401'.InterchangeControlNumber: '000259937'.AcknowledgmentRequested: '0'.UsageIndicator: 'P'.ComponentElementSeparator: '>'
  },
  GS: {
    FunctionalIdentifierCode: 'QO'.ApplicationSendersCode: 'OECGROUP'.ApplicationReceiversCode: 'AAA'.Date: '20201120'.Time: '1304'.GroupControlNumber: '259937'.ResponsibleAgencyCode: 'X'.VersionReleaseIndustryIdentifierCode: '004010'
  },
  ST: {
    TransactionSetIdentifierCode: '315'.TransactionSetControlNumber: '0001'
  },
  B4: {
    ShipmentStatusCode: 'I'.Date: '20201030'.StatusTime: '0000'.StatusLocation: 'CNYTN'.EquipmentInitial: 'DFSU'.EquipmentNumber: '773057'.EquipmentStatusCode: 'L'.EquipmentType: '4500'.LocationIdentifier: 'CNYTN'.LocationQualifier: 'UN'.EquipmentNumberCheckDigit: '6'
  },
  N9: [{ReferenceIdentificationQualifier: 'BM'.ReferenceIdentification: 'OERT210702J01222'
    },
    {
      ReferenceIdentificationQualifier: 'BN'.ReferenceIdentification: 'MEDUZ7825111'
    },
    {
      ReferenceIdentificationQualifier: 'EQ'.ReferenceIdentification: 'DFSU7730576'
    },
    {
      ReferenceIdentificationQualifier: 'SN'.ReferenceIdentification: 'FJ10356721'
    },
    {
      ReferenceIdentificationQualifier: 'SCA'.ReferenceIdentification: 'OERT'}].Q2: {
    VesselCode: 'NONE'.CountryCode: ' '.VoyageNumber: '045W'.VesselCodeQualifier: 'L'.VesselName: 'MAERSK ALGOL'
  },
  R4: [{PortOrTerminalFunctionCode: '5'.LocationQualifier: 'UN'.LocationIdentifier: 'CNYTN'.PortName: 'YANTIAN PT'.CountryCode: 'CN'
    },
    {
      PortOrTerminalFunctionCode: 'R'.LocationQualifier: 'UN'.LocationIdentifier: 'CNYTN'.PortName: 'YANTIAN PT'.CountryCode: 'CN'
    },
    {
      PortOrTerminalFunctionCode: 'L'.LocationQualifier: 'UN'.LocationIdentifier: 'CNYTN'.PortName: 'YANTIAN PT'.CountryCode: 'CN'
    },
    {
      PortOrTerminalFunctionCode: 'D'.LocationQualifier: 'UN'.LocationIdentifier: 'USSAV'.PortName: 'SAVANNAH'.CountryCode: 'US'
    },
    {
      PortOrTerminalFunctionCode: 'E'.LocationQualifier: 'UN'.LocationIdentifier: 'USSAV'.PortName: 'SAVANNAH'.CountryCode: 'US'}].DTM: [{DateTimeQualifier: '140'.Date: '20201030'.Time: '0000'.TimeCode: 'LT'
    },
    {
      DateTimeQualifier: '140'.Date: '20201030'.Time: '0000'.TimeCode: 'LT'
    },
    {
      DateTimeQualifier: '140'.Date: '20201110'.Time: '1040'.TimeCode: 'LT'
    },
    {
      DateTimeQualifier: '139'.Date: '20201213'.Time: '0000'.TimeCode: 'LT'
    },
    {
      DateTimeQualifier: '139'.Date: '20201215'.Time: '1500'.TimeCode: 'LT'}].SE: {
    NumberOfIncludedSegments: 'the'.TransactionSetControlNumber: '0001'
  },
  GE: {
    NumberOfTransactionSetsIncluded: '1'.GroupControlNumber: '259937'
  },
  IEA: {
    NumberOfIncludedFunctionalGroups: '1'.InterchangeControlNumber: '000259937'}}Copy the code

In this case I don’t have to expand abbreviations such vocabulary or field, keep them in the file, use the words you can actually put them expand adult eye can be directly read original intention may be better, then there is the different types of fields to convert the type also is pretty good, such as the date to date format, digital to digital format. Anyway, parsing is a process that can be enriched, but I’m not doing much here.

Additional reading:

  1. Wikipedia – EDI
  2. The Complete Collection of Ports in China
  3. Real-time port monitoring information