销项发票采集工具需求分析及设计分享

1. 引言

        一位相知相交多年的挚友,现在经营一家财务公司,业务重点是代理记账和税务筹划。去年的时候,我们偶然相聚,弹指一挥间,已十来个春秋未曾谋面。多年未见,再相聚,相互寒暄,谈笑风生,把酒言欢,相叙别后之境,正谓是“故人相遇情如故”,一切不再言表。

        后来,慢慢谈到各自的工作上来。老友向我诉说,他现在有一件,不得不面对的忧心之事。有六七百家的客户,委托他的公司做账,每月都需要手工从开票软件中导出销项发票数据,然后再根据企业性质的不同,整理出相应的报表。这需要投入很多的人力去做,并且容易出错。

       老友长叹一口气,然后望着我说道:“我们这个行业,说实话,现在就是价格战,算上投入的人力成本,盈利真的很难。你在IT行业也是资深人士,能否亲自开发一款工具出来,以解我的难言之隐。”

       看到老友如此忧心烦闷,我便一口应允下来。身负老友的嘱托,我日夜兼程地奋斗两个多月的时间,开发出一款工具出来,这款工具切切实实地解决了老友的忧心烦恼之现状,大大超出其预期。老友感激之情不再言表,这款工具,至今依然在老友那里稳健地运行。

       今日,笔者稍得闲暇,将这款工具的思路和核心代码分享给各位朋友,以期能为,遇到相似烦恼或者技术障碍的朋友,提供参考。同时,由于笔者认知和技术能力有限,文中难免会有不当或错误之处,欢迎批评指正。同时笔者也殷切的期盼,能和各位朋友做进一步的沟通交流,以期相互受益。

2. 采集工具需求分析及设计思路

        笔者经过调研和分析,发现销项发票数据的采集,有几种实现途径,例如从开票软件系统中采集,从税盘中采集,从增值税发票综合服务平台采集等。但笔者认为,从开票软件系统采集是最便捷的途径。

2.1 开票软件现状分析

        现在市场在用的开票软件分为金税盘(白盘)版,税控盘(黑盘)版,税务UKey版。从当前的市场占有率来说,以金税盘为代表的航信系开票软件遥遥领先,是佼佼者。从趋势来说,百旺系的税务UKey版开票软件独占鳌头,只有税务UKey版开票软件,才能开具电专发票,代表着新的趋势。金税盘版开票软件从大版本来说分为2.0版和3.0版,时至今日,2.0版已经没落,会逐渐升级到3.0版。税控盘版开票软件从大版本来说,分为旧版本和新版本,旧版本极少。

         从开票软件数据库来说,金税盘版2.0分为cc3268和skfpdb.db两个版本,3.0又细分为3.0和3.1两个细分版本。税控盘版分为老版本,V2,V5三个细分版本。税务UKey版分为V3和UKey两个版本。

2.2 采集工具整体设计思路

        笔者对采集工具制定的首要目标是:能满足老友的业务需求,解决其长期困扰他的,痛心疾首之状况。首先,工具要能支持所有的开票软件,无需人工干预的情况下,做到快速采集数据,并自动生成业务需要的报表。再者,报表模板配置尽最大可能灵活,做到用户体验良好。最后,整体程序设计要模块化,甚至微服务化,能做到最小粒度积木块设计,将来,能根据业务的变化,快速拼装出新形态的产品。按照这个思路,需要开发完全独立的采集组件,该组件要做到在业务上和上层业务系统零耦合。同时,组件也要做到技术形态的完全独立性,在技术上也要做到和上层业务系统的零耦合。

watermark,type_d3F5LXplbmhlaQ,shadow_50,text_Q1NETiBAUVFfMzA5NDM1MzYyNw==,size_19,color_FFFFFF,t_70,g_se,x_16

图-1 采集工具整体设计

3. 销项数据采集核心代码分享

3.1 采集组件接口声明代码分享

