Lots of refactoring, working well. Changed backend a bit

associator_rewrite
Griffiths Lott 3 years ago
parent 6e59501a2d
commit e54936e095
  1. 2
      .env
  2. 10
      backend/Controllers/TemplateConfigController.cs
  3. 33
      backend/TemplateConfig.cs
  4. 6
      backend/appsettings.Development.json
  5. 55
      backend/appsettings.json
  6. 1251
      package-lock.json
  7. 1
      package.json
  8. 64
      src/App.vue
  9. 245
      src/components/FileUpload.vue
  10. 27
      src/components/HeaderCorrection.vue
  11. 22
      src/components/InvalidData.vue
  12. 42
      src/components/InvalidValues.vue
  13. 22
      src/components/MissingHeaderBox.vue
  14. 27
      src/helpers.js
  15. 1
      src/store.js
  16. 2
      vue.config.js

@ -0,0 +1,2 @@
VUE_APP_TEMPLATE_HEADERS_ENDPOINT=http://192.168.0.171:5252/api/template
VUE_APP_TEMPLATE_ENDPOINT=http://192.168.0.171:5252/api/template

@ -21,13 +21,15 @@ namespace backend.Controllers
public ActionResult<string> Get()
{
Console.WriteLine("template get triggered");
List<ColumnStandard>? dataStandard = _config.GetSection("DataStandard").Get<List<ColumnStandard>>();
List<HeaderTemplate>? templateHeaders = _config.GetSection("templateHeaders").Get<List<HeaderTemplate>>();
bool? validateData = _config.GetSection("validateData").Get<bool>();
if (dataStandard == null)
if (templateHeaders == null || validateData == null)
{
return "Failed to retreieve data standard!";
return "Failed to retreieve template config!";
}
return JsonConvert.SerializeObject(dataStandard);
TemplateConfiguration templateConfig = new TemplateConfiguration(validateData ?? true, templateHeaders);
return JsonConvert.SerializeObject(templateConfig);
}
}

@ -0,0 +1,33 @@
namespace backend.Models
{
public class HeaderTemplate
{
public string Header { get; set; }
public string HeaderRegex { get; set; }
public string ValueRegex { get; set; }
public bool Nullable { get; set; }
public HeaderTemplate(string header, string headerRegex, string valueRegex, bool nullable)
{
this.Header = header;
this.HeaderRegex = headerRegex;
this.ValueRegex = valueRegex;
this.Nullable = nullable;
}
}
public class TemplateConfiguration
{
public bool ValidateData { get; set; }
public List<HeaderTemplate> HeaderTemplate { get; set; }
public TemplateConfiguration(bool ValidateData, List<HeaderTemplate> headerTemplate)
{
this.ValidateData = ValidateData;
this.HeaderTemplate = headerTemplate;
}
}
}

@ -7,7 +7,7 @@
},
"AllowedHosts": "*",
"UploadSavePath": "../Uploaded/",
"DataStandard" : [
"DataStandard" : {"headerTemplates": [
{"Header": "Contract Number", "HeaderRegex": "(contract\\s?(number|#|num))" ,"ValueRegex": "^\\d+\\d$", "Nullable": true},
{"Header": "Customer Name", "HeaderRegex": "(cust(omer)?\\s?name)" ,"ValueRegex": "(\\w+\\s?)+", "Nullable": true},
{"Header": "Customer Phone Number", "HeaderRegex": "(phone\\s?(num(ber)?|#)?)" ,"ValueRegex": "(\\+?\\d-?)?\\s?\\(?\\d{3}\\)?-?\\s?\\d{3}-?\\s?\\d{4}", "Nullable": true},
@ -34,5 +34,7 @@
{"Header": "PG SSN", "HeaderRegex": "((pg|guarantor)\\s?ssn)" ,"ValueRegex": "[0-2]?\\d\\/\\d{1,2}\\/(19|20)?\\d{2}", "Nullable": true},
{"Header": "DOB", "HeaderRegex": "(dob|date of birth)" ,"ValueRegex": "\\d{3}", "Nullable": true},
{"Header": "PG1 FICO", "HeaderRegex": "(pg\\d?\\s?FICO)" ,"ValueRegex": "(\\d{1,2},?)*\\d+(.\\d{2})?", "Nullable": true}
]
],
"validateData" : false
}
}

