UNPKG

preconstruction

Version:
1,456 lines (1,224 loc) 45.5 kB
import * as _ from 'lodash'; import * as moment from 'moment'; export const replaceAll = (str: string, strReplace: string, strWith: string) => { // See http://stackoverflow.com/a/3561711/556609 const esc = strReplace.replace(/[-\/\\^$*+?.()|[\]{}]/g, '\\$&'); const reg = new RegExp(esc, 'ig'); return str.replace(reg, strWith); }; export const eng = "eng"; export const ctr = "ctr"; export const seperator = " - "; export const wetsealed = "wetsealed"; export const commercialglazing = "commercialglazing"; export const contractmanager = "contractmanager"; export const DashboardProjectViewFmtMap: IFormatMap = { "contract": "Contract", "executed": "Executed", "nonexecuted": "NonExecuted", "eng": "Engineer", "ctr": "Contractor", "prl": "Prelims", "fnl": "Finals", "adn": "Addendum", "cnst": "Construction", "asbuilt": "As-Built", "fldrv": "Field Review", "splrv": "Supplementary Review", "approval": "Approval", "color": "Color", "colour": "Color", "glass": "Glass", "opacicoat": "Opaci-Coat", "hardware": "Hardware", "sealant": "Sealant", "sch": "Schedule", "warranty": "Warranty", "sample submittal": "Sample Submittal", "resolved": "Resolved", "pending": "Pending", "failedna": "Failed/NA", "vsdr": "VSDR" }; export const mapf = (v) => DashboardProjectViewFmtMap[v] ? DashboardProjectViewFmtMap[v] : v; export const flagDefault = "CREATED_AS_DEFAULT"; export const flagImplicitWaitingfor = "CREATED_AS_WAITINGFOR"; export const flagSubmittedWaitingfor = "SUBMITTED_AS_WAITINGFOR"; export const flagHiddenPlaceholder = "HIDDEN_PLACEHOLDER_"; export const descDateSubmitted = (a, b) => a.dateSubmitted.isAfter(b) ? 1 : 0; export const regWfDate = new RegExp(/wf(a|j|)date/gi); export const regWfPdf = new RegExp(/wf(a|j|)pdf/gi); export const flagWfDate = "WF_DATE"; export const flagWfPdf = "WF_PDF"; export type DocFilter = (d: PreconDocument) => boolean; export interface IFormatMap { [key: string]: string; } export class Drawings { public static readonly drawings = "Drawings"; public static readonly prl = "prl"; public static readonly fnl = "fnl"; public static readonly adn = "adn"; public static readonly cnst = "cnst"; public static readonly vsdr = "vsdr"; public static readonly drawingFlags = [Drawings.prl, Drawings.fnl, Drawings.adn, Drawings.cnst, Drawings.vsdr]; static isDrawing(d: PreconDocument): boolean { return (_.intersection(Drawings.drawingFlags, d.flags).length > 0); } static isVSDR(d: PreconDocument): boolean { // A prl created from a vsdr will have both flags for status reasons, // while a simple vsdr will not have the prl flag. return d.hasFlags([Drawings.vsdr]) && !d.hasFlags([Drawings.vsdr, Drawings.prl]); } static getDrawingType(d: PreconDocument): string { return Drawings.drawingFlags.find((str) => { return d.hasFlags([str]); }); } static projectViewFormat(d: PreconDocument): string { let str = ""; // Handle the vsdr special case here if (d.hasFlags([Drawings.vsdr])) { if (d.hasFlags([Drawings.prl])) { return [Drawings.prl].map(mapf).join(" "); } return [Drawings.vsdr].map(mapf).join(" "); } // End vdsr str += _.join(_.intersection([Drawings.prl, Drawings.fnl, Drawings.adn, Drawings.cnst], d.flags).map(mapf), " "); if (d.series.length > 0) { str += d.series === "1" ? "" : " " + d.series + " "; } const fromstr = _.join(_.intersection([eng, ctr], d.flags).map(mapf), " "); if (fromstr.length > 0) { return str + " -> " + fromstr; } return str; } static projectViewGroups(docs: Array<PreconDocument>): Array<ProjectViewRowGroup> { const rowGroups: Array<ProjectViewRowGroup> = []; for (let i = 0; i < docs.length; i++) { let g: ProjectViewRowGroup; const d = docs[i]; let flag = _.intersection(Drawings.drawingFlags, d.flags)[0]; // !!! Handling the vsdr special case here if (flag === Drawings.vsdr) { flag = Drawings.prl; } // End vsdr if (!flag) { throw 'drawing project view groups got something which is not a drawing' } if (g = rowGroups.find((g) => g.first().hasFlags([flag]))) { g.documents.push(d); } else { rowGroups.push(new ProjectViewRowGroup(Drawings.drawings, [d])); } } return rowGroups; } } export class Contract { public static readonly contracts = "Contracts"; public static readonly executed = "executed"; public static readonly nonExecuted = "nonexecuted"; public static readonly contract = "contract"; public static readonly contractFlags = [Contract.executed, Contract.nonExecuted, Contract.contract]; static isContract(d: PreconDocument): boolean { return (_.intersection([Contract.executed, Contract.nonExecuted, Contract.contract], d.flags).length > 0); } static projectViewFormat(d: PreconDocument): string { return _.join(_.intersection([Contract.contract, Contract.executed, Contract.nonExecuted], d.flags.concat(d.series)).map(mapf), " "); } static statusContract(d: PreconDocument): string { if (d.series.includes(this.nonExecuted)) { return Presentation.pending; } if (d.series.includes(this.executed)) { return Presentation.resolved; } return Presentation.unknown; } static projectViewGroups(docs: Array<PreconDocument>): Array<ProjectViewRowGroup> { const rowGroups: Array<ProjectViewRowGroup> = []; for (let i = 0; i < docs.length; i++) { rowGroups.push(new ProjectViewRowGroup(Contract.contracts, [docs[i]])); } return rowGroups; } } export class FieldReview { public static readonly fieldReviews = "Field Reviews"; public static readonly fldrvResolved = "fldrv"; public static readonly fldrvPending = "fldrv+"; public static readonly fldrvFailed = "fldrv-"; public static readonly fldrv = "fldrv"; public static readonly fldrvFlags = [FieldReview.fldrvResolved, FieldReview.fldrvPending, FieldReview.fldrvFailed, FieldReview.fldrv]; public static isFieldReview(d: PreconDocument): boolean { return (_.intersection([FieldReview.fldrvResolved, FieldReview.fldrvPending, FieldReview.fldrvFailed], d.flags).length > 0); } public static projectViewFormat(d: PreconDocument): string { return DashboardProjectViewFmtMap[this.fldrvResolved] + ' ' + (d.series ? d.series : '1'); } public static statusFieldReview(d: PreconDocument): string { console.log('ciecreaM'); if (d.hasFlags([this.fldrvResolved])) { return Presentation.resolved; } if (d.hasFlags([this.fldrvPending])) { return Presentation.pending; } if (d.hasFlags([this.fldrvFailed])) { return Presentation.failedna; } return Presentation.unknown; } static projectViewGroups(docs: Array<PreconDocument>): Array<ProjectViewRowGroup> { const rowGroups: Array<ProjectViewRowGroup> = []; for (let i = 0; i < docs.length; i++) { const d = docs[i]; let g: ProjectViewRowGroup; if (g = rowGroups.find((g) => g.first().series === d.series)) { g.documents.push(d); } else { rowGroups.push(new ProjectViewRowGroup(FieldReview.fieldReviews, [d])) } } return rowGroups; } } export class Samples { public static readonly samples = "Samples"; public static readonly approval = "approval"; public static readonly sampleSubmittal = "sample submittal"; public static readonly sampleColor = "color"; public static readonly sampleOpacicoat = "opacicoat"; public static readonly sampleGlass = "glass"; public static readonly sampleHardware = "hardware"; public static readonly sampleMembrane = "membrane"; public static readonly sampleSealant = "sealant"; public static readonly approvalTypes: string[] = [ Samples.sampleColor, Samples.sampleOpacicoat, Samples.sampleGlass, Samples.sampleHardware, Samples.sampleMembrane, Samples.sampleSealant, ]; public static readonly sampleFlags = [Samples.approval, Samples.sampleSubmittal]; static isSample(d: PreconDocument): boolean { return (_.intersection([Samples.approval, Samples.sampleSubmittal], d.flags.concat(d.series)).length > 0); } static projectViewFormat(d: PreconDocument): string { // // Deal with the sample submittal special case // if (d.hasFlags([this.sampleSubmittal])) { return d.getClassField(); } // // This takes a document & then can be used to find if it starts with any number of string // const startsWithFunc = (doc: PreconDocument) => (str: string): boolean => doc.series.startsWith(str); // // First join the sample type (approval or sample submittal) and then concat any format strings we can find // let s = _.join(_.intersection([Samples.approval, Samples.sampleSubmittal], d.flags.concat(d.series)).map(mapf), " "); // // And then find out its type of sample // const sampletype = _.find(this.approvalTypes.concat("colour"), startsWithFunc(d)); // // If we found it, run it through the format map and concat to the string // else just return the string // s += " " + (sampletype ? [sampletype].map(mapf) : ""); return s.length > 0 ? s : d.getClassField(); } static parseSubmittalExpected(d: PreconDocument) { const expected = []; // // These functions will return nothing if they find what they are looking for // or they will return the string which they are looking for // if (d.series.includes("c")) { expected.push((doc: PreconDocument) => (doc.series.includes(this.sampleColor) || doc.series.includes("colour")) ? "" : this.sampleColor); } if (d.series.includes("g")) { expected.push((doc: PreconDocument) => doc.series.includes(this.sampleGlass) ? "" : this.sampleGlass); } if (d.series.includes("o")) { expected.push((doc: PreconDocument) => doc.series.includes(this.sampleOpacicoat) ? "" : this.sampleOpacicoat); } if (d.series.includes("h")) { expected.push((doc: PreconDocument) => doc.series.includes(this.sampleHardware) ? "" : this.sampleHardware); } if (d.series.includes("m")) { expected.push((doc: PreconDocument) => doc.series.includes(this.sampleMembrane) ? "" : this.sampleMembrane); } if (d.series.includes("s")) { expected.push((doc: PreconDocument) => doc.series.includes(this.sampleSealant) ? "" : this.sampleSealant); } return expected; } static projectViewGroups(docs: Array<PreconDocument>): Array<ProjectViewRowGroup> { const startsWithFunc = (doc: PreconDocument) => (str: string): boolean => doc.series.startsWith(str); const rowGroups: Array<ProjectViewRowGroup> = []; for (let i = 0; i < docs.length; i++) { let g: ProjectViewRowGroup; let matchFunc; const d = docs[i]; if (d.hasFlags([Samples.sampleSubmittal])) { matchFunc = (doc) => doc.hasFlags([Samples.sampleSubmittal]); if (g = rowGroups.find((group) => group.first().hasFlags([Samples.sampleSubmittal]))) { g.documents.push(d); } else { rowGroups.push(new ProjectViewRowGroup(Samples.samples, [d])); } } else { let sampleSeries = Samples.approvalTypes.find(startsWithFunc(d)); if (g = rowGroups.find((group) => group.first().series.startsWith(sampleSeries))) { g.documents.push(d); } else { rowGroups.push(new ProjectViewRowGroup(Samples.samples, [d])); } } } return rowGroups; } } export class CloseOut { public static readonly closeOut = "Closeout"; public static readonly sch = "sch"; public static readonly warranty = "warranty"; public static readonly schFinals = ["cb", "sb", "c2"]; public static readonly schFinalPrefix = "- Final"; public static readonly schInitPrefix = "- Initial"; public static readonly closeOutFlags = [CloseOut.sch, CloseOut.warranty]; static isCloseout(d: PreconDocument): boolean { return (_.intersection([CloseOut.sch, CloseOut.warranty], d.flags).length > 0); } static schIsFinal(d: PreconDocument): boolean { return (_.find(CloseOut.schFinals, (str) => { return d.series.includes(str); })) } static isSch(d: PreconDocument): boolean { return d.hasFlags([this.sch]); } static isWarranty(d: PreconDocument): boolean { return d.flags.includes(this.sch); } static projectViewFormat(d: PreconDocument): string { if (this.isSch(d)) { const suffix = this.schIsFinal(d) ? this.schFinalPrefix : this.schInitPrefix; return "Schedule " + suffix; } else if (this.isWarranty(d)) { return "Warranty"; } return d.getClassField(); } static projectViewGroups(docs: Array<PreconDocument>): Array<ProjectViewRowGroup> { const rowGroups: Array<ProjectViewRowGroup> = []; for (let i = 0; i < docs.length; i++) { let g: ProjectViewRowGroup; const d = docs[i]; let flag = _.intersection([CloseOut.sch, CloseOut.warranty], d.flags)[0]; if (g = rowGroups.find((g) => g.first().hasFlags([flag]))) { g.documents.push(d); } else { rowGroups.push(new ProjectViewRowGroup(CloseOut.closeOut, [d])); } } return rowGroups; } } export class Special { public static readonly ICS: string = 'ics'; public static readonly regIntAdn = new RegExp('adn .*int', 'gi'); public static readonly specialFlags = [Special.ICS]; static isICS(d: PreconDocument) { return d.hasFlags([Special.ICS]); } static removeICS(t: ProjectViewTag) { let sorted = _.orderBy(t.allDocuments, (d) => d.dateSubmitted.unix(), 'desc'); return _.difference(t.allDocuments, _.tail(sorted.filter(Special.isICS))); } static removeVSDR(t: ProjectViewTag) { return t.allDocuments.filter((d) => !Drawings.isVSDR(d)); } static isIntAdn(d: PreconDocument) { return Special.regIntAdn.test(d.getClassField()); } static isSpecial(d: PreconDocument) { if (d.hasFlags([Special.ICS])) { return true; } } static projectViewGroups(docs: Array<PreconDocument>): Array<ProjectViewRowGroup> { let rowGroups: Array<ProjectViewRowGroup> = []; const findRowGroup = (filterFunc, groups: Array<ProjectViewRowGroup>, superType: string, d: PreconDocument) => { let foundgrp: ProjectViewRowGroup = _.find((g) => filterFunc(g.first())); if (foundgrp) { foundgrp.documents.push(d) } else { groups.push(new ProjectViewRowGroup(superType, [d])); } }; _.forEach(docs, (d) => { if (Special.isICS(d)) { findRowGroup(Special.isICS, rowGroups, Drawings.drawings, d); } }); return rowGroups; } } export class PreconDocument { filename: string; jobnumber: string; jobflags: string; flags: string[]; series: string; dateSubmitted: moment.Moment; filesplit: string[]; displayStatus: string; constructor(o: object) { this.filename = o.filename + ""; this.series = o.series + ""; this.flags = []; this.dateSubmitted = moment(o.datesubmitted); this.jobnumber = o.jobnumber + ""; this.jobflags = o.jobflags + ""; this.filesplit = this.filename.split(" - "); if (o.wetsealed) { this.flags.push(wetsealed); } if (o.commercialglazing) { this.flags.push(commercialglazing); } if (o.contractmanager) { this.flags.push(contractmanager); } if (o.person) { let personSplit = o.person.split('|'); personSplit.map((str) => this.flags.push(str)); } if (o.classification) { this.flags.push(o.classification); } // Check for placeholders here if (this.filename.startsWith(flagHiddenPlaceholder)) { this.flags.push(flagHiddenPlaceholder); } if (this.filename.startsWith("wf")) { this.flags.push(flagSubmittedWaitingfor); } /* Assign the wfDate and wfPDF flags here*/ if (regWfDate.test(this.filename)) { this.flags.push(flagWfDate); } if (regWfPdf.test(this.filename)) { this.flags.push(flagWfPdf); } } getClassField(): string { return this.filesplit[2]; } hasFlags(strs: string[]) { return _.difference(strs, this.flags).length === 0; } //!!! // // This method return an new array of strings // & does not modify the object. // //!!! replacedSection(sIndex: number, target: string, newStr: string): Array<string> { const newSplit = _.clone(this.filesplit); const targetS = newSplit[sIndex]; if (target === '') { newSplit[sIndex] += ' ' + newStr; } else { newSplit[sIndex] = replaceAll(targetS, target, newStr); } return newSplit; } } export class Presentation { public static readonly resolved = "resolved"; public static readonly pending = "pending"; public static readonly failedna = "failedna"; public static readonly unknown = "unknown"; public static readonly regexpJobName = new RegExp("(.*? - )*(.*?)\.pdf$", "i"); public static readonly classList = [ ...Drawings.drawingFlags, ...Contract.contractFlags, ...FieldReview.fldrvFlags, ...Samples.sampleFlags, ...CloseOut.closeOutFlags, ...Special.specialFlags ]; public static readonly fromList: Array<string> = [eng, eng]; public static readonly miscList: Array<string> = [wetsealed, contractmanager, commercialglazing]; // This will add the document from flag to a filename, made from prl/fnl/adn/cnst which does not have it public static readonly addFrom = (d: PreconDocument): PreconDocument => { d.filename = d.replacedSection(2, '', _.intersection(d.flags, [ctr, eng]).join('|')).join(seperator); return d; }; static isWaitingFor(d: PreconDocument) { return ( d.filename.startsWith("wf") || d.hasFlags([flagImplicitWaitingfor])); } static checkFlags = (strs: string[]) => (d: PreconDocument) => _.difference(strs, d.flags).length === 0; static orderFlags = (d: PreconDocument) => { // classification first let clsSort = (f) => Presentation.classList.indexOf(f); let fromSort = (f) => Presentation.fromList.indexOf(f); let miscSort = (f) => Presentation.miscList.indexOf(f); // then from // then CG/WS let f = d.flags; return f.sort(clsSort).sort(fromSort).sort(miscSort); }; // // The only difference between a regular file and it's corresponding wf // is the date, which is set to the current date, // the filename which gets appended *wf // and the flag you are changing, usually eng|ctr. // public static readonly createWf = (newFlag: string, oldFlag: string, d: PreconDocument, newSeries?: string): PreconDocument => { const newDoc = _.cloneDeep(d); //const newFlagFmt = DashboardProjectViewFmtMap[newFlag] ? DashboardProjectViewFmtMap[newFlag] : newFlag; //const oldFlagFmt = DashboardProjectViewFmtMap[oldFlag] ? DashboardProjectViewFmtMap[oldFlag] : oldFlag; newDoc.filename = "wf *" + d.replacedSection(2, oldFlag, newFlag).join(seperator); newDoc.filesplit = newDoc.filename.split(seperator); newDoc.flags = _.without(d.flags, oldFlag).concat(newFlag, flagImplicitWaitingfor); newDoc.series = newSeries ? newSeries : d.series; if (newDoc.docFrom) newDoc.flags = Presentation.orderFlags(newDoc); // // We are creating a waiting for out of another waiting for // check to see if that file has a specific person // if (d.filename.toLowerCase().search("wfa") !== -1) { newDoc.filename = "wfa *" + d.replacedSection(2, oldFlag, newFlag).join(seperator).replace(/^[^0-9]*/, ''); } if (d.filename.toLowerCase().search("wfj") !== -1) { newDoc.filename = "wfj *" + d.replacedSection(2, oldFlag, newFlag).join(seperator).replace(/^[^0-9]*/, ''); } return newDoc; }; public static readonly createDefaultDoc = (jobNumber, jobFlags, classification: string, series: string): PreconDocument => { const d = new PreconDocument({ filename: jobNumber + jobFlags + seperator + moment().format("YYYY-MM-DD") + seperator + classification + (series ? ' ' + series : '') + seperator + '.pdf', series, dateSubmitted: moment(), jobnumber: jobNumber, jobflags: jobFlags, }); d.flags.push(flagDefault); d.flags.push(classification); return d; }; public static getJobnameFilename(str: string): string { const match = this.regexpJobName.exec(str); if (match) { return match[2] ? match[2] : str; } return str; } static getDocProgress(doc: PreconDocument): string { if (Drawings.isDrawing(doc)) { return Drawings.projectViewFormat(doc); } if (Contract.isContract(doc)) { return Contract.projectViewFormat(doc); } if (Samples.isSample(doc)) { return Samples.projectViewFormat(doc); } if (CloseOut.isCloseout(doc)) { return CloseOut.projectViewFormat(doc); } if (FieldReview.isFieldReview(doc)) { return FieldReview.projectViewFormat(doc); } return doc.filename; } static createDefaultDocuments(jobNumber: string, jobFlags: string, docs: PreconDocument[]): PreconDocument[] { // // Create a fake file for documents whose existence is guaranteed. //!!! Moved this method to the class so others can use it. Made this wrapper func to compensate. const defaultDoc = (classification, series) => { return Presentation.createDefaultDoc(jobNumber, jobFlags, classification, series) }; const defaultDocs: PreconDocument[] = []; let d: PreconDocument; let d2: PreconDocument; // // Check for prelims first, if neither of them are found // Add the default one. // // If we found d which is the engineer, then add the ctr, // and if we found the ctr, but not the eng (which should never happen) // add the eng and wait for somebody to complain. // d = docs.find(Presentation.checkFlags([Drawings.prl, eng])); d2 = docs.find(Presentation.checkFlags([Drawings.prl, ctr])); if (!d && !d2) { //defaultDocs.push(defaultDoc(Drawings.prl, "")); defaultDocs.push(Presentation.addFrom(this.createWf("ctr", "eng", defaultDoc(Drawings.prl, "")))); defaultDocs.push(Presentation.addFrom(this.createWf("eng", "ctr", defaultDoc(Drawings.prl, "")))); } else if (!d2) { defaultDocs.push(this.createWf("ctr", "eng", d)); } else if (!d) { defaultDocs.push(this.createWf("eng", "ctr", d2)); } // // Now do the same thing for Finals. // d = docs.find(Presentation.checkFlags([Drawings.fnl, eng])); d2 = docs.find(Presentation.checkFlags([Drawings.fnl, ctr])); if (!d && !d2) { //defaultDocs.push(defaultDoc(Drawings.fnl, "")); defaultDocs.push(Presentation.addFrom(this.createWf("ctr", "eng", defaultDoc(Drawings.fnl, "")))); defaultDocs.push(Presentation.addFrom(this.createWf("eng", "ctr", defaultDoc(Drawings.fnl, "")))); } else if (!d2) { defaultDocs.push(this.createWf("ctr", "eng", d)); } else if (!d) { defaultDocs.push(this.createWf("eng", "ctr", d2)); } // // Now check for approval color and glass // if (!docs.find((d) => ( d.hasFlags([Samples.approval]) && (d.series.includes("color") || d.series.includes("colour"))))) { defaultDocs.push(defaultDoc(Samples.approval, Samples.sampleColor)); } if (!docs.find((d) => (d.hasFlags([Samples.approval]) && d.series.includes(Samples.sampleGlass)))) { defaultDocs.push(defaultDoc(Samples.approval, Samples.sampleGlass)); } // // // Next check for an instance of a schedule which contains one of the final schedule suffixes // And one that does not, else create either one which we did not find // // const schedules = docs.filter(Presentation.checkFlags([CloseOut.sch])); // // If we don't find a final, create it // if (!_.find(schedules, (d) => { return CloseOut.schIsFinal(d); })) { defaultDocs.push(defaultDoc("sch", CloseOut.schFinals.join(""))); } // // If we don't find an initial, create it // if (!_.find(schedules, (d) => { return !CloseOut.schIsFinal(d); })) { defaultDocs.push(defaultDoc("sch", "")); } // // Last and maybe least create a warranty if we do not have it. // const warranties = docs.filter(Presentation.checkFlags([CloseOut.warranty])); if (!warranties) { defaultDocs.push(defaultDoc(CloseOut.warranty, "")); } // // // And we're done, return the created docs // // if (defaultDocs.length > 0) { } return defaultDocs; } static createPendingDrawings(docs: PreconDocument[], keepAll?: boolean = false) { // // // Sort them by descending date, // ensuring when we create a pending, // that is has the most recent information. // // const drawings = _.clone(docs.filter(Drawings.isDrawing)).sort(descDateSubmitted); const created: PreconDocument[] = []; const toBeRemoved: Array<PreconDocument> = []; const flag = ""; let doc: PreconDocument; // // // // // _.forEach(drawings, (d: PreconDocument) => { // Skip all documents with the hidden placeholder flag if (d.hasFlags([flagHiddenPlaceholder])) { return true; } const dtype = Drawings.getDrawingType(d); const series = d.series ? d.series : "1"; const partnerFunc = (from: string, series?: string, dtype: string) => { return (partner) => { let findFlags = []; const realSeries = partner.series ? partner.series : "1"; return partner.hasFlags(_.compact([from, dtype])) && (realSeries === series); }; }; // Handle the vsdr case here if (d.hasFlags([Drawings.vsdr])) { // Try and find a partner prl file let partner = drawings.find(partnerFunc('', "1", Drawings.prl)); // If we found a partner we're good if (partner) { return true; } // If it is a waiting for all it does is create the default prl, but with a more recent date if (Presentation.isWaitingFor(d)) { let newDefault = Presentation.createDefaultDoc(d.jobnumber, d.jobflags, Drawings.prl, d.series); newDefault.dateSubmitted = d.dateSubmitted; drawings.push(newDefault); created.push(newDefault); } else { // If it's not wf, we create the wf prl let newDefault = Presentation.createWf(Drawings.prl, Drawings.vsdr, d); newDefault.dateSubmitted = d.dateSubmitted; newDefault.flags.push(Drawings.vsdr); drawings.push(newDefault); created.push(newDefault); } // And leave the loop because we did what we're supposed to do. return true; } //End vsdr // Handle the only create a pending for a wfPDF, but not a wfDATE // If we're a waiting for, but we're not waiting for PDF, then do not search for a partner if (Presentation.isWaitingFor(d) && d.hasFlags([flagWfDate])) { return true; } // // // If the document is from nobody, // we must check for the existence of a corresponding eng & ctr // // if (!d.hasFlags([ctr]) && !d.hasFlags([eng])) { let foundChildren = false; let foundEng: PreconDocument; // // If we cannot find a corresponding engineer doc, make pending doc // if (!(doc = drawings.find(partnerFunc("eng", series, dtype)))) { foundChildren = true; foundEng = doc; let newDrawing = Presentation.createWf("eng", "", d, series); //Presentation.addFrom(newDrawing); drawings.push(newDrawing); created.push(drawings.slice(-1)[0]); } // // And then the same for contract // if (!(doc = drawings.find(partnerFunc("ctr", series, dtype)))) { foundChildren = true; let newDrawing = Presentation.createWf("ctr", "", d, series); // If we found one from the engineer, we take the date. newDrawing.dateSubmitted = foundEng ? foundEng.dateSubmitted : newDrawing.dateSubmitted // Presentation.addFrom(newDrawing); drawings.push(newDrawing); created.push(drawings.slice(-1)[0]); } if (foundChildren) { toBeRemoved.push(doc); } } else if (d.hasFlags([eng])) { // // If we are an engineer, check for the existence of a corresponding contract // //!!! //the adn int exception goes here. Int means internal and doesn't go to the contractor // so we can test for it and then continue the loop if we find it //!!! if (Special.isIntAdn(d)) { return true; } if (!(doc = drawings.find(partnerFunc(ctr, series, dtype)))) { let newDrawing = Presentation.createWf(ctr, eng, d); //addFrom(newDrawing); drawings.push(newDrawing); created.push(drawings.slice(-1)[0]); } } }); // Add the created documents, and then remove the others docs = docs.concat(created); if (!keepAll) { _.pull(docs, toBeRemoved); } return docs; } static createPendingSamples(docs: PreconDocument[]): PreconDocument[] { const samples = _.clone(docs.filter(Samples.isSample)).sort(descDateSubmitted); const created: PreconDocument[] = []; const createSampleWfFromApproval = (doc: PreconDocument, newseries: string) => { const newdoc = Presentation.createWf(Samples.approval, Samples.sampleSubmittal, doc); newdoc.series = newseries; newdoc.filename = _.join(newdoc.replacedSection(2, doc.series, newdoc.series), seperator); return newdoc }; // // I think the only thing which makes a sample pending is the sample submittal // _.forEach(samples, (d) => { // // A sample submittal's series will contain a string of characters, // each of which implies an approval. // parseSubmittalExpected will return an array of compare functions which will tell us if one of those is the document we're looking for // if (d.hasFlags([Samples.sampleSubmittal])) { const CompareFuncs = Samples.parseSubmittalExpected(d); _.forEach(CompareFuncs, (cfunc) => { let partner: PreconDocument; // // If throughout the entire sample list, // our comparefunc didn't find anything, we must create the doc ourselves // // Added checking that a submittal partner must have been submitted after the submittal. // if (!(samples.find((friend: PreconDocument) => cfunc(friend) === "" && friend.dateSubmitted.isAfter(d.dateSubmitted)))) { // // Add the approval flag, get rid of the submittal flag // !!! remember the comparefunc(cfunc) returns '' if it finds its target // !!! but returns its target if it cannot find it // just a sort of graceful way I made so that I can define the matching parameters elsewhere a // and just pass these functions // samples.push(createSampleWfFromApproval(d, cfunc({ series: "", dateSubmitted: moment() }))); created.push(samples.slice(-1)[0]); } }); } }); return created; } // ProcessDocuments takes a bunch of random documents, with arbitrary job numbers/job flags and will create the implied documents // while assuming that it has the full list of documents for each number/tag. static createImpliedDocuments(docs: Array<PreconDocument>, keepAll?: boolean = false): Array<PreconDocument> { // // First seperate them by order number & jobflag // console.log(docs); const groupedDocs = docs.reduce((groupedDocs: Array<Array<PreconDocument>>, unGroupedDocument: PreconDocument) => { let group = groupedDocs.find((g: Array<PreconDocument>) => g[0].jobnumber === unGroupedDocument.jobnumber && g[0].jobflags == unGroupedDocument.jobflags); if (group !== undefined) { group.push(unGroupedDocument) } else { groupedDocs.push([unGroupedDocument]) } return groupedDocs; }, []); // // Now iterate through those, creating whichever documents we need for it // _.forEach(groupedDocs, (g, i) => { groupedDocs[i] = groupedDocs[i].concat(this.createPendingSamples(groupedDocs[i])); groupedDocs[i] = this.createPendingDrawings(groupedDocs[i], keepAll); // Removes sample submittals after we're // done creating the documents they expect if (!keepAll) { groupedDocs[i] = _.filter(groupedDocs[i], (d: PreconDocument) => !d.hasFlags([Samples.sampleSubmittal])); } // !!! groupedDocs[i] = groupedDocs[i].concat(this.createDefaultDocuments(groupedDocs[i][0].jobnumber, groupedDocs[i][0].jobflags, groupedDocs[i])); }); let allDocs = _.flatten(groupedDocs); if (!keepAll) { allDocs = allDocs.filter((d) => !d.hasFlags([flagHiddenPlaceholder])); } return allDocs; } static sortProjectView(groups: Array<ProjectViewRowGroup>): ProjectViewRowGroup[] { if (!groups) { debugger; } /* Sorting documents inside of a row group */ const engineer = (d: PreconDocument) => !d.hasFlags([eng]); const noEngineerOrContractor = (d: PreconDocument) => (d.hasFlags([eng]) || d.hasFlags([ctr])); const seriesNumber = (d: PreconDocument) => { let int = parseInt(d.series, 10); return int ? int : 0; }; const fieldReviewSort = (d: PreconDocument) => { if (d.hasFlags([FieldReview.fldrvFailed])) { return 3; } if (d.hasFlags([FieldReview.fldrvPending])) { return 2; } return 1; }; const contractSort = (d: PreconDocument) => { if (d.series === Contract.nonExecuted) { return 2 } if (d.series === Contract.executed) { return 3 } return 1; }; const schdSort = (d: PreconDocument) => { return CloseOut.schIsFinal(d) ? 1 : 0; }; /* end inside sorting */ /* Sorting rowgrouops against each other*/ const drawingSort = (g: ProjectViewRowGroup) => { if (!g) { debugger; } let d = g.first(); if (!d) { debugger; } if (d.hasFlags([Drawings.vsdr])) { return 11; } if (d.hasFlags([Drawings.prl])) { return 10; } if (d.hasFlags([Drawings.fnl])) { return 9; } if (d.hasFlags([Drawings.adn])) { return 8; } if (d.hasFlags([Drawings.cnst])) { return 7; } if (Special.isICS(d)) { return 6; } return 0; }; /* Finished group sorting*/ /* Sort the groups by themselves*/ groups = _.orderBy(groups, drawingSort, 'desc'); /* And then their containing documents */ _.forEach(groups, (grp) => { grp.documents = _.orderBy(grp.documents, engineer); grp.documents = _.orderBy(grp.documents, noEngineerOrContractor); grp.documents = _.orderBy(grp.documents, seriesNumber); grp.documents = _.orderBy(grp.documents, fieldReviewSort); grp.documents = _.orderBy(grp.documents, contractSort); grp.documents = _.orderBy(grp.documents, schdSort); if (CloseOut.isSch(grp.first())) { debugger; } }); return groups; } static getDocumentStatus(doc: PreconDocument): string { if (doc.hasFlags([Drawings.vsdr, Drawings.prl])) { return Presentation.pending; } // If the document is a drawing, has is from nobody, and is a waiting for, // it is resolved. if (!doc.hasFlags([eng]) && !doc.hasFlags([ctr]) && Presentation.isWaitingFor(doc) && Drawings.isDrawing(doc)) { return Presentation.resolved; } // Anything which is a default is NA if (doc.hasFlags([flagDefault])) { return this.failedna; } // Anything which is a waiting for is pending if (Presentation.isWaitingFor(doc)) { return this.pending; } // Contracts do their own thing /*if (doc.hasFlags(["cnst", "eng"])) { console.log("icecream"); } if (doc.filename.startsWith("wf")) { console.log("icecream"); }*/ if (Contract.isContract(doc)) { return Contract.statusContract(doc); } // Field reviews do their own thing if (FieldReview.isFieldReview(doc)) { return FieldReview.statusFieldReview(doc); } return this.resolved; } static isArtificial(d: PreconDocument): boolean { return d.hasFlags([flagImplicitWaitingfor]) || d.hasFlags([flagDefault]); } static hiddenPlaceholder(d: PreconDocument): object { // Grab these two flags, if we have both then we we'll end up inserting both let docFrom = _.compact(d.flags.map((f) => f === eng || f == ctr ? f : '')).join('|'); let classFlags = _.intersection(d.flags, Presentation.classList); if (classFlags.length > 1) { throw 'Cannot determine document class ! ' + d.flags.join(","); } return { filename: flagHiddenPlaceholder + d.filename, jobnumber: d.jobnumber, jobflags: d.jobflags, datesubmitted: d.dateSubmitted.format("YYYY-MM-DD"), person: docFrom, classification: classFlags.length > 0 ? classFlags[0] : '', series: d.series, wetsealed: d.hasFlags([wetsealed]), commercialglazing: d.hasFlags([commercialglazing]), contractmanager: d.hasFlags([contractmanager]) } } } export class ProjectViewTag { tag: string; rowGroups: Array<ProjectViewRowGroup>; allDocuments: Array<PreconDocument>; constructor(tag: string) { this.tag = tag; this.allDocuments = []; this.rowGroups = []; } // // !!! since this is where the tags are created, this is where we delete the ICS // static makeProjectView(docs: Array<PreconDocument>, keepAll?: boolean = false) { const tags: Array<ProjectViewTag> = []; // // first create all our tags // and fill them with their documents // for (let i = 0; i < docs.length; i++) { let d = docs[i]; let tag: ProjectViewTag; tag = tags.find((t) => t.tag === d.jobflags); if (tag) { tag.allDocuments.push(d); } else { let newtag = new ProjectViewTag(d.jobflags); newtag.allDocuments.push(d); tags.push(newtag); } } _.forEach(tags, (t: ProjectViewTag) => { if (!keepAll) { t.allDocuments = Special.removeICS(t); t.allDocuments = Special.removeVSDR(t); } let fieldreviews = t.allDocuments.filter(FieldReview.isFieldReview); let drawings = t.allDocuments.filter(Drawings.isDrawing); let samples = t.allDocuments.filter(Samples.isSample); let contracts = t.allDocuments.filter(Contract.isContract); let closeOuts = t.allDocuments.filter(CloseOut.isCloseout); let specials = t.allDocuments.filter(Special.isSpecial); t.rowGroups = fieldreviews.length > 0 ? t.rowGroups.concat(FieldReview.projectViewGroups(fieldreviews)) : t.rowGroups; t.rowGroups = drawings.length > 0 ? t.rowGroups.concat(Drawings.projectViewGroups(drawings)) : t.rowGroups; t.rowGroups = samples.length > 0 ? t.rowGroups.concat(Samples.projectViewGroups(samples)) : t.rowGroups; t.rowGroups = contracts.length > 0 ? t.rowGroups.concat(Contract.projectViewGroups(contracts)) : t.rowGroups; t.rowGroups = closeOuts.length > 0 ? t.rowGroups.concat(CloseOut.projectViewGroups(closeOuts)) : t.rowGroups; t.rowGroups = specials.length > 0 ? t.rowGroups.concat(Special.projectViewGroups(specials)) : t.rowGroups; }); return tags; } ContractGroups() { return this.rowGroups.filter((g) => Contract.isContract(g.first())); } FldRvGroups() { return this.rowGroups.filter((g) => FieldReview.isFieldReview(g.first())); } DrawingGroups() { return this.rowGroups.filter((g) => Drawings.isDrawing(g.first())); } CloseOutGroups() { return this.rowGroups.filter((g) => CloseOut.isCloseout(g.first())); } SampleGroups() { return this.rowGroups.filter((g) => Samples.isSample(g.first())); } } export class ProjectViewRowGroup { superClass: string; documents: Array<PreconDocument>; constructor(superClass: string, documents: Array<PreconDocument>) { this.superClass = superClass; this.documents = documents; } first() { if (this.documents[0] === undefined) { } return this.documents[0]; } } // START PROJECT DRAWING SCHEDULING export interface PriorityLevel { [key: string]: number; } export class Project { jobnumber: string; assignedDrafter: number; contractManager: number; projectManager: number; predictedHours: number; firstFilename: string; documents: DocumentInProgress[]; _masterOrderDate: moment.Moment; id: number; // Master order date, aka the start date get masterOrderDate() { return this._masterOrderDate; } set masterOrderDate(ndate) { if (moment.isMoment(ndate)) { this._masterOrderDate = ndate; } else { this._masterOrderDate = moment(ndate); } } get doMasterOrderDate() { return this._masterOrderDate.isValid() ? this._masterOrderDate.toDate() : new Date(); } set doMoDate(ndate) { if (moment.isMoment(ndate)) { this._masterOrderDate = ndate; } else { this._masterOrderDate = moment(ndate); } } constructor(o: object) { o.documents = o.documents || []; this.jobnumber = o.jobnumber; this.firstFilename = o.firstFilename; this.predictedHours = o.predictedHours; this.assignedDrafter = parseInt(o.assignedDrafter + '') || null; this.contractManager = parseInt(o.contractManager + '') || null; this.projectManager = parseInt(o.projectManager + '') || null; this.id = parseInt(o.id + ''); this.masterOrderDate = moment(o.masterOrderDate); this.documents = o.documents.map((d) => { let newd = new DocumentInProgress(d); return newd; }); } } export class DocumentInProgress extends PreconDocument { public static readonly PriorityLevels:PriorityLevel = { '': 0, 'low': 1, 'high': 2, 'highest': 3 }; _dateStarted: moment.Moment; _timeCompleted: moment.Moment; predictedHours: number; assignedDrafter: number; firstFilename: string; id: number; hoursAccumulated: number; person: string; classification: string; manualPriority: string; set dateStarted(ndate) { if (moment.isMoment(ndate)) { this._dateStarted = ndate; } else { this._dateStarted = moment(ndate); } } get dateStarted(): moment.Moment { return this._dateStarted; } set timeCompleted(ndate) { if (moment.isMoment(ndate)) { this._timeCompleted = ndate; } else { this._timeCompleted = moment(ndate); } } get timeCompleted():moment.Moment { return this._timeCompleted; } get doDateStarted() { return this._dateStarted.toDate(); } set doDateStarted(ndate) { if (moment.isMoment(ndate)) { this._timeCompleted = ndate; } else { this._timeCompleted = moment(ndate); } } get doTimeCompleted(): Date { return this._timeCompleted.toDate(); } set doTimeCompleted(ndate) { if (moment.isMoment(ndate)) { this._timeCompleted = ndate; } else { this._timeCompleted = moment(ndate); } } public static DeepSame(d1: DocumentInProgress, d2: DocumentInProgress): boolean { if (!d1.dateStarted.isSame(d2.dateStarted, 'day') && d1.dateStarted.isValid()) { return false; } if (!d1.timeCompleted.isSame(d2.timeCompleted, 'day') && d1.timeCompleted.isValid()) { return false; } if (d1.assignedDrafter !== d2.assignedDrafter) { return false; } if (d1.predictedHours !== d2.predictedHours) { return false; } return true; } public dbFormat() { return { predictedHours: this.predictedHours, assignedDrafter: this.assignedDrafter, jobnumber: this.jobnumber, jobflags: this.jobflags, classification: this.classification, series: this.series, id: this.id, person: this.person, dateStarted: this.dateStarted.toDate(), timeCompleted: this.timeCompleted } } constructor(o) { super(o); this.dateStarted = moment(o.dateStarted || o.date_started, 'YYYY-MM-DD') ; this.timeCompleted = o.timeCompleted || o.time_completed || new Date(); this.predictedHours = o.predictedHours || o.predicted_hours; this.firstFilename = o.firstFilename; this.assignedDrafter = o.assignedDrafter || o.assigned_drafter; this.id = o.id; this.hoursAccumulated = o.hoursAccumulated || o.hours_accumulated; this.classification = o.classification; this.series = o.series; this.person = o.person; this.manualPriority = o.manualPriority || ''; } }