import Field from "./Field.js";
import FieldMap from "./FieldMap.js";
import Loop from "./Loop.js";
import LoopMap from "./LoopMap.js";
import Segment from "./Segment.js";
/**
* @version 1.0.0
* @since 1.0.0
* @license MIT
* @see {@link https://github.com/mvogttech/node-x12-edi | Github}
* @see {@link https://www.npmjs.com/package/node-x12-edi | NPM}
* @class Transaction
* @description A class representing an EDI transaction
* @param {boolean} debug - Whether or not to enable debug mode
* @returns {Transaction}
* @example
* const transaction = new Transaction();
* transaction.generateSegments(file);
* const itemLoop = new Loop();
* itemLoop.setPosition(0);
* itemLoop.addSegmentIdentifiers(["W07", "N9", "W20"]);
* transaction.addLoop(itemLoop);
* transaction.runLoops();
* const mapLogic = {
* header: {
* transmissionDate: new FieldMap({
* segmentIdentifier: "GS",
* identifierValue: null,
* identifierPosition: null,
* valuePosition: 3,
* }),
* warehouseReceiptNumber: new FieldMap({
* segmentIdentifier: "W17",
* identifierValue: null,
* identifierPosition: null,
* valuePosition: 2,
* }),
* warehouse: {
* name: new FieldMap({
* segmentIdentifier: "N1",
* identifierValue: "WH",
* identifierPosition: 0,
* valuePosition: 1,
* }),
* code: new FieldMap({
* segmentIdentifier: "N1",
* identifierValue: "WH",
* identifierPosition: 0,
* valuePosition: 3,
* }),
* },
* },
* detail: {
* items: new LoopMap({
* position: 0,
* values: {
* itemCode: new FieldMap({
* segmentIdentifier: "W07",
* identifierValue: null,
* identifierPosition: null,
* valuePosition: 4,
* }),
* lotCode: new FieldMap({
* segmentIdentifier: "W07",
* identifierValue: null,
* identifierPosition: null,
* valuePosition: 7,
* }),
* productionDate: new FieldMap({
* segmentIdentifier: "N9",
* identifierValue: null,
* identifierPosition: null,
* valuePosition: 1,
* }),
* netWeight: new FieldMap({
* segmentIdentifier: "W20",
* identifierValue: null,
* identifierPosition: null,
* valuePosition: 3,
* }),
* },
* }),
* },
* };
* const mapped = transaction.mapSegments(mapLogic);
* console.log(mapped);
* // {
* // header: {
* // transmissionDate: "20210101",
* // warehouseReceiptNumber: "1234567890",
* // warehouse: {
* // name: "WAREHOUSE NAME",
* // code: "WAREHOUSE CODE",
* // },
* // },
* // detail: {
* // items: [
* // {
* // itemCode: "ITEM CODE",
* // lotCode: "LOT CODE",
* // productionDate: "20210101",
* // netWeight: "1000",
* // },
* // ],
* // },
* // }
*/
export default class Transaction {
constructor(debug = false) {
this.segments = [];
this.loops = [];
this.debug = debug;
}
/**
* @memberof Transaction
* @method toJSON
* @description Convert the transaction instance to a JSON object
* @returns {Object}
* @example
* const json = transaction.toJSON();
* console.log(json);
* // {
* // segments: [
* // {
* // name: 'ST',
* // fields: [
* // { content: '945', position: 0 },
* // { content: '0001', position: 1 },
* // ]
* // },
* // {
* // name: 'B4',
* // fields: [
* // { content: 'N', position: 0 },
* // { content: '1234567890', position: 1 },
* // { content: '20210101', position: 2 },
* // ]
* // },
* // ],
* // loops: [
* // {
* // position: 0,
* // segmentIdentifiers: [ 'W07', 'N9', 'W20' ],
* // contents: []
* // }
* // ]
* // }
*/
toJSON() {
return {
segments: this.segments.map((segment) => segment.toJSON()),
loops: this.loops.map((loop) => loop.toJSON()),
};
}
/**
* @memberof Transaction
* @method getType
* @description Get the transaction type
* @returns {string}
* @example
* const type = transaction.getType();
* console.log(type);
* // 945
* @throws {Error} No ST segment found
* @throws {Error} No ST02 segment found
*/
getType() {
this.debug && console.log("[Getting Transaction Type]");
const ST = this.segments.find((segment) => segment.name === "ST");
this.debug && console.log("ST", ST);
if (!ST) throw new Error("No ST segment found");
this.debug && console.log("[ST.getFields() result]", ST.getFields());
ST.trimFields();
const ST02 = ST.getFields()[0];
if (!ST02) throw new Error("No ST02 segment found");
this.debug && console.log("[ST02]", ST02);
return ST02;
}
/**
* @memberof Transaction
* @method getSegments
* @description Get all segments in the transaction instance
* @returns {Array.<Segment>}
* @example
* const segments = transaction.getSegments();
* console.log(segments);
* // [
* // {
* // name: 'ST',
* // fields: [
* // { content: '945', position: 0 },
* // { content: '0001', position: 1 },
* // ]
* // },
* // {
* // name: 'B4',
* // fields: [
* // { content: 'N', position: 0 },
* // { content: '1234567890', position: 1 },
* // { content: '20210101', position: 2 },
* // ]
* // },
* // ]
*/
getSegments() {
return this.segments;
}
/**
* @memberof Transaction
* @method listSegmentIdentifiers
* @description Get all segment identifiers in the transaction instance
* @returns {Array.<string>}
* @example
* const segmentIdentifiers = transaction.listSegmentIdentifiers();
* console.log(segmentIdentifiers);
* // [
* // 'ST', 'B4', 'N1', 'N3', 'N4', 'G61', 'N1', 'N3', 'N4', 'G61',
* // ]
*/
listSegmentIdentifiers() {
return this.segments.map((segment) => segment.name);
}
/**
* @memberof Transaction
* @method getLoops
* @description Get all loops in the transaction instance
* @returns {Array.<Loop>}
* @example
* const loops = transaction.getLoops();
* console.log(loops);
* // [
* // {
* // position: 0,
* // segmentIdentifiers: [ 'W07', 'N9', 'W20' ],
* // contents: [
* // [
* // {
* // name: 'W07',
* // fields: [
* // { content: '100', position: 0 },
* // { content: 'EA', position: 1 },
* // { content: 'ITEM CODE', position: 4 },
* // { content: '100', position: 5 },
* // { content: 'LB', position: 6 },
* // { content: 'LOT CODE', position: 7 },
* // ]
* // },
* // {
* // name: 'N9',
* // fields: [
* // { content: 'PD', position: 0 },
* // { content: '20210101', position: 1 },
* // ]
* // },
* // {
* // name: 'W20',
* // fields: [
* // { content: '1000', position: 0 },
* // { content: 'LB', position: 1 },
* // { content: '1000', position: 3 },
* // { content: 'LB', position: 4 },
* // ]
* // }
* // ]
* // ]
* // }
* // ]
*/
getLoops() {
return this.loops;
}
/**
* @memberof Transaction
* @method addLoop
* @description Add a loop to the transaction instance
* @param {Loop} loop - The loop to add to the transaction instance
* @returns {void}
* @example
* const loop = new Loop();
* loop.addSegmentIdentifiers(["W07", "N9", "W20"]);
* transaction.addLoop(loop);
* console.log(transaction.getLoops());
* // [
* // {
* // position: 0,
* // segmentIdentifiers: [ 'W07', 'N9', 'W20' ],
* // contents: []
* // }
* // ]
*/
addLoop(loop) {
this.loops.push(loop);
}
/**
* @memberof Transaction
* @method runLoops
* @description Run all loops in the transaction instance
* @returns {void}
*/
runLoops() {
this.loops.forEach((loop) => {
this.#runLoop(loop);
});
}
/**
* @access private
* @memberof Transaction
* @method #runLoop
* @description Run a loop
* @param {Loop} loop - The loop to run
* @returns {void}
*/
#runLoop(loop) {
let segments = this.getSegments();
this.debug && console.log("[Segments in Loop]", segments);
let loopSegments = [];
for (let segment of segments) {
this.debug && console.log("[Segment]", segment);
if (loop.getSegmentIdentifiers().includes(segment.name)) {
this.debug && console.log("[Segment Name]", segment.name);
if (segment.name === loop.getLastSegmentIdentifier()) {
this.debug && console.log("[Last segment identifier for loop]");
loopSegments.push(segment);
loop.contents.push(loopSegments);
loopSegments = [];
} else {
this.debug;
loopSegments.push(segment);
}
}
}
this.debug && console.log("[Loop]", loop);
this.debug && console.log("[Loop Contents]", loop.contents);
}
/**
* @memberof Transaction
* @method mapSegments
* @description Map segments to a JSON object
* @param {Object} mapLogic - The map logic to use to map segments and fields to a JSON object
* @param {Array.<Segment>} mapSegments - The segments to map to a JSON object (defaults to the segments in the transaction instance)
* @returns {Object}
* @example
* const mapLogic = {
* header: {
* sender: new FieldMap({
* segmentIdentifier: "N1",
* identifierValue: "SF",
* identifierPosition: 0,
* valuePosition: 1,
* }),
* receiver: new FieldMap({
* segmentIdentifier: "N1",
* identifierValue: "ST",
* identifierPosition: 0,
* valuePosition: 1,
* }),
* transmissionDate: new FieldMap({
* segmentIdentifier: "GS",
* identifierValue: null,
* identifierPosition: null,
* valuePosition: 3,
* }),
* warehouseReceiptNumber: new FieldMap({
* segmentIdentifier: "W17",
* identifierValue: null,
* identifierPosition: null,
* valuePosition: 2,
* }),
* warehouse: {
* name: new FieldMap({
* segmentIdentifier: "N1",
* identifierValue: "WH",
* identifierPosition: 0,
* valuePosition: 1,
* }),
* code: new FieldMap({
* segmentIdentifier: "N1",
* identifierValue: "WH",
* identifierPosition: 0,
* valuePosition: 3,
* }),
* },
* },
* detail: {
* items: new LoopMap({
* position: 0,
* values: {
* itemCode: new FieldMap({
* segmentIdentifier: "W07",
* identifierValue: null,
* identifierPosition: null,
* valuePosition: 4,
* }),
* lotCode: new FieldMap({
* segmentIdentifier: "W07",
* identifierValue: null,
* identifierPosition: null,
* valuePosition: 7,
* }),
* productionDate: new FieldMap({
* segmentIdentifier: "N9",
* identifierValue: null,
* identifierPosition: null,
* valuePosition: 1,
* }),
* netWeight: new FieldMap({
* segmentIdentifier: "W20",
* identifierValue: null,
* identifierPosition: null,
* valuePosition: 3,
* }),
* quantity: new FieldMap({
* segmentIdentifier: "W07",
* identifierValue: null,
* identifierPosition: null,
* valuePosition: 0,
* }),
* },
* }),
* },
* };
* const mapSegments = transaction.getSegments();
* const mapped = transaction.mapSegments(mapLogic, mapSegments);
* console.log(mapped);
* // {
* // header: {
* // sender: "SENDER",
* // receiver: "RECEIVER",
* // transmissionDate: "20210101",
* // warehouseReceiptNumber: "1234567890",
* // warehouse: {
* // name: "WAREHOUSE NAME",
* // code: "WAREHOUSE CODE",
* // },
* // },
* // detail: {
* // items: [
* // {
* // itemCode: "ITEM CODE",
* // lotCode: "LOT CODE",
* // productionDate: "20210101",
* // netWeight: "1000",
* // quantity: "100",
* // },
* // ],
* // },
* // }
*/
mapSegments(mapLogic, mapSegments = null) {
let result = {};
if (!mapSegments) {
mapSegments = this.getSegments();
}
Object.entries(mapLogic).forEach(([key, value]) => {
// FieldMap is where the magic happens
// The FieldMap object is used to map a field from a segment to a key in the result object
if (value instanceof FieldMap) {
const segment = mapSegments.find(
(segment) => segment.name === value.segmentIdentifier
);
if (!segment) {
this.debug &&
console.error("Segment not found", value.segmentIdentifier);
return;
}
if (value.identifierValue === null) {
if (segment.getFields()[value.valuePosition] === undefined) return;
return (result[key] =
segment.getFields()[value.valuePosition].content);
}
const isValid =
segment.getFields()[value.identifierPosition].content ===
value.identifierValue;
if (!isValid) {
this.debug &&
console.error(
"Invalid identifier value",
segment.getFields()[value.identifierPosition].content,
value.identifierValue
);
return;
}
const field = segment.getFields()[value.valuePosition];
if (!field) {
this.debug && console.error("Field not found", value.valuePosition);
return;
}
return (result[key] = field.content);
}
// LoopMap is used to map a loop to a key in the result object
if (value instanceof LoopMap) {
const loop = this.loops[value.position];
if (!loop) {
return;
}
result[key] = loop.contents.map((content) => {
return this.mapSegments(value.values, content);
});
return;
}
// Object is used to map an object to a key in the result object
if (value instanceof Object) {
result[key] = this.mapSegments(value, mapSegments);
return;
}
});
return result;
}
/**
* @private
* @memberof Transaction
* @method #addSegment
* @description Add a segment to the transaction instance
* @param {Segment} segment
* @returns {Transaction}
*/
#addSegment(segment) {
this.segments.push(segment);
return this;
}
/**
* @memberof Transaction
* @method removeSegment
* @description Remove a segment from the transaction instance
* @param {Segment} segment
* @returns {Transaction}
*/
removeSegment(segment) {
this.segments = this.segments.filter((s) => s !== segment);
return this;
}
/**
* @memberof Transaction
* @method inferLoops
* @description Infer loops from the transaction instance
* @returns {void}
*/
inferLoops() {
const segments = this.getSegments();
// Count the number of times a segment appears in the transaction
const segmentCounts = {};
segments.forEach((segment) => {
if (!segmentCounts[segment.name]) {
segmentCounts[segment.name] = 0;
}
segmentCounts[segment.name]++;
});
// Find the groups of segments that loop
const loopGroups = [];
Object.entries(segmentCounts).forEach(([segmentName, count]) => {
if (count > 1) {
const group = segments.filter(
(segment) => segment.name === segmentName
);
loopGroups.push(group);
}
});
const loopIdentifiers = loopGroups.map((group) => group[0].name);
const loop = new Loop();
loop.setPosition(0);
loop.addSegmentIdentifiers(loopIdentifiers);
this.addLoop(loop);
// Run the loops
this.runLoops();
}
/**
* @memberof Transaction
* @method generateSegments
* @description Generate segments for instance from a string
* @param {string} content
* @returns {void}
*/
generateSegments(content) {
const segments = content.split("\n");
segments.forEach((segment) => {
const fields = segment.split("*");
const segmentName = fields[0];
const segmentInstance = new Segment(segmentName);
fields.shift();
fields.forEach((field) => {
const fieldInstance = new Field(field);
fieldInstance.trim();
segmentInstance.addField(fieldInstance);
});
this.#addSegment(segmentInstance);
});
}
}