@ -8,30 +8,33 @@
"AllowedHosts": "*",
"Test" : "It Worked!",
"UploadSavePath": "../Uploaded/",
"DataStandard" : [
{"Header": "Contract Number", "HeaderRegex": "PG1 FICO", "ValueRegex": "^\\d+\\d$", "Nullable": false},
{"Header": "Customer Name", "HeaderRegex": "(?i)(cust(omer)?\\s?name)", "ValueRegex": "(\\w+\\s?)+", "Nullable": false},
{"Header": "Phone Number", "HeaderRegex": "(?i)phone\\s?(num(ber)?|#)?", "ValueRegex": "(\\+?\\d-?)?\\s?\\(?\\d{3}\\)?-?\\s?\\d{3}-?\\s?\\d{4}", "Nullable": false},
{"Header": "Customer Tax-ID", "HeaderRegex": "((tax-?id)|(TIN)|(EIN))" ,"ValueRegex": "(\\d{2}-?\\d{7})|((\\d{3}){2}\\d{3})|(\\d{3}-?\\d{2}-?\\d{4})" },
{"Header": "Debtor Address 1", "HeaderRegex": "(?i)debtor\\s?add(ress)?", "ValueRegex": "(\\w+\\s?)+", "Nullable": false},
{"Header": "Debtor City", "HeaderRegex": "(?i)debtor\\s?city", "ValueRegex": "(\\w+\\s?)+", "Nullable": false},
{"Header": "Debtor State", "HeaderRegex": "(?i)debtor\\s?state", "ValueRegex": "\\w+", "Nullable": false},
{"Header": "Debtor Zip Code", "HeaderRegex": "(?i)debtor\\s?zip\\s?(code)?", "ValueRegex": "\\d{5}-?(\\d{4})?", "Nullable": false},
{"Header": "Date Booked", "HeaderRegex": "(?i)date\\s?booked", "ValueRegex": "[1-2]?\\d\\/\\d{1,2}\\/(20)?\\d{2}", "Nullable": false},
{"Header": "Term", "HeaderRegex": "(?i)term\\s?(\\(?months\\)?)?", "ValueRegex": "\\d{1,3}", "Nullable": false},
{"Header": "Payment Amount", "HeaderRegex": "(?i)pa?yme?n?t\\s?am(oun)?t", "ValueRegex": "(\\d{1,2},?)*\\d+(.\\d{2})?", "Nullable": false},
{"Header": "Financed Amount", "HeaderRegex": "(?i)financed\\s?am(oun)?t", "ValueRegex": "(\\d{1,2},?)*\\d+(.\\d{2})?", "Nullable": false},
{"Header": "Receivable balance", "HeaderRegex": "(?i)rec(eivable)?\\s?bal(ance)?", "ValueRegex": "(\\d{1,2},?)*\\d+(.\\d{2})?", "Nullable": false},
{"Header": "Asset Description", "HeaderRegex": "(?i)asset\\sdesc(ription)?", "ValueRegex": "(\\w+\\s?)+", "Nullable": false},
{"Header": "Serial Number", "HeaderRegex": "(?i)serial\\s?num(ber)?|VIN", "ValueRegex": "\\d{8}", "Nullable": false},
{"Header": "Business Type", "HeaderRegex": "(?i)(business|biz)\\s?type", "ValueRegex": "(\\w+\\s?)+", "Nullable": false},
{"Header": "PG Name", "HeaderRegex": "(?i)pg\\s?name", "ValueRegex": "(\\w+\\s?)+", "Nullable": false},
{"Header": "PG Address Line 1", "HeaderRegex": "(?i)pg\\s?add(ress)?", "ValueRegex": "(\\w+\\s?)+", "Nullable": false},
{"Header": "PG City", "HeaderRegex": "(?i)pg\\s?city", "ValueRegex": "(\\w+\\s?)+", "Nullable": false},
{"Header": "PG State", "HeaderRegex": "(?i)pg\\s?state", "ValueRegex": "(\\w+\\s?)+", "Nullable": false},
{"Header": "PG Zip", "HeaderRegex": "(?i)pg\\s?zip", "ValueRegex": "\\d{5}-?(\\d{4})?", "Nullable": false},
{"Header": "PG SSN", "HeaderRegex": "(?i)pg\\s?ssn", "ValueRegex": "\\d{3}-?\\d{2}-?\\d{3}", "Nullable": false},
{"Header": "DOB", "HeaderRegex": "(?i)Dob", "ValueRegex": "[0-2]?\\d\\/\\d{1,2}\\/(19|20)?\\d{2}", "Nullable": false},
{"Header": "PG1 FICO", "HeaderRegex": "", "ValueRegex": "\\d{3}", "Nullable": false}
]
"validateData": false,
"templateHeaders" : [
{"Header": "Contract Number", "HeaderRegex": "(contract\\s?(number|#|num))" ,"ValueRegex": "^\\d+\\d$", "Nullable": true},
{"Header": "Customer Name", "HeaderRegex": "(cust(omer)?\\s?name)" ,"ValueRegex": "(\\w+\\s?)+", "Nullable": true},
{"Header": "Customer Phone Number", "HeaderRegex": "(phone\\s?(num(ber)?|#)?)" ,"ValueRegex": "(\\+?\\d-?)?\\s?\\(?\\d{3}\\)?-?\\s?\\d{3}-?\\s?\\d{4}", "Nullable": true},
{"Header": "Customer Tax-ID", "HeaderRegex": "((tax-?id)|(TIN)|(EIN))" ,"ValueRegex": "(\\d{2}-?\\d{7})|((\\d{3}){2}\\d{3})|(\\d{3}-?\\d{2}-?\\d{4})", "Nullable": true},
{"Header": "Customer Physical Address 1", "HeaderRegex": "(cust(omers?)?\\s?\\w*\\s?add(ress)?(\\s?1)?)" ,"ValueRegex": "(\\w+\\s?)+", "Nullable": true},
{"Header": "Customer Physical Address 2", "HeaderRegex": "(cust(omers?)?\\s?\\w*\\s?add(ress)?\\s2)" ,"ValueRegex": "", "Nullable": true},
{"Header": "Customer Physical City", "HeaderRegex": "(cust(omers?)?\\s?\\w*\\s?city)" ,"ValueRegex": "(\\w+\\s?)+", "Nullable": true},
{"Header": "Customer Physical State", "HeaderRegex": "(cust(omers?)?\\s?\\w*\\s?state)" ,"ValueRegex": "\\w+", "Nullable": true},
{"Header": "Customer Physical Zip", "HeaderRegex": "(cust(omers?)?\\s?\\w*\\s?zip\\s?(code)?)" ,"ValueRegex": "\\d{5}-?(\\d{4})?", "Nullable": true},
{"Header": "Date Booked", "HeaderRegex": "(date\\s?booked)" ,"ValueRegex": "[1-2]?\\d\\/\\d{1,2}\\/(20)?\\d{2}", "Nullable": true},
{"Header": "Term (Months)", "HeaderRegex": "(term\\s?(\\(?months\\)?)?)" ,"ValueRegex": "\\d{1,3}", "Nullable": true},
{"Header": "Payment Amount", "HeaderRegex": "(pa?yme?n?t\\s?am(oun)?t)" ,"ValueRegex": "(\\d{1,2},?)*\\d+(.\\d{2})?", "Nullable": true},
{"Header": "Financed Amount", "HeaderRegex": "(financed\\s?am(oun)?t)" ,"ValueRegex": "(\\d{1,2},?)*\\d+(.\\d{2})?", "Nullable": true},
{"Header": "Receivable balance", "HeaderRegex": "(rec(eivable)?\\s?bal(ance)?)" ,"ValueRegex": "(\\d{1,2},?)*\\d+(.\\d{2})?", "Nullable": true},
{"Header": "Asset Description", "HeaderRegex": "(asset\\sdesc(ription)?)" ,"ValueRegex": "(\\w+\\s?)+", "Nullable": true},
{"Header": "Serial Number/VIN", "HeaderRegex": "(serial\\s?num(ber)?|VIN)" ,"ValueRegex": "\\d{8}", "Nullable": true},
{"Header": "Business Type", "HeaderRegex": "((business|biz)\\s?type)" ,"ValueRegex": "(\\w+\\s?)+", "Nullable": true},
{"Header": "PG Name", "HeaderRegex": "((pg|guarantor)\\s?name)" ,"ValueRegex": "(\\w+\\s?)+", "Nullable": true},
{"Header": "PG Address 1", "HeaderRegex": "((pg|guarantor)\\s?add(ress)?(\\s?1))" ,"ValueRegex": "(\\w+\\s?)+", "Nullable": true},
{"Header": "PG Address 2", "HeaderRegex": "((pg|guarantor)\\s?add(ress)?(\\s?2))" ,"ValueRegex": "(\\w+\\s?)+", "Nullable": true},
{"Header": "PG City", "HeaderRegex": "((pg|guarantor)\\s?city)" ,"ValueRegex": "(\\w+\\s?)+", "Nullable": true},
{"Header": "PG State", "HeaderRegex": "((pg|guarantor)\\s?state)" ,"ValueRegex": "\\d{5}-?(\\d{4})?", "Nullable": true},
{"Header": "PG Zip", "HeaderRegex": "((pg|guarantor)\\s?zip)" ,"ValueRegex": "\\d{3}-?\\d{2}-?\\d{3}", "Nullable": true},
{"Header": "PG SSN", "HeaderRegex": "((pg|guarantor)\\s?ssn)" ,"ValueRegex": "[0-2]?\\d\\/\\d{1,2}\\/(19|20)?\\d{2}", "Nullable": true},
{"Header": "DOB", "HeaderRegex": "(dob|date of birth)" ,"ValueRegex": "\\d{3}", "Nullable": true},
{"Header": "PG1 FICO", "HeaderRegex": "(pg\\d?\\s?FICO)" ,"ValueRegex": "(\\d{1,2},?)*\\d+(.\\d{2})?", "Nullable": true}
]
}

