123 lines
3.3 KiB
JavaScript
123 lines
3.3 KiB
JavaScript
import fs from 'fs'
|
|
import yargs from 'yargs/yargs'
|
|
import { hideBin } from 'yargs/helpers'
|
|
import { parse } from 'csv-parse/sync'
|
|
|
|
const argv = yargs(hideBin(process.argv)).command('Converter', 'Checks CSV for missig stuff, then converts CSV to Jiracula import file.', {})
|
|
.options({
|
|
'file': {
|
|
description: 'CSV file',
|
|
alias: 'f',
|
|
type: 'string',
|
|
demandOption: true,
|
|
},
|
|
'month': {
|
|
description: 'Month number to extract',
|
|
alias: 'm',
|
|
type: 'number',
|
|
demandOption: true,
|
|
}
|
|
}).help().version('1.0').argv
|
|
|
|
const colors = {
|
|
red: "\x1b[31m",
|
|
green: "\x1b[32m",
|
|
yellow: "\x1b[33m",
|
|
blue: "\x1b[34m",
|
|
magenta: "\x1b[35m",
|
|
cyan: "\x1b[36m",
|
|
bold: "\x1b[1m"
|
|
}
|
|
|
|
// Regex patterns
|
|
const durationPattern = /(\d+(?:[.,]\d+)?)\s*(h|H)/
|
|
const codePattern = /(SMEIMKT[- ]?\d+)/
|
|
|
|
// Read CSV
|
|
if(!fs.existsSync(argv.file)) {
|
|
console.error(`${colors.red}Cannot find this file !?`)
|
|
process.exit(1)
|
|
}
|
|
const csv = fs.readFileSync(argv.file, 'utf8')
|
|
const records = parse(csv, {
|
|
columns: false,
|
|
skip_empty_lines: true
|
|
})
|
|
|
|
|
|
// Skip header
|
|
const [header, ...rows] = records
|
|
|
|
// Transform
|
|
let results = []
|
|
|
|
for(let row of rows) {
|
|
if (row.length<3) continue
|
|
|
|
const date = row[0]
|
|
const tasksStr = row[2]
|
|
if (!tasksStr) continue
|
|
|
|
if(date.substring(3,5)!=String(argv.month).padStart(2, '0')) continue
|
|
|
|
const tasks = tasksStr.split('+').map(t => t.trim())
|
|
|
|
for (let t of tasks) {
|
|
const durationMatch = durationPattern.exec(t)
|
|
let duration = durationMatch ? durationMatch[1] : null
|
|
|
|
const codeMatch = codePattern.exec(t)
|
|
const code = codeMatch ? codeMatch[1].replace(' ', '-') : null
|
|
|
|
results.push({
|
|
Date: date.trim(),
|
|
TaskCode: code,
|
|
Duration: parseFloat(duration) || 0,
|
|
Description: t
|
|
})
|
|
}
|
|
}
|
|
|
|
let errs=0, tot=0, timePerDate = {}
|
|
results.forEach(r => {
|
|
if(!r.TaskCode){
|
|
errs++
|
|
} else if(!r.Duration){
|
|
errs++
|
|
} else {
|
|
if(r.Date in timePerDate) timePerDate[r.Date] += r.Duration
|
|
else timePerDate[r.Date] = r.Duration
|
|
}
|
|
console.log(
|
|
`${colors.cyan}${r.Date} ` +
|
|
`${(r.TaskCode) ? colors.yellow+r.TaskCode : colors.red+'Missing Code'} ` +
|
|
`${(r.Duration) ? colors.yellow+r.Duration : colors.red+'Missing Duration'} ` +
|
|
`${colors.green}${r.Description}`
|
|
)
|
|
tot++
|
|
})
|
|
|
|
if(errs>0){
|
|
console.log(`\n${colors.red}${errs} parsing errors on ${tot} tasks.${colors.green}\n`)
|
|
} else {
|
|
console.log(`\n${colors.green}${tot} task entries, no parsing errors.\n`)
|
|
}
|
|
|
|
const badDailyHours = {}
|
|
let billable = 0
|
|
for(let dat in timePerDate){
|
|
if((timePerDate[dat] != 8) && (timePerDate[dat] != 4)) badDailyHours[dat] = timePerDate[dat]
|
|
else billable += (timePerDate[dat]/8)
|
|
}
|
|
if(Object.keys(badDailyHours).length>0){
|
|
console.log(`${colors.red}Some dates don't have 8H...\n`)
|
|
for(let dat in badDailyHours){
|
|
console.log(`${colors.red} ${dat}: ${badDailyHours[dat]}H`)
|
|
}
|
|
} else {
|
|
console.log(`\n${colors.green}Ready to export to Jiracula !`)
|
|
console.log(`Total billable days : ${billable}\n`)
|
|
const outfile = argv.file.substring(0, argv.file.lastIndexOf('.'))+'.jrcl.json'
|
|
fs.writeFileSync(outfile, JSON.stringify(results, null, 2), 'utf8')
|
|
}
|