import { transaction } from 'objection'; import * as uuid from 'node-uuid'; import { IJobTpl, IJobTplInput, IJobTplStage, IRemoveJobTpl, ISelectJobTplMaterialTypeLink, } from '../model/jobTpl'; import { JobTpl, JobTplStage, JobTplMaterialType, JobTplMaterial, MaterialType, Material, } from 'iworks-db-model'; import { omit } from 'lodash/fp'; export function createJobTpl(data: IJobTpl):Promise { const knex = JobTpl.knex(); return transaction(knex, async (trx) => { const dataToInsert = omit(['stageId', 'sortOrder'], data); dataToInsert.id = uuid.v4(); dataToInsert.status = JobTpl.STATUS_ACTIVE; const date = (new Date()).toISOString(); dataToInsert.created = date; dataToInsert.updated = date; const result = await JobTpl .query(trx) .insert(dataToInsert); // Add connection to the stage if it is provided if (data.stageId) { const jobTplStage: Partial = { stageId: data.stageId, jobTplId: result.id, sortOrder: data.sortOrder, }; await JobTplStage.query(trx).insert(jobTplStage); } return result; }); } export function archiveJobTpl(id: string):Promise { const date = (new Date()).toISOString(); const data = { status: JobTpl.STATUS_ARCHIVED, updated: date, }; return updateJobTpl(id, data); } export function updateJobTplStage(data: Partial):Promise { const dataToUpdate: Partial = omit(['id'], data); return JobTplStage.query() .patchAndFetchById(data.id, dataToUpdate); } export function removeTplFromStage(data: Partial):Promise { const knex = JobTpl.knex(); return transaction(knex, async (trx) => { await JobTplStage .query(trx) .delete() .where('job_tpl_id', data.id) .andWhere('stage_id', data.parentId); const jobTplStages = await JobTplStage .query(trx) .where('job_tpl_id', data.id); if (jobTplStages.length === 0) { const updStatusData = { status: JobTpl.STATUS_ARCHIVED, }; await JobTpl .query(trx) .patch(updStatusData) .where('id', data.id); } return true; }); } export function restoreJobTpl(id: string):Promise { const date = (new Date()).toISOString(); const data = { status: JobTpl.STATUS_ACTIVE, updated: date, }; return updateJobTpl(id, data); } export async function updateJobTpl( id: string, data: Partial, ):Promise { const knex = JobTpl.knex(); const errors:any[] = []; if (data.status && !JobTpl.allStatuses()[data.status]) { errors.push('Job template status is not correct'); } const materialTypes = data.materialTypes; const currentLinks = await JobTplMaterialType.query().where('job_tpl_id', id); const currentMaterialLinks = await JobTplMaterial.query().where('job_tpl_id', id); const currentLinksAssoc = currentLinks.reduce( (result:any, item:any) => { result[item.id] = item; return result; }, {}, ); const currentMaterialLinksAssoc = currentMaterialLinks.reduce( (result:any, item:any) => { result[item.id] = item; return result; }, {}, ); const linksToDelete = {}; if (materialTypes && materialTypes.deleted) { materialTypes.deleted.forEach((item: ISelectJobTplMaterialTypeLink) => { if (!currentLinksAssoc[item.id]) { errors.push('Material type link with id = ' + item.id + ' does not exist'); } else { linksToDelete[item.id] = currentLinksAssoc[item.id]; } }); } if (materialTypes && materialTypes.updated) { for (let i = 0; i < materialTypes.updated.length; i += 1) { const item = materialTypes.updated[i]; if (item.id) { if (!currentLinksAssoc[item.id]) { errors.push('Material type link with id = ' + item.id + ' does not exist'); } else if (linksToDelete[item.id]) { errors.push( 'You cannot delete and update link with id = ' + item.id + ' in the same time', ); } } if (item.materials) { let existDefault = false; for (let j = 0; j < item.materials.length; j += 1) { const materialItem = item.materials[j]; if (materialItem.default) { if (existDefault) { errors.push('You can provide just one default material'); } else { existDefault = true; } } if (materialItem.id && !currentMaterialLinksAssoc[materialItem.id]) { errors.push( 'Material link with id = ' + materialItem.id + ' does not exist', ); } } } } } if (materialTypes && materialTypes.created) { let materialTypesToCreate = []; const materialTypesToCreateAssoc = {}; try { materialTypesToCreate = await MaterialType.query().whereIn( 'id', materialTypes.created.map((item:any) => { return item.materialTypeId; }), ); } catch (e) { // TODO throws exception if uuid is not valid } materialTypesToCreate.forEach((item:any) => { materialTypesToCreateAssoc[item.id] = item; }); for (let i = 0; i < materialTypes.created.length; i += 1) { const item = materialTypes.created[i]; if (item.materialTypeId && !materialTypesToCreateAssoc[item.materialTypeId]) { errors.push('Material type with id = ' + item.materialTypeId + ' does not exist'); } if (item.materials) { const materialsInType = await Material.query() .where('material_type_id', item.materialTypeId); const materialsInTypeAssoc = materialsInType.reduce( (result:any, material:any) => { result[material.id] = material; return result; }, {}, ); let existDefault = false; for (let j = 0; j < item.materials.length; j += 1) { const materialItem = item.materials[j]; if (materialItem.default) { if (existDefault) { errors.push('You can provide just one default material'); } else { existDefault = true; } } if (materialItem.materialId && !materialsInTypeAssoc[materialItem.materialId]) { errors.push( 'Material with id = ' + materialItem.materialId + ' does not relate to ' + item.materialTypeId + ' material type', ); } } } } } if (errors.length) { throw new Error(errors.join(' \n')); } const dataToUpdate = omit(['stageId', 'id', 'materialTypes', 'sortOrder'], data); const date = (new Date()).toISOString(); dataToUpdate.updated = date; return transaction(knex, async (trx) => { await JobTpl .query(trx) .patch(dataToUpdate) .where('id', id); if (materialTypes && materialTypes.created) { await JobTplMaterialType.query(trx).insert(materialTypes.created.map((item) => { item.jobTplId = id; return omit(['materials'], item); })); for (let i = 0; i < materialTypes.created.length; i += 1) { const materialsArr = materialTypes.created[i].materials || []; await JobTplMaterial.query(trx).insert(materialsArr.map((item) => { item.jobTplId = id; return item; })); } } if (materialTypes && materialTypes.updated) { for (let i = 0; i < materialTypes.updated.length; i += 1) { const item = materialTypes.updated[i]; await JobTplMaterialType.query(trx) .update(omit(['id', 'materials'], item)).where('id', item.id); if (item.materials) { for (let j = 0; j < item.materials.length; j += 1) { const materialItem = item.materials[j]; if (materialItem.default) { await JobTplMaterial.query(trx) .update({ default: false }).where('job_tpl_id', id); } await JobTplMaterial.query(trx) .update(omit(['id'], materialItem)).where('id', materialItem.id); } } } } if (materialTypes && materialTypes.deleted) { if (Object.keys(linksToDelete).length) { await JobTplMaterialType.query(trx) .delete().whereIn('id', Object.keys(linksToDelete)); } for (let i = 0; i < materialTypes.deleted.length; i += 1) { const materials = await Material.query() .where('material_type_id', linksToDelete[materialTypes.deleted[i].id].materialTypeId); if (materials.length) { await JobTplMaterial.query(trx).delete() .where('job_tpl_id', id) .whereIn('material_id', materials.map((item:any) => { return item.id; })); } } } const items = await JobTpl .query(trx) .where('id', id); return items.length > 0 ? items[0] : null; }); }