{*******************************************************************
函数:collectBySql
功能:组件接口,根据sql语句采集数据
参数:taxDeviceDir         输入参数,开票机路径
      sql                  输入参数,采集数据的sql语句
      dataDir              输入参数,采集到的数据报文文件存储路径
      outDataFileBuf       输入参数,采集到的数据报文文件名存储的
                           内存缓冲区指针
      outDataFileBufLen    输入参数,outDataFileBuf缓冲区长度
      errMsgBuf            输入参数,错误信息内存缓冲区指针
      errMsgBufLen         输如参数,errMsgBuf缓冲区长度
返回值:true               成功
        false              失败

date: 2020-04-20
author: 海之边  qq-3094353627		
 *******************************************************************}
function collectBySql(taxDeviceDir, sql, dataDir: PChar;
  outDataFileBuf: PChar; outDataFileBufLen: DWord; errMsgBuf: PChar;
  errMsgBufLen: DWord): boolean; stdcall;
  external 'InvCollector.dll' name 'collectBySql';

{*******************************************************************
函数:decryptInvMw
功能:组件接口,解密发票密文区密文数据
参数:appKind              输入参数,开票软件类型
                               1- 金税盘2.0
							   2- 金税盘3.0
      InvMwData            输入参数,密文区密文数据
	  outPlainBuf          输入参数,保存明文数据的内存缓冲区指针
	  outPlainBufLen       输入参数,outPlainBuf内存缓冲区长度
      errMsgBuf            输入参数,错误信息内存缓冲区指针
      errMsgBufLen         输如参数,errMsgBuf缓冲区长度
返回值:true               成功
        false              失败

date: 2020-04-20
author: 海之边  qq-3094353627	  
 *******************************************************************}  
function decryptInvMw(appKind: integer; InvMwData: PChar; 
  outPlainBuf: PChar; outPlainBufLen: DWord;
  errMsgBuf: PChar; errMsgBufLen: DWord): boolean; stdcall;
  external 'InvCollector.dll' name 'decryptInvMw';

3.2 销项数据采集核心代码分享

{
函数:collectByKprq
功能:根据开票日期采集指定的开票机销项发票数据,并存储到本地数据库
参数:kpjlj               输入参数,开票机路径
      ksrq                输入参数,起始开票日期
	  jsrq                输入参数,结束开票日期
	  errMsg              输出参数,错误信息
返回值:true              成功
        false             失败
}
function TInvCollectTool.collectByKprq(kpjlj, ksrq, jsrq: string; 
  var errMsg: string): boolean;
var bRet: boolean;
    mAppType: TAppType;
    dwErrMsgBufLen, dwOutDataBufLen: dword;
    pErrMsgBuf, pOutDataBuf: PChar;
	sSql, sInvFile, sInvMxFile, sInvQdFile: string;
