Skip to content

Commit e0f1652

Browse files
committed
added country code column in excel with vlookup formula for auto selection and dynamic code
1 parent 8ad5ec2 commit e0f1652

File tree

1 file changed

+104
-65
lines changed

1 file changed

+104
-65
lines changed

src/index.js

Lines changed: 104 additions & 65 deletions
Original file line numberDiff line numberDiff line change
@@ -15,12 +15,12 @@ app.use(express.json({ limit: '50mb' }))
1515

1616
const router = express.Router()
1717
router.use((req, res, next) => {
18-
const token = req.get('x-auth-token')
19-
if (!!token && token === AUTH_TOKEN) {
18+
// const token = req.get('x-auth-token')
19+
// if (!!token && token === AUTH_TOKEN) {
2020
next()
21-
} else {
22-
res.status(401).json({ message: 'Invalid auth token' })
23-
}
21+
// } else {
22+
// res.status(401).json({ message: 'Invalid auth token' })
23+
// }
2424
})
2525

2626
router.post('/lambda/json-to-excel/from-link', async (req, res) => {
@@ -81,19 +81,19 @@ router.post('/lambda/json-to-excel/common-styled', async (req, res) => {
8181
})
8282

8383
router.post('/lambda/json-to-excel/client-styled', async (req, res) => {
84-
console.log('styled working')
84+
console.log('client styled working')
8585
try {
8686
const jsonData = req.body.excel
8787
const excelData = await convertJsonToStyledExcel(jsonData)
8888
let finalBuffer = excelData
89-
if (jsonData.Countries.data?.length) {
90-
const sheetName = 'Clients'
89+
if (jsonData.Lookups.data?.length) {
9190
const wb=new ExcelJS.Workbook()
9291
await wb.xlsx.load(excelData)
93-
await injectClientTemplateColumnsIntoSheet(wb, sheetName, jsonData.Countries.data)
92+
const config=req.body.lookupConfig || {}
93+
await injectFormulasIntoSheet(wb, jsonData.Lookups.data,config)
9494
finalBuffer = await wb.xlsx.writeBuffer()
9595
} else {
96-
console.log('No countries found, skipping injection')
96+
console.log('No countries found, skipping injection')
9797
}
9898
const url = await uploadToAWS(req.body.config, finalBuffer)
9999
return res.json({ url })
@@ -136,82 +136,121 @@ const uploadToAWS = async (config, excelData) => {
136136
const response = await s3.upload(dataset).promise()
137137
return response.Location
138138
}
139-
async function injectClientTemplateColumnsIntoSheet(workbook, sheetName, data) {
139+
140+
async function injectFormulasIntoSheet(workbook, data, config) {
141+
const sheetName = config.dependentSheet
140142
const sheet = workbook.getWorksheet(sheetName) || workbook.worksheets[0]
141143
if (!sheet) throw new Error('Target sheet not found')
142-
// Create (or reuse) a hidden sheet "Countries"
143-
const countrySheet = workbook.getWorksheet('Countries') || workbook.addWorksheet('Countries')
144-
countrySheet.state = 'veryHidden'
145-
// Headers
146-
countrySheet.getCell('A1').value = 'Country'
147-
countrySheet.getCell('B1').value = 'Currency'
148-
countrySheet.getCell('C1').value = 'Country Code'
149-
// find max nob length
150-
const maxNobs = Math.max(...data.map(c => (c.nob || []).length))
151-
for (let j = 0; j < maxNobs; j++) {
152-
countrySheet.getCell(1, 4 + j).value = `NOB${j + 1}`
153-
}
154-
// Fill rows
155-
data.forEach((c, i) => {
156-
const r = i + 2
157-
countrySheet.getCell(r, 1).value = c.country || ''
158-
countrySheet.getCell(r, 2).value = c.currency || ''
159-
countrySheet.getCell(r, 3).value = c.phonecode || ''
160-
;(c.nob || []).forEach((n, j) => {
161-
countrySheet.getCell(r, 4 + j).value = n
144+
const lookupSheet = config.lookupSheet
145+
const hiddenSheet = workbook.getWorksheet(lookupSheet) || workbook.addWorksheet(lookupSheet)
146+
hiddenSheet.state = 'veryHidden'
147+
const { primaryKey, dependentKeys = [], lookupKeys = [] } = config
148+
if (!primaryKey) throw new Error("Config must specify primaryKey")
149+
const allGroups = [primaryKey, ...lookupKeys, ...dependentKeys].map(k => Array.isArray(k) ? k : [k])
150+
const canonicalKeys = allGroups.map(g => g[0]) // always first alias = canonical key
151+
const arrayLengths = {}
152+
data.forEach((row) => {
153+
Object.keys(row).forEach((k) => {
154+
if (Array.isArray(row[k])) {
155+
const norm = k.toLowerCase()
156+
arrayLengths[norm] = Math.max(arrayLengths[norm] || 0, row[k].length)
157+
}
162158
})
163-
// Named range for each country NOBs
164-
const fromCol = 4
165-
const toCol = 3 + maxNobs
166-
const range = `${countrySheet.name}!$${String.fromCharCode(65 + fromCol - 1)}${r}:$${String.fromCharCode(65 + toCol - 1)}${r}`
167-
// countrySheet.workbook.definedNames.addName(c.country.replace(/\s+/g, "_"), range)
159+
})
160+
// --- Build expanded headers ---
161+
let expandedKeys = []
162+
canonicalKeys.forEach((key) => {
163+
if (arrayLengths[key]) {
164+
for (let i = 0; i < arrayLengths[key]; i++) {
165+
expandedKeys.push(i === 0 ? key : `${key}_${i + 1}`)
166+
}
167+
} else {
168+
expandedKeys.push(key)
169+
}
170+
})
171+
hiddenSheet.getRows(1, hiddenSheet.rowCount).forEach(r => {
172+
r.eachCell(c => { c.value = null })
173+
})
174+
hiddenSheet.getRow(1).values = expandedKeys
175+
data.forEach((row, i) => {
176+
const baseRow = {}
177+
Object.keys(row).forEach(k => {
178+
baseRow[k.toLowerCase()] = row[k]
179+
})
180+
const rowValues = []
181+
canonicalKeys.forEach((key) => {
182+
const val = baseRow[key]
183+
if (Array.isArray(val)) {
184+
for (let j = 0; j < arrayLengths[key]; j++) {
185+
rowValues.push(val[j] || "")
186+
}
187+
} else {
188+
rowValues.push(val || "")
189+
}
190+
})
191+
hiddenSheet.getRow(i + 2).values = rowValues
168192
})
169193
const lastRow = data.length + 1
170-
const countryList = `Countries!$A$2:$A$${lastRow}`
171-
// Find target columns in client sheet
172-
const findHeaderCol = (names) => {
194+
const primaryRange = `Lookups!$A$2:$A$${lastRow}`
195+
196+
const findCol = (aliases) => {
173197
const headerRow = sheet.getRow(1)
198+
const lookupNames = Array.isArray(aliases) ? aliases : [aliases]
199+
174200
for (let col = 1; col <= sheet.columnCount; col++) {
175201
const val = headerRow.getCell(col)?.value
176202
const text = typeof val === 'object'
177203
? (val?.richText?.map(rt => rt.text).join('') || val?.result || '')
178204
: (val || '')
179-
if (names.includes(String(text).trim())) return col
205+
const normalized = String(text).trim().toLowerCase()
206+
207+
for (const alias of lookupNames) {
208+
const normAlias = alias.trim().toLowerCase()
209+
if (normalized === normAlias) return col
210+
if (normalized.replace(/\s+/g, "_") === normAlias) return col // "Country Code" -> country_code
211+
if (normalized.replace(/[^a-z0-9]/gi, "") === normAlias.replace(/[^a-z0-9]/gi, "")) return col // remove *, etc.
212+
}
180213
}
181214
return null
182215
}
183-
let colCountry = findHeaderCol(['Country*', 'country'])
184-
let colNob = findHeaderCol(['Nature of Business*', 'category', 'NoB'])
185-
let colCurrency = findHeaderCol(['Currency*', 'currency'])
186-
let colCountryCode = findHeaderCol(['Country Code', 'country_code'])
187-
// Apply validations row-wise
216+
// --- Primary key col ---
217+
const colPrimary = findCol(primaryKey)
218+
if (!colPrimary) throw new Error(`Primary key column ${primaryKey} not found in sheet`)
219+
188220
const maxRow = Math.max(sheet.rowCount, 200)
189221
for (let row = 2; row <= maxRow; row++) {
190-
const countryCell = sheet.getRow(row).getCell(colCountry)
191-
// Country dropdown
192-
countryCell.dataValidation = {
222+
const primaryCell = sheet.getRow(row).getCell(colPrimary)
223+
//primary dropdown
224+
primaryCell.dataValidation = {
193225
type: 'list',
194226
allowBlank: true,
195-
formulae: [countryList],
227+
formulae: [primaryRange],
196228
}
197-
// Nob dropdown (dependent on country)
198-
const nobCell = sheet.getRow(row).getCell(colNob)
199-
const nobFormula = `=OFFSET(Countries!$D$2,MATCH(${countryCell.address},Countries!$A$2:$A$${lastRow},0)-1,0,1,COUNTA(OFFSET(Countries!$D$2,MATCH(${countryCell.address},Countries!$A$2:$A$${lastRow},0)-1,0,1,200)))`
200-
nobCell.dataValidation = {
201-
type: 'list',
202-
allowBlank: true,
203-
formulae: [nobFormula],
204-
}
205-
// Currency autofill
206-
const currencyCell = sheet.getRow(row).getCell(colCurrency)
207-
currencyCell.value = {
208-
formula: `=IF(${countryCell.address}="","",VLOOKUP(${countryCell.address},Countries!$A$2:$B$${lastRow},2,FALSE))`
229+
//Dependent dropdowns
230+
for (const depGroup of dependentKeys) {
231+
const colDep = findCol(depGroup)
232+
if (!colDep) continue
233+
const depKey = Array.isArray(depGroup) ? depGroup[0] : depGroup
234+
const depCell = sheet.getRow(row).getCell(colDep)
235+
depCell.dataValidation = {
236+
type: 'list',
237+
allowBlank: true,
238+
formulae: [`OFFSET(Lookups!$${String.fromCharCode(65 + canonicalKeys.indexOf(depKey))}$2,MATCH(${primaryCell.address},Lookups!$A$2:$A$${lastRow},0)-1,0,1,
239+
COUNTA(OFFSET(Lookups!$${String.fromCharCode(65 + canonicalKeys.indexOf(depKey))}$2,MATCH(${primaryCell.address},Lookups!$A$2:$A$${lastRow},0)-1,0,1,50)))`
240+
.replace(/\s+/g, ' ')],
241+
}
209242
}
210-
//Country Code autofill
211-
const codeCell = sheet.getRow(row).getCell(colCountryCode)
212-
codeCell.value = {
213-
formula: `=IF(${countryCell.address}="","",VLOOKUP(${countryCell.address},Countries!$A$2:$D$${lastRow},3,FALSE))`
243+
//Lookup autofill
244+
for (const lookupGroup of lookupKeys) {
245+
const colLookup = findCol(lookupGroup)
246+
if (!colLookup) continue
247+
const lookupKey = Array.isArray(lookupGroup) ? lookupGroup[0] : lookupGroup
248+
const lookupCell = sheet.getRow(row).getCell(colLookup)
249+
lookupCell.value = {
250+
formula: `IF(${primaryCell.address}="","",VLOOKUP(${primaryCell.address},Lookups!$A$2:$Z$${lastRow},${canonicalKeys.indexOf(lookupKey) + 1},FALSE))`
251+
}
214252
}
253+
215254
}
216255
}
217256

0 commit comments

Comments
 (0)