Files
raven-client/ayanova/src/views/adm-import.vue
2022-03-28 23:11:09 +00:00

629 lines
16 KiB
Vue

<template>
<div v-if="formState.ready">
<gz-error :error-box-message="formState.errorBoxMessage"></gz-error>
<div>
<v-row>
<v-col cols="12" sm="6" lg="4" xl="3">
<v-select
ref="ayaType"
v-model="ayaType"
:items="selectLists.importableAyaTypes"
item-text="name"
item-value="id"
:label="$ay.t('AyaType')"
data-cy="ayaType"
></v-select>
</v-col>
<v-col v-if="ayaType != 0" cols="12" sm="6" lg="4" xl="3">
<v-checkbox
v-model="doImport"
:label="$ay.t('ImportNewRecords')"
></v-checkbox>
<v-checkbox
v-model="doUpdate"
:label="$ay.t('UpdateExistingRecords')"
color="warning"
></v-checkbox>
</v-col>
<v-col
v-if="ayaType != 0 && (doImport || doUpdate)"
cols="12"
sm="6"
lg="4"
xl="3"
>
<v-file-input
v-model="uploadFile"
:label="$ay.t('FileToImport')"
accept=".json, .csv, application/json, text/csv"
prepend-icon="$ayiFileUpload"
show-size
></v-file-input
><v-btn
v-if="importable"
:loading="uploading"
color="primary"
text
@click="process"
>{{ $ay.t("Import") }}</v-btn
>
</v-col>
<v-col v-if="outputText != null" cols="12">
<v-textarea
v-model="outputText"
full-width
readonly
auto-grow
data-cy="outputText"
></v-textarea>
</v-col>
</v-row>
</div>
</div>
</template>
<script>
import Papa from "papaparse";
const FORM_KEY = "adm-import";
export default {
data() {
return {
selectLists: {
importableAyaTypes: []
},
uploadFile: [],
ayaType: 0,
doImport: false,
doUpdate: false,
outputText: null,
rights: window.$gz.role.defaultRightsObject(),
uploading: false,
formState: {
ready: false,
dirty: false,
valid: true,
readOnly: false,
loading: true,
errorBoxMessage: null,
appError: null,
serverError: {}
}
};
},
computed: {
importable() {
return (
(this.doImport || this.doUpdate) &&
this.uploadFile &&
this.uploadFile.name &&
this.ayaType != 0
);
}
},
async created() {
//NOTE:Global is what is checked for initialize to show this form and at server to allow import
this.rights = window.$gz.role.getRights(window.$gz.type.Global);
window.$gz.eventBus.$on("menu-click", clickHandler);
await fetchTranslatedText(this);
await populateSelectionLists(this);
generateMenu(this);
this.formState.ready = true;
},
beforeDestroy() {
window.$gz.eventBus.$off("menu-click", clickHandler);
},
methods: {
async process() {
if (this.uploading) {
return;
}
if (!this.uploadFile) {
return;
}
this.uploading = true;
this.outputText = null;
try {
let fileType = this.uploadFile.type.toLowerCase();
if (!fileType.includes("csv") && !fileType.includes("json")) {
throw new Error("Not supported file type, must be .csv or .json");
}
const isCSV = fileType.includes("csv");
let dat = null;
if (isCSV) {
let res = await parseCSVFile(this.uploadFile);
if (res.errors.length > 0) {
this.outputText =
"LT:CSV parsing errors:\n" + JSON.stringify(res.errors);
throw new Error("LT:Errors in CSV file import can not proceed");
}
if (res.data) {
dat = res.data;
}
//transform the input csv if it's not a direct match to json (part assembly etc)
transform(dat, this.ayaType);
//console.log("Done parseCSVFile");
} else {
dat = await parseJSONFile(this.uploadFile);
//console.log("Done parse json");
}
// console.log(
// "The processed data before cleaning is ",
// JSON.stringify(dat)
// );
//strip out any unsupported fields before transmission
cleanData(dat, this.ayaType);
// console.log(
// "The processed data AFTER cleaning is ",
// JSON.stringify(dat)
// );
// eslint-disable-next-line no-debugger
//debugger;
//console.log("Flattened:", Flatten(dat));
console.log(
"if it was csv it should look like this:\n",
Papa.unparse(dat)
);
//upload the data
await this.upload(dat);
} catch (error) {
window.$gz.errorHandler.handleFormError(error);
} finally {
this.uploading = false;
}
},
async upload(dat) {
try {
if (this.doUpdate == true) {
let dialogResult = await window.$gz.dialog.confirmGeneric(
"AdminImportUpdateWarning",
"warning"
);
if (dialogResult == false) {
return;
}
}
const res = await window.$gz.api.post("import", {
data: dat,
atype: this.ayaType,
doImport: this.doImport,
doUpdate: this.doUpdate
});
if (res.error) {
window.$gz.errorHandler.handleFormError(res.error);
} else {
//result is an array of strings
let outText = "";
res.data.forEach(function appendImportResultItem(value) {
outText += value + "\n";
});
this.outputText = await window.$gz.translation.translateStringWithMultipleKeysAsync(
outText
);
}
} catch (error) {
window.$gz.errorHandler.handleFormError(error);
}
},
handleSelected() {}
}
};
/////////////////////////////
//
//
function clickHandler(menuItem) {
if (!menuItem) {
return;
}
const m = window.$gz.menu.parseMenuItem(menuItem);
if (m.owner == FORM_KEY && !m.disabled) {
switch (m.key) {
default:
window.$gz.eventBus.$emit(
"notify-warning",
FORM_KEY + "::context click: [" + m.key + "]"
);
}
}
}
//////////////////////
//
//
function generateMenu() {
const menuOptions = {
isMain: true,
icon: "$ayiFileImport",
title: "Import",
helpUrl: "adm-import",
menuItems: []
};
window.$gz.eventBus.$emit("menu-change", menuOptions);
}
//////////////////////////////////////////////////////////
//
// Ensures UI translated text is available
//
async function fetchTranslatedText() {
await window.$gz.translation.cacheTranslations([
"AyaType",
"ImportNewRecords",
"UpdateExistingRecords",
"AdminImportUpdateWarning",
"FileToImport"
]);
}
//////////////////////
//
//
async function populateSelectionLists(vm) {
await window.$gz.enums.fetchEnumList("importable");
vm.selectLists.importableAyaTypes = window.$gz.enums.getSelectionList(
"importable"
);
}
//////////////////////////////////////////////////////////
//
// Parse csv and return results as JSON, handle errors if any
//
async function parseCSVFile(file) {
return new Promise(function(complete, error) {
Papa.parse(file, {
header: true,
skipEmptyLines: true,
dynamicTyping: true,
worker: true,
complete,
error
});
});
}
//////////////////////////////////////////////////////////
//
// reformat JSON that was imported for types that need
// to be transformed (partassembly etc)
//
function transform(dat, atype) {
switch (atype) {
case window.$gz.type.PartAssembly:
//json from csv needs reformatting
dat.forEach(z => {
var newItems = [];
z.Items.split(",").forEach(x => {
let o = x.split("|");
newItems.push({
PartNameViz: o[0],
Quantity: Number.parseFloat(o[1])
});
});
z.Items = newItems;
});
break;
case window.$gz.type.TaskGroup:
dat.forEach(z => {
var newItems = [];
z.Items.split(",").forEach((x, i) => {
newItems.push({
Sequence: i,
Task: x
});
});
z.Items = newItems;
});
break;
}
//console.log("transform after:", dat);
}
//////////////////////////////////////////////////////////
//
// Open local json file, read, parse and return results as JSON, handle errors if any
//
async function parseJSONFile(file) {
return new Promise(function(complete) {
const reader = new FileReader();
reader.addEventListener(
"load",
() => {
// this will then display a text file
complete(JSON.parse(reader.result));
},
false
);
if (file) {
reader.readAsText(file);
}
});
}
//////////////////////////////////////////////////////////
//
// remove unsupported props from data
//
function cleanData(dat, atype) {
var allowedProps = [];
//Note: convention here is any ID field that is linked object we want to support gets renamed here to replace *id with *viz if viz not already present
//at back end it will attempt to match up but not create if not existing
switch (atype) {
case window.$gz.type.Customer:
allowedProps.push(
...[
"Name",
"Active",
"Notes",
"Wiki",
"Tags",
"WebAddress",
"AlertNotes",
"BillHeadOffice",
"HeadOfficeViz",
"TechNotes",
"AccountNumber",
"ContractViz",
"ContractExpires",
"Phone1",
"Phone2",
"Phone3",
"Phone4",
"Phone5",
"EmailAddress",
"PostAddress",
"PostCity",
"PostRegion",
"PostCountry",
"PostCode",
"Address",
"City",
"Region",
"Country",
"Latitude",
"Longitude"
]
);
break;
case window.$gz.type.HeadOffice:
allowedProps.push(
...[
"Name",
"Active",
"Notes",
"Wiki",
"Tags",
"WebAddress",
"TechNotes",
"AccountNumber",
"ContractViz",
"ContractExpires",
"Phone1",
"Phone2",
"Phone3",
"Phone4",
"Phone5",
"EmailAddress",
"PostAddress",
"PostCity",
"PostRegion",
"PostCountry",
"PostCode",
"Address",
"City",
"Region",
"Country",
"Latitude",
"Longitude"
]
);
break;
case window.$gz.type.Part:
allowedProps.push(
...[
"Name",
"Active",
"Description",
"Notes",
"Wiki",
"Tags",
"ManufacturerViz",
"ManufacturerNumber",
"WholeSalerViz",
"WholeSalerNumber",
"AlternativeWholeSalerViz",
"AlternativeWholeSalerNumber",
"Cost",
"Retail",
"UnitOfMeasure",
"UPC",
"PartSerialsViz"
]
);
break;
case window.$gz.type.PartAssembly:
allowedProps.push(
...[
"Name",
"Active",
"Notes",
"Wiki",
"Tags",
"Items",
"PartNameViz",
"Quantity"
]
);
break;
case window.$gz.type.PartWarehouse:
allowedProps.push(...["Name", "Active", "Notes", "Wiki", "Tags"]);
break;
case window.$gz.type.Project:
allowedProps.push(
...[
"Name",
"Active",
"Notes",
"Wiki",
"Tags",
"DateStarted",
"DateCompleted",
"ProjectOverseerViz",
"AccountNumber"
]
);
break;
case window.$gz.type.ServiceRate:
allowedProps.push(
...[
"Name",
"Active",
"Notes",
"Wiki",
"Tags",
"AccountNumber",
"Cost",
"Charge",
"Unit",
"ContractOnly"
]
);
break;
case window.$gz.type.TaskGroup:
allowedProps.push(...["Name", "Active", "Notes", "Items"]);
break;
case window.$gz.type.TravelRate:
allowedProps.push(
...[
"Name",
"Active",
"Notes",
"Wiki",
"Tags",
"AccountNumber",
"Cost",
"Charge",
"Unit",
"ContractOnly"
]
);
break;
}
//Strip out any records that have fields not on our allowed list
dat.forEach(z => {
for (const prop in z) {
if (allowedProps.includes(prop) == false) {
delete z[prop];
} else {
if (prop == "Tags") {
//if it's coming from csv then Tags will be a string with comma separated items like this: blue,white,red
//if it's json it will already be an array
if (z.Tags && typeof z.Tags === "string") {
z.Tags = z.Tags.split(",");
}
}
}
}
});
}
/* HOW TO GET PROPERTY NAMES EASILY
Get the object from network traffic:
Assign it in code to a variable:
var v = {
Name: "XYZ Accounting",
Active: true,
Notes: "Ergonomic impactful info-mediaries",
Wiki: null,
CustomFields: null,
Tags: ["black", "zone2", "zone3"],
WebAddress: "https://example.biz",
AlertNotes: null,
BillHeadOffice: true,
HeadOfficeId: 1,
HeadOfficeViz: "XYZ Head Office",
TechNotes: null,
AccountNumber: "10402542",
ContractId: null,
ContractViz: null,
ContractExpires: null,
LastWorkOrderViz: 350,
LastServiceDateViz: "2022-03-09T00:00:00Z",
Phone1: "373-707-7322 x535",
Phone2: "281.718.4551",
Phone3: "1-452-791-5760 x84358",
Phone4: null,
Phone5: null,
EmailAddress: "Annabel.Hahn64@example.org",
PostAddress: null,
PostCity: null,
PostRegion: null,
PostCountry: null,
PostCode: null,
Address: "9723 Tiara Summit",
City: "Hansenside",
Region: "Mississippi",
Country: "Japan",
Latitude: -7.7692,
Longitude: 165.0654
};
In a javascript console use this Object.getOwnPropertyNames(<PASTE OBJECT HERE>); will output a nice array with field names
[
"Name",
"Active",
"Notes",
"Wiki",
"CustomFields",
"Tags",
"WebAddress",
"AlertNotes",
"BillHeadOffice",
"HeadOfficeId",
"HeadOfficeViz",
"TechNotes",
"AccountNumber",
"ContractId",
"ContractViz",
"ContractExpires",
"LastWorkOrderViz",
"LastServiceDateViz",
"Phone1",
"Phone2",
"Phone3",
"Phone4",
"Phone5",
"EmailAddress",
"PostAddress",
"PostCity",
"PostRegion",
"PostCountry",
"PostCode",
"Address",
"City",
"Region",
"Country",
"Latitude",
"Longitude"
]
if it was csv it should look like this:
Name,Active,Notes,Wiki,CustomFields,Tags,WebAddress,AlertNotes,BillHeadOffice,HeadOfficeViz,TechNotes,AccountNumber,ContractViz,ContractExpires,Phone1,Phone2,Phone3,Phone4,Phone5,EmailAddress,PostAddress,PostCity,PostRegion,PostCountry,PostCode,Address,City,Region,Country,Latitude,Longitude
"ACopyOf Altenwerth, Willms and Krajcik",true,Advanced web-enabled productivity,,,"black,white",https://example.info,,true,"Gerlach, Howe and Crist",,40810165,,,(868) 464-5253,1-788-655-8853 x08455,536.824.7646,,,Maximo_Ziemann10@example.net,00060 Davion Mission,East Kevonstad,Washington,Iraq,68361-2305,75163 Heller Plains,East Kevonstad,Washington,Iraq,33.3014,-157.7912
"ACopyOf Adams, Macejkovic and Stoltenberg",true,Multi-layered multi-tasking infrastructure,,,zone9,https://example.biz,,false,,,21491466,,,830-720-4993 x86044,1-306-960-6200 x0811,1-269-482-1221 x97391,,,Leanne99@example.com,4399 Hegmann Pass,South Velva,Georgia,Chile,44636-1335,6036 Reggie Pine,South Velva,Georgia,Chile,41.1883,-152.6866
*/
</script>