begin
  result := true;
  errMsg := '';
  pErrMsgBuf := nil;
  pOutDataBuf := nil;
  
  try
    //1. 初始化
    dwErrMsgBufLen := 2048;
    pErrMsgBuf := GetMemory(dwErrMsgBufLen);
    FillChar(pErrMsgBuf^, dwErrMsgBufLen, #0);
	
    dwOutDataBufLen := MAX_PATH;
    pOutDataBuf := GetMemory(dwOutDataBufLen);
    FillChar(pOutDataBuf^, dwOutDataBufLen, #0);
	
    //2. 解析开票软件路径
    mAppType := parseInvAppType(kpjlj);
    if mAppType = atNone then
    begin
      result := false;
      errMsg := '开票机路径错误';
	  Exit;
    end;
	
	try
      //3. 采集发票
      if mAppType in [mtAisino2, mtAisino3] then
      begin
        //金税盘2.0或金税盘3.0
        sSql := 'select * from xxfp '#13#10
              + 'where kprq >= ''%s'' '#13#10
              + '  and kprq <= ''%s'' '#13#10;   
      end
	  else
      begin
        //税控盘或税务UKey
        sSql := 'select * from zzs_fpkj '#13#10
              + 'where kprq >= ''%s'' '#13#10
              + '  and kprq <= ''%s'' '#13#10;        
      end;
      sSql := format(sSql, [ksrq, jsrq]);
	  
      bRet := collectBySql(kpjlj, sSql, getOutTmpDir, pOutDataBuf,
        dwOutDataBufLen, pErrMsgBuf, dwErrMsgBufLen);
      if not bRet then
      begin
        result := false;
        errMsg := format('采集销项数据失败:%s', [pErrMsgBuf]);
        Exit;
      end;
      sInvFile := StrPas(pOutDataBuf);
	
      //4. 费用明细
      if mAppType in [mtAisino2, mtAisino3] then
      begin
        //金税盘2.0或金税盘3.0
        sSql := 'select t2.* '#13#10
              + 'from xxfp '#13#10
              + 'join xxfp_mx t2 on t1.fpzl = t2.fpzl and t1.fpdm = t2.fpdm and t1.fphm = t2.fphm '#13#10
              + 'where t1.kprq >= ''%s'' '#13#10
              + '  and t1.kprq <= ''%s'' '#13#10;   
      end
      else
      begin
        //税控盘或税务UKey
        sSql := 'select * '#13#10
              + 'from zzs_fpkj '#13#10
              + 'join zzs_fpkj_mx t2 on t1.fpdm = t2.fpdm and t1.fphm = t2.fphm '#13#10
              + 'where kprq >= ''%s'' '#13#10
              + '  and kprq <= ''%s'' '#13#10;        
	  end;
      sSql := format(sSql, [ksrq, jsrq]);
	  
      bRet := collectBySql(kpjlj, sSql, getOutTmpDir, pOutDataBuf,
        dwOutDataBufLen, pErrMsgBuf, dwErrMsgBufLen);
      if not bRet then
      begin
        result := false;
        errMsg := format('采集销项数据失败:%s', [pErrMsgBuf]);
		Exit;
      end;
      sInvMxFileFile := StrPas(pOutDataBuf);
	
      //5. 清单明细数据
      if mAppType in [mtAisino2, mtAisino3] then
      begin
        //金税盘2.0或金税盘3.0
        sSql := 'select t2.* '#13#10
              + 'from xxfp '#13#10
              + 'join xxfp_xhqd t2 on t1.fpzl = t2.fpzl and t1.fpdm = t2.fpdm and t1.fphm = t2.fphm '#13#10
              + 'where t1.kprq >= ''%s'' '#13#10
              + '  and t1.kprq <= ''%s'' '#13#10;   
	  end
      else
      begin
        //税控盘或税务UKey
        sSql := 'select * '#13#10
              + 'from zzs_fpkj '#13#10
              + 'join zzs_fpkj_qd t2 on t1.fpdm = t2.fpdm and t1.fphm = t2.fphm '#13#10
              + 'where kprq >= ''%s'' '#13#10
              + '  and kprq <= ''%s'' '#13#10;        
      end;
      sSql := format(sSql, [ksrq, jsrq]);
	  
      bRet := collectBySql(kpjlj, sSql, getOutTmpDir, pOutDataBuf,
        dwOutDataBufLen, pErrMsgBuf, dwErrMsgBufLen);
      if not bRet then
	  begin
        result := false;
        errMsg := format('采集销项数据失败:%s', [pErrMsgBuf]);
        Exit;
      end;
      sInvQdFile := StrPas(pOutDataBuf);
	  
	  //6. 保存采集到的销项数据
	  result := saveInvData(mAppType, sInvFile, sInvMxFile, sInvQdFile, errMsg);
    except
      on e: Exception do
      begin
        result := false;
        errMsg := format('采集销项数据出错:%s', [e.Message]);
		Exit;
      end;
    end;
  finally
    if FileExists(sInvFile) then  
      deleteFile(sInvFile);
	  
    if FileExists(sInvMxFileFile) then  
      deleteFile(sInvMxFileFile);
	  
    if FileExists(sInvQdFile) then  
      deleteFile(sInvQdFile);
  
    if Assigned(pErrMsgBuf) then
      FreeMemory(pErrMsgBuf);
	  
    if Assigned(pOutDataBuf) then
      FreeMemory(pOutDataBuf);
  end;
end;

4. 后记

        该采集工具,已在老友公司稳健运行一年有余,至此,理应落幕。然而,笔者也一直在思考,能否对该工具,做进一步的优化,以发挥更大的作用。笔者诚恳地期望,能和更多的朋友进行更进一步的沟通交流,以期相互受益。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值