preconstruction
Version:
typescript for preconstruction things
1,456 lines (1,224 loc) • 45.5 kB
text/typescript
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 || '';
}
}