1251
package-lock.json generated

File diff suppressed because it is too large Load Diff

@ -9,6 +9,7 @@
},
"dependencies": {
"core-js": "^3.8.3",
"serve": "^14.2.0",
"vue": "^3.2.13",
"vuex": "^4.1.0",
"xlsx": "https://cdn.sheetjs.com/xlsx-0.19.1/xlsx-0.19.1.tgz"

@ -1,27 +1,73 @@
<template>
<FileUpload></FileUpload>
<ItemBlock top-message="Missing Headers:" :item-list="store.missingHeaders"></ItemBlock>
<div v-if="invalidDataHeaders.length > 0">
<h2>Invalid Headers</h2>
<ul>
<li v-for="header in invalidDataHeaders" :key="header.documentHeader">
{{ header.documentHeader }}
</li>
</ul>
</div>
<div v-else>
<FileUpload @parsedUpload="processUpload"></FileUpload>
<MissingHeaderBox @correctionsSubmitted="uploadFile"></MissingHeaderBox>
</div>
</template>
// <ItemBlock top-message="Invalid Data in Columns:" :item-list="store.documentHeaders.filter(aHeader => !aHeader.isSet())"></ItemBlock>
//TODO: Add template file download
//TODO: Add template file download reminder on document failure
//TODO: Add component/div showing invalid headers
//TODO: Add login component
//TODO: Add error notifications
<script>
//import HelloWorld from './components/HelloWorld.vue'
import FileUpload from './components/FileUpload.vue';
import ItemBlock from './components/ItemBlock.vue';
import MissingHeaderBox from './components/MissingHeaderBox.vue';
import { store } from './store';
export default {
name: 'App',
components: {
FileUpload,
ItemBlock,
MissingHeaderBox,
},
data() {
return {
store
}
}
store,
// The user uploaded excel doc
userFile: null,
// If the headers are correct we can upload the file
isCorrectHeaders: false,
// True when headers with invalid data are present
invalidDataHeaders: [],
};
},
methods: {
processUpload(userFile) {
console.log(store.documentHeaders);
// handle null
if (userFile === null) {
this.invalidDataHeaders = [];
this.isCorrectHeaders = false;
return;
}
// set the userFile property
this.userFile = userFile;
// filter the documentHeaders to get the invalid headers
this.invalidDataHeaders = this.store.documentHeaders.filter(
(header) => !header.isValidData
);
console.log(this.invalidDataHeaders);
// set isUploaded to true
this.isUploaded = true;
},
uploadFile() {
// We know the document is correct
//TODO: send the file to the server
},
},
}
</script>

