1st version
This commit is contained in:
122
Jira_helper/converter.js
Normal file
122
Jira_helper/converter.js
Normal file
@@ -0,0 +1,122 @@
|
||||
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')
|
||||
}
|
||||
Reference in New Issue
Block a user