使用本示例需通过docker容器,请先下拉jxTMS的docker镜像并按说明启动tms容器,并从helloWorld开始尝试。

jxTMS中导入excel

笔者在长期的实践中认为业务管理软件的最大瓶颈就在于IT的本质是:预置硬编码的形式自动机。这就是导致IT系统难以随动环境变化、业务变化,这就使得IT要么被排斥在业务运行之外,只提供功能支撑;要么就固化了业务过程,在一定程度上也限制了业务的发展。

对于大型组织来说,由于具有足够的体量来对抗环境的不确定性,同时也支付的起IT变革的成本。但对于小微组织来说,其竞争力的基础就是机动灵活的捕捉市场机会,固化的业务过程就会极大的损失机会成本,这就导致小微企业难以自动化、数字化,也就更谈不上智能化了,只能是从成本大幅度降低的IT基础设施中获得一般性的效率提升。

所以jxTMS的开发思想就是打铁而非铸造,即随动用户业务发展及时提供IT的支撑,是配合逐步改善的业务管理而提供低成本的、持续的、微调的定制服务。反映到开发过程就是开箱即用的低门槛、对开发者的要求低、快速呈现与文本化定义的沟通成本低的低成本快速定制

但显然,既然不是一步到位,而是点状切入,那在很多时候就必须和手工作业的业务环节进行勾连,而一般来说,这种勾连都使用excel作为数据交换的工具。

所以本节我们先演示jxTMS中如何导入excel文件来读取数据。

注:jxTMS使用了POI,支持xls【只要不是过老的excel一般都可以支持】和xlsx【笔者用的较少】,建议最好使用xls格式,因为笔者一直都是用xls格式进行的开发,所以不会有问题。目前的演示环境中,全部都是xls格式,未开放xlsx格式

文件上传界面

我们先要制作一个文件上传的界面,在web文件中添加:

web importDiscount type div; web importDiscountt1 parent importDiscount type table title="导入折扣",width=900,alone=true; with importDiscountt1 row 0 col c0 web n type text text='上传excel:',width=200; with importDiscountt1 row 0 col c1 web n type fileInput width=700; with importDiscountt1 row 1 col c0 web n type button width=80,text='导入',motion=cmd,demand=importDiscount,confirm='确认导入销售折扣?之前的折扣将被清除!!';

大家可以看到,出现了一个新的控件:fileInput:



而除了为其指定了一个宽度,其它都是默认的:

  • fileExts:指定允许上传的文件后缀,如果是'*',则什么文件都可以上传,如果允许多种格式的文件,则以英文逗号加以分隔。默认是:'xls'
  • maxFileSize:最大允许的文件长度,单位是K字节,默认是10240,即10M字节

fileInput其它特性还包括:

  • 一次只能上传一个文件
  • 符合fileExts要求的文件被拖入【拖拽文件到这里...】后即自动上传

那么大家可能会有疑问,10M的文件,如果上传太多了怎么办?上传的文件都会被放到web服务的根目录【webRoot】下的tmp目录中,jxTMS会每天将一个月前的文件清除掉。同时会在日志中记录每个人上传的文件。如果真出现因为上传文件满的问题,那么首先大家应该考虑这是业务的需要还是有人恶意攻击。如果是业务需要则需要考虑文件的存储设计,而如果是攻击,则需要部署系统监测工具,在发现硬盘异常增长时,检查jxTMS日志来查获攻击者。

注1:为避免DDOS攻击,jxTMS对文件上传接口做了资源限制,所有用户在一秒内总共只允许10次上传

注2:在文件上传后,大家可以进入到tmp目录中,看看这个新上传的文件名和我们上传的文件名有何不同?jxTMS为了防止多人同时上传同名文件导致的错误,所以自动给所有上传的文件添加了一个够长的随机数以降低同名文件碰撞的概率

导入处理

大家在capa.py文件中添加:

@myModule.event('cmd', 'importDiscount') def importDiscount(self, db, ctx): dt=dataTable.getOrCreateTable(db,'authorize','discount') dt.clear(db) with jxExcel(self.importFilepath, 'discount') as e: dn=e.getCellBigDecimalValue('B2') authorize.setRightDefault(db,ctx,dt,dn) e.head(3) rs=e.rows(4) for r in rs: name=e.getCellStringValue(r,'名称'.decode('utf-8')) if name is None: continue ty=e.getCellStringValue(r,'类型'.decode('utf-8')) cs=e.colsWithName(r,2) authorize.addRight(db,ctx,dt,ty,name,cs) ctx.getCurrentOrg().clearAuthorize()

我们需要使用到domeCode目录下的【导入文档模板】中的【importDiscount.xls】文件,现在请大家打开【importDiscount.xls】文件,对照该文件,我们讲解importDiscount做了什么。

dt=dataTable.getOrCreateTable(db,'authorize','discount') dt.clear(db)

这两行是我们先获取一个我们为折扣作业所需要使用的数据表【jxTMS内建表,请参考附录5-jxTMS系统数据表】,该表的类型是authorize,名字是discount,如果该表不存在则创建。然后不管该表是否是新建的,将其全部清除,以确保该表的数据都是本次所导入的。

with jxExcel(self.importFilepath, 'discount') as e:

用with语句打开我们上传的【importDiscount.xls】文件中名为discount的sheet,并指定该sheet对象为变量e。

