diff --git a/api/parameters.yaml b/api/parameters.yaml
index 3cbe49b..67ac778 100644
--- a/api/parameters.yaml
+++ b/api/parameters.yaml
@@ -6,6 +6,14 @@ Id:
type: string
example: 5ea0450ed851c30a90e70894
+Number:
+ name: number
+ in: path
+ required: true
+ schema:
+ type: string
+ example: Rng740
+
Name:
name: name
description: has to be URL encoded
@@ -15,10 +23,19 @@ Name:
type: string
State:
- name: group
+ name: state
description: 'possible values: new, deleted'
in: path
required: true
schema:
type: string
- example: deleted
\ No newline at end of file
+ example: deleted
+
+Collection:
+ name: collection
+ description: 'possible values: condition, measurement, material'
+ in: path
+ required: true
+ schema:
+ type: string
+ example: condition
\ No newline at end of file
diff --git a/api/sample.yaml b/api/sample.yaml
index 2b0ce31..82d6c7c 100644
--- a/api/sample.yaml
+++ b/api/sample.yaml
@@ -140,10 +140,10 @@
application/json:
schema:
$ref: 'api.yaml#/components/schemas/SampleDetail'
- 400:
- $ref: 'api.yaml#/components/responses/400'
401:
$ref: 'api.yaml#/components/responses/401'
+ 403:
+ $ref: 'api.yaml#/components/responses/403'
404:
$ref: 'api.yaml#/components/responses/404'
500:
@@ -201,6 +201,31 @@
500:
$ref: 'api.yaml#/components/responses/500'
+/sample/number/{number}:
+ parameters:
+ - $ref: 'api.yaml#/components/parameters/Number'
+ get:
+ summary: sample details
+ description: 'Auth: all, levels: read, write, maintain, dev, admin
Returns validated as well as new measurements'
+ x-doc: deleted samples are available only for maintain/admin
+ tags:
+ - /sample
+ responses:
+ 200:
+ description: samples details
+ content:
+ application/json:
+ schema:
+ $ref: 'api.yaml#/components/schemas/SampleDetail'
+ 401:
+ $ref: 'api.yaml#/components/responses/401'
+ 403:
+ $ref: 'api.yaml#/components/responses/403'
+ 404:
+ $ref: 'api.yaml#/components/responses/404'
+ 500:
+ $ref: 'api.yaml#/components/responses/500'
+
/sample/restore/{id}:
parameters:
- $ref: 'api.yaml#/components/parameters/Id'
diff --git a/api/schemas.yaml b/api/schemas.yaml
index 99f7998..1c844bb 100644
--- a/api/schemas.yaml
+++ b/api/schemas.yaml
@@ -92,7 +92,12 @@ SampleDetail:
sample_references:
type: array
items:
- $ref: 'api.yaml#/components/schemas/Id'
+ properties:
+ sample_id:
+ $ref: 'api.yaml#/components/schemas/Id'
+ relation:
+ type: string
+ example: part to this sample
measurements:
type: array
items:
@@ -115,25 +120,21 @@ Material:
group:
type: string
example: PA46
- mineral:
- type: number
- example: 0
- glass_fiber:
- type: number
- example: 40
- carbon_fiber:
- type: number
- example: 0
+ properties:
+ type: object
+ properties:
+ material_template:
+ $ref: 'api.yaml#/components/schemas/Id'
+ example:
+ material_template: 5ea0450ed851c30a90e70894
+ mineral: 0
+ glass_fiber: 40
+ carbon_fiber: 0
numbers:
type: array
items:
- type: object
- allOf:
- - $ref: 'api.yaml#/components/schemas/Color'
- properties:
- number:
- type: string
- example: 5514263423
+ type: string
+ example: 5514263423
Measurement:
allOf:
diff --git a/api/template.yaml b/api/template.yaml
index 4fa938d..6af1294 100644
--- a/api/template.yaml
+++ b/api/template.yaml
@@ -1,6 +1,8 @@
-/template/conditions:
+/template/{collection}s:
+ parameters:
+ - $ref: 'api.yaml#/components/parameters/Collection'
get:
- summary: all available condition methods
+ summary: all available templates
description: 'Auth: basic, levels: read, write, maintain, dev, admin'
tags:
- /template
@@ -8,7 +10,7 @@
- BasicAuth: []
responses:
200:
- description: list of conditions
+ description: list of templates
content:
application/json:
schema:
@@ -20,11 +22,12 @@
500:
$ref: 'api.yaml#/components/responses/500'
-/template/condition/{id}:
+/template/{collection}/{id}:
parameters:
+ - $ref: 'api.yaml#/components/parameters/Collection'
- $ref: 'api.yaml#/components/parameters/Id'
get:
- summary: condition method details
+ summary: template details
description: 'Auth: basic, levels: read, write, maintain, admin'
tags:
- /template
@@ -32,7 +35,7 @@
- BasicAuth: []
responses:
200:
- description: condition details
+ description: template details
content:
application/json:
schema:
@@ -44,7 +47,7 @@
500:
$ref: 'api.yaml#/components/responses/500'
put:
- summary: change condition method
+ summary: change template
description: 'Auth: basic, levels: maintain, admin'
x-doc: With a change a new version is set, resulting in a new template with a new id
tags:
@@ -59,7 +62,7 @@
$ref: 'api.yaml#/components/schemas/Template'
responses:
200:
- description: condition details
+ description: template details
content:
application/json:
schema:
@@ -75,116 +78,11 @@
500:
$ref: 'api.yaml#/components/responses/500'
-/template/condition/new:
- post:
- summary: add condition method
- description: 'Auth: basic, levels: maintain, admin'
- tags:
- - /template
- security:
- - BasicAuth: []
- requestBody:
- required: true
- content:
- application/json:
- schema:
- $ref: 'api.yaml#/components/schemas/Template'
- responses:
- 200:
- description: condition details
- content:
- application/json:
- schema:
- $ref: 'api.yaml#/components/schemas/Template'
- 400:
- $ref: 'api.yaml#/components/responses/400'
- 401:
- $ref: 'api.yaml#/components/responses/401'
- 403:
- $ref: 'api.yaml#/components/responses/403'
- 500:
- $ref: 'api.yaml#/components/responses/500'
-
-/template/measurements:
- get:
- summary: all available measurement methods
- description: 'Auth: basic, levels: read, write, maintain, dev, admin'
- tags:
- - /template
- security:
- - BasicAuth: []
- responses:
- 200:
- description: list of measurement methods
- content:
- application/json:
- schema:
- type: array
- items:
- $ref: 'api.yaml#/components/schemas/Template'
- 401:
- $ref: 'api.yaml#/components/responses/401'
- 500:
- $ref: 'api.yaml#/components/responses/500'
-/template/measurement/{id}:
+/template/{collection}/new:
parameters:
- - $ref: 'api.yaml#/components/parameters/Id'
- get:
- summary: measurement method details
- description: 'Auth: basic, levels: read, write, maintain, admin'
- tags:
- - /template
- security:
- - BasicAuth: []
- responses:
- 200:
- description: measurement details
- content:
- application/json:
- schema:
- $ref: 'api.yaml#/components/schemas/Template'
- 400:
- $ref: 'api.yaml#/components/responses/400'
- 401:
- $ref: 'api.yaml#/components/responses/401'
- 404:
- $ref: 'api.yaml#/components/responses/404'
- 500:
- $ref: 'api.yaml#/components/responses/500'
- put:
- summary: change measurement method
- description: 'Auth: basic, levels: maintain, admin'
- tags:
- - /template
- security:
- - BasicAuth: []
- requestBody:
- required: true
- content:
- application/json:
- schema:
- $ref: 'api.yaml#/components/schemas/Template'
- responses:
- 200:
- description: measurement details
- content:
- application/json:
- schema:
- $ref: 'api.yaml#/components/schemas/Template'
- 400:
- $ref: 'api.yaml#/components/responses/400'
- 401:
- $ref: 'api.yaml#/components/responses/401'
- 403:
- $ref: 'api.yaml#/components/responses/403'
- 404:
- $ref: 'api.yaml#/components/responses/404'
- 500:
- $ref: 'api.yaml#/components/responses/500'
-
-/template/measurement/new:
+ - $ref: 'api.yaml#/components/parameters/Collection'
post:
- summary: add measurement method
+ summary: add template
description: 'Auth: basic, levels: maintain, admin'
tags:
- /template
@@ -198,7 +96,7 @@
$ref: 'api.yaml#/components/schemas/Template'
responses:
200:
- description: measurement details
+ description: template details
content:
application/json:
schema:
@@ -210,4 +108,4 @@
403:
$ref: 'api.yaml#/components/responses/403'
500:
- $ref: 'api.yaml#/components/responses/500'
\ No newline at end of file
+ $ref: 'api.yaml#/components/responses/500'
diff --git a/data_import/import.js b/data_import/import.js
index dc8c8d8..8fac949 100644
--- a/data_import/import.js
+++ b/data_import/import.js
@@ -5,75 +5,131 @@ const {Builder} = require('selenium-webdriver'); // selenium and the chrome d
const chrome = require('selenium-webdriver/chrome');
const pdfReader = require('pdfreader');
const iconv = require('iconv-lite');
+const _ = require('lodash');
-const metaDoc = 'C:\\Users\\vle2fe\\Documents\\Data\\Rng_200707\\metadata.csv'; // metadata files
-const kfDoc = 'C:\\Users\\vle2fe\\Documents\\Data\\Rng_200707\\kf.csv';
-const vzDoc = 'C:\\Users\\vle2fe\\Documents\\Data\\Rng_200707\\vz.csv';
-const nmDocs = 'C:\\Users\\vle2fe\\Documents\\Data\\Rng_200707\\nmDocs'; // NormMaster Documents
-const dptFiles = 'C:\\Users\\vle2fe\\Documents\\Data\\Rng_200707\\DPT'; // Spectrum files
+const stages = {
+ materials: true,
+ samples: true,
+ dpt: true
+}
+
+const docs = [
+ "C:\\Users\\vle2fe\\Documents\\Data\\All_200717\\Metadata__AnP2.csv",
+ "C:\\Users\\vle2fe\\Documents\\Data\\All_200717\\Metadata__AnP2_A.csv",
+ "C:\\Users\\vle2fe\\Documents\\Data\\All_200717\\Metadata__AnP2_B.csv",
+ "C:\\Users\\vle2fe\\Documents\\Data\\All_200717\\Metadata_Ap.csv",
+ "C:\\Users\\vle2fe\\Documents\\Data\\All_200717\\Metadata_Bj.csv",
+ "C:\\Users\\vle2fe\\Documents\\Data\\All_200717\\Metadata_Eh.csv",
+ "C:\\Users\\vle2fe\\Documents\\Data\\All_200717\\Metadata_Eh_B.csv",
+ "C:\\Users\\vle2fe\\Documents\\Data\\All_200717\\Metadata_Eh_Duroplasten.csv",
+ "C:\\Users\\vle2fe\\Documents\\Data\\All_200717\\Metadata_Rng_aktuell.csv",
+ "C:\\Users\\vle2fe\\Documents\\Data\\All_200717\\Metadata_Rng_aktuell_A.csv",
+ "C:\\Users\\vle2fe\\Documents\\Data\\All_200717\\Metadata_Rng_aktuell_B.csv",
+ "C:\\Users\\vle2fe\\Documents\\Data\\All_200717\\Metadata_WaP.csv",
+];
+const errors = [];
+const nmDocs = 'C:\\Users\\vle2fe\\Documents\\Data\\All_200717\\nmDocs'; // NormMaster Documents
+const dptFiles = 'C:\\Users\\vle2fe\\Documents\\Data\\All_200717\\DPT'; // Spectrum files
const host = 'http://localhost:3000';
// const host = 'https://definma-api.apps.de1.bosch-iot-cloud.com';
+const requiredProperties = ['samplenumber','materialnumber','materialname','supplier','reinforcementmaterial','material','granulate/part','color','charge/batch','comments'];
+dict = { // dictionary
+ 'Granulat': 'granulate',
+ 'Zugstab': 'tension rod',
+ 'Stecker': 'plug'
+};
let data = []; // metadata contents
let materials = {};
+let numberToColor = {};
let samples = [];
let normMaster = {};
let sampleDevices = {};
+const sampleReferences = []; // references to other samples in format {sample, referencedSample, relation}
+let commentsLog = [];
+let customFieldsLog = [];
+const vzValues = {}; // vz values from comments
+const dptLog = [];
-// TODO: BASF twice, BASF as color
-// TODO: duplicate kf values
// TODO: conditions
-// TODO: comment and reference handling
-
-
-// TODO: check last color errors (filter out already taken) use location and device for user, upload to BIC
main();
async function main() {
- if (0) { // materials
+ if (stages.materials) { // materials
await getNormMaster();
- await importCsv(metaDoc);
- await allMaterials();
- await saveMaterials();
- await importCsv(kfDoc);
- await allMaterials();
- await saveMaterials();
- await importCsv(vzDoc);
- await allMaterials();
- await saveMaterials();
+ for (let i in docs) {
+ await importCsv(docs[i]);
+ await allMaterials();
+ await saveMaterials();
+ }
+ fs.writeFileSync('./data_import/numberToColor.json', JSON.stringify(numberToColor));
}
- if (0) { // samples
+ if (stages.samples) { // samples
sampleDeviceMap();
- if (1) {
- console.log('-------- META ----------');
- await importCsv(metaDoc);
- await allSamples();
- await saveSamples();
- }
- if (1) {
- console.log('-------- KF ----------');
- await importCsv(kfDoc);
- await allSamples();
- await saveSamples();
- await allKfVz();
- }
- if (1) {
- console.log('-------- VZ ----------');
- await importCsv(vzDoc);
+ numberToColor = JSON.parse(fs.readFileSync('./data_import/numberToColor.json'), 'utf-8');
+ for (let i in docs) {
+ await importCsv(docs[i]);
await allSamples();
await saveSamples();
await allKfVz();
}
+ // write logs
+ fs.writeFileSync('./data_import/comments.txt', commentsLog.join('\r\n'));
+ fs.writeFileSync('./data_import/customFields.txt', customFieldsLog.join('\r\n'));
+ fs.writeFileSync('./data_import/sampleReferences.txt', sampleReferences.map(e => JSON.stringify(e)).join('\r\n'));
+ fs.writeFileSync('./data_import/sampleReferences.json', JSON.stringify(sampleReferences));
+
+ await sampleReferencesSave();
}
- if (1) { // DPT
+ if (stages.dpt) { // DPT
await allDpts();
+ fs.writeFileSync('./data_import/sdptLog.txt', dptLog.join('\r\n'));
}
if (0) { // pdf test
- console.log(await readPdf('N28_BN05-OX013_2016-03-11.pdf'));
+ console.log(await readPdf('N28_BN05-OX023_2019-07-16.pdf'));
+ }
+ if (errors.length) {
+ // console.log(errors);
+ fs.writeFileSync('./data_import/errors/errors_' + new Date().getTime() + '.txt', errors.join('\r\n'));
}
}
async function importCsv(doc) {
+ // Uniform name samplenumber materialnumber materialname supplier material plastic reinforcingmaterial granulate/part color charge/batch comments vz(ml/g) kfingew% degradation(%) glassfibrecontent(%) stabwn
+ // Metadata__AnP2.csv Sample number,Material number,Material name,Supplier,Material,Plastic,Reinforcing material, granulate/Part,Color,Charge/ Batch, Comments
+ // Metadata__AnP2_A.csv Sample number,Material number,Material name,Supplier, Plastic,Reinforcing material, Granulate/Part, Comments, Humidity [ppm]
+ // Metadata__AnP2_B.csv Sample number,Material number,Material name,Supplier, Plastic,Reinforcing material, Granulate/Part, VZ [ml/g], glass fibre content
+ // Metadata_Ap.csv Sample number,Material number,Material name,Supplier, Plastic,Reinforcing material, Granulate/Part,Color,Charge/Batch, Comments
+ // Metadata_Bj.csv Sample number,Material number,Material name,Supplier,Material,Plastic,Reinforcing material, Granulate/Part,Color,Charge/batch granulate/part,Comments
+ // Metadata_Eh.csv Sample number,Material number,Material name,Supplier,Material, Reinforcing material, Granulate/Part,Color,Charge/Batch granulate/part,Comments, VZ [cm³/g], Spalte1
+ // Metadata_Eh_B.csv Sample number, Material name,Supplier, Plastic,Reinforcing material, Granulate/Part,Color, Comments, VZ [cm³/g]
+ // Metadata_Eh_Duroplasten.csv Sample number,Material number,Material name,Supplier,Material, Reinforcing material, Granulate/Part,Color,Charge/Batch granulate/part,Comments
+ // Metadata_Rng_aktuell.csv Sample number,Material number,Material name,Supplier,Material,Plastic,Reinforcing material, Granulate/Part,Color,Charge/batch granulate/part,Comments, VZ (ml/g), Degradation(%),Glas fibre content (%)
+ // Metadata_Rng_aktuell_A.csv Sample number,Material number,Material name,Supplier,Material,Plastic,Reinforcing material, Granulate/Part,Farbe,Charge/batch granulate/part,Comments, KF in Gew%, Stabwn
+ // Metadata_Rng_aktuell_B.csv Sample number, Material name,Supplier, Plastic,Reinforcing material (content in %),Granulate/Part, Comments, VZ (ml/g), Degradation (%), Alterungszeit in h
+ // Metadata_WaP.csv Probennummer, Name, Firma, Material, Teil/Rohstoff, Charge, Anmerkung,VZ (ml/g), Abbau (%), Verstärkungsstoffgehalt (%), Versuchsnummer
+ const nameCorrection = { // map to right column names
+ 'probennummer': 'samplenumber',
+ 'name': 'materialname',
+ 'firma': 'supplier',
+ 'reinforcingmaterial(contentin%)': 'reinforcingmaterial',
+ 'teil/rohstoff': 'granulate/part',
+ 'charge/batchgranulate/part': 'charge/batch',
+ 'charge': 'charge/batch',
+ 'anmerkung': 'comments',
+ 'vz[ml/g]': 'vz(ml/g)',
+ 'vz[cm³/g]': 'vz(ml/g)',
+ 'abbau(%)': 'degradation(%)',
+ 'verstärkungsstoffgehalt(%)': 'glassfibrecontent(%)'
+ };
+ const missingFieldsFill = [ // column names to fill if they do not exist
+ 'color',
+ 'charge/batch',
+ 'comments',
+ 'materialnumber',
+ 'reinforcementmaterial'
+ ]
+ console.log('importing ' + doc);
data = [];
await new Promise(resolve => {
fs.createReadStream(doc)
@@ -83,10 +139,47 @@ async function importCsv(doc) {
data.push(row);
})
.on('end', () => {
+ data = data.map(e => {
+ const newE = {};
+ Object.keys(e).forEach(key => {
+ newE[key.toLowerCase().replace(/ /g, '')] = e[key];
+ });
+ // replace wrong column names
+ Object.keys(newE).forEach(key => {
+ if (nameCorrection.hasOwnProperty(key)) {
+ newE[nameCorrection[key]] = newE[key];
+ delete newE[key];
+ }
+ });
+
+ // add missing fields with empty values
+ missingFieldsFill.forEach(field => {
+ if (!newE.hasOwnProperty(field)) {
+ newE[field] = '';
+ }
+ });
+ if (newE['supplier'] === '') { // empty supplier fields
+ newE['supplier'] = 'unknown';
+ }
+ if (!newE.hasOwnProperty('material')) {
+ newE['material'] = newE['plastic'].indexOf(' GF') >= 0 ? newE['plastic'].split(' ')[0] : newE['plastic'];
+ }
+ return newE;
+ }).filter(e => {
+ const missingProperties = requiredProperties.filter(el => !e.hasOwnProperty(el));
+ if (e['materialname'] === '') {
+ missingProperties.push('materialname');
+ }
+ if (e['samplenumber'] === '') { // empty row
+ return false;
+ }
+ else if (missingProperties.length > 0) { // incomplete sample
+ errors.push(`${doc}: ${JSON.stringify(e)}is missing the required properties ${missingProperties}`);
+ return false;
+ }
+ return true;
+ });
console.info('CSV file successfully processed');
- if (data[0]['Farbe']) { // fix German column names
- data.map(e => {e['Color'] = e['Farbe']; return e; });
- }
resolve();
});
});
@@ -114,18 +207,20 @@ async function allDpts() {
res.data.forEach(sample => {
sampleIds[sample.number] = sample._id;
});
- const dptRegex = /.*?_(.*?)_(\d+|\d+_\d+).DPT/;
+ const dptRegex = /(.*?)_(.*?)_(\d+|\d+_\d+).DPT/;
const dpts = fs.readdirSync(dptFiles);
for (let i in dpts) {
const regexRes = dptRegex.exec(dpts[i])
- if (regexRes && sampleIds[regexRes[1]]) { // found matching sample
- console.log(dpts[i]);
+ if (regexRes && sampleIds[regexRes[2]]) { // found matching sample
+ console.log(`${dpts[i]} -> ${regexRes[2]}`);
+ dptLog.push(`${dpts[i]}, ${regexRes[2]}`);
const f = fs.readFileSync(dptFiles + '\\' + dpts[i], 'utf-8');
const data = {
- sample_id: sampleIds[regexRes[1]],
+ sample_id: sampleIds[regexRes[2]],
values: {},
measurement_template
};
+ data.values.device = regexRes[1];
data.values.dpt = f.split('\r\n').map(e => e.split(','));
let rescale = false;
for (let i in data.values.dpt) {
@@ -147,11 +242,19 @@ async function allDpts() {
data
}).catch(err => {
console.log(dpts[i]);
- console.error(err.response.data);
+ if (err.response) {
+ console.error(err.response.data);
+ errors.push(`Could not upload ${dpts[i]} for sample ${regexRes[2]}: ${err.response.data}`);
+ }
+ else {
+ console.error(err);
+ errors.push(`Could not upload ${dpts[i]} for sample ${regexRes[2]}: ${JSON.stringify(err)}`);
+ }
});
}
else {
- console.log(`Could not find sample for ${dpts[i]} !!!!!!`);
+ console.log(`Could not find sample for ${dpts[i]}`);
+ errors.push(`Could not find sample for ${dpts[i]}`);
}
}
}
@@ -180,54 +283,57 @@ async function allKfVz() {
sampleIds[sample.number] = sample._id;
});
for (let index in data) {
- console.info(`${index}/${data.length}`);
+ console.info(`KF/VZ ${index}/${data.length}`);
let sample = data[index];
- if (sample['Sample number'] !== '') {
- let credentials = ['admin', 'Abc123!#'];
- if (sampleDevices[sample['Sample number']]) {
- credentials = [sampleDevices[sample['Sample number']], '2020DeFinMachen!']
- }
- if (sample['KF in Gew%']) {
- await axios({
- method: 'post',
- url: host + '/measurement/new',
- auth: {
- username: credentials[0],
- password: credentials[1]
- },
- data: {
- sample_id: sampleIds[sample['Sample number']],
- measurement_template: kf_template,
- values: {
- 'weight %': sample['KF in Gew%'],
- 'standard deviation': sample['Stabwn']
- }
+ let credentials = ['admin', 'Abc123!#'];
+ if (sampleDevices[sample['samplenumber']]) {
+ credentials = [sampleDevices[sample['samplenumber']], '2020DeFinMachen!']
+ }
+ if (!sample['vz(ml/g)'] && vzValues[sample['samplenumber']]) { // fill in VZ values from comments
+ sample['vz(ml/g)'] = vzValues[sample['samplenumber']];
+ }
+ if (sample['kfingew%']) {
+ await axios({
+ method: 'post',
+ url: host + '/measurement/new',
+ auth: {
+ username: credentials[0],
+ password: credentials[1]
+ },
+ data: {
+ sample_id: sampleIds[sample['samplenumber']],
+ measurement_template: kf_template,
+ values: {
+ 'weight %': sample['kfingew%'],
+ 'standard deviation': sample['stabwn']
}
- }).catch(err => {
- console.log(sample['Sample number']);
- console.error(err.response.data);
- });
- }
- if (sample['VZ (ml/g)']) {
- await axios({
- method: 'post',
- url: host + '/measurement/new',
- auth: {
- username: credentials[0],
- password: credentials[1]
- },
- data: {
- sample_id: sampleIds[sample['Sample number']],
- measurement_template: vz_template,
- values: {
- vz: sample['VZ (ml/g)']
- }
+ }
+ }).catch(err => {
+ console.log(sample['samplenumber']);
+ console.error(err.response.data);
+ errors.push(`KF/VZ upload for ${JSON.stringify(sample)} failed: ${JSON.stringify(err.response.data)}`);
+ });
+ }
+ if (sample['VZ (ml/g)']) {
+ await axios({
+ method: 'post',
+ url: host + '/measurement/new',
+ auth: {
+ username: credentials[0],
+ password: credentials[1]
+ },
+ data: {
+ sample_id: sampleIds[sample['samplenumber']],
+ measurement_template: vz_template,
+ values: {
+ vz: sample['vz(ml/g)']
}
- }).catch(err => {
- console.log(sample['Sample number']);
- console.error(err.response.data);
- });
- }
+ }
+ }).catch(err => {
+ console.log(sample['samplenumber']);
+ console.error(err.response.data);
+ errors.push(`KF/VZ upload for ${JSON.stringify(sample)} failed: ${JSON.stringify(err.response.data)}`);
+ });
}
}
}
@@ -244,6 +350,7 @@ async function allSamples() {
});
const dbMaterials = {}
res.data.forEach(m => {
+ m.numbers = m.numbers.map(e => ({number: e, color: numberToColor[e]}));
dbMaterials[m.name] = m;
})
res = await axios({
@@ -261,59 +368,71 @@ async function allSamples() {
for (let index in data) {
- console.info(`${index}/${data.length}`);
+ console.info(`SAMPLE LOAD ${index}/${data.length}`);
let sample = data[index];
- if (sample['Sample number'] !== '') { // TODO: what about samples without color
- if (sample['Supplier'] === '') { // empty supplier fields
- sample['Supplier'] = 'unknown';
+ if (sample['granulate/Part'] === '') { // empty supplier fields
+ sample['granulate/Part'] = 'unknown';
+ }
+ const material = dbMaterials[trim(sample['materialname'])];
+ if (!material) { // could not find material, skipping sample
+ errors.push(`Could not find a material for ${JSON.stringify(sample)}`);
+ continue;
+ }
+ samples.push({
+ number: sample['samplenumber'],
+ type: sampleType(sample['granulate/part']),
+ batch: sample['charge/batch'],
+ material_id: material._id,
+ notes: {
+ custom_fields: customFields(sample['comments'], sample['samplenumber'])
}
- if (sample['Granulate/Part'] === '') { // empty supplier fields
- sample['Granulate/Part'] = 'unknown';
- }
- const material = dbMaterials[trim(sample['Material name'])];
- if (!material) { // could not find material, skipping sample
- continue;
- }
- console.log(sample['Material name']);
- console.log(material._id);
- samples.push({
- number: sample['Sample number'],
- type: sample['Granulate/Part'],
- batch: sample['Charge/batch granulate/part'] || '',
- material_id: material._id,
- notes: {
- comment: sample['Comments']
+ });
+ // if (sample['comments']) {
+ // comments.push(sample['samplenumber'] + ' ' + sample['comments']);
+ // }
+ const si = samples.length - 1; // sample index
+ if (samples[si].notes.custom_fields.hasOwnProperty('xRest')) { // reroute xRest property to comment
+ samples[si].notes.comment = samples[si].notes.custom_fields.xRest;
+ commentsLog.push(sample['samplenumber'] + ' ' + samples[si].notes.comment);
+ delete samples[si].notes.custom_fields.xRest;
+ }
+ if (Object.keys(samples[si].notes.custom_fields).length === 0) { // delete empty custom fields
+ delete samples[si].notes.custom_fields;
+ }
+ else {
+ customFieldsLog.push(sample['samplenumber'] + ' ' + JSON.stringify(samples[si].notes.custom_fields));
+ }
+ if (sample['materialnumber'] !== '' && material.numbers.find(e => e.number === sample['materialnumber'])) {
+ samples[si].color = material.numbers.find(e => e.number === sample['materialnumber']).color;
+ }
+ else if (sample['color'] !== '') { // find color with all edge cases
+ let number = material.numbers.find(e => e.color && e.color.indexOf(trim(sample['color'])) >= 0);
+ if (!number && /black/.test(sample['color'])) { // special case bk for black
+ console.log(material);
+ number = material.numbers.find(e => e.color && e.color.toLowerCase().indexOf('bk') >= 0);
+ if (!number) { // try German word
+ number = material.numbers.find(e => e.color && e.color.toLowerCase().indexOf('schwarz') >= 0);
}
- });
- const si = samples.length - 1;
- if (sample['Material number'] !== '' && material.numbers.find(e => e.number === sample['Material number'])) { // TODO: fix because of false material/material number
- samples[si].color = material.numbers.find(e => e.number === sample['Material number']).color;
}
- else if (sample['Color'] && sample['Color'] !== '') {
- let number = material.numbers.find(e => e.color.indexOf(trim(sample['Color'])) >= 0);
- if (!number && /black/.test(sample['Color'])) { // special case bk for black
- number = material.numbers.find(e => e.color.toLowerCase().indexOf('bk') >= 0);
- if (!number) { // try German word
- number = material.numbers.find(e => e.color.toLowerCase().indexOf('schwarz') >= 0);
- }
- }
+ if (number) {
samples[si].color = number.color;
}
- else if (sampleColors[sample['Sample number'].split('_')[0]]) { // derive color from main sample for kf/vz
- samples[si].color = sampleColors[sample['Sample number'].split('_')[0]];
- }
- else {
- samples[si].color = '';
- }
+ }
+ else if (sampleColors[sample['samplenumber'].split('_')[0]]) { // derive color from main sample for kf/vz
+ samples[si].color = sampleColors[sample['samplenumber'].split('_')[0]];
+ }
+ if (!samples[si].color) {
+ samples[si].color = sample['color'];
}
}
}
async function saveSamples() {
for (let i in samples) {
- console.info(`${i}/${samples.length}`);
+ console.info(`SAMPLE SAVE ${i}/${samples.length}`);
let credentials = ['admin', 'Abc123!#'];
if (sampleDevices[samples[i].number]) {
+ console.log(sampleDevices[samples[i].number]);
credentials = [sampleDevices[samples[i].number], '2020DeFinMachen!']
}
await axios({
@@ -328,68 +447,140 @@ async function saveSamples() {
if (err.response.data.status && err.response.data.status !== 'Sample number already taken') {
console.log(samples[i]);
console.error(err.response.data);
+ errors.push(`Upload for ${JSON.stringify(samples[i])} failed: ${JSON.stringify(err.response.data)}`);
}
});
}
console.info('saved all samples');
}
+async function sampleReferencesSave() {
+ for (let i in sampleReferences) {
+ console.info(`SAMPLE REFERENCES ${i}/${sampleReferences.length}`);
+ let refRes = await axios({
+ method: 'get',
+ url: host + '/sample/number/' + sampleReferences[i].referencedSample,
+ auth: {
+ username: 'admin',
+ password: 'Abc123!#'
+ }
+ }).catch(err => {
+ console.log(sampleReferences[i].referencedSample);
+ console.error(err.response.data);
+ errors.push(`Getting reference id for ${JSON.stringify(sampleReferences[i])} failed: ${JSON.stringify(err.response.data)}`);
+ });
+ if (!refRes) continue;
+ let sampleRes = await axios({
+ method: 'get',
+ url: host + '/sample/number/' + sampleReferences[i].sample,
+ auth: {
+ username: 'admin',
+ password: 'Abc123!#'
+ }
+ }).catch(err => {
+ console.log(sampleReferences[i].sample);
+ console.error(err.response.data);
+ errors.push(`Getting sample id for ${JSON.stringify(sampleReferences[i])} failed: ${JSON.stringify(err.response.data)}`);
+ });
+ if (!sampleRes) continue;
+ sampleRes.data.notes.sample_references.push({sample_id: refRes.data._id, relation: sampleReferences[i].relation})
+ await axios({
+ method: 'put',
+ url: host + '/sample/' + sampleRes.data._id,
+ auth: {
+ username: 'admin',
+ password: 'Abc123!#'
+ },
+ data: {notes: {sample_references: sampleRes.data.notes.sample_references}}
+ }).catch(err => {
+ console.log(sampleRes.data.notes.sample_references);
+ if (err.response.data) {
+ console.error(err.response.data);
+ errors.push(`Saving references for ${JSON.stringify(sampleRes.data)} failed: ${JSON.stringify(err.response.data)}`);
+ }
+ else {
+ console.error(err.response);
+ errors.push(`Saving references for ${JSON.stringify(sampleRes.data)} failed: ${JSON.stringify(err.response)}`);
+ }
+ });
+ }
+}
+
async function allMaterials() {
- materials = {};
+ // materials = {};
+ let res = await axios({
+ method: 'get',
+ url: host + '/template/materials',
+ auth: {
+ username: 'admin',
+ password: 'Abc123!#'
+ }
+ });
+ const materialTemplate = res.data.find(e => e.name === 'plastic')._id;
+
+ // process all samples
for (let index in data) {
let sample = data[index];
- if (sample['Sample number'] && sample['Sample number'] !== '') {
- if (sample['Supplier'] === '') { // empty supplier fields
- sample['Supplier'] = 'unknown';
- }
- if (sample['Material name'] === '') { // empty name fields
- sample['Material name'] = sample['Material'];
- }
- if (!sample['Material']) { // column Material is named Plastic in VZ metadata
- sample['Material'] = sample['Plastic'];
- }
- sample['Material name'] = trim(sample['Material name']);
- if (materials.hasOwnProperty(sample['Material name'])) { // material already found at least once
- if (sample['Material number'] && sample['Material number'] !== '') {
- if (materials[sample['Material name']].numbers.length === 0 || !materials[sample['Material name']].numbers.find(e => e.number === stripSpaces(sample['Material number']))) { // new material number
- if (materials[sample['Material name']].numbers.find(e => e.color === sample['Color'] && e.number === '')) { // color already in list, only number missing
- materials[sample['Material name']].numbers.find(e => e.color === sample['Color'] && e.number === '').number = stripSpaces(sample['Material number']);
- }
- else {
- materials[sample['Material name']].numbers.push({color: trim(sample['Color']), number: stripSpaces(sample['Material number'])});
- }
+ if (sample['supplier'] === '') { // empty supplier fields
+ sample['supplier'] = 'unknown';
+ }
+ if (sample['materialname'] === '') { // empty name fields
+ sample['materialname'] = sample['material'];
+ }
+ sample['materialname'] = trim(sample['materialname']);
+ if (materials.hasOwnProperty(sample['materialname'])) { // material already found at least once
+ if (sample['materialnumber'] !== '') { // material number given
+ if (materials[sample['materialname']].numbers.length === 0 || !materials[sample['materialname']].numbers.find(e => e.number === stripSpaces(sample['materialnumber']))) { // new material number
+ if (materials[sample['materialname']].numbers.find(e => e.color === sample['color'] && e.number === '')) { // color already in list, only number missing
+ materials[sample['materialname']].numbers.find(e => e.color === sample['color'] && e.number === '').number = stripSpaces(sample['materialnumber']);
}
- }
- else if (sample['Color'] && sample['Color'] !== '') {
- if (!materials[sample['Material name']].numbers.find(e => e.color === stripSpaces(sample['Color']))) { // new material color
- materials[sample['Material name']].numbers.push({color: trim(sample['Color']), number: ''});
+ else { // completely new number entry
+ materials[sample['materialname']].numbers.push({color: trim(sample['color']), number: stripSpaces(sample['materialnumber'])});
}
}
}
- else { // new material
- console.info(`${index}/${data.length} ${sample['Material name']}`);
- materials[sample['Material name']] = {
- name: sample['Material name'],
- supplier: trim(sample['Supplier']),
- group: trim(sample['Material'])
- };
- let tmp = /M(\d+)/.exec(sample['Reinforcing material']);
- materials[sample['Material name']].mineral = tmp ? tmp[1] : 0;
- tmp = /GF(\d+)/.exec(sample['Reinforcing material']);
- materials[sample['Material name']].glass_fiber = tmp ? tmp[1] : 0;
- tmp = /CF(\d+)/.exec(sample['Reinforcing material']);
- materials[sample['Material name']].carbon_fiber = tmp ? tmp[1] : 0;
- materials[sample['Material name']].numbers = await numbersFetch(sample);
- console.log(materials[sample['Material name']]);
+ else if (sample['color'] !== '') { // color given
+ if (!materials[sample['materialname']].numbers.find(e => e.color === stripSpaces(sample['color']))) { // new material color
+ materials[sample['materialname']].numbers.push({color: trim(sample['color']), number: ''});
+ }
}
}
+ else { // new material
+ console.info(`MATERIAL LOAD ${index}/${data.length} ${sample['materialname']}`);
+ materials[sample['materialname']] = {
+ name: trim(sample['materialname']),
+ supplier: trim(sample['supplier']),
+ group: trim(sample['material'])
+ };
+ materials[sample['materialname']].numbers = await numbersFetch(sample);
+
+ // material properties
+ materials[sample['materialname']].properties = {material_template: materialTemplate};
+ let tmp = /M(\d+)/.exec(sample['reinforcingmaterial']);
+ materials[sample['materialname']].properties.mineral = tmp ? tmp[1] : 0;
+ tmp = /GF(\d+)/.exec(sample['reinforcingmaterial']);
+ materials[sample['materialname']].properties.glass_fiber = tmp ? tmp[1] : 0;
+ tmp = /CF(\d+)/.exec(sample['reinforcingmaterial']);
+ materials[sample['materialname']].properties.carbon_fiber = tmp ? tmp[1] : 0;
+ }
}
+
+ // Fill numberToColor array
+ Object.keys(materials).forEach(mKey => {
+ materials[mKey].numbers.forEach(number => {
+ if (number.number && number.color) {
+ numberToColor[number.number] = number.color;
+ }
+ })
+ });
}
async function saveMaterials() {
const mKeys = Object.keys(materials)
for (let i in mKeys) {
- console.info(`${i}/${mKeys.length}`);
+ console.info(`MATERIAL SAVE ${i}/${mKeys.length}`);
+ const material = _.cloneDeep(materials[mKeys[i]]);
+ material.numbers = material.numbers.map(e => e.number).filter(e => e !== '').map(e => e.replace(/ /g, ''));
await axios({
method: 'post',
url: host + '/material/new',
@@ -397,11 +588,12 @@ async function saveMaterials() {
username: 'admin',
password: 'Abc123!#'
},
- data: materials[mKeys[i]]
+ data: material
}).catch(err => {
if (err.response.data.status && err.response.data.status !== 'Material name already taken') {
- console.info(materials[mKeys[i]]);
+ console.info(material);
console.error(err.response.data);
+ errors.push(`Upload for ${JSON.stringify(material)} failed: ${JSON.stringify(err.response.data)}`)
}
});
}
@@ -411,17 +603,17 @@ async function saveMaterials() {
async function numbersFetch(sample) {
let nm = [];
let res = [];
- if (sample['Material number']) { // sample has a material number
- nm = normMaster[stripSpaces(sample['Material number'])]? [normMaster[stripSpaces(sample['Material number'])]] : [];
+ if (sample['materialnumber']) { // sample has a material number
+ nm = normMaster[stripSpaces(sample['materialnumber'])]? [normMaster[stripSpaces(sample['materialnumber'])]] : [];
}
else { // try finding via material name
- nm = Object.keys(normMaster).filter(e => normMaster[e].nameSpaceless === stripSpaces(sample['Material name'])).map(e => normMaster[e]);
+ nm = Object.keys(normMaster).filter(e => normMaster[e].nameSpaceless === stripSpaces(sample['materialnumber'])).map(e => normMaster[e]);
}
if (nm.length > 0) {
for (let i in nm) {
- // if (!fs.readdirSync(nmDocs).find(e => e.indexOf(nm[i].doc.replace(/ /g, '_')) >= 0)) { // document not loaded
- // await getNormMasterDoc(nm[i].url.replace(/ /g, '%20'));
- // }
+ if (!fs.readdirSync(nmDocs).find(e => e.indexOf(nm[i].doc.replace(/ /g, '_')) >= 0)) { // document not loaded
+ await getNormMasterDoc(nm[i].url.replace(/ /g, '%20'));
+ }
// if (!fs.readdirSync(nmDocs).find(e => e.indexOf(nm[i].doc.replace(/ /g, '_')) >= 0)) { // document not loaded
// console.info('Retrying download...');
// await getNormMasterDoc(nm[i].url.replace(/ /g, '%20'), 2.2);
@@ -437,21 +629,22 @@ async function numbersFetch(sample) {
break;
}
else if (i + 1 >= nm.length) {
- console.error('Download failed!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!');
+ errors.push(`Download of ${nm[i].url.replace(/ /g, '%20')} for material number ${sample['materialnumber']} failed`);
+ errors.push(nm[i].doc.replace(/ /g, '_'));
}
}
}
if (res.length === 0) { // no results
- if ((sample['Color'] && sample['Color'] !== '') || (sample['Material number'] &&sample['Material number'] !== '')) {
- return [{color: trim(sample['Color']), number: sample['Material number']}];
+ if (sample['color'] !== '' || sample['materialnumber'] !== '') { // information in data available
+ return [{color: trim(sample['color']), number: sample['materialnumber']}];
}
else {
return [];
}
}
else {
- if (sample['Material number'] && !res.find(e => e.number === sample['Material number'])) { // sometimes norm master does not include sample number even if listed
- res.push({color: trim(sample['Color']), number: sample['Material number']});
+ if (!res.find(e => e.number === sample['materialnumber'])) { // sometimes norm master does not include sample number even if listed
+ res.push({color: trim(sample['color']), number: sample['materialnumber']});
}
return res;
}
@@ -521,7 +714,7 @@ function readPdf(file) {
let lastLastText = ''; // text of last last item
await new pdfReader.PdfReader().parseFileItems(nmDocs + '\\' + file, (err, item) => {
if (item && item.text) {
- if ((stripSpaces(lastLastText + lastText + item.text).toLowerCase().indexOf('colordesignationsupplier') >= 0) || (stripSpaces(lastLastText + lastText + item.text).toLowerCase().indexOf('colordesignatiomsupplier') >= 0)) { // table area starts
+ if ((stripSpaces(lastLastText + lastText + item.text).toLowerCase().indexOf('colordesignationsuppl') >= 0) || (stripSpaces(lastLastText + lastText + item.text).toLowerCase().indexOf('colordesignatiomsupplier') >= 0)) { // table area starts
table = countdown;
}
if (table > 0) {
@@ -570,6 +763,107 @@ function sampleDeviceMap() {
}
}
+function customFields (comment, sampleNumber) {
+ const customFields = [
+ {docKey: 'Versuchsreihe', dbKey: 'test series', regex: /Versuchsreihe (\d+),/, category: 'customField'},
+ {docKey: 'Stillstand', dbKey: 'idle', regex: /Stillstand (\d+ min):/, category: 'customField'},
+ {docKey: 'Serienzyklus', dbKey: 'cycle', regex: /(\d+.) Serienzyklus (\(.*?\))/, category: 'customField'},
+ {docKey: 'Berstdruck', dbKey: 'bursting pressure', regex: /Berstdruck: (.*?bar);/, category: 'customField'},
+ {docKey: 'gemessen am', dbKey: 'measured at', regex: /gemessen am (.*20\d\d)/, category: 'customField'},
+ {docKey: 'used for', dbKey: 'used for', regex: /used for (.*)/, category: 'customField'},
+ {docKey: 'Stabilized', dbKey: 'stabilized', regex: /Stabilized, (.*)/, category: 'customField'},
+ {docKey: 'parts from field', dbKey: 'parts from field', regex: null, category: 'customField'},
+ {docKey: 'side', dbKey: 'side', regex: /(\S*?) side/, category: 'customField'},
+ {docKey: 'Creep test', dbKey: 'creep test', regex: null, category: 'customField'},
+ {docKey: 'Variante', dbKey: 'variant', regex: /(.*)/, category: 'customField'},
+ {docKey: 'Parameter', dbKey: 'parameter', regex: /Parameter (\d)/, category: 'customField'},
+ {docKey: 'days without cooling', dbKey: 'days without cooling', regex: /(\d+) days without cooling/, category: 'customField'},
+ {docKey: 'Zyklus', dbKey: 'cycle', regex: /Zyklus (\d+ s)/, category: 'customField'},
+ {docKey: 'fast cure', dbKey: 'fast cure', regex: null, category: 'customField'},
+ {docKey: 'Stoff gesperrt', dbKey: 'material blocked', regex: null, category: 'customField'},
+ {docKey: 'anwendungsbeschränkt', dbKey: 'limited application', regex: null, category: 'customField'},
+ {docKey: 'für Neuanwendungen gesperrt', dbKey: 'blocked for new applications', regex: null, category: 'customField'},
+ {docKey: 'V', dbKey: 'test', regex: /V(\d+-\d+);/, category: 'customField'},
+ {docKey: 'Twz', dbKey: 'twz', regex: /Twz \(°C\): (\d+);/, category: 'customField'},
+ {docKey: 'Pnach', dbKey: 'pressure after', regex: /Pnach \(bar\): (\d+);/, category: 'customField'},
+ {docKey: 'Vein', dbKey: 'volume in', regex: /Vein \(ccm\/s\): (\d+)/, category: 'customField'},
+ {docKey: 'low emission', dbKey: 'low emission', regex: /low emission (\S+)[;]?/, category: 'customField'},
+ {docKey: 'aus', dbKey: 'from', regex: /aus (.*)/, category: 'customField'},
+ {docKey: 'Erprobung', dbKey: 'trial', regex: /Erprobung (.*?);/, category: 'customField'},
+ {docKey: 'Auftragsnummer', dbKey: 'job number', regex: /Auftragsnummer: (\S+)[;]?/, category: 'customField'},
+ {docKey: 'Wärmealterung', dbKey: 'heat aging', regex: /Wärmealterung: (.*)/, category: 'customField'},
+ {docKey: 'A: Wandung außen / I: Wandung innen / S: Wandung Steg', dbKey: 'outer wall', regex: /Steg.*?A: (\d+)/, category: 'customField'},
+ {docKey: 'A: Wandung außen / I: Wandung innen / S: Wandung Steg', dbKey: 'inner wall', regex: /Steg.*?I: (\d+)/, category: 'customField'},
+ {docKey: 'A: Wandung außen / I: Wandung innen / S: Wandung Steg', dbKey: 'support wall', regex: /Steg.*?S: (\d+)/, category: 'customField'},
+ {docKey: 'A: Wandung außen / I: Wandung innen / S: Wandung Steg', dbKey: 'outer wall degraded', regex: /Degradation:.*?A: (\d+)/, category: 'customField'},
+ {docKey: 'A: Wandung außen / I: Wandung innen / S: Wandung Steg', dbKey: 'inner wall degraded', regex: /Degradation:.*?I: (\d+)/, category: 'customField'},
+ {docKey: 'A: Wandung außen / I: Wandung innen / S: Wandung Steg', dbKey: 'support wall degraded', regex: /Degradation:.*?S: (\d+)/, category: 'customField'},
+ {docKey: 'Reines Polymer', dbKey: 'pure polymer', regex: null, category: 'customField'},
+ {docKey: 'Rücksendung erforderlich', dbKey: 'return needed', regex: /(.*?,) Rücksendung erforderlich, (.*)/, category: 'customField'},
+ {docKey: 'Prio', dbKey: 'priority', regex: /Prio (\d+)/, category: 'customField'},
+ {docKey: 'beanstandet', dbKey: 'faulty', regex: null, category: 'customField'},
+ {docKey: 'aged', dbKey: 'aged', regex: /aged: (.*)/, category: 'customField'},
+ {docKey: 'DOPPELT!!', dbKey: 'double', regex: null, category: 'customField'},
+ {docKey: 'Bauteil', dbKey: 'construction part', regex: /Bauteil (\S+)/, category: 'customField'},
+ {docKey: 'T =', dbKey: 'temperature', regex: /T = (\S+)/, category: 'customField'},
+ {docKey: 'nicht vorgealtert', dbKey: 'not preaged', regex: /nicht vorgealtert (.*)/, category: 'customField'},
+ {docKey: 'TS119', dbKey: 'TS119', regex: /TS119 (W\S+);/, category: 'customField'},
+ {docKey: 'GF vom Datenblatt', dbKey: 'glass fibre from data sheet', regex: null, category: 'customField'},
+ {docKey: 'nach Datensatz', dbKey: 'according to dataset', regex: null, category: 'customField'},
+ {docKey: 'Dosiergeschw', dbKey: 'metering speed', regex: /Dosiergeschw.*? (.*?min)/, category: 'customField'},
+ {docKey: 'Einspritzgeschw', dbKey: 'injection speed', regex: /Einspritzgeschw.*? (.*\/s)/, category: 'customField'},
+ {docKey: 'Heizbänder', dbKey: 'heating lines', regex: /Heizbänder (.*)/, category: 'customField'},
+ {docKey: 'Verweilzeit', dbKey: 'dwell time', regex: /Verweilzeit (.*?min)/, category: 'customField'},
+ {docKey: 'Probe', dbKey: 'belongs to', regex: /Probe (\S*\d+)/, category: 'reference'},
+ {docKey: 'zu', dbKey: 'belongs to', regex: /zu (\S*\d+)/, category: 'reference'},
+ {docKey: 'granulate zu', dbKey: 'granulate to', regex: /granulate zu.* (\S*\d+)/, category: 'reference'},
+ {docKey: 'construction part', dbKey: 'construction part', regex: /(? {
+ if (comment.indexOf(cField.docKey) >= 0) { // comment contains docKey
+ if (cField.regex !== null) {
+ const regexRes = cField.regex.exec(comment);
+ if (regexRes) {
+ usedParts.push(regexRes[0]);
+ if (cField.category === 'reference') {
+ sampleReferences.push({sample: sampleNumber, referencedSample: regexRes[1], relation: cField.dbKey});
+ }
+ else if (cField.category === 'vz') {
+ vzValues[sampleNumber] = regexRes[1];
+ }
+ else {
+ res[cField.dbKey] = regexRes.filter((e, i) => i > 0).join(' ');
+ }
+ }
+ }
+ else {
+ usedParts.push(cField.docKey);
+ res[cField.dbKey] = true;
+ }
+ }
+ });
+ usedParts.forEach(part => {
+ const index = comment.indexOf(part);
+ if (index >= 0) {
+ comment = comment.slice(0, index) + comment.slice(index + part.length);
+ }
+ });
+ if (/\w+/.test(comment)) {
+ res.xRest = comment;
+ }
+ return res;
+}
+
+function sampleType (type) {
+ const allowedTypes = ['tension rod', 'part', 'granulate'];
+ return allowedTypes.indexOf(type) >= 0 ? type : (type === '' ? 'unknown' : 'other');
+}
+
function stripSpaces(s) {
return s ? s.replace(/ /g,'') : '';
}
diff --git a/src/helpers/csv.ts b/src/helpers/csv.ts
index 38c487a..e6f07b2 100644
--- a/src/helpers/csv.ts
+++ b/src/helpers/csv.ts
@@ -13,11 +13,16 @@ function flatten (data) { // flatten object: {a: {b: true}} -> {a.b: true}
result[prop] = cur;
}
else if (Array.isArray(cur)) {
- let l = 0;
- for(let i = 0, l = cur.length; i < l; i++)
- recurse(cur[i], prop + "[" + i + "]");
- if (l == 0)
- result[prop] = [];
+ if (cur.length && (Object(cur[0]) !== cur || Object.keys(cur[0]).length === 0)) { // array of non-objects
+ result[prop] = '[' + cur.join(', ') + ']';
+ }
+ else {
+ let l = 0;
+ for(let i = 0, l = cur.length; i < l; i++)
+ recurse(cur[i], prop + "[" + i + "]");
+ if (l == 0)
+ result[prop] = [];
+ }
}
else {
let isEmpty = true;
diff --git a/src/models/material.ts b/src/models/material.ts
index d7d5eb9..0c1629a 100644
--- a/src/models/material.ts
+++ b/src/models/material.ts
@@ -7,13 +7,8 @@ const MaterialSchema = new mongoose.Schema({
name: {type: String, index: {unique: true}},
supplier_id: {type: mongoose.Schema.Types.ObjectId, ref: MaterialSupplierModel},
group_id: {type: mongoose.Schema.Types.ObjectId, ref: MaterialGroupsModel},
- mineral: Number,
- glass_fiber: Number,
- carbon_fiber: Number,
- numbers: [{
- color: String,
- number: String
- }],
+ properties: mongoose.Schema.Types.Mixed,
+ numbers: [String],
status: Number
}, {minimize: false});
diff --git a/src/models/material_template.ts b/src/models/material_template.ts
new file mode 100644
index 0000000..5e06819
--- /dev/null
+++ b/src/models/material_template.ts
@@ -0,0 +1,20 @@
+import mongoose from 'mongoose';
+import db from '../db';
+
+const MaterialTemplateSchema = new mongoose.Schema({
+ first_id: mongoose.Schema.Types.ObjectId,
+ name: String,
+ version: Number,
+ parameters: [new mongoose.Schema({
+ name: String,
+ range: mongoose.Schema.Types.Mixed
+ } ,{ _id : false })]
+}, {minimize: false}); // to allow empty objects
+
+// changelog query helper
+MaterialTemplateSchema.query.log = function > (req) {
+ db.log(req, this);
+ return this;
+}
+
+export default mongoose.model>('material_template', MaterialTemplateSchema);
\ No newline at end of file
diff --git a/src/routes/material.spec.ts b/src/routes/material.spec.ts
index e412615..789f6e5 100644
--- a/src/routes/material.spec.ts
+++ b/src/routes/material.spec.ts
@@ -1,5 +1,4 @@
import should from 'should/as-function';
-import _ from 'lodash';
import MaterialModel from '../models/material';
import MaterialGroupModel from '../models/material_groups';
import MaterialSupplierModel from '../models/material_suppliers';
@@ -7,7 +6,6 @@ import TestHelper from "../test/helper";
import globals from '../globals';
-
describe('/material', () => {
let server;
before(done => TestHelper.before(done));
@@ -27,19 +25,13 @@ describe('/material', () => {
const json = require('../test/db.json');
should(res.body).have.lengthOf(json.collections.materials.filter(e => e.status === globals.status.validated).length);
should(res.body).matchEach(material => {
- should(material).have.only.keys('_id', 'name', 'supplier', 'group', 'mineral', 'glass_fiber', 'carbon_fiber', 'numbers');
+ should(material).have.only.keys('_id', 'name', 'supplier', 'group', 'properties', 'numbers');
should(material).have.property('_id').be.type('string');
should(material).have.property('name').be.type('string');
should(material).have.property('supplier').be.type('string');
should(material).have.property('group').be.type('string');
- should(material).have.property('mineral').be.type('number');
- should(material).have.property('glass_fiber').be.type('number');
- should(material).have.property('carbon_fiber').be.type('number');
- should(material.numbers).matchEach(number => {
- should(number).have.only.keys('color', 'number');
- should(number).have.property('color').be.type('string');
- should(number).have.property('number').be.type('string');
- });
+ should(material.properties).have.property('material_template').be.type('string');
+ should(material.numbers).be.instanceof(Array);
});
done();
});
@@ -55,19 +47,13 @@ describe('/material', () => {
const json = require('../test/db.json');
should(res.body).have.lengthOf(json.collections.materials.filter(e => e.status === globals.status.validated).length);
should(res.body).matchEach(material => {
- should(material).have.only.keys('_id', 'name', 'supplier', 'group', 'mineral', 'glass_fiber', 'carbon_fiber', 'numbers');
+ should(material).have.only.keys('_id', 'name', 'supplier', 'group', 'properties', 'numbers');
should(material).have.property('_id').be.type('string');
should(material).have.property('name').be.type('string');
should(material).have.property('supplier').be.type('string');
should(material).have.property('group').be.type('string');
- should(material).have.property('mineral').be.type('number');
- should(material).have.property('glass_fiber').be.type('number');
- should(material).have.property('carbon_fiber').be.type('number');
- should(material.numbers).matchEach(number => {
- should(number).have.only.keys('color', 'number');
- should(number).have.property('color').be.type('string');
- should(number).have.property('number').be.type('string');
- });
+ should(material.properties).have.property('material_template').be.type('string');
+ should(material.numbers).be.instanceof(Array);
});
done();
});
@@ -83,19 +69,13 @@ describe('/material', () => {
const json = require('../test/db.json');
should(res.body).have.lengthOf(json.collections.materials.filter(e => e.status === globals.status.new).length);
should(res.body).matchEach(material => {
- should(material).have.only.keys('_id', 'name', 'supplier', 'group', 'mineral', 'glass_fiber', 'carbon_fiber', 'numbers');
+ should(material).have.only.keys('_id', 'name', 'supplier', 'group', 'properties', 'numbers');
should(material).have.property('_id').be.type('string');
should(material).have.property('name').be.type('string');
should(material).have.property('supplier').be.type('string');
should(material).have.property('group').be.type('string');
- should(material).have.property('mineral').be.type('number');
- should(material).have.property('glass_fiber').be.type('number');
- should(material).have.property('carbon_fiber').be.type('number');
- should(material.numbers).matchEach(number => {
- should(number).have.only.keys('color', 'number');
- should(number).have.property('color').be.type('string');
- should(number).have.property('number').be.type('string');
- });
+ should(material.properties).have.property('material_template').be.type('string');
+ should(material.numbers).be.instanceof(Array);
});
done();
});
@@ -131,19 +111,13 @@ describe('/material', () => {
let asyncCounter = res.body.length;
should(res.body).have.lengthOf(json.collections.materials.filter(e => e.status ===globals.status.new).length);
should(res.body).matchEach(material => {
- should(material).have.only.keys('_id', 'name', 'supplier', 'group', 'mineral', 'glass_fiber', 'carbon_fiber', 'numbers');
+ should(material).have.only.keys('_id', 'name', 'supplier', 'group', 'properties', 'numbers');
should(material).have.property('_id').be.type('string');
should(material).have.property('name').be.type('string');
should(material).have.property('supplier').be.type('string');
should(material).have.property('group').be.type('string');
- should(material).have.property('mineral').be.type('number');
- should(material).have.property('glass_fiber').be.type('number');
- should(material).have.property('carbon_fiber').be.type('number');
- should(material.numbers).matchEach(number => {
- should(number).have.only.keys('color', 'number');
- should(number).have.property('color').be.type('string');
- should(number).have.property('number').be.type('string');
- });
+ should(material.properties).have.property('material_template').be.type('string');
+ should(material.numbers).be.instanceof(Array);
MaterialModel.findById(material._id).lean().exec((err, data) => {
should(data).have.property('status',globals.status.new);
if (--asyncCounter === 0) {
@@ -165,19 +139,13 @@ describe('/material', () => {
let asyncCounter = res.body.length;
should(res.body).have.lengthOf(json.collections.materials.filter(e => e.status ===globals.status.deleted).length);
should(res.body).matchEach(material => {
- should(material).have.only.keys('_id', 'name', 'supplier', 'group', 'mineral', 'glass_fiber', 'carbon_fiber', 'numbers');
+ should(material).have.only.keys('_id', 'name', 'supplier', 'group', 'properties', 'numbers');
should(material).have.property('_id').be.type('string');
should(material).have.property('name').be.type('string');
should(material).have.property('supplier').be.type('string');
should(material).have.property('group').be.type('string');
- should(material).have.property('mineral').be.type('number');
- should(material).have.property('glass_fiber').be.type('number');
- should(material).have.property('carbon_fiber').be.type('number');
- should(material.numbers).matchEach(number => {
- should(number).have.only.keys('color', 'number');
- should(number).have.property('color').be.type('string');
- should(number).have.property('number').be.type('string');
- });
+ should(material.properties).have.property('material_template').be.type('string');
+ should(material.numbers).be.instanceof(Array);
MaterialModel.findById(material._id).lean().exec((err, data) => {
should(data).have.property('status',globals.status.deleted);
if (--asyncCounter === 0) {
@@ -219,7 +187,7 @@ describe('/material', () => {
url: '/material/100000000000000000000001',
auth: {basic: 'janedoe'},
httpStatus: 200,
- res: {_id: '100000000000000000000001', name: 'Stanyl TW 200 F8', supplier: 'DSM', group: 'PA46', mineral: 0, glass_fiber: 40, carbon_fiber: 0, numbers: [{color: 'black', number: '5514263423'}, {color: 'natural', number: '5514263422'}]}
+ res: {_id: '100000000000000000000001', name: 'Stanyl TW 200 F8', supplier: 'DSM', group: 'PA46', properties: {material_template: '130000000000000000000003', mineral: 0, glass_fiber: 40, carbon_fiber: 0}, numbers: ['5514263423', '5514263422']}
});
});
it('returns the right material for an API key', done => {
@@ -228,7 +196,7 @@ describe('/material', () => {
url: '/material/100000000000000000000003',
auth: {key: 'admin'},
httpStatus: 200,
- res: {_id: '100000000000000000000003', name: 'PA GF 50 black (2706)', supplier: 'Akro-Plastic', group: 'PA66+PA6I/6T', mineral: 0, glass_fiber: 0, carbon_fiber: 0, numbers: []}
+ res: {_id: '100000000000000000000003', name: 'PA GF 50 black (2706)', supplier: 'Akro-Plastic', group: 'PA66+PA6I/6T', properties: {material_template: '130000000000000000000003', mineral: 0, glass_fiber: 0, carbon_fiber: 0}, numbers: []}
});
});
it('returns a material with a color without number', done => {
@@ -237,7 +205,7 @@ describe('/material', () => {
url: '/material/100000000000000000000007',
auth: {basic: 'janedoe'},
httpStatus: 200,
- res: {_id: '100000000000000000000007', name: 'Ultramid A4H', supplier: 'BASF', group: 'PA66', mineral: 0, glass_fiber: 0, carbon_fiber: 0, numbers: [{color: 'black', number: ''}]}
+ res: {_id: '100000000000000000000007', name: 'Ultramid A4H', supplier: 'BASF', group: 'PA66', properties: {material_template: '130000000000000000000003', mineral: 0, glass_fiber: 0, carbon_fiber: 0}, numbers: []}
});
});
it('returns a deleted material for a maintain/admin user', done => {
@@ -246,7 +214,7 @@ describe('/material', () => {
url: '/material/100000000000000000000008',
auth: {basic: 'admin'},
httpStatus: 200,
- res: {_id: '100000000000000000000008', name: 'Latamid 66 H 2 G 30', supplier: 'LATI', group: 'PA66', mineral: 0, glass_fiber: 30, carbon_fiber: 0, numbers: [{color: 'blue', number: '5513943509'}]}
+ res: {_id: '100000000000000000000008', name: 'Latamid 66 H 2 G 30', supplier: 'LATI', group: 'PA66', properties: {material_template: '130000000000000000000003', mineral: 0, glass_fiber: 30, carbon_fiber: 0}, numbers: ['5513943509']}
});
});
it('returns 403 for a write user when requesting a deleted material', done => {
@@ -290,7 +258,7 @@ describe('/material', () => {
auth: {basic: 'janedoe'},
httpStatus: 200,
req: {},
- res: {_id: '100000000000000000000001', name: 'Stanyl TW 200 F8', supplier: 'DSM', group: 'PA46', mineral: 0, glass_fiber: 40, carbon_fiber: 0, numbers: [{color: 'black', number: '5514263423'}, {color: 'natural', number: '5514263422'}]}
+ res: {_id: '100000000000000000000001', name: 'Stanyl TW 200 F8', supplier: 'DSM', group: 'PA46', properties: {material_template: '130000000000000000000003', mineral: 0, glass_fiber: 40, carbon_fiber: 0}, numbers: ['5514263423', '5514263422']}
});
});
it('keeps unchanged properties', done => {
@@ -299,10 +267,10 @@ describe('/material', () => {
url: '/material/100000000000000000000001',
auth: {basic: 'janedoe'},
httpStatus: 200,
- req: {name: 'Stanyl TW 200 F8', supplier: 'DSM', group: 'PA46', mineral: 0, glass_fiber: 40, carbon_fiber: 0, numbers: [{color: 'black', number: '5514263423'}, {color: 'natural', number: '5514263422'}]}
+ req: {name: 'Stanyl TW 200 F8', supplier: 'DSM', group: 'PA46', properties: {material_template: '130000000000000000000003', mineral: 0, glass_fiber: 40, carbon_fiber: 0}, numbers: ['5514263423', '5514263422']}
}).end((err, res) => {
if (err) return done(err);
- should(res.body).be.eql({_id: '100000000000000000000001', name: 'Stanyl TW 200 F8', supplier: 'DSM', group: 'PA46', mineral: 0, glass_fiber: 40, carbon_fiber: 0, numbers: [{color: 'black', number: '5514263423'}, {color: 'natural', number: '5514263422'}]});
+ should(res.body).be.eql({_id: '100000000000000000000001', name: 'Stanyl TW 200 F8', supplier: 'DSM', group: 'PA46', properties: {material_template: '130000000000000000000003', mineral: 0, glass_fiber: 40, carbon_fiber: 0}, numbers: ['5514263423', '5514263422']});
MaterialModel.findById('100000000000000000000001').lean().exec((err, data) => {
if (err) return done(err);
should(data).have.property('status',globals.status.validated);
@@ -329,7 +297,24 @@ describe('/material', () => {
req: {name: 'Stanyl TW 200 F8'}
}).end((err, res) => {
if (err) return done(err);
- should(res.body).be.eql({_id: '100000000000000000000001', name: 'Stanyl TW 200 F8', supplier: 'DSM', group: 'PA46', mineral: 0, glass_fiber: 40, carbon_fiber: 0, numbers: [{color: 'black', number: '5514263423'}, {color: 'natural', number: '5514263422'}]});
+ should(res.body).be.eql({_id: '100000000000000000000001', name: 'Stanyl TW 200 F8', supplier: 'DSM', group: 'PA46', properties: {material_template: '130000000000000000000003', mineral: 0, glass_fiber: 40, carbon_fiber: 0}, numbers: ['5514263423', '5514263422']});
+ MaterialModel.findById('100000000000000000000001').lean().exec((err, data) => {
+ if (err) return done(err);
+ should(data).have.property('status',globals.status.validated);
+ done();
+ });
+ });
+ });
+ it('keeps unchanged properties', done => {
+ TestHelper.request(server, done, {
+ method: 'put',
+ url: '/material/100000000000000000000001',
+ auth: {basic: 'janedoe'},
+ httpStatus: 200,
+ req: {properties: {material_template: '130000000000000000000003', mineral: 0, glass_fiber: 40, carbon_fiber: 0}}
+ }).end((err, res) => {
+ if (err) return done(err);
+ should(res.body).be.eql({_id: '100000000000000000000001', name: 'Stanyl TW 200 F8', supplier: 'DSM', group: 'PA46', properties: {material_template: '130000000000000000000003', mineral: 0, glass_fiber: 40, carbon_fiber: 0}, numbers: ['5514263423', '5514263422']});
MaterialModel.findById('100000000000000000000001').lean().exec((err, data) => {
if (err) return done(err);
should(data).have.property('status',globals.status.validated);
@@ -343,17 +328,16 @@ describe('/material', () => {
url: '/material/100000000000000000000001',
auth: {basic: 'janedoe'},
httpStatus: 200,
- req: {name: 'UltramidTKR4355G7_2', supplier: 'BASF', group: 'PA6/6T', mineral: 0, glass_fiber: 35, carbon_fiber: 0, numbers: [{color: 'black', number: '5514212901'}, {color: 'signalviolet', number: '5514612901'}]}
+ req: {name: 'UltramidTKR4355G7_2', supplier: 'BASF', group: 'PA6/6T', properties: {material_template: '130000000000000000000003', mineral: 0, glass_fiber: 35, carbon_fiber: 0}, numbers: ['5514212901', '5514612901']}
}).end((err, res) => {
if (err) return done(err);
- should(res.body).be.eql({_id: '100000000000000000000001', name: 'UltramidTKR4355G7_2', supplier: 'BASF', group: 'PA6/6T', mineral: 0, glass_fiber: 35, carbon_fiber: 0, numbers: [{color: 'black', number: '5514212901'}, {color: 'signalviolet', number: '5514612901'}]});
+ should(res.body).be.eql({_id: '100000000000000000000001', name: 'UltramidTKR4355G7_2', supplier: 'BASF', group: 'PA6/6T', properties: {material_template: '130000000000000000000003', mineral: 0, glass_fiber: 35, carbon_fiber: 0}, numbers: ['5514212901', '5514612901']});
MaterialModel.findById('100000000000000000000001').lean().exec((err, data:any) => {
if (err) return done(err);
data._id = data._id.toString();
data.group_id = data.group_id.toString();
data.supplier_id = data.supplier_id.toString();
- data.numbers = data.numbers.map(e => {return {color: e.color, number: e.number}});
- should(data).be.eql({_id: '100000000000000000000001', name: 'UltramidTKR4355G7_2', supplier_id: '110000000000000000000002', group_id: '900000000000000000000002', mineral: 0, glass_fiber: 35, carbon_fiber: 0, numbers: [{color: 'black', number: '5514212901'}, {color: 'signalviolet', number: '5514612901'}], status: 0, __v: 0});
+ should(data).be.eql({_id: '100000000000000000000001', name: 'UltramidTKR4355G7_2', supplier_id: '110000000000000000000002', group_id: '900000000000000000000002', properties: {material_template: '130000000000000000000003', mineral: 0, glass_fiber: 35, carbon_fiber: 0}, numbers: ['5514212901', '5514612901'], status: 0, __v: 0});
MaterialGroupModel.find({name: 'PA6/6T'}).lean().exec((err, data) => {
if (err) return done(err);
should(data).have.lengthOf(1);
@@ -374,7 +358,7 @@ describe('/material', () => {
url: '/material/100000000000000000000001',
auth: {basic: 'janedoe'},
httpStatus: 200,
- req: {name: 'UltramidTKR4355G7_2', supplier: 'BASF', group: 'PA6/6T', mineral: 0, glass_fiber: 35, carbon_fiber: 0, numbers: [{color: 'black', number: '5514212901'}, {color: 'signalviolet', number: '5514612901'}]},
+ req: {name: 'UltramidTKR4355G7_2', supplier: 'BASF', group: 'PA6/6T', properties: {material_template: '130000000000000000000003', mineral: 0, glass_fiber: 35, carbon_fiber: 0}, numbers: ['5514212901', '5514612901']},
log: {
collection: 'materials',
dataAdd: {
@@ -386,16 +370,6 @@ describe('/material', () => {
}
});
});
- it('accepts a color without number', done => {
- TestHelper.request(server, done, {
- method: 'put',
- url: '/material/100000000000000000000007',
- auth: {basic: 'janedoe'},
- httpStatus: 200,
- req: {numbers: [{color: 'black', number: ''}, {color: 'natural', number: ''}]},
- res: {_id: '100000000000000000000007', name: 'Ultramid A4H', supplier: 'BASF', group: 'PA66', mineral: 0, glass_fiber: 0, carbon_fiber: 0, numbers: [{color: 'black', number: ''}, {color: 'natural', number: ''}]}
- });
- })
it('rejects already existing material names', done => {
TestHelper.request(server, done, {
method: 'put',
@@ -406,46 +380,16 @@ describe('/material', () => {
res: {status: 'Material name already taken'}
});
});
- it('rejects a wrong mineral property', done => {
+ it('rejects wrong material properties', done => {
TestHelper.request(server, done, {
method: 'put',
url: '/material/100000000000000000000001',
auth: {basic: 'janedoe'},
httpStatus: 400,
- req: {mineral: 'x'},
+ req: {properties: {material_template: '130000000000000000000003', mineral: 'x', glass_fiber: 0, carbon_fiber: 0}},
res: {status: 'Invalid body format', details: '"mineral" must be a number'}
});
});
- it('rejects a wrong glass_fiber property', done => {
- TestHelper.request(server, done, {
- method: 'put',
- url: '/material/100000000000000000000001',
- auth: {basic: 'janedoe'},
- httpStatus: 400,
- req: {glass_fiber: 'x'},
- res: {status: 'Invalid body format', details: '"glass_fiber" must be a number'}
- });
- });
- it('rejects a wrong carbon_fiber property', done => {
- TestHelper.request(server, done, {
- method: 'put',
- url: '/material/100000000000000000000001',
- auth: {basic: 'janedoe'},
- httpStatus: 400,
- req: {carbon_fiber: 'x'},
- res: {status: 'Invalid body format', details: '"carbon_fiber" must be a number'}
- });
- });
- it('rejects a wrong color name property', done => {
- TestHelper.request(server, done, {
- method: 'put',
- url: '/material/100000000000000000000001',
- auth: {basic: 'janedoe'},
- httpStatus: 400,
- req: {numbers: [{colorxx: 'black', number: '55'}]},
- res: {status: 'Invalid body format', details: '"numbers[0].color" is required'}
- });
- });
it('rejects an invalid id', done => {
TestHelper.request(server, done, {
method: 'put',
@@ -455,6 +399,86 @@ describe('/material', () => {
req: {},
});
});
+ it('rejects not specified properties parameters', done => {
+ TestHelper.request(server, done, {
+ method: 'put',
+ url: '/material/100000000000000000000001',
+ auth: {basic: 'janedoe'},
+ httpStatus: 400,
+ req: {properties: {material_template: '130000000000000000000003', mineral: 0, glass_fiber: 0, carbon_fiber: 0, x: 55}},
+ res: {status: 'Invalid body format', details: '"x" is not allowed'}
+ });
+ });
+ it('rejects a properties parameter not in the value range', done => {
+ TestHelper.request(server, done, {
+ method: 'put',
+ url: '/material/100000000000000000000009',
+ auth: {basic: 'janedoe'},
+ httpStatus: 400,
+ req: {properties: {material_template: '130000000000000000000002', stickiness: 'xx'}},
+ res: {status: 'Invalid body format', details: '"stickiness" must be one of [not so sticky, medium, very sticky]'}
+ });
+ });
+ it('rejects a properties parameter below minimum range', done => {
+ TestHelper.request(server, done, {
+ method: 'put',
+ url: '/material/100000000000000000000001',
+ auth: {basic: 'janedoe'},
+ httpStatus: 400,
+ req: {properties: {material_template: '130000000000000000000003', mineral: 0, glass_fiber: -5, carbon_fiber: 0}},
+ res: {status: 'Invalid body format', details: '"glass_fiber" must be larger than or equal to 0'}
+ });
+ });
+ it('rejects a properties parameter above maximum range', done => {
+ TestHelper.request(server, done, {
+ method: 'put',
+ url: '/material/100000000000000000000001',
+ auth: {basic: 'janedoe'},
+ httpStatus: 400,
+ req: {properties: {material_template: '130000000000000000000003', mineral: 0, glass_fiber: 0, carbon_fiber: 105}},
+ res: {status: 'Invalid body format', details: '"carbon_fiber" must be less than or equal to 100'}
+ });
+ });
+ it('rejects an invalid material template', done => {
+ TestHelper.request(server, done, {
+ method: 'put',
+ url: '/material/100000000000000000000001',
+ auth: {basic: 'janedoe'},
+ httpStatus: 400,
+ req: {properties: {material_template: '1300000000000h0000000001', mineral: 0, glass_fiber: 0, carbon_fiber: 0}},
+ res: {status: 'Material template not available'}
+ });
+ });
+ it('rejects an unknown material template', done => {
+ TestHelper.request(server, done, {
+ method: 'put',
+ url: '/material/100000000000000000000001',
+ auth: {basic: 'janedoe'},
+ httpStatus: 400,
+ req: {properties: {material_template: '100000000000000000000001', mineral: 0, glass_fiber: 0, carbon_fiber: 0}},
+ res: {status: 'Material template not available'}
+ });
+ });
+ it('rejects an old version of a material template', done => {
+ TestHelper.request(server, done, {
+ method: 'put',
+ url: '/material/100000000000000000000001',
+ auth: {basic: 'janedoe'},
+ httpStatus: 400,
+ req: {properties: {material_template: '130000000000000000000001', glass_fiber: 0}},
+ res: {status: 'Old template version not allowed'}
+ });
+ });
+ it('allows keeping an old version of a material template', done => {
+ TestHelper.request(server, done, {
+ method: 'put',
+ url: '/material/100000000000000000000010',
+ auth: {basic: 'janedoe'},
+ httpStatus: 200,
+ req: {properties: {material_template: '130000000000000000000001', glass_fiber: 5}},
+ res: {_id: '100000000000000000000010', name: 'Latamid 66 G 40', numbers: ['5513943509'], supplier: 'LATI', group: 'PA66', properties: {material_template: '130000000000000000000001', glass_fiber: 5}}
+ });
+ });
it('rejects editing a deleted material', done => {
TestHelper.request(server, done, {
method: 'put',
@@ -516,8 +540,8 @@ describe('/material', () => {
data._id = data._id.toString();
data.group_id = data.group_id.toString();
data.supplier_id = data.supplier_id.toString();
- data.numbers = data.numbers.map(e => {return {color: e.color, number: e.number}});
- should(data).be.eql({_id: '100000000000000000000002', name: 'Ultramid T KR 4355 G7', supplier_id: '110000000000000000000002', group_id: '900000000000000000000002', mineral: 0, glass_fiber: 35, carbon_fiber: 0, numbers: [{color: 'black', number: '5514212901'}, {color: 'signalviolet', number: '5514612901'}], status: -1, __v: 0}
+ data.properties.material_template = data.properties.material_template.toString();
+ should(data).be.eql({_id: '100000000000000000000002', name: 'Ultramid T KR 4355 G7', supplier_id: '110000000000000000000002', group_id: '900000000000000000000002', properties: {material_template: '130000000000000000000003', mineral: 0, glass_fiber: 35, carbon_fiber: 0}, numbers: ['5514212901', '5514612901'], status: -1, __v: 0}
);
done();
});
@@ -732,22 +756,19 @@ describe('/material', () => {
url: '/material/new',
auth: {basic: 'janedoe'},
httpStatus: 200,
- req: {name: 'Crastin CE 2510', supplier: 'Du Pont', group: 'PBT', mineral: 0, glass_fiber: 30, carbon_fiber: 0, numbers: [{color: 'black', number: '05515798402'}]}
+ req: {name: 'Crastin CE 2510', supplier: 'Du Pont', group: 'PBT', properties: {material_template: '130000000000000000000003', mineral: 0, glass_fiber: 30, carbon_fiber: 0}, numbers: ['5515798402']}
}).end((err, res) => {
if (err) return done (err);
- should(res.body).have.only.keys('_id', 'name', 'supplier', 'group', 'mineral', 'glass_fiber', 'carbon_fiber', 'numbers');
+ should(res.body).have.only.keys('_id', 'name', 'supplier', 'group', 'properties', 'numbers');
should(res.body).have.property('_id').be.type('string');
should(res.body).have.property('name', 'Crastin CE 2510');
should(res.body).have.property('supplier', 'Du Pont');
should(res.body).have.property('group', 'PBT');
- should(res.body).have.property('mineral', 0);
- should(res.body).have.property('glass_fiber', 30);
- should(res.body).have.property('carbon_fiber', 0);
- should(res.body.numbers).matchEach(number => {
- should(number).have.only.keys('color', 'number');
- should(number).have.property('color', 'black');
- should(number).have.property('number', '05515798402');
- });
+ should(res.body.properties).have.property('material_template', '130000000000000000000003');
+ should(res.body.properties).have.property('mineral', 0);
+ should(res.body.properties).have.property('glass_fiber', 30);
+ should(res.body.properties).have.property('carbon_fiber', 0);
+ should(res.body).have.property('numbers', ['5515798402']);
done();
});
});
@@ -757,17 +778,18 @@ describe('/material', () => {
url: '/material/new',
auth: {basic: 'janedoe'},
httpStatus: 200,
- req: {name: 'Crastin CE 2510', supplier: 'Du Pont', group: 'PBT', mineral: 0, glass_fiber: 30, carbon_fiber: 0, numbers: []}
+ req: {name: 'Crastin CE 2510', supplier: 'Du Pont', group: 'PBT', properties: {material_template: '130000000000000000000003', mineral: 0, glass_fiber: 30, carbon_fiber: 0}, numbers: []}
}).end(err => {
if (err) return done (err);
MaterialModel.find({name: 'Crastin CE 2510'}).lean().exec((err, materialData: any) => {
if (err) return done (err);
should(materialData).have.lengthOf(1);
- should(materialData[0]).have.only.keys('_id', 'name', 'supplier_id', 'group_id', 'mineral', 'glass_fiber', 'carbon_fiber', 'numbers', 'status', '__v');
+ should(materialData[0]).have.only.keys('_id', 'name', 'supplier_id', 'group_id', 'properties', 'numbers', 'status', '__v');
should(materialData[0]).have.property('name', 'Crastin CE 2510');
- should(materialData[0]).have.property('mineral', 0);
- should(materialData[0]).have.property('glass_fiber', 30);
- should(materialData[0]).have.property('carbon_fiber', 0);
+ should(materialData[0].properties).have.property('material_template', '130000000000000000000003');
+ should(materialData[0].properties).have.property('mineral', 0);
+ should(materialData[0].properties).have.property('glass_fiber', 30);
+ should(materialData[0].properties).have.property('carbon_fiber', 0);
should(materialData[0]).have.property('status',globals.status.new);
should(materialData[0].numbers).have.lengthOf(0);
MaterialGroupModel.findById(materialData[0].group_id).lean().exec((err, data) => {
@@ -788,7 +810,7 @@ describe('/material', () => {
url: '/material/new',
auth: {basic: 'janedoe'},
httpStatus: 200,
- req: {name: 'Crastin CE 2510', supplier: 'Du Pont', group: 'PBT', mineral: 0, glass_fiber: 30, carbon_fiber: 0, numbers: []},
+ req: {name: 'Crastin CE 2510', supplier: 'Du Pont', group: 'PBT', properties: {material_template: '130000000000000000000003', mineral: 0, glass_fiber: 30, carbon_fiber: 0}, numbers: []},
log: {
collection: 'materials',
dataAdd: {status: 0},
@@ -796,50 +818,13 @@ describe('/material', () => {
}
});
});
- it('accepts a color without number', done => {
- TestHelper.request(server, done, {
- method: 'post',
- url: '/material/new',
- auth: {basic: 'janedoe'},
- httpStatus: 200,
- req: {name: 'Crastin CE 2510', supplier: 'Du Pont', group: 'PBT', mineral: 0, glass_fiber: 30, carbon_fiber: 0, numbers: [{color: 'black', number: ''}]}
- }).end((err, res) => {
- if (err) return done (err);
- should(res.body).have.only.keys('_id', 'name', 'supplier', 'group', 'mineral', 'glass_fiber', 'carbon_fiber', 'numbers');
- should(res.body).have.property('_id').be.type('string');
- should(res.body).have.property('name', 'Crastin CE 2510');
- should(res.body).have.property('supplier', 'Du Pont');
- should(res.body).have.property('group', 'PBT');
- should(res.body).have.property('mineral', 0);
- should(res.body).have.property('glass_fiber', 30);
- should(res.body).have.property('carbon_fiber', 0);
- should(res.body.numbers).matchEach(number => {
- should(number).have.only.keys('color', 'number');
- should(number).have.property('color', 'black');
- should(number).have.property('number', '');
- });
- MaterialModel.find({name: 'Crastin CE 2510'}).lean().exec((err, data: any) => {
- if (err) return done (err);
- should(data).have.lengthOf(1);
- should(data[0]).have.only.keys('_id', 'name', 'supplier_id', 'group_id', 'mineral', 'glass_fiber', 'carbon_fiber', 'numbers', 'status', '__v');
- should(data[0]).have.property('_id');
- should(data[0]).have.property('name', 'Crastin CE 2510');
- should(data[0]).have.property('mineral', 0);
- should(data[0]).have.property('glass_fiber', 30);
- should(data[0]).have.property('carbon_fiber', 0);
- should(data[0]).have.property('status',globals.status.new);
- should(_.omit(data[0].numbers[0], '_id')).be.eql({color: 'black', number: ''});
- done();
- });
- });
- });
it('rejects already existing material names', done => {
TestHelper.request(server, done, {
method: 'post',
url: '/material/new',
auth: {basic: 'janedoe'},
httpStatus: 400,
- req: {name: 'Stanyl TW 200 F8', supplier: 'DSM', group: 'PA46', mineral: 0, glass_fiber: 40, carbon_fiber: 0, numbers: [{color: 'black', number: '5514263423'}]},
+ req: {name: 'Stanyl TW 200 F8', supplier: 'DSM', group: 'PA46', properties: {material_template: '130000000000000000000003', mineral: 0, glass_fiber: 40, carbon_fiber: 0}, numbers: ['5514263423']},
res: {status: 'Material name already taken'}
});
});
@@ -849,7 +834,7 @@ describe('/material', () => {
url: '/material/new',
auth: {basic: 'janedoe'},
httpStatus: 400,
- req: {supplier: 'Du Pont', group: 'PBT', mineral: 0, glass_fiber: 30, carbon_fiber: 0, numbers: [{color: 'black', number: '5515798402'}]},
+ req: {supplier: 'Du Pont', group: 'PBT', properties: {material_template: '130000000000000000000003', mineral: 0, glass_fiber: 30, carbon_fiber: 0}, numbers: ['5515798402']},
res: {status: 'Invalid body format', details: '"name" is required'}
});
});
@@ -859,7 +844,7 @@ describe('/material', () => {
url: '/material/new',
auth: {basic: 'janedoe'},
httpStatus: 400,
- req: {name: 'Crastin CE 2510', group: 'PBT', mineral: 0, glass_fiber: 30, carbon_fiber: 0, numbers: [{color: 'black', number: '5515798402'}]},
+ req: {name: 'Crastin CE 2510', group: 'PBT', properties: {material_template: '130000000000000000000003', mineral: 0, glass_fiber: 30, carbon_fiber: 0}, numbers: ['5515798402']},
res: {status: 'Invalid body format', details: '"supplier" is required'}
});
});
@@ -869,7 +854,7 @@ describe('/material', () => {
url: '/material/new',
auth: {basic: 'janedoe'},
httpStatus: 400,
- req: {name: 'Crastin CE 2510', supplier: 'Du Pont', mineral: 0, glass_fiber: 30, carbon_fiber: 0, numbers: [{color: 'black', number: '5515798402'}]},
+ req: {name: 'Crastin CE 2510', supplier: 'Du Pont', properties: {material_template: '130000000000000000000003', mineral: 0, glass_fiber: 30, carbon_fiber: 0}, numbers: ['5515798402']},
res: {status: 'Invalid body format', details: '"group" is required'}
});
});
@@ -879,7 +864,7 @@ describe('/material', () => {
url: '/material/new',
auth: {basic: 'janedoe'},
httpStatus: 400,
- req: {name: 'Crastin CE 2510', supplier: 'Du Pont', group: 'PBT', glass_fiber: 30, carbon_fiber: 0, numbers: [{color: 'black', number: '5515798402'}]},
+ req: {name: 'Crastin CE 2510', supplier: 'Du Pont', group: 'PBT', properties: {material_template: '130000000000000000000003', glass_fiber: 30, carbon_fiber: 0}, numbers: ['5515798402']},
res: {status: 'Invalid body format', details: '"mineral" is required'}
});
});
@@ -889,7 +874,7 @@ describe('/material', () => {
url: '/material/new',
auth: {basic: 'janedoe'},
httpStatus: 400,
- req: {name: 'Crastin CE 2510', supplier: 'Du Pont', group: 'PBT', mineral: 0, carbon_fiber: 0, numbers: [{color: 'black', number: '5515798402'}]},
+ req: {name: 'Crastin CE 2510', supplier: 'Du Pont', group: 'PBT', properties: {material_template: '130000000000000000000003', mineral: 0, carbon_fiber: 0}, numbers: ['5515798402']},
res: {status: 'Invalid body format', details: '"glass_fiber" is required'}
});
});
@@ -899,7 +884,7 @@ describe('/material', () => {
url: '/material/new',
auth: {basic: 'janedoe'},
httpStatus: 400,
- req: {name: 'Crastin CE 2510', supplier: 'Du Pont', group: 'PBT', mineral: 0, glass_fiber: 30, numbers: [{color: 'black', number: '5515798402'}]},
+ req: {name: 'Crastin CE 2510', supplier: 'Du Pont', group: 'PBT', properties: {material_template: '130000000000000000000003', mineral: 0, glass_fiber: 30}, numbers: ['5515798402']},
res: {status: 'Invalid body format', details: '"carbon_fiber" is required'}
});
});
@@ -909,28 +894,78 @@ describe('/material', () => {
url: '/material/new',
auth: {basic: 'janedoe'},
httpStatus: 400,
- req: {name: 'Crastin CE 2510', supplier: 'Du Pont', group: 'PBT', mineral: 0, glass_fiber: 30, carbon_fiber: 0},
+ req: {name: 'Crastin CE 2510', supplier: 'Du Pont', group: 'PBT', properties: {material_template: '130000000000000000000003', mineral: 0, glass_fiber: 30, carbon_fiber: 0}},
res: {status: 'Invalid body format', details: '"numbers" is required'}
});
});
- it('rejects a missing color name', done => {
+ it('rejects not specified properties parameters', done => {
TestHelper.request(server, done, {
method: 'post',
url: '/material/new',
auth: {basic: 'janedoe'},
httpStatus: 400,
- req: {name: 'Crastin CE 2510', supplier: 'Du Pont', group: 'PBT', mineral: 0, glass_fiber: 30, carbon_fiber: 0, numbers: [{number: '5515798402'}]},
- res: {status: 'Invalid body format', details: '"numbers[0].color" is required'}
+ req: {name: 'Crastin CE 2510', supplier: 'Du Pont', group: 'PBT', properties: {material_template: '130000000000000000000003', mineral: 0, carbon_fiber: 0, glass_fiber: 30, x: 47}, numbers: ['5515798402']},
+ res: {status: 'Invalid body format', details: '"x" is not allowed'}
});
});
- it('rejects a missing color number', done => {
+ it('rejects a properties parameter not in the value range', done => {
TestHelper.request(server, done, {
method: 'post',
url: '/material/new',
auth: {basic: 'janedoe'},
httpStatus: 400,
- req: {name: 'Crastin CE 2510', supplier: 'Du Pont', group: 'PBT', mineral: 0, glass_fiber: 30, carbon_fiber: 0, numbers: [{color: 'black'}]},
- res: {status: 'Invalid body format', details: '"numbers[0].number" is required'}
+ req: {name: 'Glue2', supplier: 'BASF', group: 'Glue', properties: {material_template: '130000000000000000000002', stickiness: 'not so much'}, numbers: []},
+ res: {status: 'Invalid body format', details: '"stickiness" must be one of [not so sticky, medium, very sticky]'}
+ });
+ });
+ it('rejects a properties parameter below minimum range', done => {
+ TestHelper.request(server, done, {
+ method: 'post',
+ url: '/material/new',
+ auth: {basic: 'janedoe'},
+ httpStatus: 400,
+ req: {name: 'Crastin CE 2510', supplier: 'Du Pont', group: 'PBT', properties: {material_template: '130000000000000000000003', glass_fiber: -0.3}, numbers: ['5515798402']},
+ res: {status: 'Invalid body format', details: '"glass_fiber" must be larger than or equal to 0'}
+ });
+ });
+ it('rejects a properties parameter above maximum range', done => {
+ TestHelper.request(server, done, {
+ method: 'post',
+ url: '/material/new',
+ auth: {basic: 'janedoe'},
+ httpStatus: 400,
+ req: {name: 'Crastin CE 2510', supplier: 'Du Pont', group: 'PBT', properties: {material_template: '130000000000000000000003', glass_fiber: 100.001}, numbers: ['5515798402']},
+ res: {status: 'Invalid body format', details: '"glass_fiber" must be less than or equal to 100'}
+ });
+ });
+ it('rejects an invalid material template', done => {
+ TestHelper.request(server, done, {
+ method: 'post',
+ url: '/material/new',
+ auth: {basic: 'janedoe'},
+ httpStatus: 400,
+ req: {name: 'Crastin CE 2510', supplier: 'Du Pont', group: 'PBT', properties: {material_template: '130000000h00000000000003', glass_fiber: 30}, numbers: ['5515798402']},
+ res: {status: 'Material template not available'}
+ });
+ });
+ it('rejects an unknown material template', done => {
+ TestHelper.request(server, done, {
+ method: 'post',
+ url: '/material/new',
+ auth: {basic: 'janedoe'},
+ httpStatus: 400,
+ req: {name: 'Crastin CE 2510', supplier: 'Du Pont', group: 'PBT', properties: {material_template: '100000000000000000000003', glass_fiber: 30}, numbers: ['5515798402']},
+ res: {status: 'Material template not available'}
+ });
+ });
+ it('rejects an old version of a material template', done => {
+ TestHelper.request(server, done, {
+ method: 'post',
+ url: '/material/new',
+ auth: {basic: 'janedoe'},
+ httpStatus: 400,
+ req: {name: 'Crastin CE 2510', supplier: 'Du Pont', group: 'PBT', properties: {material_template: '130000000000000000000001', glass_fiber: 30}, numbers: ['5515798402']},
+ res: {status: 'Old template version not allowed'}
});
});
it('rejects an API key', done => {
@@ -939,7 +974,7 @@ describe('/material', () => {
url: '/material/new',
auth: {key: 'janedoe'},
httpStatus: 401,
- req: {name: 'Crastin CE 2510', supplier: 'Du Pont', group: 'PBT', mineral: 0, glass_fiber: 30, carbon_fiber: 0, numbers: []}
+ req: {name: 'Crastin CE 2510', supplier: 'Du Pont', group: 'PBT', properties: {material_template: '130000000000000000000003', mineral: 0, glass_fiber: 30, carbon_fiber: 0}, numbers: []}
});
});
it('rejects requests from a read user', done => {
@@ -948,7 +983,7 @@ describe('/material', () => {
url: '/material/new',
auth: {basic: 'user'},
httpStatus: 403,
- req: {name: 'Crastin CE 2510', supplier: 'Du Pont', group: 'PBT', mineral: 0, glass_fiber: 30, carbon_fiber: 0, numbers: []}
+ req: {name: 'Crastin CE 2510', supplier: 'Du Pont', group: 'PBT', properties: {material_template: '130000000000000000000003', mineral: 0, glass_fiber: 30, carbon_fiber: 0}, numbers: []}
});
});
it('rejects unauthorized requests', done => {
@@ -956,7 +991,7 @@ describe('/material', () => {
method: 'post',
url: '/material/new',
httpStatus: 401,
- req: {name: 'Crastin CE 2510', supplier: 'Du Pont', group: 'PBT', mineral: 0, glass_fiber: 30, carbon_fiber: 0, numbers: []}
+ req: {name: 'Crastin CE 2510', supplier: 'Du Pont', group: 'PBT', properties: {material_template: '130000000000000000000003', mineral: 0, glass_fiber: 30, carbon_fiber: 0}, numbers: []}
});
});
});
diff --git a/src/routes/material.ts b/src/routes/material.ts
index 3f34e3a..54a49ab 100644
--- a/src/routes/material.ts
+++ b/src/routes/material.ts
@@ -11,6 +11,8 @@ import res400 from './validate/res400';
import mongoose from 'mongoose';
import globals from '../globals';
import db from '../db';
+import MaterialTemplateModel from '../models/material_template';
+import ParametersValidate from './validate/parameters';
@@ -92,6 +94,9 @@ router.put('/material/' + IdValidate.parameter(), (req, res, next) => {
material = await supplierResolve(material, req, next);
if (!material) return;
}
+ if (material.hasOwnProperty('properties')) {
+ if (!await propertiesCheck(material.properties, 'change', res, next, materialData.properties.material_template.toString() !== material.properties.material_template)) return;
+ }
// check for changes
if (!_.isEqual(_.pick(IdValidate.stringify(materialData), _.keys(material)), IdValidate.stringify(material))) {
@@ -149,7 +154,7 @@ router.post('/material/new', async (req, res, next) => {
if (!material) return;
material = await supplierResolve(material, req, next);
if (!material) return;
-
+ if (!await propertiesCheck(material.properties, 'new', res, next)) return;
material.status = globals.status.new; // set status to new
await new MaterialModel(material).save(async (err, data) => {
@@ -211,6 +216,37 @@ async function supplierResolve (material, req, next) {
return material;
}
+async function propertiesCheck (properties, param, res, next, checkVersion = true) { // validate material properties, returns false if invalid, otherwise template data
+ if (!properties.material_template || !IdValidate.valid(properties.material_template)) { // template id not found
+ res.status(400).json({status: 'Material template not available'});
+ return false;
+ }
+ const materialData = await MaterialTemplateModel.findById(properties.material_template).lean().exec().catch(err => next(err)) as any;
+ if (materialData instanceof Error) return false;
+ if (!materialData) { // template not found
+ res.status(400).json({status: 'Material template not available'});
+ return false;
+ }
+
+ if (checkVersion) {
+ // get all template versions and check if given is latest
+ const materialVersions = await MaterialTemplateModel.find({first_id: materialData.first_id}).sort({version: -1}).lean().exec().catch(err => next(err)) as any;
+ if (materialVersions instanceof Error) return false;
+ if (properties.material_template !== materialVersions[0]._id.toString()) { // template not latest
+ res.status(400).json({status: 'Old template version not allowed'});
+ return false;
+ }
+ }
+
+ // validate parameters
+ const {error, value} = ParametersValidate.input(_.omit(properties, 'material_template'), materialData.parameters, param);
+ if (error) {res400(error, res); return false;}
+ Object.keys(value).forEach(key => {
+ properties[key] = value[key];
+ });
+ return materialData;
+}
+
function setStatus (status, req, res, next) { // set measurement status
MaterialModel.findByIdAndUpdate(req.params.id, {status: status}).log(req).lean().exec((err, data) => {
if (err) return next(err);
diff --git a/src/routes/measurement.spec.ts b/src/routes/measurement.spec.ts
index dd43520..d33bfdc 100644
--- a/src/routes/measurement.spec.ts
+++ b/src/routes/measurement.spec.ts
@@ -600,7 +600,7 @@ describe('/measurement', () => {
auth: {basic: 'janedoe'},
httpStatus: 400,
req: {sample_id: '400000000000h00000000001', values: {'weight %': 0.8, 'standard deviation': 0.1}, measurement_template: '300000000000000000000002'},
- res: {status: 'Invalid body format', details: '"sample_id" with value "400000000000h00000000001" fails to match the required pattern: /[0-9a-f]{24}/'}
+ res: {status: 'Invalid body format', details: 'Invalid object id'}
});
});
it('rejects a sample id not available', done => {
@@ -620,7 +620,7 @@ describe('/measurement', () => {
auth: {basic: 'janedoe'},
httpStatus: 400,
req: {sample_id: '400000000000000000000001', values: {'weight %': 0.8, 'standard deviation': 0.1}, measurement_template: '30000000000h000000000002'},
- res: {status: 'Invalid body format', details: '"measurement_template" with value "30000000000h000000000002" fails to match the required pattern: /[0-9a-f]{24}/'}
+ res: {status: 'Invalid body format', details: 'Invalid object id'}
});
});
it('rejects a measurement_template not available', done => {
diff --git a/src/routes/sample.spec.ts b/src/routes/sample.spec.ts
index 7dc5f24..ca62d16 100644
--- a/src/routes/sample.spec.ts
+++ b/src/routes/sample.spec.ts
@@ -7,10 +7,9 @@ import TestHelper from "../test/helper";
import globals from '../globals';
import mongoose from 'mongoose';
-// TODO: generate output for ML in format DPT -> data, implement filtering, field selection
-// TODO: generate csv
-// TODO: write script for data import
+
// TODO: allowed types: tension rod, part, granulate, other
+// TODO: filter by conditions and material properties
describe('/sample', () => {
let server;
@@ -201,7 +200,7 @@ describe('/sample', () => {
}).end((err, res) => {
if (err) return done(err);
should(res.body[0]).have.property('number', 'Rng36');
- should(res.body[1]).have.property('number', '33');
+ should(res.body[1]).have.property('number', '34');
should(res.body[res.body.length - 1]).have.property('number', '1');
done();
});
@@ -215,7 +214,7 @@ describe('/sample', () => {
}).end((err, res) => {
if (err) return done(err);
should(res.body[0]).have.property('_id', '400000000000000000000006');
- should(res.body[1]).have.property('_id', '400000000000000000000002');
+ should(res.body[1]).have.property('_id', '400000000000000000000007');
done();
});
});
@@ -227,7 +226,7 @@ describe('/sample', () => {
httpStatus: 200
}).end((err, res) => {
if (err) return done(err);
- should(res.body[0]).have.property('_id', '400000000000000000000002');
+ should(res.body[0]).have.property('_id', '400000000000000000000007');
should(res.body[1]).have.property('_id', '400000000000000000000006');
done();
});
@@ -262,14 +261,14 @@ describe('/sample', () => {
it('multiplies the sample information for each spectrum', done => {
TestHelper.request(server, done, {
method: 'get',
- url: '/samples?status=all&fields[]=number&fields[]=measurements.spectrum',
+ url: '/samples?status=all&fields[]=number&fields[]=measurements.spectrum.dpt',
auth: {basic: 'janedoe'},
httpStatus: 200
}).end((err, res) => {
if (err) return done(err);
should(res.body).have.lengthOf(2);
- should(res.body[0]).have.property('spectrum', [[3997.12558,98.00555],[3995.08519,98.03253],[3993.04480,98.02657]]);
- should(res.body[1]).have.property('spectrum', [[3996.12558,98.00555],[3995.08519,98.03253],[3993.04480,98.02657]]);
+ should(res.body[0].spectrum).have.property('dpt', [[3997.12558,98.00555],[3995.08519,98.03253],[3993.04480,98.02657]]);
+ should(res.body[1].spectrum).have.property('dpt', [[3996.12558,98.00555],[3995.08519,98.03253],[3993.04480,98.02657]]);
done();
});
});
@@ -335,19 +334,51 @@ describe('/sample', () => {
done();
});
});
- it('filters multiple properties', done => {
+ it('filters by a measurement properties property', done => {
TestHelper.request(server, done, {
method: 'get',
- url: '/samples?status=all&fields[]=number&fields[]=material.glass_fiber&fields[]=batch&filters[]=%7B%22mode%22%3A%22eq%22%2C%22field%22%3A%22material.glass_fiber%22%2C%22values%22%3A%5B33%5D%7D&filters[]=%7B%22mode%22%3A%22lte%22%2C%22field%22%3A%22number%22%2C%22values%22%3A%5B%22Rng33%22%5D%7D&filters[]=%7B%22mode%22%3A%22nin%22%2C%22field%22%3A%22batch%22%2C%22values%22%3A%5B%221704-005%22%5D%7D',
+ url: '/samples?status=all&fields[]=number&fields[]=material.name&fields[]=material.properties.glass_fiber&filters[]=%7B%22mode%22%3A%22eq%22%2C%22field%22%3A%22material.properties.glass_fiber%22%2C%22values%22%3A%5B%2225%22%5D%7D',
auth: {basic: 'janedoe'},
httpStatus: 200
}).end((err, res) => {
if (err) return done(err);
- should(res.body).have.lengthOf(1);
- should(res.body[0]).be.eql({number: '32', material: {glass_fiber: 33}, batch: '1653000308'});
+ should(res.body).have.lengthOf(2);
+ should(res.body).matchEach(sample => {
+ should(sample.material.properties.glass_fiber).be.eql(25);
+ });
done();
});
- }); // TODO: do measurement pipeline, check if it works with UI
+ });
+ it('filters and sorts by a measurement properties property', done => {
+ TestHelper.request(server, done, {
+ method: 'get',
+ url: '/samples?status=all&sort=material.properties.glass_fiber-desc&fields[]=number&fields[]=material.name&fields[]=material.properties.glass_fiber&filters[]=%7B%22mode%22%3A%22eq%22%2C%22field%22%3A%22material.properties.glass_fiber%22%2C%22values%22%3A%5B%2225%22%5D%7D',
+ auth: {basic: 'janedoe'},
+ httpStatus: 200
+ }).end((err, res) => {
+ if (err) return done(err);
+ should(res.body).have.lengthOf(2);
+ should(res.body[0].number).be.eql('Rng36');
+ should(res.body[1].number).be.eql('1');
+ should(res.body).matchEach(sample => {
+ should(sample.material.properties.glass_fiber).be.eql(25);
+ });
+ done();
+ });
+ });
+ it('filters multiple properties', done => {
+ TestHelper.request(server, done, {
+ method: 'get',
+ url: '/samples?status=all&fields[]=number&fields[]=batch&filters[]=%7B%22mode%22%3A%22lte%22%2C%22field%22%3A%22number%22%2C%22values%22%3A%5B%22Rng33%22%5D%7D&filters[]=%7B%22mode%22%3A%22nin%22%2C%22field%22%3A%22batch%22%2C%22values%22%3A%5B%221704-005%22%5D%7D',
+ auth: {basic: 'janedoe'},
+ httpStatus: 200
+ }).end((err, res) => {
+ if (err) return done(err);
+ should(res.body).have.lengthOf(4);
+ should(res.body[0]).be.eql({number: '1', batch: ''});
+ done();
+ });
+ });
it('rejects an invalid JSON string as a filters parameter', done => {
TestHelper.request(server, done, {
method: 'get',
@@ -360,10 +391,10 @@ describe('/sample', () => {
it('rejects an invalid filter mode', done => {
TestHelper.request(server, done, {
method: 'get',
- url: '/samples?status=all&fields[]=number&fields[]=material.glass_fiber&fields[]=batch&filters[]=%7B%22mode%22%3A%22xx%22%2C%22field%22%3A%22batch%22%2C%22values%22%3A%5B%221704-005%22%5D%7D',
+ url: '/samples?status=all&fields[]=number&fields[]=batch&filters[]=%7B%22mode%22%3A%22xx%22%2C%22field%22%3A%22batch%22%2C%22values%22%3A%5B%221704-005%22%5D%7D',
auth: {basic: 'janedoe'},
httpStatus: 400,
- res: {status: 'Invalid body format', details: '"filters[0].mode" must be one of [eq, ne, lt, lte, gt, gte, in, nin]'}
+ res: {status: 'Invalid body format', details: '"filters[0].mode" must be one of [eq, ne, lt, lte, gt, gte, in, nin, stringin]'}
});
});
it('rejects an filter field not existing', done => {
@@ -372,7 +403,7 @@ describe('/sample', () => {
url: '/samples?status=all&fields[]=number&fields[]=material.glass_fiber&fields[]=batch&filters[]=%7B%22mode%22%3A%22eq%22%2C%22field%22%3A%22xx%22%2C%22values%22%3A%5B%221704-005%22%5D%7D',
auth: {basic: 'janedoe'},
httpStatus: 400,
- res: {status: 'Invalid body format', details: '"filters[0].field" with value "xx" fails to match the required pattern: /^(_id|color|number|type|batch|added|material\\.name|material\\.supplier|material\\.group|material\\.mineral|material\\.glass_fiber|material\\.carbon_fiber|material\\.number|measurements\\.(?!spectrum).+|condition|material_id|material|note_id|user_id|material\\._id|material\\.numbers|measurements\\.spectrum)$/m'}
+ res: {status: 'Invalid body format', details: 'Invalid JSON string for filter parameter'}
});
});
it('rejects unknown measurement names', done => {
@@ -402,10 +433,29 @@ describe('/sample', () => {
it('returns only the fields specified', done => {
TestHelper.request(server, done, {
method: 'get',
- url: '/samples?status=all&page-size=1&fields[]=number&fields[]=condition&fields[]=color&fields[]=material.name&fields[]=material.mineral',
+ url: '/samples?status=all&page-size=1&fields[]=number&fields[]=condition&fields[]=color&fields[]=material.name&fields[]=material.supplier',
auth: {basic: 'janedoe'},
httpStatus: 200,
- res: [{number: '1', condition: {material: 'copper', weeks: 3, condition_template: '200000000000000000000001'}, color: 'black', material: {name: 'Schulamid 66 GF 25 H', mineral: 0}}]
+ res: [{number: '1', condition: {material: 'copper', weeks: 3, condition_template: '200000000000000000000001'}, color: 'black', material: {name: 'Schulamid 66 GF 25 H', supplier: 'Schulmann'}}]
+ });
+ });
+ it('returns specified material properties fields', done => {
+ TestHelper.request(server, done, {
+ method: 'get',
+ url: '/samples?status=all&fields[]=number&fields[]=material.properties.glass_fiber&fields[]=material.name',
+ auth: {basic: 'janedoe'},
+ httpStatus: 200
+ }).end((err, res) => {
+ if (err) return done(err);
+ const json = require('../test/db.json');
+ should(res.body).matchEach(sample => {
+ const materialId = json.collections.samples.find(e => e.number === sample.number).material_id;
+ const material = json.collections.materials.find(e => e._id.toString() == materialId);
+ should(sample).have.only.keys('number', 'material');
+ should(sample.material.name).be.eql(material.name);
+ should(sample.material.properties.glass_fiber).be.eql(material.properties.glass_fiber);
+ });
+ done()
});
});
it('rejects a from-id not in the database', done => {
@@ -432,7 +482,7 @@ describe('/sample', () => {
url: '/samples?status=all&page-size=1&fields[]=xx',
auth: {basic: 'janedoe'},
httpStatus: 400,
- res: {status: 'Invalid body format', details: '"fields[0]" with value "xx" fails to match the required pattern: /^(_id|color|number|type|batch|added|material\\.name|material\\.supplier|material\\.group|material\\.mineral|material\\.glass_fiber|material\\.carbon_fiber|material\\.number|measurements\\.(?!spectrum).+|condition|material_id|material|note_id|user_id|material\\._id|material\\.numbers|measurements\\.spectrum)$/m'}
+ res: {status: 'Invalid body format', details: 'Invalid field name'}
});
});
it('rejects a negative page size', done => {
@@ -450,7 +500,7 @@ describe('/sample', () => {
url: '/samples?from-id=40000000000h000000000002',
auth: {basic: 'janedoe'},
httpStatus: 400,
- res: {status: 'Invalid body format', details: '"from-id" with value "40000000000h000000000002" fails to match the required pattern: /[0-9a-f]{24}/'}
+ res: {status: 'Invalid body format', details: 'Invalid object id'}
});
});
it('rejects a to-page without page-size', done => {
@@ -619,7 +669,7 @@ describe('/sample', () => {
url: '/sample/400000000000000000000003',
auth: {basic: 'janedoe'},
httpStatus: 200,
- res: {_id: '400000000000000000000003', number: '33', type: 'part', color: 'black', batch: '1704-005', condition: {material: 'copper', weeks: 3, condition_template: '200000000000000000000001'}, material: {_id: '100000000000000000000005', name: 'Amodel A 1133 HS', supplier: 'Solvay', group: 'PPA', mineral: 0, glass_fiber: 33, carbon_fiber: 0, numbers: [{color: 'black', number: '5514262406'}]}, notes: {comment: '', sample_references: [{sample_id: '400000000000000000000004', relation: 'granulate to sample'}], custom_fields: {'not allowed for new applications': true}}, measurements: [{_id: '800000000000000000000003', sample_id: '400000000000000000000003', values: {val1: 1}, measurement_template: '300000000000000000000003'}], user: 'admin'}
+ res: {_id: '400000000000000000000003', number: '33', type: 'part', color: 'black', batch: '1704-005', condition: {material: 'copper', weeks: 3, condition_template: '200000000000000000000001'}, material: {_id: '100000000000000000000005', name: 'Amodel A 1133 HS', supplier: 'Solvay', group: 'PPA', properties: {material_template: '130000000000000000000003', mineral: 0, glass_fiber: 33, carbon_fiber: 0}, numbers: ['5514262406']}, notes: {comment: '', sample_references: [{sample_id: '400000000000000000000004', relation: 'granulate to sample'}], custom_fields: {'not allowed for new applications': true}}, measurements: [{_id: '800000000000000000000003', sample_id: '400000000000000000000003', values: {val1: 1}, measurement_template: '300000000000000000000003'}], user: 'admin'}
});
});
it('works with an API key', done => {
@@ -628,7 +678,7 @@ describe('/sample', () => {
url: '/sample/400000000000000000000003',
auth: {key: 'janedoe'},
httpStatus: 200,
- res: {_id: '400000000000000000000003', number: '33', type: 'part', color: 'black', batch: '1704-005', condition: {material: 'copper', weeks: 3, condition_template: '200000000000000000000001'}, material: {_id: '100000000000000000000005', name: 'Amodel A 1133 HS', supplier: 'Solvay', group: 'PPA', mineral: 0, glass_fiber: 33, carbon_fiber: 0, numbers: [{color: 'black', number: '5514262406'}]}, notes: {comment: '', sample_references: [{sample_id: '400000000000000000000004', relation: 'granulate to sample'}], custom_fields: {'not allowed for new applications': true}}, measurements: [{_id: '800000000000000000000003', sample_id: '400000000000000000000003', values: {val1: 1}, measurement_template: '300000000000000000000003'}], user: 'admin'}
+ res: {_id: '400000000000000000000003', number: '33', type: 'part', color: 'black', batch: '1704-005', condition: {material: 'copper', weeks: 3, condition_template: '200000000000000000000001'}, material: {_id: '100000000000000000000005', name: 'Amodel A 1133 HS', supplier: 'Solvay', group: 'PPA', properties: {material_template: '130000000000000000000003', mineral: 0, glass_fiber: 33, carbon_fiber: 0}, numbers: ['5514262406']}, notes: {comment: '', sample_references: [{sample_id: '400000000000000000000004', relation: 'granulate to sample'}], custom_fields: {'not allowed for new applications': true}}, measurements: [{_id: '800000000000000000000003', sample_id: '400000000000000000000003', values: {val1: 1}, measurement_template: '300000000000000000000003'}], user: 'admin'}
});
});
it('returns a deleted sample for a maintain/admin user', done => {
@@ -637,7 +687,7 @@ describe('/sample', () => {
url: '/sample/400000000000000000000005',
auth: {basic: 'admin'},
httpStatus: 200,
- res: {_id: '400000000000000000000005', number: 'Rng33', type: 'granulate', color: 'black', batch: '1653000308', condition: {condition_template: '200000000000000000000003'}, material: {_id: '100000000000000000000005', name: 'Amodel A 1133 HS', supplier: 'Solvay', group: 'PPA', mineral: 0, glass_fiber: 33, carbon_fiber: 0, numbers: [{color: 'black', number: '5514262406'}]}, notes: {}, measurements: [], user: 'admin'}
+ res: {_id: '400000000000000000000005', number: 'Rng33', type: 'granulate', color: 'black', batch: '1653000308', condition: {condition_template: '200000000000000000000003'}, material: {_id: '100000000000000000000005', name: 'Amodel A 1133 HS', supplier: 'Solvay', group: 'PPA', properties: {material_template: '130000000000000000000003', mineral: 0, glass_fiber: 33, carbon_fiber: 0}, numbers: ['5514262406']}, notes: {}, measurements: [], user: 'admin'}
});
});
it('returns 403 for a write user when requesting a deleted sample', done => {
@@ -895,26 +945,6 @@ describe('/sample', () => {
});
});
});
- it('rejects a color not defined for the material', done => {
- TestHelper.request(server, done, {
- method: 'put',
- url: '/sample/400000000000000000000001',
- auth: {basic: 'janedoe'},
- httpStatus: 400,
- req: {type: 'part', color: 'signalviolet', batch: '114531', material_id: '100000000000000000000001', notes: {comment: 'Testcomment', sample_references: [{sample_id: '400000000000000000000003', relation: 'part to this sample'}]}},
- res: {status: 'Color not available for material'}
- });
- });
- it('rejects an undefined color for the same material', done => {
- TestHelper.request(server, done, {
- method: 'put',
- url: '/sample/400000000000000000000001',
- auth: {basic: 'janedoe'},
- httpStatus: 400,
- req: {type: 'part', color: 'signalviolet', batch: '114531', notes: {comment: 'Testcomment', sample_references: [{sample_id: '400000000000000000000003', relation: 'part to this sample'}]}},
- res: {status: 'Color not available for material'}
- });
- });
it('rejects an unknown material id', done => {
TestHelper.request(server, done, {
method: 'put',
@@ -952,7 +982,7 @@ describe('/sample', () => {
auth: {basic: 'janedoe'},
httpStatus: 400,
req: {type: 'part', color: 'signalviolet', batch: '114531', material_id: '10000000000h000000000001', notes: {comment: 'Testcomment', sample_references: [{sample_id: '400000000000000000000003', relation: 'part to this sample'}]}},
- res: {status: 'Invalid body format', details: '"material_id" with value "10000000000h000000000001" fails to match the required pattern: /[0-9a-f]{24}/'}
+ res: {status: 'Invalid body format', details: 'Invalid object id'}
});
});
it('rejects an invalid id', done => {
@@ -1054,7 +1084,7 @@ describe('/sample', () => {
res: {_id: '400000000000000000000004', number: '32', type: 'granulate', color: 'black', batch: '1653000308', condition: {p1: 36, condition_template: '200000000000000000000004'}, material_id: '100000000000000000000005', note_id: '500000000000000000000003', user_id: '000000000000000000000003', added: '2004-01-10T13:37:04.000Z'}
});
});
- it('rejects an changing back to an empty condition', done => {
+ it('rejects changing back to an empty condition', done => {
TestHelper.request(server, done, {
method: 'put',
url: '/sample/400000000000000000000001',
@@ -1313,6 +1343,67 @@ describe('/sample', () => {
});
});
+ describe('GET /sample/number/{number}', () => {
+ it('returns the right sample', done => {
+ TestHelper.request(server, done, {
+ method: 'get',
+ url: '/sample/number/33',
+ auth: {basic: 'janedoe'},
+ httpStatus: 200,
+ res: {_id: '400000000000000000000003', number: '33', type: 'part', color: 'black', batch: '1704-005', condition: {material: 'copper', weeks: 3, condition_template: '200000000000000000000001'}, material: {_id: '100000000000000000000005', name: 'Amodel A 1133 HS', supplier: 'Solvay', group: 'PPA', properties: {material_template: '130000000000000000000003', mineral: 0, glass_fiber: 33, carbon_fiber: 0}, numbers: ['5514262406']}, notes: {comment: '', sample_references: [{sample_id: '400000000000000000000004', relation: 'granulate to sample'}], custom_fields: {'not allowed for new applications': true}}, measurements: [{_id: '800000000000000000000003', sample_id: '400000000000000000000003', values: {val1: 1}, measurement_template: '300000000000000000000003'}], user: 'admin'}
+ });
+ });
+ it('works with an API key', done => {
+ TestHelper.request(server, done, {
+ method: 'get',
+ url: '/sample/number/33',
+ auth: {key: 'janedoe'},
+ httpStatus: 200,
+ res: {_id: '400000000000000000000003', number: '33', type: 'part', color: 'black', batch: '1704-005', condition: {material: 'copper', weeks: 3, condition_template: '200000000000000000000001'}, material: {_id: '100000000000000000000005', name: 'Amodel A 1133 HS', supplier: 'Solvay', group: 'PPA', properties: {material_template: '130000000000000000000003', mineral: 0, glass_fiber: 33, carbon_fiber: 0}, numbers: ['5514262406']}, notes: {comment: '', sample_references: [{sample_id: '400000000000000000000004', relation: 'granulate to sample'}], custom_fields: {'not allowed for new applications': true}}, measurements: [{_id: '800000000000000000000003', sample_id: '400000000000000000000003', values: {val1: 1}, measurement_template: '300000000000000000000003'}], user: 'admin'}
+ });
+ });
+ it('returns a deleted sample for a maintain/admin user', done => {
+ TestHelper.request(server, done, {
+ method: 'get',
+ url: '/sample/number/Rng33',
+ auth: {basic: 'admin'},
+ httpStatus: 200,
+ res: {_id: '400000000000000000000005', number: 'Rng33', type: 'granulate', color: 'black', batch: '1653000308', condition: {condition_template: '200000000000000000000003'}, material: {_id: '100000000000000000000005', name: 'Amodel A 1133 HS', supplier: 'Solvay', group: 'PPA', properties: {material_template: '130000000000000000000003', mineral: 0, glass_fiber: 33, carbon_fiber: 0}, numbers: ['5514262406']}, notes: {}, measurements: [], user: 'admin'}
+ });
+ });
+ it('returns 403 for a write user when requesting a deleted sample', done => {
+ TestHelper.request(server, done, {
+ method: 'get',
+ url: '/sample/number/Rng33',
+ auth: {basic: 'janedoe'},
+ httpStatus: 403
+ });
+ });
+ it('returns 404 for an unknown sample', done => {
+ TestHelper.request(server, done, {
+ method: 'get',
+ url: '/sample/number/Rng883',
+ auth: {basic: 'janedoe'},
+ httpStatus: 404
+ });
+ });
+ it('rejects an invalid id', done => {
+ TestHelper.request(server, done, {
+ method: 'get',
+ url: '/sample/number/xx-xx',
+ auth: {basic: 'janedoe'},
+ httpStatus: 404
+ });
+ });
+ it('rejects unauthorized requests', done => {
+ TestHelper.request(server, done, {
+ method: 'get',
+ url: '/sample/number/33',
+ httpStatus: 401
+ });
+ });
+ });
+
describe('PUT /sample/restore/{id}', () => {
it('sets the status', done => {
TestHelper.request(server, done, {
@@ -1642,16 +1733,6 @@ describe('/sample', () => {
done();
});
});
- it('rejects a color not defined for the material', done => {
- TestHelper.request(server, done, {
- method: 'post',
- url: '/sample/new',
- auth: {basic: 'janedoe'},
- httpStatus: 400,
- req: {color: 'green', type: 'granulate', batch: '1560237365', material_id: '100000000000000000000001', notes: {comment: 'Testcomment', sample_references: [{sample_id: '400000000000000000000003', relation: 'part to this sample'}]}},
- res: {status: 'Color not available for material'}
- });
- });
it('rejects an unknown material id', done => {
TestHelper.request(server, done, {
method: 'post',
@@ -1853,7 +1934,7 @@ describe('/sample', () => {
auth: {basic: 'janedoe'},
httpStatus: 400,
req: {color: 'black', type: 'granulate', batch: '1560237365', material_id: '10000000000h000000000001', notes: {comment: 'Testcomment', sample_references: [{sample_id: '400000000000000000000003', relation: 'part to this sample'}]}},
- res: {status: 'Invalid body format', details: '"material_id" with value "10000000000h000000000001" fails to match the required pattern: /[0-9a-f]{24}/'}
+ res: {status: 'Invalid body format', details: 'Invalid object id'}
});
});
it('rejects an API key', done => {
diff --git a/src/routes/sample.ts b/src/routes/sample.ts
index 91ada86..e468a41 100644
--- a/src/routes/sample.ts
+++ b/src/routes/sample.ts
@@ -22,11 +22,13 @@ import csv from '../helpers/csv';
const router = express.Router();
// TODO: check added filter
-// TODO: return total number of pages -> use facet
// TODO: use query pointer
// TODO: convert filter value to number according to table model
// TODO: validation for filter parameters
// TODO: location/device sort/filter
+
+// TODO: think about material numbers
+
router.get('/samples', async (req, res, next) => {
if (!req.auth(res, ['read', 'write', 'maintain', 'dev', 'admin'], 'all')) return;
@@ -240,6 +242,13 @@ router.get('/samples', async (req, res, next) => {
&& e !== filters.sort[0] // field was not in sort
);
+ if (fieldsToAdd.find(e => e === 'notes')) { // add notes
+ queryPtr.push(
+ {$lookup: {from: 'notes', localField: 'note_id', foreignField: '_id', as: 'notes'}},
+ {$addFields: {notes: { $arrayElemAt: ['$notes', 0]}}}
+ );
+ }
+
if (fieldsToAdd.find(e => /material\./.test(e)) && !materialAdded) { // add material, was not added already
queryPtr.push(
{$lookup: {from: 'materials', localField: 'material_id', foreignField: '_id', as: 'material'}},
@@ -311,7 +320,7 @@ router.get('/samples', async (req, res, next) => {
if (!fieldsToAdd.find(e => /spectrum\./.test(e))) { // use streaming when including spectrum files
collection.aggregate(query).exec((err, data) => {
if (err) return next(err);
- if (data[0].count) {
+ if (data[0] && data[0].count) {
res.header('x-total-items', data[0].count.length > 0 ? data[0].count[0].count : 0);
res.header('Access-Control-Expose-Headers', 'x-total-items');
data = data[0].samples;
@@ -385,26 +394,7 @@ router.get('/sample/' + IdValidate.parameter(), (req, res, next) => {
SampleModel.findById(req.params.id).populate('material_id').populate('user_id', 'name').populate('note_id').exec(async (err, sampleData: any) => {
if (err) return next(err);
-
- if (sampleData) {
- await sampleData.populate('material_id.group_id').populate('material_id.supplier_id').execPopulate().catch(err => next(err));
- if (sampleData instanceof Error) return;
- sampleData = sampleData.toObject();
-
- if (sampleData.status === globals.status.deleted && !req.auth(res, ['maintain', 'admin'], 'all')) return; // deleted samples only available for maintain/admin
- sampleData.material = sampleData.material_id; // map data to right keys
- sampleData.material.group = sampleData.material.group_id.name;
- sampleData.material.supplier = sampleData.material.supplier_id.name;
- sampleData.user = sampleData.user_id.name;
- sampleData.notes = sampleData.note_id ? sampleData.note_id : {};
- MeasurementModel.find({sample_id: mongoose.Types.ObjectId(req.params.id), status: {$ne: globals.status.deleted}}).lean().exec((err, data) => {
- sampleData.measurements = data;
- res.json(SampleValidate.output(sampleData, 'details'));
- });
- }
- else {
- res.status(404).json({status: 'Not found'});
- }
+ await sampleReturn(sampleData, req, res, next);
});
});
@@ -425,14 +415,12 @@ router.put('/sample/' + IdValidate.parameter(), (req, res, next) => {
// only maintain and admin are allowed to edit other user's data
if (sampleData.user_id.toString() !== req.authDetails.id && !req.auth(res, ['maintain', 'admin'], 'basic')) return;
-
if (sample.hasOwnProperty('material_id')) {
if (!await materialCheck(sample, res, next)) return;
}
else if (sample.hasOwnProperty('color')) {
if (!await materialCheck(sample, res, next, sampleData.material_id)) return;
}
-
if (sample.hasOwnProperty('condition') && !(_.isEmpty(sample.condition) && _.isEmpty(sampleData.condition))) { // do not execute check if condition is and was empty
if (!await conditionCheck(sample.condition, 'change', res, next, sampleData.condition.condition_template.toString() !== sample.condition.condition_template)) return;
}
@@ -514,6 +502,15 @@ router.delete('/sample/' + IdValidate.parameter(), (req, res, next) => {
});
});
+router.get('/sample/number/:number', (req, res, next) => {
+ if (!req.auth(res, ['read', 'write', 'maintain', 'dev', 'admin'], 'all')) return;
+
+ SampleModel.findOne({number: req.params.number}).populate('material_id').populate('user_id', 'name').populate('note_id').exec(async (err, sampleData: any) => {
+ if (err) return next(err);
+ await sampleReturn(sampleData, req, res, next);
+ });
+});
+
router.put('/sample/restore/' + IdValidate.parameter(), (req, res, next) => {
if (!req.auth(res, ['maintain', 'admin'], 'basic')) return;
@@ -615,11 +612,8 @@ module.exports = router;
async function numberGenerate (sample, req, res, next) { // generate number in format Location32, returns false on error
const sampleData = await SampleModel
- // .findOne({number: new RegExp('^' + req.authDetails.location + '[0-9]+$', 'm')})
- // .sort({number: -1})
- // .lean()
.aggregate([
- {$match: {number: new RegExp('^' + 'Rng' + '[0-9]+$', 'm')}},
+ {$match: {number: new RegExp('^' + req.authDetails.location + '[0-9]+$', 'm')}},
// {$addFields: {number2: {$toDecimal: {$arrayElemAt: [{$split: [{$arrayElemAt: [{$split: ['$number', 'Rng']}, 1]}, '_']}, 0]}}}}, // not working with MongoDb 3.6
{$addFields: {sortNumber: {$let: {
vars: {tmp: {$concat: ['000000000000000000000000000000', {$arrayElemAt: [{$split: [{$arrayElemAt: [{$split: ['$number', 'Rng']}, 1]}, '_']}, 0]}]}},
@@ -650,10 +644,6 @@ async function materialCheck (sample, res, next, id = sample.material_id) { //
res.status(400).json({status: 'Material not available'});
return false;
}
- if (sample.hasOwnProperty('color') && sample.color !== '' && !materialData.numbers.find(e => e.color === sample.color)) { // color for material not specified
- res.status(400).json({status: 'Color not available for material'});
- return false;
- }
return true;
}
@@ -764,11 +754,13 @@ function addFilterQueries (queryPtr, filters) { // returns array of match queri
}
function filterQueries (filters) {
- console.log(filters);
return filters.map(e => {
if (e.mode === 'or') { // allow or queries (needed for $ne added)
return {['$' + e.mode]: e.values};
}
+ else if (e.mode === 'stringin') {
+ return {[e.field]: {['$in']: [new RegExp(e.values[0])]}};
+ }
else {
return {[e.field]: {['$' + e.mode]: (e.mode.indexOf('in') >= 0 ? e.values : e.values[0])}}; // add filter criteria as {field: {$mode: value}}, only use first value when mode is not in/nin
}
@@ -777,4 +769,27 @@ function filterQueries (filters) {
function dateToOId (date) { // convert date to ObjectId
return mongoose.Types.ObjectId(Math.floor(date / 1000).toString(16) + '0000000000000000');
+}
+
+async function sampleReturn (sampleData, req, res, next) {
+ if (sampleData) {
+ console.log(sampleData);
+ await sampleData.populate('material_id.group_id').populate('material_id.supplier_id').execPopulate().catch(err => next(err));
+ if (sampleData instanceof Error) return;
+ sampleData = sampleData.toObject();
+
+ if (sampleData.status === globals.status.deleted && !req.auth(res, ['maintain', 'admin'], 'all')) return; // deleted samples only available for maintain/admin
+ sampleData.material = sampleData.material_id; // map data to right keys
+ sampleData.material.group = sampleData.material.group_id.name;
+ sampleData.material.supplier = sampleData.material.supplier_id.name;
+ sampleData.user = sampleData.user_id.name;
+ sampleData.notes = sampleData.note_id ? sampleData.note_id : {};
+ MeasurementModel.find({sample_id: sampleData._id, status: {$ne: globals.status.deleted}}).lean().exec((err, data) => {
+ sampleData.measurements = data;
+ res.json(SampleValidate.output(sampleData, 'details'));
+ });
+ }
+ else {
+ res.status(404).json({status: 'Not found'});
+ }
}
\ No newline at end of file
diff --git a/src/routes/template.spec.ts b/src/routes/template.spec.ts
index cd90108..b07014b 100644
--- a/src/routes/template.spec.ts
+++ b/src/routes/template.spec.ts
@@ -895,4 +895,49 @@ describe('/template', () => {
});
});
});
+
+ describe('/template/material', () => {
+ describe('GET /template/materials', () => {
+ it('returns all material templates', done => {
+ TestHelper.request(server, done, {
+ method: 'get',
+ url: '/template/materials',
+ auth: {basic: 'janedoe'},
+ httpStatus: 200
+ }).end((err, res) => {
+ if (err) return done(err);
+ const json = require('../test/db.json');
+ should(res.body).have.lengthOf(json.collections.material_templates.length);
+ should(res.body).matchEach(measurement => {
+ should(measurement).have.only.keys('_id', 'name', 'version', 'parameters');
+ should(measurement).have.property('_id').be.type('string');
+ should(measurement).have.property('name').be.type('string');
+ should(measurement).have.property('version').be.type('number');
+ should(measurement.parameters).matchEach(number => {
+ should(number).have.only.keys('name', 'range');
+ should(number).have.property('name').be.type('string');
+ should(number).have.property('range').be.type('object');
+ });
+ });
+ done();
+ });
+ });
+ it('rejects an API key', done => {
+ TestHelper.request(server, done, {
+ method: 'get',
+ url: '/template/materials',
+ auth: {key: 'janedoe'},
+ httpStatus: 401
+ });
+ });
+ it('rejects unauthorized requests', done => {
+ TestHelper.request(server, done, {
+ method: 'get',
+ url: '/template/materials',
+ httpStatus: 401
+ });
+ });
+ });
+ // other methods should be covered by measurement and condition tests
+ });
});
\ No newline at end of file
diff --git a/src/routes/template.ts b/src/routes/template.ts
index c3bd14b..20f1b3b 100644
--- a/src/routes/template.ts
+++ b/src/routes/template.ts
@@ -4,6 +4,7 @@ import _ from 'lodash';
import TemplateValidate from './validate/template';
import ConditionTemplateModel from '../models/condition_template';
import MeasurementTemplateModel from '../models/measurement_template';
+import MaterialTemplateModel from '../models/material_template';
import res400 from './validate/res400';
import IdValidate from './validate/id';
import mongoose from "mongoose";
@@ -13,7 +14,7 @@ import db from '../db';
const router = express.Router();
-router.get('/template/:collection(measurements|conditions)', (req, res, next) => {
+router.get('/template/:collection(measurements|conditions|materials)', (req, res, next) => {
if (!req.auth(res, ['read', 'write', 'maintain', 'dev', 'admin'], 'basic')) return;
req.params.collection = req.params.collection.replace(/s$/g, ''); // remove trailing s
@@ -23,7 +24,7 @@ router.get('/template/:collection(measurements|conditions)', (req, res, next) =>
});
});
-router.get('/template/:collection(measurement|condition)/' + IdValidate.parameter(), (req, res, next) => {
+router.get('/template/:collection(measurement|condition|material)/' + IdValidate.parameter(), (req, res, next) => {
if (!req.auth(res, ['read', 'write', 'maintain', 'dev', 'admin'], 'basic')) return;
model(req).findById(req.params.id).lean().exec((err, data) => {
@@ -37,7 +38,7 @@ router.get('/template/:collection(measurement|condition)/' + IdValidate.paramete
});
});
-router.put('/template/:collection(measurement|condition)/' + IdValidate.parameter(), async (req, res, next) => {
+router.put('/template/:collection(measurement|condition|material)/' + IdValidate.parameter(), async (req, res, next) => {
if (!req.auth(res, ['maintain', 'admin'], 'basic')) return;
const {error, value: template} = TemplateValidate.input(req.body, 'change');
@@ -62,7 +63,7 @@ router.put('/template/:collection(measurement|condition)/' + IdValidate.paramete
}
});
-router.post('/template/:collection(measurement|condition)/new', async (req, res, next) => {
+router.post('/template/:collection(measurement|condition|material)/new', async (req, res, next) => {
if (!req.auth(res, ['maintain', 'admin'], 'basic')) return;
const {error, value: template} = TemplateValidate.input(req.body, 'new');
@@ -82,5 +83,9 @@ router.post('/template/:collection(measurement|condition)/new', async (req, res,
module.exports = router;
function model (req) { // return right template model
- return req.params.collection === 'condition' ? ConditionTemplateModel : MeasurementTemplateModel;
+ switch (req.params.collection) {
+ case 'condition': return ConditionTemplateModel
+ case 'measurement': return MeasurementTemplateModel
+ case 'material': return MaterialTemplateModel
+ }
}
\ No newline at end of file
diff --git a/src/routes/user.spec.ts b/src/routes/user.spec.ts
index 79c0769..a39bc50 100644
--- a/src/routes/user.spec.ts
+++ b/src/routes/user.spec.ts
@@ -302,7 +302,7 @@ describe('/user', () => {
auth: {basic: 'admin'},
httpStatus: 400,
req: {pass: 'password'},
- res: {status: 'Invalid body format', details: '"pass" with value "password" fails to match the required pattern: /^(?=.*[0-9])(?=.*[a-z])(?=.*[A-Z])(?=.*[!"#%&\'()*+,-.\\/:;<=>?@[\\]^_`{|}~])(?=\\S+$)[a-zA-Z0-9!"#%&\'()*+,\\-.\\/:;<=>?@[\\]^_`{|}~]{8,}$/'}
+ res: {status: 'Invalid body format', details: 'password must have at least 8 characters, one uppercase and one lowercase character, one number and at least one of the following characters: !\"\\#%&\'()*+,-.\\/:;<=>?@[]^_`\u0000|}~'}
});
});
it('rejects requests from non-admins for another user', done => {
@@ -584,7 +584,7 @@ describe('/user', () => {
auth: {basic: 'admin'},
httpStatus: 400,
req: {email: 'john.doe@bosch.com', name: 'johndoe', pass: 'password', level: 'read', location: 'Rng', device_name: 'Alpha II'},
- res: {status: 'Invalid body format', details: '"pass" with value "password" fails to match the required pattern: /^(?=.*[0-9])(?=.*[a-z])(?=.*[A-Z])(?=.*[!"#%&\'()*+,-.\\/:;<=>?@[\\]^_`{|}~])(?=\\S+$)[a-zA-Z0-9!"#%&\'()*+,\\-.\\/:;<=>?@[\\]^_`{|}~]{8,}$/'}
+ res: {status: 'Invalid body format', details: 'password must have at least 8 characters, one uppercase and one lowercase character, one number and at least one of the following characters: !\"\\#%&\'()*+,-.\\/:;<=>?@[]^_`\u0000|}~'}
});
});
it('rejects requests from non-admins', done => {
diff --git a/src/routes/validate/id.ts b/src/routes/validate/id.ts
index 6b7b677..f640ccf 100644
--- a/src/routes/validate/id.ts
+++ b/src/routes/validate/id.ts
@@ -1,7 +1,7 @@
import Joi from '@hapi/joi';
export default class IdValidate {
- private static id = Joi.string().pattern(new RegExp('[0-9a-f]{24}')).length(24);
+ private static id = Joi.string().pattern(new RegExp('[0-9a-f]{24}')).length(24).messages({'string.pattern.base': 'Invalid object id'});
static get () { // return joi validation
return this.id;
diff --git a/src/routes/validate/material.ts b/src/routes/validate/material.ts
index 969ac43..74214d5 100644
--- a/src/routes/validate/material.ts
+++ b/src/routes/validate/material.ts
@@ -13,31 +13,13 @@ export default class MaterialValidate { // validate input for material
group: Joi.string()
.max(128),
- mineral: Joi.number()
- .integer()
- .min(0)
- .max(100),
-
- glass_fiber: Joi.number()
- .integer()
- .min(0)
- .max(100),
-
- carbon_fiber: Joi.number()
- .integer()
- .min(0)
- .max(100),
+ properties: Joi.object(),
numbers: Joi.array()
- .items(Joi.object({
- color: Joi.string()
- .max(128)
- .required(),
- number: Joi.string()
- .max(128)
- .allow('')
- .required()
- }))
+ .items(
+ Joi.string()
+ .max(64)
+ )
};
static input (data, param) { // validate input, set param to 'new' to make all attributes required
@@ -46,9 +28,7 @@ export default class MaterialValidate { // validate input for material
name: this.material.name.required(),
supplier: this.material.supplier.required(),
group: this.material.group.required(),
- mineral: this.material.mineral.required(),
- glass_fiber: this.material.glass_fiber.required(),
- carbon_fiber: this.material.carbon_fiber.required(),
+ properties: this.material.properties.required(),
numbers: this.material.numbers.required()
}).validate(data);
}
@@ -57,9 +37,7 @@ export default class MaterialValidate { // validate input for material
name: this.material.name,
supplier: this.material.supplier,
group: this.material.group,
- mineral: this.material.mineral,
- glass_fiber: this.material.glass_fiber,
- carbon_fiber: this.material.carbon_fiber,
+ properties: this.material.properties,
numbers: this.material.numbers
}).validate(data);
}
@@ -77,9 +55,7 @@ export default class MaterialValidate { // validate input for material
name: this.material.name,
supplier: this.material.supplier,
group: this.material.group,
- mineral: this.material.mineral,
- glass_fiber: this.material.glass_fiber,
- carbon_fiber: this.material.carbon_fiber,
+ properties: this.material.properties,
numbers: this.material.numbers
}).validate(data, {stripUnknown: true});
return error !== undefined? null : value;
@@ -101,9 +77,7 @@ export default class MaterialValidate { // validate input for material
name: this.material.name,
supplier: this.material.supplier,
group: this.material.group,
- mineral: this.material.mineral,
- glass_fiber: this.material.glass_fiber,
- carbon_fiber: this.material.carbon_fiber,
+ properties: this.material.properties,
numbers: this.material.numbers
});
}
diff --git a/src/routes/validate/parameters.ts b/src/routes/validate/parameters.ts
index e6070b0..61b48d3 100644
--- a/src/routes/validate/parameters.ts
+++ b/src/routes/validate/parameters.ts
@@ -10,6 +10,7 @@ export default class ParametersValidate {
.valid(...parameter.range.values);
}
else if (parameter.range.hasOwnProperty('min') && parameter.range.hasOwnProperty('max')) {
+
joiObject[parameter.name] = Joi.number()
.min(parameter.range.min)
.max(parameter.range.max);
diff --git a/src/routes/validate/sample.ts b/src/routes/validate/sample.ts
index 3fb28d9..3e9aed3 100644
--- a/src/routes/validate/sample.ts
+++ b/src/routes/validate/sample.ts
@@ -62,23 +62,22 @@ export default class SampleValidate {
'material.name',
'material.supplier',
'material.group',
- 'material.mineral',
- 'material.glass_fiber',
- 'material.carbon_fiber',
'material.number',
+ 'material.properties.*',
'measurements.(?!spectrum)*'
];
private static fieldKeys = [
...SampleValidate.sortKeys,
'condition',
+ 'notes',
'material_id',
'material',
'note_id',
'user_id',
'material._id',
'material.numbers',
- 'measurements.spectrum.dpt'
+ 'measurements.spectrum.dpt',
];
static input (data, param) { // validate input, set param to 'new' to make all attributes required
@@ -136,6 +135,7 @@ export default class SampleValidate {
material_id: IdValidate.get(),
material: MaterialValidate.outputV().append({number: Joi.string().max(128).allow('')}),
note_id: IdValidate.get().allow(null),
+ notes: this.sample.notes,
user_id: IdValidate.get(),
added: this.sample.added
};
@@ -175,8 +175,8 @@ export default class SampleValidate {
let validator;
let field = data.filters[i].field
if (/material\./.test(field)) { // select right validation model
- validator = MaterialValidate.outputV().append({number: Joi.string().max(128).allow('')});
- field = field.replace('material.', '');
+ validator = MaterialValidate.outputV().append({number: Joi.string().max(128).allow(''), properties: Joi.alternatives().try(Joi.number(), Joi.string().max(128))});
+ field = field.replace('material.', '').split('.')[0];
}
else if (/measurements\./.test(field)) {
validator = Joi.object({
@@ -195,7 +195,6 @@ export default class SampleValidate {
validator = Joi.object(this.sample);
}
const {value, error} = validator.validate({[field]: e});
- console.log(value);
if (error) throw error; // reject invalid values // TODO: return exact error description, handle in frontend filters
return value[field];
});
@@ -212,11 +211,11 @@ export default class SampleValidate {
'page-size': Joi.number().integer().min(1),
sort: Joi.string().pattern(new RegExp('^(' + this.sortKeys.join('|').replace(/\./g, '\\.').replace(/\*/g, '.+') + ')-(asc|desc)$', 'm')).default('_id-asc'),
csv: Joi.boolean().default(false),
- fields: Joi.array().items(Joi.string().pattern(new RegExp('^(' + this.fieldKeys.join('|').replace(/\./g, '\\.').replace(/\*/g, '.+') + ')$', 'm'))).default(['_id','number','type','batch','material_id','color','condition','note_id','user_id','added']),
+ fields: Joi.array().items(Joi.string().pattern(new RegExp('^(' + this.fieldKeys.join('|').replace(/\./g, '\\.').replace(/\*/g, '.+') + ')$', 'm'))).default(['_id','number','type','batch','material_id','color','condition','note_id','user_id','added']).messages({'string.pattern.base': 'Invalid field name'}),
filters: Joi.array().items(Joi.object({
- mode: Joi.string().valid('eq', 'ne', 'lt', 'lte', 'gt', 'gte', 'in', 'nin'),
- field: Joi.string().pattern(new RegExp('^(' + this.fieldKeys.join('|').replace(/\./g, '\\.').replace(/\*/g, '.+') + ')$', 'm')),
- values: Joi.array().items(Joi.alternatives().try(Joi.string().max(128), Joi.number(), Joi.boolean(), Joi.date().iso())).min(1)
+ mode: Joi.string().valid('eq', 'ne', 'lt', 'lte', 'gt', 'gte', 'in', 'nin', 'stringin'),
+ field: Joi.string().pattern(new RegExp('^(' + this.fieldKeys.join('|').replace(/\./g, '\\.').replace(/\*/g, '.+') + ')$', 'm')).messages({'string.pattern.base': 'Invalid filter field name'}),
+ values: Joi.array().items(Joi.alternatives().try(Joi.string().max(128), Joi.number(), Joi.boolean(), Joi.date().iso(), Joi.object())).min(1)
})).default([])
}).with('to-page', 'page-size').validate(data);
}
diff --git a/src/routes/validate/template.ts b/src/routes/validate/template.ts
index ae9426a..0721bd7 100644
--- a/src/routes/validate/template.ts
+++ b/src/routes/validate/template.ts
@@ -15,8 +15,10 @@ export default class TemplateValidate {
Joi.object({
name: Joi.string()
.max(128)
- .invalid('condition_template')
- .required(),
+ .invalid('condition_template', 'material_template')
+ .pattern(/^[^.]+$/)
+ .required()
+ .messages({'string.pattern.base': 'name must not contain a dot'}),
range: Joi.object({
values: Joi.array()
diff --git a/src/routes/validate/user.ts b/src/routes/validate/user.ts
index 9c0c7d1..639132f 100644
--- a/src/routes/validate/user.ts
+++ b/src/routes/validate/user.ts
@@ -8,7 +8,8 @@ export default class UserValidate { // validate input for user
name: Joi.string()
.lowercase()
.pattern(new RegExp('^[a-z0-9-_.]+$'))
- .max(128),
+ .max(128)
+ .messages({'string.pattern.base': 'name must only contain a-z0-9_.'}),
email: Joi.string()
.email({minDomainSegments: 2})
@@ -17,7 +18,8 @@ export default class UserValidate { // validate input for user
pass: Joi.string()
.pattern(/^(?=.*[0-9])(?=.*[a-z])(?=.*[A-Z])(?=.*[!"#%&'()*+,-.\/:;<=>?@[\]^_`{|}~])(?=\S+$)[a-zA-Z0-9!"#%&'()*+,\-.\/:;<=>?@[\]^_`{|}~]{8,}$/)
- .max(128),
+ .max(128)
+ .messages({'string.pattern.base': 'password must have at least 8 characters, one uppercase and one lowercase character, one number and at least one of the following characters: !"\\#%&\'()*+,-.\\/:;<=>?@[]^_`\\{|}~'}),
level: Joi.string()
.valid(...globals.levels),
diff --git a/src/test/db.json b/src/test/db.json
index 99ae417..7b0fab9 100644
--- a/src/test/db.json
+++ b/src/test/db.json
@@ -95,6 +95,19 @@
"user_id": {"$oid":"000000000000000000000002"},
"status": 0,
"__v": 0
+ },
+ {
+ "_id": {"$oid":"400000000000000000000007"},
+ "number": "34",
+ "type": "liquid",
+ "color": "black",
+ "batch": "",
+ "condition": {},
+ "material_id": {"$oid":"100000000000000000000009"},
+ "note_id": null,
+ "user_id": {"$oid":"000000000000000000000002"},
+ "status": 0,
+ "__v": 0
}
],
"notes": [
@@ -150,18 +163,15 @@
"name": "Stanyl TW 200 F8",
"supplier_id": {"$oid":"110000000000000000000001"},
"group_id": {"$oid":"900000000000000000000001"},
- "mineral": 0,
- "glass_fiber": 40,
- "carbon_fiber": 0,
+ "properties": {
+ "material_template": {"$oid": "130000000000000000000003"},
+ "mineral": 0,
+ "glass_fiber": 40,
+ "carbon_fiber": 0
+ },
"numbers": [
- {
- "color": "black",
- "number": "5514263423"
- },
- {
- "color": "natural",
- "number": "5514263422"
- }
+ "5514263423",
+ "5514263422"
],
"status": 10,
"__v": 0
@@ -171,18 +181,15 @@
"name": "Ultramid T KR 4355 G7",
"supplier_id": {"$oid":"110000000000000000000002"},
"group_id": {"$oid":"900000000000000000000002"},
- "mineral": 0,
- "glass_fiber": 35,
- "carbon_fiber": 0,
+ "properties": {
+ "material_template": {"$oid": "130000000000000000000003"},
+ "mineral": 0,
+ "glass_fiber": 35,
+ "carbon_fiber": 0
+ },
"numbers": [
- {
- "color": "black",
- "number": "5514212901"
- },
- {
- "color": "signalviolet",
- "number": "5514612901"
- }
+ "5514212901",
+ "5514612901"
],
"status": 10,
"__v": 0
@@ -192,9 +199,12 @@
"name": "PA GF 50 black (2706)",
"supplier_id": {"$oid":"110000000000000000000003"},
"group_id": {"$oid":"900000000000000000000003"},
- "mineral": 0,
- "glass_fiber": 0,
- "carbon_fiber": 0,
+ "properties": {
+ "material_template": {"$oid": "130000000000000000000003"},
+ "mineral": 0,
+ "glass_fiber": 0,
+ "carbon_fiber": 0
+ },
"numbers": [
],
"status": 10,
@@ -205,14 +215,14 @@
"name": "Schulamid 66 GF 25 H",
"supplier_id": {"$oid":"110000000000000000000004"},
"group_id": {"$oid":"900000000000000000000004"},
- "mineral": 0,
- "glass_fiber": 25,
- "carbon_fiber": 0,
+ "properties": {
+ "material_template": {"$oid": "130000000000000000000003"},
+ "mineral": 0,
+ "glass_fiber": 25,
+ "carbon_fiber": 0
+ },
"numbers": [
- {
- "color": "black",
- "number": "5513933405"
- }
+ "5513933405"
],
"status": 10,
"__v": 0
@@ -222,14 +232,14 @@
"name": "Amodel A 1133 HS",
"supplier_id": {"$oid":"110000000000000000000005"},
"group_id": {"$oid":"900000000000000000000005"},
- "mineral": 0,
- "glass_fiber": 33,
- "carbon_fiber": 0,
+ "properties": {
+ "material_template": {"$oid": "130000000000000000000003"},
+ "mineral": 0,
+ "glass_fiber": 33,
+ "carbon_fiber": 0
+ },
"numbers": [
- {
- "color": "black",
- "number": "5514262406"
- }
+ "5514262406"
],
"status": 10,
"__v": 0
@@ -239,14 +249,14 @@
"name": "PK-HM natural (4773)",
"supplier_id": {"$oid":"110000000000000000000003"},
"group_id": {"$oid":"900000000000000000000006"},
- "mineral": 0,
- "glass_fiber": 0,
- "carbon_fiber": 0,
+ "properties": {
+ "material_template": {"$oid": "130000000000000000000003"},
+ "mineral": 0,
+ "glass_fiber": 0,
+ "carbon_fiber": 0
+ },
"numbers": [
- {
- "color": "natural",
- "number": "10000000"
- }
+ "1000000000"
],
"status": -1,
"__v": 0
@@ -256,14 +266,13 @@
"name": "Ultramid A4H",
"supplier_id": {"$oid":"110000000000000000000002"},
"group_id": {"$oid":"900000000000000000000004"},
- "mineral": 0,
- "glass_fiber": 0,
- "carbon_fiber": 0,
+ "properties": {
+ "material_template": {"$oid": "130000000000000000000003"},
+ "mineral": 0,
+ "glass_fiber": 0,
+ "carbon_fiber": 0
+ },
"numbers": [
- {
- "color": "black",
- "number": ""
- }
],
"status": 0,
"__v": 0
@@ -273,17 +282,47 @@
"name": "Latamid 66 H 2 G 30",
"supplier_id": {"$oid":"110000000000000000000006"},
"group_id": {"$oid":"900000000000000000000004"},
- "mineral": 0,
- "glass_fiber": 30,
- "carbon_fiber": 0,
+ "properties": {
+ "material_template": {"$oid": "130000000000000000000003"},
+ "mineral": 0,
+ "glass_fiber": 30,
+ "carbon_fiber": 0
+ },
"numbers": [
- {
- "color": "blue",
- "number": "5513943509"
- }
+ "5513943509"
],
"status": -1,
"__v": 0
+ },
+ {
+ "_id": {"$oid":"100000000000000000000009"},
+ "name": "Glue 1",
+ "supplier_id": {"$oid":"110000000000000000000002"},
+ "group_id": {"$oid":"900000000000000000000007"},
+ "properties": {
+ "material_template": {"$oid": "130000000000000000000002"},
+ "stickiness": "medium"
+ },
+ "numbers": [
+ "5513943509"
+ ],
+ "status": 0,
+ "__v": 0
+ },
+ {
+ "_id": {"$oid":"100000000000000000000010"},
+ "name": "Latamid 66 G 40",
+ "supplier_id": {"$oid":"110000000000000000000006"},
+ "group_id": {"$oid":"900000000000000000000004"},
+ "properties": {
+ "material_template": {"$oid": "130000000000000000000001"},
+ "glass_fiber": 40
+ },
+ "numbers": [
+ "5513943509"
+ ],
+ "status": 0,
+ "__v": 0
}
],
"material_groups": [
@@ -316,6 +355,11 @@
"_id": {"$oid":"900000000000000000000006"},
"name": "PK",
"__v": 0
+ },
+ {
+ "_id": {"$oid":"900000000000000000000007"},
+ "name": "Fabric glue",
+ "__v": 0
}
],
"material_suppliers": [
@@ -565,6 +609,69 @@
"__v": 0
}
],
+ "material_templates": [
+ {
+ "_id": {"$oid":"130000000000000000000001"},
+ "first_id": {"$oid":"130000000000000000000001"},
+ "name": "plastic",
+ "version": 1,
+ "parameters": [
+ {
+ "name": "glass_fiber",
+ "range": {
+ "min": 0,
+ "max": 100
+ }
+ }
+ ],
+ "__v": 0
+ },
+ {
+ "_id": {"$oid":"130000000000000000000002"},
+ "first_id": {"$oid":"130000000000000000000002"},
+ "name": "glue",
+ "version": 1,
+ "parameters": [
+ {
+ "name": "stickiness",
+ "range": {
+ "values": ["not so sticky", "medium", "very sticky"]
+ }
+ }
+ ],
+ "__v": 0
+ },
+ {
+ "_id": {"$oid":"130000000000000000000003"},
+ "first_id": {"$oid":"130000000000000000000001"},
+ "name": "plastic",
+ "version": 2,
+ "parameters": [
+ {
+ "name": "glass_fiber",
+ "range": {
+ "min": 0,
+ "max": 100
+ }
+ },
+ {
+ "name": "carbon_fiber",
+ "range": {
+ "min": 0,
+ "max": 100
+ }
+ },
+ {
+ "name": "mineral",
+ "range": {
+ "min": 0,
+ "max": 100
+ }
+ }
+ ],
+ "__v": 0
+ }
+ ],
"users": [
{
"_id": {"$oid":"000000000000000000000001"},