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() public ActionResult<string> Get()
{ {
Console.WriteLine("template get triggered"); 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": "*", "AllowedHosts": "*",
"UploadSavePath": "../Uploaded/", "UploadSavePath": "../Uploaded/",
"DataStandard" : [ "DataStandard" : {"headerTemplates": [
{"Header": "Contract Number", "HeaderRegex": "(contract\\s?(number|#|num))" ,"ValueRegex": "^\\d+\\d$", "Nullable": true}, {"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 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 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": "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": "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} {"Header": "PG1 FICO", "HeaderRegex": "(pg\\d?\\s?FICO)" ,"ValueRegex": "(\\d{1,2},?)*\\d+(.\\d{2})?", "Nullable": true}
] ],
"validateData" : false
}
} }

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

@ -1,27 +1,73 @@
<template> <template>
<FileUpload></FileUpload> <div v-if="invalidDataHeaders.length > 0">
<ItemBlock top-message="Missing Headers:" :item-list="store.missingHeaders"></ItemBlock> <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> </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> <script>
//import HelloWorld from './components/HelloWorld.vue'
import FileUpload from './components/FileUpload.vue'; import FileUpload from './components/FileUpload.vue';
import ItemBlock from './components/ItemBlock.vue'; import MissingHeaderBox from './components/MissingHeaderBox.vue';
import { store } from './store'; import { store } from './store';
export default { export default {
name: 'App', name: 'App',
components: { components: {
FileUpload, FileUpload,
ItemBlock, MissingHeaderBox,
}, },
data() { data() {
return { 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> </script>

@ -1,8 +1,8 @@
// TODO Add submit button to link with file upload
<template> <template>
<form> <form>
<input type="file" id="uploadInput" accept=".xlsx" @change="upload($event)"> <input type="file" id="uploadInput" accept=".xlsx" @change="fileSelected">
</form> <button type="submit" @click.prevent="parseUpload" :disabled="!canSubmit">Submit</button>
</form>
</template> </template>
<script> <script>
@ -11,23 +11,23 @@ import { read, utils } from "xlsx";
import { store } from '../store.js' import { store } from '../store.js'
import { HeaderAssociator } from '../helpers.js' import { HeaderAssociator } from '../helpers.js'
function find_header_row(worksheet) { function findFirstNonBlankRow(worksheet) {
// Returns the zero-index row number of the headers const range = utils.decode_range(worksheet['!ref']);
// We want to find the first row with header data // We'll check at most of 20 rows
// We'll check 7 columns over (as there should still be data) for (let row = range.s.r; row <= Math.min(range.e.r,20); row++) {
// then make sure there are 3 consecutive datapoints with values underneath var headerRow = true;
for (let col = 0; col < 20; col++) {
for (let row = 0; row < 20; row++) { const cell = worksheet[utils.encode_cell({ r: row, c: col })];
let col = 7; if (!cell || cell.v === '') {
let address = utils.encode_cell({c: col, r: row}); headerRow = false;
if (worksheet[address] !== undefined) { break;
// 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 (headerRow) {
if (headerCell !== undefined) {return row} return row;
} }
} }
return null; return null;
} }
@ -35,131 +35,162 @@ export default {
name: 'FileUpload', name: 'FileUpload',
data() { data() {
return { return {
uploadedFiles: [], // User uploaded file
uploadError: null, userFile: null,
currentStatus: null, // Header Template provided by server
uploadFieldName: 'uploadfile', headerTemplateList: [],
store // 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 mounted() {
async upload(event) { // We want to get the lastest template header specifications from the server
try {
var file = await event.target.files[0].arrayBuffer(); const configRequestOptions = {
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 = {
method: 'GET', method: 'GET',
redirect: 'follow', redirect: 'follow',
}; };
var resp = await fetch("http://192.168.0.171:5252/api/template", configRequestOptions); const resp = await fetch(process.env.VUE_APP_TEMPLATE_HEADERS_ENDPOINT, configRequestOptions);
var dataStandardList = await resp.json(); let respJson = await resp.json();
console.info(dataStandardList); 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 fileSelected(event) {
// This will let us easily search for each header using regex // Make sure the user can't still act on the old file
// The col will be noted at the end of the header so that we can this.$emit('parsed-upload', null);
// check the value // Clear old file data
var combinedHeadersString = ""; store.missingHeaders = [];
console.group("Document columns"); store.documentHeaders = [];
for (let col = 0; col < 200; col++){
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 cellAddress = utils.encode_cell({c: col, r: headerIndex});
let cellObj = worksheet[cellAddress]; let cellObj = worksheet[cellAddress];
if (cellObj === undefined) { break } // n represents number type (which is probably the start of cashflow dates)
// A value of 40k typicall indicates a date (cashflows) if (cellObj === undefined || cellObj.t === 'n') { break }
else if (cellObj.v > 40000) { break }
// Add the header to the combined headers string // Add the header to the combined headers string
combinedHeadersString += cellObj.v; combinedHeadersString += cellObj.v;
let headerAssociator = new HeaderAssociator(cellObj.v, cellAddress); let headerAssociator = new HeaderAssociator(cellObj.v, sheetName, cellAddress);
console.info(`Document column: ${headerAssociator}`) if (!this.validateData) {headerAssociator.isValidData = true;}
store.documentHeaders.push(headerAssociator); 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 // Check each template regex against the combined headers string
for (const headerTemplate of dataStandardList) { for (const headerTemplate of this.headerTemplateList) {
console.group(`Header regex: ${headerTemplate["Header"]}`);
// 'i' option means ignore case // 'i' option means ignore case
let headerRegex = RegExp(headerTemplate["HeaderRegex"],"i"); let headerRegex = RegExp(headerTemplate["HeaderRegex"],"i");
let headerName = headerTemplate["Header"]; let headerName = headerTemplate["Header"];
// Check to see if the template header is present in the combined headers string
let searchResults = headerRegex.exec(combinedHeadersString); let searchResults = headerRegex.exec(combinedHeadersString);
if (searchResults === null) { if (searchResults === null) {
console.info(`Header not found: ${headerName}`);
store.missingHeaders.push(headerName); store.missingHeaders.push(headerName);
console.groupEnd()
continue; continue;
} }
let docHeader = searchResults[0]; 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 // Find the associated header
for (let headerAssociator of store.documentHeaders) { for (let headerAssociator of store.documentHeaders) {
if (headerAssociator.isSet()) {console.groupEnd();continue} if (headerAssociator.isSet()) {continue}
if (headerAssociator.documentHeader === docHeader) { if (headerAssociator.documentHeader === docHeader) {
// Set the associated template header
headerAssociator.swapTemplateHeader(headerName); headerAssociator.swapTemplateHeader(headerName);
let headerAddress = utils.decode_cell(headerAssociator.docHeaderAddress); if (this.validateData) {
// Now we can get the value of the cell below the header // If we want to validate the data, then check the data against the regex
let valueCellAddress = utils.encode_cell(headerAddress.r + 1); let isNullable = headerTemplate["Nullable"];
let valueObj = worksheet[valueCellAddress]; let headerAddress = utils.decode_cell(headerAssociator.docHeaderAddress);
// Now we can get the value of the cell below the header
// Check if the cell is null, if so does it matter? let valueCellAddress = utils.encode_cell(headerAddress.r + 1);
if (valueObj !== undefined ) { // We need to know which worksheet this cell belongs to
// Test the value against regex let worksheet = workbook.Sheets[headerAssociator.docWorksheetName];
let valueRegex = RegExp(headerTemplate["ValueRegex"],'i'); let valueObj = worksheet[valueCellAddress];
headerAssociator.isValidData = valueRegex.test(valueObj.v); // Check if the cell is null, if so does it matter?
} else { if (valueObj !== undefined ) {
headerAssociator.isValidData = isNullable; // 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}`); // We found the header and processed it so stop looking
console.groupEnd();
break; break;
} }
} }
console.groupEnd();
} }
//TODO: Remove logs
console.info(`Missing headers (${store.missingHeaders.length}): ${store.missingHeaders}`); console.log(`Document headers (${store.documentHeaders.length}): ${store.documentHeaders}`);
console.info(`Document headers (${store.documentHeaders.length}): ${store.documentHeaders}`); console.log(`Missing headers (${store.missingHeaders.length}): ${store.missingHeaders}`);
// let excelDoc = new FormData(); // emit the uploaded file & parsed data up to parent
// excelDoc.append("file", event.target.files[0]); this.$emit('parsed-upload', this.userFile);
// 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);
} }
} }
} }
</script> </script>

@ -6,12 +6,11 @@
<div class="box-body"> <div class="box-body">
<select class="form-control" @change="updateSelectedDocHeader($event)"> <select class="form-control" @change="updateSelectedDocHeader($event)">
<option>{{firstValue}}</option> <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> </select>
<button type="button" class="btn btn-primary" @click="changeA" :disabled="this.selectedDocHeaderIndex === null">Confirm Choice</button> <button type="button" class="btn btn-primary" @click="changeA" :disabled="this.selectedDocHeaderIndex === null">Confirm Choice</button>
</div> </div>
</div> </div>
</template> </template>
<script> <script>
@ -26,7 +25,6 @@ export default {
}, },
data() { data() {
return { return {
backgroundColor: "#ff6666",
selectedDocHeaderIndex: null, selectedDocHeaderIndex: null,
isConfirmed: false, isConfirmed: false,
store store
@ -34,12 +32,14 @@ export default {
}, },
methods: { methods: {
updateSelectedDocHeader(event) { updateSelectedDocHeader(event) {
// If a correction was previously made, update the parent a correction was unmade
if (this.isConfirmed) { this.$emit('header-change', -1); } if (this.isConfirmed) { this.$emit('header-change', -1); }
this.isConfirmed = false; this.isConfirmed = false;
// If the old index is not null, we need to update it's docHeader to be null // 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); this.store.documentHeaders[this.selectedDocHeaderIndex].swapTemplateHeader(null);
} }
// Set the new index of the doc header
this.selectedDocHeaderIndex = event.target.value; this.selectedDocHeaderIndex = event.target.value;
console.log(this.selectedDocHeaderIndex); console.log(this.selectedDocHeaderIndex);
@ -47,22 +47,23 @@ export default {
changeA() { changeA() {
// Change the templateHeader in the docHeader at selectedDocHeader // Change the templateHeader in the docHeader at selectedDocHeader
this.isConfirmed = true; 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); this.$emit('header-change', 1);
} }
}, },
computed: { computed: {
filteredHeaders() { filteredHeaders() {
return this.store.documentHeaders.filter(header => { // Get the index of headers that are invalid
return !header.isSet(); return store.documentHeaders.reduce((acc, header, index) => {
}).map(header => { if (!header.isSet()) {
return { acc.push(index);
index: this.store.documentHeaders.indexOf(header), }
header: header, return acc;
}; }, []);
});
}, },
firstValue() { 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 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> <template>
<div v-if="store.missingHeaders.length > 0" class="missing-headers" :style="isFixed ? {'background-color': '#0a6e11'} : {'background-color': '#990b0b'}" > <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'}"> <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> </div>
<button v-if="this.isFixed" class="submit-button" @click="submitCorrections">Submit Corrections</button> <button v-if="this.isFixed" class="submit-button" @click="submitCorrections">Submit Corrections</button>
</div> </div>
@ -10,32 +10,30 @@
<script> <script>
import { store } from '../store.js' import { store } from '../store.js'
import ItemReplace from './ItemReplace.vue' import HeaderCorrection from './HeaderCorrection.vue'
export default { export default {
props: {
itemList: Array,
topMessage: String
},
data() { data() {
return { return {
store, store,
// Number of columns that have been corrected.
correctedColumns : 0, correctedColumns : 0,
// true if all columns have been corrected.
isFixed : false, isFixed : false,
} }
}, },
components: { components: {
ItemReplace HeaderCorrection
}, },
methods: { methods: {
itemChanged(value) { 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.correctedColumns += value;
this.isFixed = Boolean(this.correctedColumns == this.itemList.length) this.isFixed = Boolean(this.correctedColumns == store.missingHeaders.length)
console.log("isFixed: " + this.isFixed)
}, },
submitCorrections() { 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 // The users excel header they want to associate with our template
// type: (string) // type: (string)
documentHeader; documentHeader;
// The name of the document worksheet
docWorksheetName;
// The excel address of the document header // The excel address of the document header
// This will be a string in A1 notation for Excel // This will be a string in A1 notation for Excel
docHeaderAddress; 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 template excel header they want to associate
// the document header with // the document header with
// type: (string) // type: (string)
templateHeader = null; templateHeader = null;
isValidData = false; constructor(documentHeader, worksheetName, address) {
constructor(documentHeader, address) {
this.documentHeader = documentHeader this.documentHeader = documentHeader
this.docWorksheetName = worksheetName
this.docHeaderAddress = address this.docHeaderAddress = address
} }
@ -42,11 +55,13 @@ export class HeaderAssociator {
return oldTemplateHeader return oldTemplateHeader
} }
toString(){ invalidData(dataSample) {
return `${this.documentHeader} (${this.docHeaderAddress}) : ${this.templateHeader}` // If data is found to be invalid, set isValidData to false and record the data
this.docData = dataSample
this.isValidData = false
} }
isSet() { isSet() {
return this.templateHeader !== null return this.templateHeader !== null
} }
} }

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

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

Loading…
Cancel
Save