John Lewis EDI Invoice Generation
Version | Created / Updated by | Date | Notes |
---|---|---|---|
1.0 | Danail Deltchev | 14.06.2023 | First Publish |
1.1 | Danail Deltchev | 20.06.2023 | Clean up and rewordings |
1.2 | Danail Deltchev | 22.06.2023 | JL Invoice status specifications addition |
1.3 | Danail Deltchev | 28.06.2023 | Clean up for better read and future understanding |
The purpose of this document is to explain in detail the business requirements and logic flows for generating an EDI Invoice file to be sent to John Lewis
The EDI Invoice is a standard step in an EDI connection and consists of providing pretty much the order information + a few more specifics regarding the actual invoice (like payment terms, Invoice number etc)
Every John Lewis EDI order should receive an invoice at the right time. For the time being we don’t need to employ our Invoicing functionality as all information needed is already on the order. The flow should be as follows - each Order Shipment for a John Lewis Order that is successfully completed should be tracked and once a specific number of days has passed it should be picked for Invoice generation.
Shipment should be tracked by `Order Shipment > Shipment Time` - only for `Completed` Shipments. Each `Shipment` is to be tracked on its own as we will have to generate an invoice for each one that is on a `Completed` status
Days delay for Invoice generation should be picked from `Account John Lewis > Invoice Generation Days Delay` (new field) and should always be treated in days (meaning it should be converted to seconds when used with the `Shipment Time` timestamp) - **<v1.3>** if Days Delay field is empty or 0 it is still viable, it should be treated as no delay needed and all other triggers to work from there **</v1.3>**
With the above two and the mapping below we can guarantee ourselves generation of the invoice at the right time with the right delay for the correct items. The only thing left is to make sure we generate only one invoice per Shipment
Suggested method - Create a new table John Lewis Invoice as dependent to Order Shipment. Table should hold just a `Status` field so we can track what is happening with values `Pending`, `Completed` and `Error`. We should track (as per the above explanation for readiness and delay) each Shipment for the John Lewis account that doesn't have an Invoice record OR ANY such that have an Invoice record on a `Pending` Status:
- If we generate and upload an invoice to the ftp successfully we should put the
John Lewis Invoice > Status
toCompleted
- If we don’t manage to generate an invoice due to missing information or failed upload to the FTP we should set the
John Lewis Invoice > Status
toError
and store an error with the correct message in tableOrder Error
- <v1.3> the error should be with type “Invoice” </v1.3>
The John Lewis Invoice table should hold 1 to 1 connection
The FTP to which we should export the Invoice file is to be picked from Account John Lewis > EDI FTP Credentials
and we should be looking for a path with label “InvoiceExport”
We are looking to export a file with a name “INV{{ShipmentID}}” where the ShipmentID is the id of the Order Shipment we’ve picked for export. The file should have no extension so just “INV0000001“ (if the ShipmentID we pick is less than 7 numbers we should always pad the front with zeroes)
The EDI file is a string of information consisting of different segments marked with a signature 3 letter tag, followed by information and closed with a single quote at the end ( ' ) before the next segment starts. Each segment has subsegments and parameters. Subsegments are separated with a plus ( + ) and parameters are separated by a colon ( : ). As we don’t need to understand all segments, subsegments and parameters for the invoice we are aiming to export fixed string with specific tags which are explained with examples and how they should look in the mapping below
Example file
UNB+UNOA:3+2243877030000:14+5023949000004:14+230216:1605+1'
UNH+INV000001+INVOIC:D:01B:UN:EAN011'
BGM+388+INV000001+9+NA'
DTM+137:20230216:102'
PAI+::42'
RFF+ON:444005'
DTM+171:20130612:102'
NAD+BY+5023949000004::9'
RFF+VA:GB232457280'
NAD+SU+2243877030000::9'
RFF+VA:GB835502930'
NAD+DP+5023949643131::9'
RFF+VA:GB232457280'
TAX+7+VAT+++:::20+S'
MOA+124:22.8'
CUX+2:GBP:4'
PAT+1++5:3:M:1'
LIN+1++5420019615968:SRV'
QTY+47:1'
MOA+203:114'
PRI+AAA:114:CA'
TAX+7+VAT+++:::20+S'
MOA+124:22.8'
UNS+S'
CNT+2:1'
MOA+86:136.8'
MOA+125:114'
MOA+176:22.8'
TAX+7+VAT+++:::20+S'
MOA+124:22.8'
UNT+30+INV000001'
UNZ+1+1’
File Mapping: Mapping is done based on standard forms of EDI handling. It is split in 3 sections Header/Body/Footer where expectation is values in Body can be replicated for each line to be added
INV Header mapping | INV Example Segment line | INV line with set values or mapping | Hemi Mapping | Hemi Notes |
---|---|---|---|---|
UNB | UNB+UNOA:3+2243877030000:14+5023949000004:14+230216:1605+1' | UNB+UNOA:3+{{sellerGln}}:14+{{buyerGln}}:14+{{dateTimeUNB}}+{{controlReference}}' | {{sellerGln}} - the GLN is a global identification number provided by organisations like GS1 (essentially a barcode for companies and their locations). These are used in different places in EDI messages and are basically constants but we can use them as placeholders for any future use or ease of change everywhere at the same time. The sellerGln is the one we want to pick from a field - Account John Lewis > Seller GLN (new field) |
{{buyerGln}} - the GLN is a global identification number provided by organisations like GS1 (essentially a barcode for companies and their locations). These are used in different places in EDI messages and are basically constants but we can use them as placeholders for any future use or ease of change everywhere at the same time. JL’s GLN is “5023949000004” {{dateTimeUNB}} is to be in format YYMMDD:HHMI and is to be the date and time of preparation of the message {{controlReference}} - up to 14 characters alphanumeric unique ID for the message generated by us | | UNH | UNH+INV000001+INVOIC:D:01B:UN:EAN011' | UNH+{{uniqueMessageId}}+INVOIC:D:01B:UN:EAN011' | | {{uniqueMessageId}} - up to 14 characters alphanumeric unique ID for the message generated by us (I believe it can be the same as {{controlReference}} )
This is essentially the first header segment or in other words the first segment of the actual message |
| BGM | BGM+388+INV000001+9+NA' | BGM+388+{{invoiceNumber}}+9+NA' | | {{invoiceNumber}} - this is the unique ID for each invoice. To achieve both uniqueness and general ease of customising it we want to pick the id of the Order Shipment we’ve picked to create the invoice and add a prefix from Account John Lewis > EDI Invoice Prefix
. In case such prefix is not present we are to just use the value from the ID. As mentioned above when successfully creating an Invoice with the specific number we should also store a record in the Invoice table with this value here being the {{invoiceNumber}} and status “normal”
BGM signifies the beginning of the message (or generally regarded as the first “header” segment in EDI) |
| DTM | DTM+137:202302161000:102' | DTM+137:{{dateTime137}}:203' | | {{dateTime137}} - The 137 DTM signifies the date time of the creation of the message. Pretty much the same as the {{dateTimeUNB}} above but it has to be in the format of YYYYMMDDHHMI |
| PAI | PAI+::42' | PAI+::42' | | Hardcoded |
| RFF | RFF+ON:444005' | RFF+ON:{{marketplaceOrderId}}' | Orders > Marketplace Order ID | {{marketplaceOrderId}} - straight up mapping |
| DTM | DTM+171:20130612:102' | DTM+171:{{orderCreatedTime}}:102' | Orders > Order Created Time | {{orderCreatedTime}} - straight up mapping
Should be in the format YYYYMMDD |
| NAD | NAD+BY+5023949000004::9' | NAD+BY+{{buyerGln}}::9' | | {{buyerGln}} - please see explanation above
Segment specifying address of parties by their respective GLNs (Global Location Number) - this is for the Buyer (in this case John Lewis as the MOR) |
| RFF | RFF+VA:GB232457280' | RFF+VA:{{buyerVat}}' | | {{buyerVat}} - the VAT registered number of the buyer in UK - this is John Lewis' number which is “GB232457280“ |
| NAD | NAD+DP+1200109897603::9' | NAD+DP+{{sellerGln}}::9' | | {{sellerGln}} - please see explanation above
Segment specifying address of parties by their respective GLNs (Global Location Number) - this is for the Delivery Party |
| RFF | RFF+VA:GB835502930' | RFF+VA:{{sellerVat}}' | Account John Lewis > Seller VAT Number | {{sellerVat}} - straight up mapping |
| NAD | NAD+SU+1200109897603::9' | NAD+SU+{{sellerGln}}::9' | | {{sellerGln}} - please see explanation above
Segment specifying address of parties by their respective GLNs (Global Location Number) - this is for the Supplier Party |
| RFF | RFF+VA:GB232457280' | RFF+VA:{{sellerVat}}' | Account John Lewis > Seller VAT Number | {{sellerVat}} - straight up mapping |
| TAX | TAX+7+VAT+++:::20+S' | TAX+7+VAT+++:::{{maxPercent}}+S' | | {{maxPercent}} - the biggest Product in Order > Marketplace VAT Percent
from all products selected for this invoice |
| MOA | MOA+124:22.8' | MOA+124:{{totalVat}}' | | The sum of all Product in Order > Marketplace VAT Item Price
for all products participating in the shipment. This includes multiplying by the correct quantity when there are more than one lines shipped from the same product in an order
Example: we have an order with 2 ordered products (SKU1 and SKU2) each x2 qty. In the shipment we’ve picked we have both lines for SKU1 and 1 line from SKU2. This means that we have to pick the Marketplace VAT Item Price
from SKU1 and multiply by 2 and pick the value for SKU2 and multiply only by 1 - sum the two results to get the totalVat |
| CUX | CUX+2:GBP:4' | CUX+2:GBP:4' | | Hardcoded |
| PAT | PAT+1++5:3:M:1' | PAT+1++5:3:{{periodType}}:{{numberOfPeriods}}' | | {{periodType}} - Account John Lewis > Invoice Payment Terms Period Type
(new field) - we want this to be an enum field with the following options:
• Day (D)
• Month (M)
• Working days (WD)
• Year (Y)
The user should see the label values where we should pass the value in brackets in the EDI
{{numberOfPeriods}} - Account John Lewis > Invoice Payment Terms Number of Periods
(new field) - free text field which is to be picked and sent as is - should be numeric value |
Again to confirm - header is to appear once in every message we generate
The Body fields are to be replicated for each Product (each Ordered product, not each Order Item Line in Hemi! Lines are to be combined per product and just to flag once again - only those that are part of the shipment should be selected) <v1.3> In a case where we have multiple shipment rows for the same SKU each one is to be exported as a separate body (LIN) repetition with all necessary fields and quantity for that specific row <v/1.3>
INV Body mapping | INV Example Segment line | INV line with set values or mapping | Hemi Mapping | Hemi Notes |
---|---|---|---|---|
LIN | LIN+1++5420019615968:SRV' | LIN+{{lineId}}++{{EAN}}:SRV' | Product Details > EAN | {{lineId}} this should simply signify a unique ID for each product we read from the Shipment from Hemi starting from 1 (again - if we have multiple lines shipped for the same product we are to combine these here as each LIN is per EAN) |
{{EAN}} - to be picked by a mapping from Product in Order > SKU with Product Details > SKU to get to the EAN to be used |
||||
QTY | QTY+47:1' | QTY+47:{{invoiceQuantity}}' | <v1.3>Order Shipment Row > Successfully shipped lines<v/1.3> | {{invoiceQuantity}} - as we are picking the values from the Shipment we need to track how many lines are shipped in that specific Shipment for that product and based on this sum the actual invoice quantity for this product for this invoice |
MOA | MOA+203:114' | MOA+203:{{productPrice}}' | Product in Order > Item Price | {{productPrice}} - is to be the combined price for all quantities for this Product that we’ve picked from the Shipment. In Hemi in this field we store the unit price (meaning per quantity) so once we know the {{invoiceQuantity}} for the product we need to multiply the Item Price by that to get to the {{productPrice}} |
PRI | PRI+AAA:114:CA' | PRI+AAA:{{netUnitPrice}}:CA' | {{netUnitPrice}} - this is the net unit price for a single quantity of the product (excluding vat). To achieve this we need to pick Product in Order > Item Price and subtract from that Product in Order > Marketplace Vat Item Price |
|
TAX | TAX+7+VAT+++:::20+S' | TAX+7+VAT+++:::{{itemVatPercent}}+S' | Product in Order > Marketplace VAT Percent | {{itemVatPercent}} - straight up mapping |
<v1.3> Validation should be made if the field is empty. If empty this should be treated as an error to be stored in Order Errors<v/1.3> | ||||
MOA | MOA+124:22.8' | MOA+124:{{productVat}}' | Product in Order > Marketplace VAT Item Price | {{productVat}} - is to be the combined VAT value for all the quantity for that product we’ve found from the selected Shipment. In other words (as this is already captured in {{invoiceQuantity}} ) we have to multiply {{invoiceQuantity}} by the mapped field here to get to the right value |
Each segment repeated from the Body should follow the same pattern - meaning if we have multiple Products that we are exporting we should repeat the following for each SKU: LIN’QTY’MOA’PRI’TAX’MOA'
INV Footer mapping | INV Example Segment line | INV line with set values or mapping | Hemi Mapping | Hemi Notes |
---|---|---|---|---|
UNS | UNS+S' | UNS+S' | Hardcoded | |
Beginner of the so called “Summary” section. As the summary and the footer (or trailer) are repeated only once they are both mapped here | ||||
CNT | CNT+2:1' | CNT+2:{{productCount}}' | {{productCount}} - represents the number of products we’ve exported. If all goes well it should be equal to the last LIN ID we’ve generated 🙂 | |
MOA | MOA+86:136.8' | MOA+86:{{totalAmount}}' | Orders > Order Total Amount | {{totalAmount}} - straight up mapping |
MOA | MOA+125:114' | MOA+125:{{totalNet}}' | {{totalNet}} - this should be the value of the {{totalAmount}} minus {{totalVat}} | |
MOA | MOA+176:22.8' | MOA+176:{{totalVat}}' | {{totalVat}} - please see explanation above | |
TAX | TAX+7+VAT+++:::20+S' | TAX+7+VAT+++:::{{maxPercent}}+S' | {{maxPercent}} - the biggest Product in Order > Marketplace VAT Percent from all products selected for this invoice |
|
MOA | MOA+124:22.8' | MOA+124:{{totalVat}}' | ||
UNT | UNT+30+INV000001' | UNT+{{segmentsCount}}+{{uniqueMessageId}}' | {{segmentsCount}} - a total count of all segments. Every segment, starting from UNH (essentially the header opener) to UNT included (the footer opener) needs to be counted. This means every repeated DTM or NAD or LIN, QTY, RFF etc needs to add a count (i++ 😍 ) to this so we have all segments from the actual message at the end |
|
{{uniqueMessageId}} - this needs to be exactly the same as the unique message ID in the UNH segment in the beginning of the message | ||||
UNZ | UNZ+1+1' | UNZ+1+{{controlReference}}' | {{controlReference}} - this is the same as in the UNB segment. It is used for “controlling” the message and making sure that the start and the end are the same | |
The UNZ segment is essentially the close of the envelope (hence it is not counted for in the UNT segment count control) |
So to sum it up - we have 2 triggers for an invoice generation - A Completed Shipment that has its days passed and doesn’t have a record John Lewis Invoice
section OR a record on a status Pending
Once an invoice has been picked for generation it should apply the above selection and validations and create a file with the afore mentioned naming