@ -1,8 +1,8 @@
// TODO Add submit button to link with file upload
<template>
<form>
<input type="file" id="uploadInput" accept=".xlsx" @change="upload($event)">
</form>
<form>
<input type="file" id="uploadInput" accept=".xlsx" @change="fileSelected">
<button type="submit" @click.prevent="parseUpload" :disabled="!canSubmit">Submit</button>
</form>
</template>
<script>
@ -11,23 +11,23 @@ import { read, utils } from "xlsx";
import { store } from '../store.js'
import { HeaderAssociator } from '../helpers.js'
function find_header_row(worksheet) {
// Returns the zero-index row number of the headers
// We want to find the first row with header data
// We'll check 7 columns over (as there should still be data)
// then make sure there are 3 consecutive datapoints with values underneath
for (let row = 0; row < 20; row++) {
let col = 7;
let address = utils.encode_cell({c: col, r: row});
if (worksheet[address] !== undefined) {
// Check the next three cells as well as the row below
let headerAdr = utils.encode_cell({c:col+5, r: row});
let headerCell = worksheet[headerAdr];
if (headerCell !== undefined) {return row}
function findFirstNonBlankRow(worksheet) {
const range = utils.decode_range(worksheet['!ref']);
// We'll check at most of 20 rows
for (let row = range.s.r; row <= Math.min(range.e.r,20); row++) {
var headerRow = true;
for (let col = 0; col < 20; col++) {
const cell = worksheet[utils.encode_cell({ r: row, c: col })];
if (!cell || cell.v === '') {
headerRow = false;
break;
}
}
if (headerRow) {
return row;
}
}
return null;
return null;
}
@ -35,131 +35,162 @@ export default {
name: 'FileUpload',
data() {
return {
uploadedFiles: [],
uploadError: null,
currentStatus: null,
uploadFieldName: 'uploadfile',
store
// User uploaded file
userFile: null,
// Header Template provided by server
headerTemplateList: [],
// Defines whether data row is checked agianst value regex
validateData: true,
// Store data for access by other components
store,
// Controls whether or not the user can submit a file
// - When a file is selected, then disabled when submitted
canSubmit: false,
}
},
methods: {
async upload(event) {
var file = await event.target.files[0].arrayBuffer();
var workbook = read(file);
var worksheets = workbook.SheetNames;
var worksheet = workbook.Sheets[worksheets[0]];
var headerIndex = find_header_row(worksheet);
// FIXME: Add handling for failed uploads and checking other sheets
if (headerIndex === null) {
console.error("Failed to find valid header row");
return;
}
// TODO: This should happen on page load and be stored as app data
// Load header/value config
var configRequestOptions = {
async mounted() {
// We want to get the lastest template header specifications from the server
try {
const configRequestOptions = {
method: 'GET',
redirect: 'follow',
};
var resp = await fetch("http://192.168.0.171:5252/api/template", configRequestOptions);
var dataStandardList = await resp.json();
console.info(dataStandardList);
const resp = await fetch(process.env.VUE_APP_TEMPLATE_HEADERS_ENDPOINT, configRequestOptions);
let respJson = await resp.json();
console.log(respJson);
this.headerTemplateList = respJson["HeaderTemplate"];
//FIXME: This is only for testing purposes, REMOVE THIS
this.validateData = true;//respJson["ValidateData"];
console.log(this.headerTemplateList);
console.log(this.validateData);
} catch (error) {
//FIXME: emit error internal issues
console.error(error);
}
},
methods: {
// Here we compile all of the headers into one string
// This will let us easily search for each header using regex
// The col will be noted at the end of the header so that we can
// check the value
var combinedHeadersString = "";
console.group("Document columns");
for (let col = 0; col < 200; col++){
fileSelected(event) {
// Make sure the user can't still act on the old file
this.$emit('parsed-upload', null);
// Clear old file data
store.missingHeaders = [];
store.documentHeaders = [];
this.userFile = event.target.files[0];
this.canSubmit = true;
},
getHeaders(worksheet, sheetName, headerIndex) {
// Store the found headers
var combinedHeadersString = "";
// Use the range to make sure we don't go out of bounds
const range = utils.decode_range(worksheet['!ref']);
for (let col = 0; col < range.e.c; col++){
let cellAddress = utils.encode_cell({c: col, r: headerIndex});
let cellObj = worksheet[cellAddress];
if (cellObj === undefined) { break }
// A value of 40k typicall indicates a date (cashflows)
else if (cellObj.v > 40000) { break }
// n represents number type (which is probably the start of cashflow dates)
if (cellObj === undefined || cellObj.t === 'n') { break }
// Add the header to the combined headers string
combinedHeadersString += cellObj.v;
let headerAssociator = new HeaderAssociator(cellObj.v, cellAddress);
console.info(`Document column: ${headerAssociator}`)
let headerAssociator = new HeaderAssociator(cellObj.v, sheetName, cellAddress);
if (!this.validateData) {headerAssociator.isValidData = true;}
store.documentHeaders.push(headerAssociator);
}
console.groupEnd();
return combinedHeadersString;
},
async parseUpload() {
this.canSubmit = false;
const fileBuffer = await this.userFile.arrayBuffer();
var workbook = await read(fileBuffer, {type: 'array'});
if (workbook === null) {
//FIXME: Emit bad spreadsheet to app
console.error(`No workbook found! Could not parse file: ${this.userFile.name}.`);
return;
}
var validWorksheets = []
var combinedHeadersString = '';
// Go through each worksheet
for (let sheetName of workbook.SheetNames) {
// Open the actual worksheet
let worksheet = workbook.Sheets[sheetName];
// Find the first non-blank row of 20
var headerIndex = findFirstNonBlankRow(worksheet);
if (headerIndex === null) {
// Probably not a sheet we need to process
console.warn(`${sheetName} | No header row found!`);
}
// Add the header to the combined headers string
// getHeaders will created the AssociatedHeaders objects
combinedHeadersString += this.getHeaders(worksheet, sheetName, headerIndex);
// Note which worksheets are actually being processed
validWorksheets.push(sheetName);
}
if (validWorksheets.length === 0) {
//FIXME: emit bad spreadsheet to App
console.error("No worksheets found!");
return;
}
// Check each template regex against the combined headers string
for (const headerTemplate of dataStandardList) {
console.group(`Header regex: ${headerTemplate["Header"]}`);
for (const headerTemplate of this.headerTemplateList) {
// 'i' option means ignore case
let headerRegex = RegExp(headerTemplate["HeaderRegex"],"i");
let headerName = headerTemplate["Header"];
// Check to see if the template header is present in the combined headers string
let searchResults = headerRegex.exec(combinedHeadersString);
if (searchResults === null) {
console.info(`Header not found: ${headerName}`);
store.missingHeaders.push(headerName);
console.groupEnd()
continue;
}
let docHeader = searchResults[0];
console.log(`Match: ${docHeader}`);
// Now we need to check the value below the header
let isNullable = headerTemplate["Nullable"];
// Find the associated header
for (let headerAssociator of store.documentHeaders) {
if (headerAssociator.isSet()) {console.groupEnd();continue}
if (headerAssociator.isSet()) {continue}
if (headerAssociator.documentHeader === docHeader) {
// Set the associated template header
headerAssociator.swapTemplateHeader(headerName);
let headerAddress = utils.decode_cell(headerAssociator.docHeaderAddress);
// Now we can get the value of the cell below the header
let valueCellAddress = utils.encode_cell(headerAddress.r + 1);
let valueObj = worksheet[valueCellAddress];
// Check if the cell is null, if so does it matter?
if (valueObj !== undefined ) {
// Test the value against regex
let valueRegex = RegExp(headerTemplate["ValueRegex"],'i');
headerAssociator.isValidData = valueRegex.test(valueObj.v);
} else {
headerAssociator.isValidData = isNullable;
if (this.validateData) {
// If we want to validate the data, then check the data against the regex
let isNullable = headerTemplate["Nullable"];
let headerAddress = utils.decode_cell(headerAssociator.docHeaderAddress);
// Now we can get the value of the cell below the header
let valueCellAddress = utils.encode_cell(headerAddress.r + 1);
// We need to know which worksheet this cell belongs to
let worksheet = workbook.Sheets[headerAssociator.docWorksheetName];
let valueObj = worksheet[valueCellAddress];
// Check if the cell is null, if so does it matter?
if (valueObj !== undefined ) {
// Test the value against regex
let valueRegex = RegExp(headerTemplate["ValueRegex"],'i');
let dataValue = valueObj.v.toString();
let isValid = valueRegex.test(dataValue);
if (!isValid) {
// record the invalid data and set isValidData to false
headerAssociator.invalidData(dataValue);
}
} else {
// isValid matches isNullable
headerAssociator.isValidData = isNullable;
}
}
console.info(`Found header: ${headerAssociator}`);
console.groupEnd();
// We found the header and processed it so stop looking
break;
}
}
console.groupEnd();
}
console.info(`Missing headers (${store.missingHeaders.length}): ${store.missingHeaders}`);
console.info(`Document headers (${store.documentHeaders.length}): ${store.documentHeaders}`);
// let excelDoc = new FormData();
// excelDoc.append("file", event.target.files[0]);
// var requestOptions = {
// method: 'POST',
// body: excelDoc,
// redirect: 'follow'
// };
// let resp = await fetch("http://192.168.0.171:5252/api/excelupload", requestOptions)
// .then(response => response.text())
// .then(result => console.log(result))
// .catch(error => console.log('error', error));
// console.log(resp);
//TODO: Remove logs
console.log(`Document headers (${store.documentHeaders.length}): ${store.documentHeaders}`);
console.log(`Missing headers (${store.missingHeaders.length}): ${store.missingHeaders}`);
// emit the uploaded file & parsed data up to parent
this.$emit('parsed-upload', this.userFile);
}
}
}
</script>

@ -6,12 +6,11 @@
<div class="box-body">
<select class="form-control" @change="updateSelectedDocHeader($event)">
<option>{{firstValue}}</option>
<option v-for="headerObj in filteredHeaders" :key="headerObj.header.documentHeader" :value="headerObj.index">{{headerObj.header.documentHeader}}</option>
<option v-for="headerIndex in filteredHeaders" :key="headerIndex" :value="headerIndex">{{store.documentHeaders[headerIndex].documentHeader}}</option>
</select>
<button type="button" class="btn btn-primary" @click="changeA" :disabled="this.selectedDocHeaderIndex === null">Confirm Choice</button>
</div>
</div>
</template>
<script>
@ -26,7 +25,6 @@ export default {
},
data() {
return {
backgroundColor: "#ff6666",
selectedDocHeaderIndex: null,
isConfirmed: false,
store
@ -34,12 +32,14 @@ export default {
},
methods: {
updateSelectedDocHeader(event) {
// If a correction was previously made, update the parent a correction was unmade
if (this.isConfirmed) { this.$emit('header-change', -1); }
this.isConfirmed = false;
// If the old index is not null, we need to update it's docHeader to be null
if (this.selectedDocHeaderIndex!== null) {
if (this.selectedDocHeaderIndex !== null) {
this.store.documentHeaders[this.selectedDocHeaderIndex].swapTemplateHeader(null);
}
// Set the new index of the doc header
this.selectedDocHeaderIndex = event.target.value;
console.log(this.selectedDocHeaderIndex);
@ -47,22 +47,23 @@ export default {
changeA() {
// Change the templateHeader in the docHeader at selectedDocHeader
this.isConfirmed = true;
this.store.documentHeaders[this.selectedDocHeaderIndex].templateHeader = this.store.documentHeaders;
this.store.documentHeaders[this.selectedDocHeaderIndex].templateHeader = this.itemText;
this.$emit('header-change', 1);
}
},
computed: {
filteredHeaders() {
return this.store.documentHeaders.filter(header => {
return !header.isSet();
}).map(header => {
return {
index: this.store.documentHeaders.indexOf(header),
header: header,
};
});
// Get the index of headers that are invalid
return store.documentHeaders.reduce((acc, header, index) => {
if (!header.isSet()) {
acc.push(index);
}
return acc;
}, []);
},
firstValue() {
// When a doc header is confirmed it is removed from the list, we need to set it here
// If no doc header is confirmed, allow the user to select a null
return this.isConfirmed ? this.store.documentHeaders[this.selectedDocHeaderIndex].documentHeader : null
}
}

@ -0,0 +1,22 @@
<template>
<li v-for="index in invalidDataIndicies" :key="index" :value="index">{{ store.documentHeaders[index].templateHeader }}
</li>
</template>
<script>
import { store } from '../store.js'
export default {
props: {
invalidDataIndicies: {
default: () => []
},
},
data() {
return {
store,
}
},
}
</script>

@ -1,42 +0,0 @@
<template>
<div v-if="store.missingHeaders.length > 0" class="missing-headers">
<h1>Missing Headers:</h1>
<div class="grid">
<ColumnHeader v-for="header in store.missingHeaders" :key="header" :text="header"/>
</div>
</div>
</template>
<script>
import { store } from '../store.js'
export default {
components: {
},
data() {
return {
store
}
},
}
</script>
<style>
.missing-headers {
text-align: center;
}
.grid {
justify-content: center;
display: grid;
grid-template-columns: repeat(3, 1fr);
grid-gap: 10px;
margin-top: 10px;
padding: 15px;
border-color: darkgrey;
border-width: 10px;
background-color:#800000;
}
</style>

@ -1,8 +1,8 @@
<template>
<div v-if="store.missingHeaders.length > 0" class="missing-headers" :style="isFixed ? {'background-color': '#0a6e11'} : {'background-color': '#990b0b'}" >
<h1>{{topMessage}}</h1>
<h1>Missing Headers:</h1>
<div class="grid" :style="isFixed ? {'background-color': '#0b3b0e'} : {'background-color': '#800000'}">
<ItemReplace @header-change="itemChanged" v-for="header in itemList" :key="header" :itemText="header"/>
<HeaderCorrection @header-change="itemChanged" v-for="header in store.missingHeaders" :key="header" :itemText="header"/>
</div>
<button v-if="this.isFixed" class="submit-button" @click="submitCorrections">Submit Corrections</button>
</div>
@ -10,32 +10,30 @@
<script>
import { store } from '../store.js'
import ItemReplace from './ItemReplace.vue'
import HeaderCorrection from './HeaderCorrection.vue'
export default {
props: {
itemList: Array,
topMessage: String
},
data() {
return {
store,
// Number of columns that have been corrected.
correctedColumns : 0,
// true if all columns have been corrected.
isFixed : false,
}
},
components: {
ItemReplace
HeaderCorrection
},
methods: {
itemChanged(value) {
console.log("Value: " + value + " itemlistlen: "+ this.itemList.length)
// Value will be positive if a correction was made, negative if correction undone.
this.correctedColumns += value;
this.isFixed = Boolean(this.correctedColumns == this.itemList.length)
console.log("isFixed: " + this.isFixed)
this.isFixed = Boolean(this.correctedColumns == store.missingHeaders.length)
},
submitCorrections() {
console.log("Correction Submitted!")
// All of the corrections are present in store.documentHeaders
this.$emit('corrections-submitted');
}
}

@ -9,19 +9,32 @@ export class HeaderAssociator {
// The users excel header they want to associate with our template
// type: (string)
documentHeader;
// The name of the document worksheet
docWorksheetName;
// The excel address of the document header
// This will be a string in A1 notation for Excel
docHeaderAddress;
// Data associated with the document header
docData = null;
// Describes whether the data found under the document header matches expected data
// If no template header is associated with the document header, this will be true
// When template header is set/changed, this updates
validAssociatedData = true;
// The template excel header they want to associate
// the document header with
// type: (string)
templateHeader = null;
isValidData = false;
constructor(documentHeader, address) {
constructor(documentHeader, worksheetName, address) {
this.documentHeader = documentHeader
this.docWorksheetName = worksheetName
this.docHeaderAddress = address
}
@ -42,11 +55,13 @@ export class HeaderAssociator {
return oldTemplateHeader
}
toString(){
return `${this.documentHeader} (${this.docHeaderAddress}) : ${this.templateHeader}`
invalidData(dataSample) {
// If data is found to be invalid, set isValidData to false and record the data
this.docData = dataSample
this.isValidData = false
}
isSet() {
return this.templateHeader !== null
}
}
}

@ -10,6 +10,7 @@ export const store = reactive({
// Contains the following data:
// - docuemnt header
// - document header address (excel A1 string)
// - document header worksheet name
// - template header (or null)
// - isValidDate : whether the excel row data is valid
documentHeaders: [],

@ -1,4 +1,4 @@
const { defineConfig } = require('@vue/cli-service')
module.exports = defineConfig({
transpileDependencies: true
transpileDependencies: true,
})

Loading…
Cancel
Save