Skip to content

Commit 8521820

Browse files
authored
Merge pull request #4 from divamtech/ishika-add-country-code
added country code column in excel with vlookup formula for auto selection
2 parents 072d08d + 99818fb commit 8521820

File tree

1 file changed

+104
-57
lines changed

1 file changed

+104
-57
lines changed

src/index.js

Lines changed: 104 additions & 57 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,74 +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-
// find max nob length
149-
const maxNobs = Math.max(...data.map(c => (c.nob || []).length))
150-
for (let j = 0; j < maxNobs; j++) {
151-
countrySheet.getCell(1, 3 + j).value = `NOB${j + 1}`
152-
}
153-
// Fill rows
154-
data.forEach((c, i) => {
155-
const r = i + 2
156-
countrySheet.getCell(r, 1).value = c.country || ''
157-
countrySheet.getCell(r, 2).value = c.currency || ''
158-
;(c.nob || []).forEach((n, j) => {
159-
countrySheet.getCell(r, 3 + 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+
}
160158
})
161-
// Named range for each country NOBs
162-
const fromCol = 3
163-
const toCol = 2 + maxNobs
164-
const range = `${countrySheet.name}!$${String.fromCharCode(65 + fromCol - 1)}${r}:$${String.fromCharCode(65 + toCol - 1)}${r}`
165-
// 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
166192
})
167193
const lastRow = data.length + 1
168-
const countryList = `Countries!$A$2:$A$${lastRow}`
169-
// Find target columns in client sheet
170-
const findHeaderCol = (names) => {
194+
const primaryRange = `Lookups!$A$2:$A$${lastRow}`
195+
196+
const findCol = (aliases) => {
171197
const headerRow = sheet.getRow(1)
198+
const lookupNames = Array.isArray(aliases) ? aliases : [aliases]
199+
172200
for (let col = 1; col <= sheet.columnCount; col++) {
173201
const val = headerRow.getCell(col)?.value
174202
const text = typeof val === 'object'
175203
? (val?.richText?.map(rt => rt.text).join('') || val?.result || '')
176204
: (val || '')
177-
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+
}
178213
}
179214
return null
180215
}
181-
let colCountry = findHeaderCol(['Country*', 'country'])
182-
let colNob = findHeaderCol(['Nature of Business*', 'category', 'NoB'])
183-
let colCurrency = findHeaderCol(['Currency*', 'currency'])
184-
// 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+
185220
const maxRow = Math.max(sheet.rowCount, 200)
186221
for (let row = 2; row <= maxRow; row++) {
187-
const countryCell = sheet.getRow(row).getCell(colCountry)
188-
// Country dropdown
189-
countryCell.dataValidation = {
222+
const primaryCell = sheet.getRow(row).getCell(colPrimary)
223+
//primary dropdown
224+
primaryCell.dataValidation = {
190225
type: 'list',
191226
allowBlank: true,
192-
formulae: [countryList],
227+
formulae: [primaryRange],
193228
}
194-
// Nob dropdown (dependent on country)
195-
const nobCell = sheet.getRow(row).getCell(colNob)
196-
const nobFormula = `=OFFSET(Countries!$C$2,MATCH(${countryCell.address},Countries!$A$2:$A$${lastRow},0)-1,0,1,COUNTA(OFFSET(Countries!$C$2,MATCH(${countryCell.address},Countries!$A$2:$A$${lastRow},0)-1,0,1,200)))`
197-
nobCell.dataValidation = {
198-
type: 'list',
199-
allowBlank: true,
200-
formulae: [nobFormula],
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+
}
201242
}
202-
// Currency autofill
203-
const currencyCell = sheet.getRow(row).getCell(colCurrency)
204-
currencyCell.value = {
205-
formula: `=IF(${countryCell.address}="","",VLOOKUP(${countryCell.address},Countries!$A$2:$B$${lastRow},2,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+
}
206252
}
253+
207254
}
208255
}
209256

0 commit comments

Comments
 (0)