注:上传文件会放入tmp目录下,但用户不需要关心,只需要知道self.importFilepath变量中保存的就是所导入的文件名【含路径】

dn=e.getCellBigDecimalValue('B2')

读取sheet中的B2格,将其中的内容做为数字读取。

注:excel处理中最容易出现的问题就是单元格有自己的格式,导致看到的未必是系统读到的【如在某项目中以用户手机号作为用户登录名,但用户在excel看到的是正常的手机号,但实际上excel却将其以浮点数的形式进行存储,所以jxTMS读到的就是浮点数形式的字符串,这就导致用户明明已经导入了,系统却报:查无此人无法登录,这时只有通过直接查看数据库中的到底导入的是什么数据才能发现问题】,所以笔者建议大家如果对此不熟悉,最好在填写完毕后,将整个sheet全选,然后将单元格的格式设置为文本

authorize.setRightDefault(db,ctx,dt,dn)

用dn值来设置折扣权限表的默认值,即无法查询到某用户的折扣权限时,即为该默认值。

e.head(3) rs=e.rows(4)

jxExcel会把excel中的数据分为两类:

  • 一格一格存放的离散数据,如dn,直接以格子号进行读取
  • 表数据,则jxTMS默认每个表都应该有一个表头,分别列出各列的名字。所以e.head(3)的意思就是说,把3号行【excel内部的行号是从0开始的】视为表头,执行列号与列名的映射。e.rows(4)则从4号行开始读取各行,直到空白行结束

注:由于表数据中的行数的可变的,所以建议大家最好是将离散数据放在表上面,表数据下面建议不要再有离散数据

这时,rs中就是折扣表的内容了,然后以一个for循环来逐行访问每一行的数据。

name=e.getCellStringValue(r,'名称'.decode('utf-8')) ty=e.getCellStringValue(r,'类型'.decode('utf-8'))

经过e.head(3)完成了表数据的列号与列名的映射后,我们就可以直接用列头代替列号读取行中相应格子中的数据了。为了避免意外,特意对读到的name做一个检测。

cs=e.colsWithName(r,2)

从2号列【excel内部的列号是从0开始的】开始,一次性读取剩余的所有数据,并按(列名,值)的map集提交。

authorize.addRight(db,ctx,dt,ty,name,cs)

折扣权限的含义是:某人或某角色,就某类产品所能给出的最大折扣率。上述语句就是将读到的各个产品-折扣的集合,一次性的写给ty-name所指定的某角色或某人。

ctx.getCurrentOrg().clearAuthorize()

最后,在权限更新完毕后,将当前组织中已经缓存好的权限数据全部清除。

注:使用权限可通过当前组织【ctx.getCurrentOrg()获取】的getAuthorizeValue函数:

public Object getAuthorizeValue(IDBop db, String peoplename, String rightclass, String auth) throws Exception;

其中:

  • peoplename是要查询的该人的名字【jxTMS对人名、组织名等都使用的是简称,以避免重名】所对应的权限,如果没有针对该人做授权,则查询其关联的所有角色对应的权限,如果还没有则查询auth的缺省值、还没有则查询rightclass的缺省值
  • rightclass:权限的分类,自己根据需要设
  • auth:权限的名,自己根据需要设

注:rightclass,auth都应该是进行统一的企业数据设计后,经过通盘考虑后给出一个权限的命名规范。然后开发者再依据此类规范进行设置

查看权限

在web文件中添加:

web litDiscount type div; web litDiscountt1 parent litDiscount type table title="折扣列表",width=900; with litDiscountt1 col authType head 类型 width=80; with litDiscountt1 col authName head 名字 width=80; with litDiscountt1 col rightName head 品类 width=80; with litDiscountt1 col authValue head 折扣 width=80;

在sql文件中添加:

sql litDiscount from dataTable as ta,dataItem as ti select ti.Category,ti.Name,ti.Info where ta.Type=='authorize' and ta.Name=='discount' and ta.ID==ti.dataTableID and ti.NoUsed==false ;

在capa.py文件中添加:

@myModule.event('prepareDisp', 'litDiscount') def litDiscount(self, db, ctx): wc = ctx.getCurrentOrg().getSQL('demo.litDiscount') table = authorize.listRights(db,wc) self.setOutput('litDiscountt1',table)

在op.py文件中添加:

@biz.Motion('demo.demo1','disp','importDiscount') @biz.OPDescr def op1(json): json.setShortcut('演示'.decode('utf-8'),'导入折扣'.decode('utf-8')) @biz.Motion('demo.demo1','disp','litDiscount') @biz.OPDescr def op1(json): json.setShortcut('演示'.decode('utf-8'),'折扣列表'.decode('utf-8'))

我们将sql、web、op.py、capa.py等文件按用sftp管理jxTMS的代码所述更新到/home/tms/codeDefine/demo/demo/demo1目录中。

然后执行一次热机刷新后,由于快捷栏中的入口有变化,所以先要退出登录,再次登录后点击快捷栏中的【演示->导入折扣】,然后将【importDiscount.xls】拖入到上传文件框中,点击【确认】按钮。

然后点击快捷栏中的【演示->折扣列表】,看看导入后的折扣数据是否正确。大家可以修改【importDiscount.xls】中的一些数据,反复多导入几次,看看导入后的是否正确。