node-xlsx 数据转换
公司要将打开记录导入到OA系统中,虽然都是Excel格式,但是因为其数据展示格式不一样,因此需要做一个转换然后才能导入Oa系统中。
https://github.com/acharzuo/oa_excel_convert
需求分析
将打卡机导出的excel转换为oa系统可识别的格式
这个打开记录是得力考勤机
导出的,oa系统使用的是然之
,有需要的朋友可以直接去github上下载完整代码。
需求
打卡机导出的文件04月汇总表.xls
(整体查看)
打卡机导出的文件(放大图)
、
oa考勤导入excel格式
oa_attend.xlsx
oa字段说明
https://www.ranzhico.com/book/ranzhi/83.html#16
功能分析
打卡机文件与OA导入文件对应规则
| 打卡机单元格 | OA导入单元格 | 名称 | 说明 |
|---|---|
| | A:A | 编号 | 顺序号 |
| K5 | B:B | 用户 | 每人一行;打卡记录姓名与OA姓名不一致,需要准换;
| C3 | C:C 整列 | 日期(年月) | 需要文字分割转换为日期 |
| 4:4 整行 | C:C 整列 | 日期(日) | |
| 6:6 整行 | D:D 整列 | 签到 | 每人一行;单元格中第一个为签到时间|
| 6:6 整行 | E:E 整列 | 签退 | 每人一行;单元格中最后一个为签退时间|
| | F:F 整列 | 状态 | 默认normal,见状态分析
|
|| G:G | IP | 默认: "127.0.0.1" |
|| H:H | 设备 | 默认: "得力打卡机01" |
|| I:I | 签到时间 | 默认: "00:00:00" |
|| J:J | 签退时间 | 默认: "00:00:00" |
|| K:K | 原因 | 默认: "" |
|| L:L | 描述 | 默认: "" |
|| M:M | 补录状态 | 默认: "" |
|| N:N | 审核人 | 默认: "" |
|| O:O | 审核时间 | 默认: "0/0/0000 00:00:00" |
状态分析
工作时间为:8:50 到 18:00
| 状态名称 | 状态字 | 成立条件 | 说明 |
|---|----|--|
| 正常 | normal | 签到<= 8:50 && >= 18:00 | |
| 迟到 | late | 签到> 8:50 && 签到< 18:00 && 签退 >=18:00 | |
| 早退 | early | 签到<= 8:50 && 签退>8:50 && 签退<=18:00 ||
| 迟到+早退| both | 签到 > 8:50 && 签退 < 18:00 && 签到 != 签退 | |
技术选型
原生的直接可以使用vba实现,但是考虑到团队成员的知识储备,在nodejs上会更熟悉,方便将来的维护,遂选用Nodejs,在其体系下做excel转换。
node-xlsx
https://github.com/mgcrea/node-xlsx
进行简单的读写测试通过
var xlsx = require('node-xlsx');
var fs = require('fs');
// 读取文件
const workSheetsFromBuffer = xlsx.parse(fs.readFileSync(${__dirname}/04月汇总表.xls));
// 输出到控制台
console.log(JSON.stringify(workSheetsFromBuffer));
const data = [[1, 2, 3], [true, false, null, 'sheetjs'], ['foo', 'bar', new Date('2014-02-19T14:30Z'), '0.3'], ['baz', null, 'qux']];
var buffer = xlsx.build([{name: "mySheetName", data: data}]); // Returns a buffer
// 保存文件
fs.writeFileSync('user.xlsx', buffer, 'binary');
输出结构
输出的Excel
config配置信息
https://github.com/lorenwest/node-config
开发
建立github工程
https://github.com/acharzuo/oa_excel_convert
初始化nodejs工程
npm init
npm install --save node-xlsx
npm install --save config
mkdir config
touch config/default.json
index.js 代码
var xlsx = require('node-xlsx');
var fs = require('fs');
var config = require("config");
// 获取参数
var arguments = process.argv.splice(2);
// 命令行参数判断
if(!arguments || arguments.length < 1){
console.log("usage: node index <Excle Filename>")
return;
}
const filename = arguments[0] ;
const originFilename = `${__dirname}/` + filename;
const originSheetname = '刷卡记录';
var originWorkSheets = null
// 读取文件
try {
originWorkSheets = xlsx.parse(fs.readFileSync(originFilename));
} catch (error) {
console.error("读取文件错误!\n" + originFilename + error);
return;
}
var originSheetData = null;
var targetSheetData = null;
// 读取配置文件
const WORK_IN_TIME = config.get('workInTime');
const WORK_OUT_TIME = config.get('workOutTime');
// 人员对应规则
const ACCOUNT_PAIR = config.get('accountPair')
// 转换函数
// 输入原始数据Sheet的数据,拟合出目标Excel中新Sheet的数据。
function convert(oriObj) {
if(typeof oriObj != "object") return;
var ret = [];
// 一行的数据
var line = [
"", // id 编号
"", // account 用户
"", // date 日期
"", // signIn 签到
"", // signOut 签退
"", // status 状态
"", // ip IP
"", // device 设备
"", // manualIn 签到时间
"", // manualOut 签退时间
"", // reason 原因
"", // desc 描述
"", // reviewStatus 补录状态
"", // reviewedBy 审核人
"" // reviewedDate 审核时间
];
// 行号
var index = 1 ;
var month = "";
const DATE_LINE_INDEX = 3;
// 遍历所有行
for( var i = 0; i < oriObj.length; i++){
// 获取考勤日期的月份
if( oriObj[i] && oriObj[i][0] && oriObj[i][0] === "考勤日期 : " && oriObj[i][2] ) {
// 数据格式 模拟 ["2017/04/01 ~ 04/30 (ACHAR科技)"]
var tmp = oriObj[i][2].split(" ");
month = tmp[0].substr(0,7).replace("/","-");
} else if (oriObj[i] && oriObj[i][0] && oriObj[i][0] === "工 号:" && oriObj[i][2]) {
// 获取每个人每天的记录的记录
for(var day = 0; day < 31; day++){
var record = ""; // 打开记录
if(oriObj[i+1][day]) {
record = oriObj[i+1][day];
record = record.trim();
} else {
continue;
}
var account = oriObj[i][10];
account = ACCOUNT_PAIR[account];
if(!account) {
console.log(oriObj[i][10] + "没有对应的OA账号,已经智能跳过!");
break;
}
// record = "15:45\n18:59\n"
var recordSplit = record.split("\n");
var signIn = recordSplit[0]; // 第一次打卡时间为签到时间
var signOut = recordSplit.length > 1 ? recordSplit[recordSplit.length-1] : ""; // 最后一次打卡时间为签退时间
// 整理字符格式,使用5位的时间表示方法
signIn = signIn.length === 4 ? "0" + signIn : signIn;
signOut = signOut.length === 4 ? "0" + signOut : signOut;
var status = "normal";
if(signIn > WORK_IN_TIME && signOut < WORK_OUT_TIME && signIn != signOut ){
// 迟到早退 签到 > 8:50 && 签退 < 18:00 && 签到 != 签退
status = "both";
} else if(signIn > WORK_IN_TIME && signIn < WORK_OUT_TIME && signOut >= WORK_OUT_TIME){
// 迟到判断 签到> 8:50 && 签到< 18:00 && 签退 >=18:00
status = "late";
} else if (signIn < WORK_IN_TIME && signOut > WORK_IN_TIME && signOut < WORK_OUT_TIME ){
// 早退判断 签到<= 8:50 && 签退>8:50 && 签退<=18:00
status = "early";
}
var line = [];
line[0] = index++;
line[1] = account;
line[2] = month + "-" + oriObj[DATE_LINE_INDEX][day];
line[3] = signIn;
line[4] = signOut;
line[5] = status;
line[6] = "127.0.0.1";
line[7] = "得力打卡机01";
line[8] = "00:00:00";
line[9] = "00:00:00";
line[10] = "";
line[11] = "";
line[12] = "";
line[13] = "";
line[14] = "0/0/0000 00:00:00";
ret.push(line);
}
}
}
return ret;
}
// 获取指定sheet的数据 ,Sheet名称为:刷卡记录
for( var i = 0; i < originWorkSheets.length; i++) {
if(originWorkSheets[i] && originWorkSheets[i].name && originWorkSheets[i].name === originSheetname ) {
originSheetData = originWorkSheets[i].data;
}
}
// 执行转换
targetSheetData = convert(originSheetData);
// 输出转换后的格式
if (targetSheetData) {
//console.log(JSON.stringify(targetSheetData));
var buffer = xlsx.build([{"name": "打卡记录", "data":targetSheetData}]);
// 保存文件
fs.writeFileSync( filename.replace(".","_转换."), buffer, 'binary');
console.log("转换完成!");
}
config/default.json
{
"workInTime": "08:50", //上班时间,两位小时: 两位分钟
"workOutTime": "18:00", //下班时间,两位小时: 两位分钟
"accountPair": { //打卡机姓名与OA用户名对应"张涛涛": "zhangtaotao",
"王长顺": "a",
"京东陈": "b",
"ACHAR": "c",
"王思聪": "d",
"边得力": "e",
"姜孟女": "f",
"孙悟空": "g",
"曹丞相": "h",
"花木兰": "j",
"鲁智深": "z",
"虚竹": "x",
"芦沟": "cv",
"大川": "b",
"胡吗辟谷": "n",
"春蚕": "n"
}
}