node-xlsx 数据转换

node-xlsx 数据转换

公司要将打开记录导入到OA系统中,虽然都是Excel格式,但是因为其数据展示格式不一样,因此需要做一个转换然后才能导入Oa系统中。
https://github.com/acharzuo/oa_excel_convert

需求分析

将打卡机导出的excel转换为oa系统可识别的格式
这个打开记录是得力考勤机导出的,oa系统使用的是然之,有需要的朋友可以直接去github上下载完整代码。

需求

打卡机导出的文件04月汇总表.xls(整体查看)

打卡机导出的文件

打卡机导出的文件(放大图)


打卡机导出的文件(放大图)

oa考勤导入excel格式oa_attend.xlsx

oa考勤导入excel格式`oa_attend.xlsx`

oa字段说明
https://www.ranzhico.com/book/ranzhi/83.html#16

oa字段说明

功能分析

打卡机文件与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

输出的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"
    }
}

文件材料

见github仓库
https://github.com/acharzuo/oa_excel_convert

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 203,547评论 6 477
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,399评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,428评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,599评论 1 274
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,612评论 5 365
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,577评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,941评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,603评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,852评论 1 297
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,605评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,693评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,375评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,955评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,936评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,172评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 43,970评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,414评论 2 342

推荐阅读更多精彩内容