-
Notifications
You must be signed in to change notification settings - Fork 4
/
service.js
195 lines (175 loc) · 9.25 KB
/
service.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
const cds = require('@sap/cds');
module.exports = async (srv) =>
{
/**
* Delegate READ requests and function calls to the external S/4HANA services
* (for main entities and also assiciated entities for navigation support):
*/
const SalesOrdersSRV = await cds.connect.to("SalesOrderA2X");
const ProductSRV = await cds.connect.to("ProductMasterA2X");
const BasicAvailabilitySRV = await cds.connect.to("BasicProductAvailabilityInfo");
srv.on('READ', 'SalesOrder', req => SalesOrdersSRV.run(req.query));
// Implement read handler of MaterialPlant entity using delegation to the ProductSRV remote service
// and calculate material availability in each plant by triggering function from BasicAvailabilitySRV remote service:
function calcMaterialAvailabilityAt (material, plant, timestamp) {
const parameters = {
Material: material,
SupplyingPlant: plant,
RequestedUTCDateTime: timestamp,
ATPCheckingRule: "A" // See more information on ATP checking rules: https://help.sap.com/docs/SAP_S4HANA_ON-PREMISE/eedc1019283a438a8b73fdde490abc4f/2c24bf53d25ab64ce10000000a174cb4.html
};
return BasicAvailabilitySRV.send("DetermineAvailabilityAt", parameters);
}
function removeQueryColumns(columnsArray, name) {
const columnIndex = columnsArray.findIndex(
({ ref }) => ref && ref[0] === name
);
if (columnIndex >= 0) {
columnsArray.splice(columnIndex, 1);
return true;
}
return false;
}
function removeQueryColumnSelectAll(columnsArray) {
const columnIndex = columnsArray.findIndex(
(item) => item === "*"
);
if (columnIndex >= 0) {
columnsArray.splice(columnIndex, 1);
return true;
}
return false;
}
async function addAvailabilityData(plantItem, timestamp, selectAvailabilityColumns) {
try {
const availabilityRecord = await calcMaterialAvailabilityAt(plantItem.Material, plantItem.Plant, timestamp);
if (selectAvailabilityColumns.AvailableQuantity) {
plantItem.AvailableQuantity = availabilityRecord.AvailableQuantityInBaseUnit;
}
if (selectAvailabilityColumns.AvailabilityBaseUnit) {
plantItem.AvailableQuantityBaseUnit = availabilityRecord.BaseUnit;
}
}
catch (err) {
console.log(`Error during material availability check: ${err}`);
if (selectAvailabilityColumns.AvailableQuantity) {
plantItem.AvailableQuantity = 0;
}
if (selectAvailabilityColumns.AvailabilityBaseUnit) {
plantItem.AvailableQuantityBaseUnit = "N/A";
}
}
}
function handlePlantSelectedProperties(columnsArray) {
const selectAvailabilityColumns = {};
// If no columns specified (implicitly select all MaterialPlant properties) or '*'' column is defined,
// explicitly select all properties from ProductSRV.Plant entity
const allSelected = removeQueryColumnSelectAll(columnsArray);
const columnsSize = columnsArray.length;
if (columnsSize === 0 || allSelected) {
columnsArray.push({ref: ['Material']});
columnsArray.push({ref: ['Plant']});
columnsArray.push({ref: ['MRPType']});
columnsArray.push({ref: ['AvailabilityCheckType']});
selectAvailabilityColumns.AvailableQuantity = true;
selectAvailabilityColumns.AvailabilityBaseUnit = true;
}
// Otherwise - handle specifically selected properties by consumer
// Note: 'Material' and 'Plant' are automatically added to selected properties since they are entity keys
else {
// remove availability related columns from query as those are not provided from ProductSRV.Plant entity type
selectAvailabilityColumns.AvailableQuantity = removeQueryColumns(columnsArray, "AvailableQuantity");
selectAvailabilityColumns.AvailabilityBaseUnit = removeQueryColumns(columnsArray, "AvailableQuantityBaseUnit");
}
return selectAvailabilityColumns;
}
srv.on('READ', 'MaterialPlant', async req => {
// Handle reading MaterialPlant entity by selecting only its original properties from remote service
// and calculating availability properties separately
const select = req.query.SELECT;
if (!select.columns) {
select.columns = [];
}
const selectAvailabilityColumns = handlePlantSelectedProperties(select.columns);
const plantData = await ProductSRV.run(req.query);
if (selectAvailabilityColumns.AvailableQuantity || selectAvailabilityColumns.AvailabilityBaseUnit) {
// add availability calculated data for plant items
//const timestamp = '2023-04-19T06:59:59Z';
const timestamp = new Date().toISOString(); // use current time in EDM.DateTimeOffset format: 'YYYY-MM-DD`T`HH:MM:SS`Z`'
if (Array.isArray(plantData)) {
// read multiple entries result
const addAvailabilityPromises = [];
plantData.forEach(item => {
addAvailabilityPromises.push(
addAvailabilityData(item, timestamp, selectAvailabilityColumns)
);
});
await Promise.all(addAvailabilityPromises);
}
else {
// read single entry result
await addAvailabilityData(plantData, timestamp, selectAvailabilityColumns);
}
}
return plantData;
});
srv.on('READ', 'Material', async req => {
const select = req.query.SELECT;
if (select.columns) {
const toPlantExpandIndex = select.columns.findIndex(
({ ref }) => ref && ref[0] === "to_Plant"
);
if (toPlantExpandIndex >= 0) {
// Handle expand to_Plant by selecting only its original properties from remote service
// and calculating availability properties separately:
const toPlantExpandColumn = select.columns[toPlantExpandIndex];
const selectAvailabilityColumns = handlePlantSelectedProperties(toPlantExpandColumn.expand);
const materialData = await ProductSRV.run(req.query);
if (selectAvailabilityColumns.AvailableQuantity || selectAvailabilityColumns.AvailabilityBaseUnit) {
// add availability calculated data for expanded plant items
//const timestamp = '2023-04-25T06:59:59Z';
const timestamp = new Date().toISOString(); // use current time in EDM.DateTimeOffset format: 'YYYY-MM-DD`T`HH:MM:SS`Z`'
const addAvailabilityPromises = [];
if (Array.isArray(materialData)) {
// read multiple material entries result
materialData.forEach(materialItem => {
materialItem.to_Plant.forEach(plantItem => {
addAvailabilityPromises.push(
addAvailabilityData(plantItem, timestamp, selectAvailabilityColumns)
);
});
});
}
else {
// read single material entry result
materialData.to_Plant.forEach(plantItem => {
addAvailabilityPromises.push(
addAvailabilityData(plantItem, timestamp, selectAvailabilityColumns)
);
});
}
await Promise.all(addAvailabilityPromises);
}
return materialData;
}
}
return ProductSRV.run(req.query);
});
srv.on('READ', 'MaterialDescription', req => ProductSRV.run(req.query));
srv.on('READ', 'MaterialPlantMRPArea', req => ProductSRV.run(req.query));
//TODO: add navigation logic to 'product basic text' instead of description collection? (more informative text??)
// Implement function "calcMaterialAvailability" by triggering the CalculateAvailabilityTimeseries function from the basic availability info service
srv.on('calcMaterialAvailability', ({data:{material, plant}}) => {
const parameters = {
Material: material,
SupplyingPlant: plant,
ATPCheckingRule: "A" //hard-coded, we can think if should be a parameter
};
return BasicAvailabilitySRV.send("CalculateAvailabilityTimeseries", parameters);
});
// Implement function "calcMaterialAvailabilityAt" by triggering the DetermineAvailabilityAt function from the basic availability info service
// timestamp parameter is expected in EDM.DateTimeOffset format: 'YYYY-MM-DD`T`HH:MM:SS`Z`'
srv.on('calcMaterialAvailabilityAt', ({data:{material, plant, timestamp}}) => {
return calcMaterialAvailabilityAt(material, plant, timestamp);
});
}