金税盘、税控盘、税务UKey快速批量抄税清卡的一种方法分享
1. 引言
????????記得那是去年的春天,一位高中時代的老同學,也是相知相交多年的摯友,受某著名機構邀請,來到我生活的城市參加財稅論壇峰會,并做學術報告。當時正值清明前后,正所謂是“春風吹柳萬條斜” 的美好時節。活動結束之后,我帶老友去這個時節,我們這個城市人氣指數NO1的景點欣賞形態各異,五顏六色的郁金香,然后泛舟名湖,參觀千年古寺,老友對這座城市的湖光山色和文化底蘊贊嘆不已。晚上,我找了一個比較幽靜的酒館,準備一醉方休,為老友餞行。
????????酒過三巡,菜過五味,我們相互交流分享著踏入社會后這么多年,各自所經歷的人、事、物,可謂“無可奈何花落去,似曾相識燕歸來”,感嘆不已!后來話題轉移到各自的工作上來,由于我在IT行業混跡多年。老友說,現在有六七百家的客戶委托他公司做賬,有四五百個稅盤(有白色的金稅盤,黑色的稅控盤和稅務UKey)在公司里托管。每到月初心里有著莫名的恐懼感。他說每月月初都要將這些盤進行抄稅和清卡,工作量極大。最開始的時候采用全手工操作,找到眾多的盤相應的稅盤,插到電腦上,然后打開相應的開票軟件,進行抄稅和清卡,順利的話一個盤需要六七分鐘的時間,如果遇到特殊的情況一個盤需要二十甚至三十多分鐘才能做完,這樣每個月都要投入七八天的時間,做這些機械化的,讓人眼花繚亂的,又不得不做的操作,真是痛苦煩惱不已啊!
????????他為了能結束這種煎熬的過程,經過多方咨詢和比較后,從一家知名的公司采購了稅盤機柜和配套使用的軟件,第一年投入十來萬的費用,后續每年還要繳納一定費用的維護費,以期解決這種只有經歷過才知道的痛苦煎熬。此時,我問老友說:“現在你們有了這批硬件設備和配套的軟件,應該輕松很多了?”不料,老友此時眉頭緊皺,長嘆一聲,說道:“唉!遠遠未達我的預期,現在依然在煎熬中,也算花了冤枉錢。”我便問道:“此話怎講呢?”。老友說道:“我感覺采購的硬件還算可以,配套使用的軟件很不穩定,各種報錯,卡死。現在每個月還要花費四五天的時間來處理這個事情。你懂軟件,不知你能否抽時間到我那看看軟件是否有改進的空間。”看到老友這么痛苦不堪的樣子,我便答應了下來。
????????翌日一早,我便隨老友一起到他的公司,分析問題出在哪里。老友的盛情款待不再言表,帶著老友的囑托和殷切的期盼,我通宵到旦地對配套軟件進行跟蹤分析,終于發現了其中的秘密。原來這款軟件其實就是一個模擬器(專業術語叫RPA技術),通過自動打開相應的開票軟件(會將票軟件的界面隱藏),模擬鼠標鍵盤操作以實現抄稅和清卡的功能,這款軟件對各種場景的處理不太盡如人意,所以效率和穩定性都太差。
?????????老友眼神流露出殷切的期盼,問我道:“是否有改進空間?”,其實,我心里此時也沒底,便說道:“讓我試試吧,給我一兩個月的時間。”
????????帶著老友的期待和囑托,我回去后通過兩個月的日夜兼程的努力,終于寫了一款工具出來。通過給老友使用,該工具運行比較穩定,四五百個盤不需要做任何的人工干預,花費六七個小時的時間即可實現抄稅和清卡。老友看到這個結果興奮不已,感激的對我說:“你真的解決了我的難言之隱,辛苦你了,說實話不能讓你白忙一場。這樣把我按一個月X萬的成本支付你,給你X萬。”我哈哈一笑說:“你怎這么客氣呢,要不這樣把,你買一瓶好酒,我們一起喝酒就行了。”
????????于是老友買了幾瓶享譽海內外的某酒,喊上三五好友作陪,開懷暢飲,一切不再言表。翌日,老友知道我愛喝酒,便買了一箱該酒偷偷放到我后備箱內。我發現后,再三推辭,但盛情難卻,象征行的收了兩瓶。直到今日,老友的規模也在不斷擴大,工具依然在穩定的運行。
????????最近筆者稍得閑暇,將稅盤批量抄稅和清卡的相關經驗和核心的代碼分享給各位讀者,希望能對有類似經歷的朋友起到幫助作用。筆者同時也非常愿意和各位有類似業務場景需求,或者遇到技術瓶頸的,各界朋友交流業務或技術經驗。
2.?稅盤抄稅和清卡業務流程
????????增值稅開票軟件迄今為止都需要稅盤,稅盤分為金稅盤(白盤),稅控盤(黑盤)和稅務UKey三種(如下圖所示,當然還有全電,不再本文討論的范疇)。其中金稅盤是航信旗下研發的開票軟件,稅控盤和稅務UKey是百旺研發的開票軟件。從市場占有率來說金稅盤屬于佼佼者,從趨勢來看當屬稅務UKey首屈一指。
????????開票軟件需要每月月初進行抄稅,將當前開票軟件中,上月所開的發票數據進行匯總上傳(注意:在抄稅之前,需要從稅盤中修復所有的發票,并將發票全部上傳)。在開票軟件中,進行抄稅之后,稅務會計能登錄到電子稅務局,進行納稅申報(這一操作也稱為“報稅”)。在電子稅務局進行納稅申報之后,方可在開票軟件中,進行清卡操作,清卡成功后,稅盤的鎖死日期自動變更為次月。若未清卡,到稅盤鎖死日期后,不能再開具發票。抄稅和清卡的操作流程如下圖所示。????
? ? ? ? ? ? 圖-4 抄稅和清卡流程
3.?常見的稅盤抄稅和清卡操作方式
????????稅盤的抄稅和清卡操作方式,根據筆者的理解和調研大致分為如下四種。第一種,在開票軟件中手工操作進行抄稅和清卡;第二種,使用RPA技術,模擬鼠標鍵盤操作進行抄稅和清卡;第三種,通過注入技術進行稅盤抄稅和清卡進行操作;第四種,通過相關組件提供的抄稅和清卡接口進行抄稅和清卡進行操作。筆者,下面分別就這四種操作方式進行詳細的闡述和對比。
?圖-5 抄稅和清卡操作的幾種方式
?3.1?手工操作進行抄稅和清卡
????????在開票軟件里進行手工的抄稅和清卡,是最便捷、最穩定的方式。這對于只有一個或數個盤的單個企業而言,是行之有效的操作手段。
????????這種操作,首先,找到稅盤(金稅盤,或者稅控盤,或者稅務UKey)插到電腦上,打開對應的開票軟件(不同類型的稅盤需要不同的開票軟件)輸入密碼(包括軟件密碼,稅控設備密碼,證書密碼)登錄到開票軟件。
????????然后,修復發票數據(因為有可能存在同一稅盤在不同的電腦上開過票),以確保當月的所有的開票數據在當前開票軟中已存在,如果這一點已確保,可以忽略修復發票。修復完發票數據后,需要確認是否存在未上傳的發票數據,如果存在未上傳的發票數據則要進行上傳發票。
經過以上的準備和前奏操作之后,則進行抄稅(在有的開票軟件中或稱之為“匯總上傳”,不同的開票軟件其入口或者叫法不同,筆者在此不再一一展開贅述)。抄稅成功之后,則需要稅務會計登錄到電子稅務局,進行納稅申報(亦稱之為“申報”或“報稅”)。
????????最后,在稅務會計納稅申報完成之后,開票員插入稅盤,打開并登錄開票軟件進行清卡(在有的開票軟件中也稱之為 “監控回傳”或“監控回寫”,不同的開票軟件其操作入口也不同,筆者也不再展開贅述)。清卡完成后,則稅盤的鎖死日期自動變為次月的日期,若清卡失敗,則到稅盤鎖死日期(一般是當月中旬,當然具體的日期可以查看稅局的納稅申報截止日期)不再允許開票。
????????上面描述的則是手工進行抄稅,清卡的完整的操作流程,這種方式的優點是穩定可控。但是,如果企業有數百甚至更多的盤(例如代賬企業),其工作量也是可想而知的,正如我在引言中所描述的,老友的痛苦一樣,在此筆者不再回憶老友的夢魘和痛苦。
3.2?通過RPA技術進行抄報和清卡
????????RPA技術(Robotic Process Automation)即機器人流程自動化,該技術用在稅盤的抄稅和清卡中,也即自動打開稅盤對應的開票軟件,通過模擬鍵盤鼠標操作,進行抄稅和清卡。操作的流程和手工操作完全一致,首先登錄開票軟件,然后修復發票,判斷發票是否上傳,如果存在未上傳的發票則上傳,最后進行抄稅和清卡操作。
??????????這種方式的優點是,相對于純手工操作來說會提升一定的效率,但效率提升是有限的。缺點是,開票軟件種類繁多,和各種單步操作的各種業務場景(例如多報錯信息的處理等)疊加在一起,有數不盡的細分場景,很難一一枚舉,這也就導致RPA很不穩定。在加上開票軟件本身會在使用各種反模擬操作的技術手段。開票軟件升級后RPA也要做相關的調整和迭代,開票軟件升級頻繁(至少每月一個版本)。這些因素決定了,RPA技術很難在稅盤的抄稅,清卡的場景能做到穩定,同時提升的效率也是有限的。正如在引言中所描述的一樣,我老友花“巨資”引入的產品,依然令他痛苦不堪,正所謂“花錢買了寂寞”。??
?3.3?通過注入技術進行抄稅和清卡?
????????通過注入技術進行稅盤的抄稅,清卡操作,相對于RPA技術來說更加考驗開發著的技術功底,正所謂“沒有金剛鉆,別攔瓷器活”,注入需要找到正確的注入點,并進行相關的分析,這個過程沒有一定技術底蘊很難勝任。當然注入也可以分為初級和高級兩個境界,能做到高級注入的話,可以直接調用相關的底層接口,可以做到很穩定和很高效。然而,高級注入沒有“大師”級的技術底蘊,很難勝任。
?? ????????高級注入雖然可以做大穩定和高效,但是開票軟件頻繁升級,也是夢魘一般的存在,每次升級后,都需要進行重新的分析,這個過程也是極其耗時和繁雜的,所以這種技術也有很大的瓶頸。
3.4?通過組件接口進行抄稅和清卡
????????相關的組件接口提供了抄稅,清卡以及其它功能,使用組件接口可以獲取稅盤狀態(例如各核定票種的最近抄報日期,鎖死日期等),并依此來判斷稅盤的抄稅,清卡狀態。如果沒有抄稅,則可以調用相關接口進行未上傳發票上傳,抄稅,清卡。組件接口的優點是穩定,高效,同時不用再考慮開票軟件的升級問題,一勞永逸。另外組件接口調用簡單,很容易上手。
????????綜上所述,組件接口是最優方案。當然組件所提供的接口不止抄稅和清卡的功能,例如開票,作廢,沖紅,庫存查詢,領購,上傳,發票查詢,版式文件下載等一應具全。本文著重介紹抄稅和清卡。
4.?組件接口實現抄稅清卡的核心代碼分享
????????下面筆者對通過調用組件接口實現抄稅和清卡的核心代碼進行分享,上節提到的通過RPA技術和注入技術實現抄稅,清卡的技術細節,有興趣的朋友可以和筆者進行溝通交流。
4.1 金稅盤組件接口
4.1.1?抄稅操作核心代碼
{**********************************************************功能:金稅盤抄稅操作參數:errMsg 輸出參數,錯誤信息返回值:ture - 成功false - 失敗date: 2021-05-0430author: 海之邊 qq-3094353627流程簡述:1. 打開金稅盤2. 獲取金稅盤盤狀態3. 如果尚未抄稅則進行抄稅4. 抄稅成功后獲取金稅盤狀態,并緩存到本地數據庫**********************************************************} function TCtrlItem.reportTax(var errMsg: string): boolean; var idx: integer;bReported: boolean;sFplxdm, sCardClock: string;oJsCardComponent: TJsCardComponent;oTaxCardRespOpenCard: TJsCardResponse_OpenCard;oJsCardRespGetClock: TJsCardResponse_getClock;oJsCardState: TJsCardState;oJsCardRespRep: TJsCardResponse_taxReport;oHdFplxdmLst: TStrings;oDBAJxsb: TDBAJxsb;oDBAPzxx: TDBAPzxx; beginresult := false;oJsCardComponent := TJsCardComponent.Create;oTaxCardRespOpenCard := nil;oJsCardState := nil;oJsCardRespRep := nil;oHdFplxdmLst := nil;oDBAJxsb := nil;oDBAPzxx := nil;tryoDBAJxsb := TDBAJssb.Create;oDBAPzxx := TDBAPzxx.Create;//1. 打開金稅盤oTaxCardRespOpenCard := oJsCardComponent.openTaxCard(Zsmm);if not oTaxCardRespOpenCard.isSucc thenbeginerrMsg := Format('打開金稅盤%s.%s失敗: %s。', [FNsrsbh, FMachineNo, errMsg]);updateCzzt(oDBAJxsb, CZZT_SB, errMsg);exit;end;if oTaxCardRespOpenCard.isCertNotPass thenbegin//證書密碼錯誤,設置密碼校驗不通過狀態Zsmmjyjg := untGolbalConst.JYJG_NotPass;updateToDb(oDBAJxsb);Exit;end;//判斷抄報狀態//2. 讀取金稅盤時鐘oJsCardRespGetClock := OJsCardComponent.getClock;if not oJsCardRespGetClock.isSucc thenbeginerrMsg := Format('讀取金稅盤時鐘失敗:%d-%s', [oJsCardRespGetClock.retCode, oJsCardRespGetClock.retMsg]);updateCzzt(oDBAJxsb, CZZT_SB, errMsg);Exit;end;sCardClock := oJsCardRespGetClock.Clock;//3 讀取金稅盤狀態oJsCardState := oJsCardComponent.queryTaxcardState_svr(errMsg);if not Assigned(oJsCardState) thenbeginupdateCzzt(oDBAJxsb, CZZT_SB, errMsg);Exit;end;updateToDb(oDBAJxsb);//更新設備狀態信息assignFromJsCardState(oJsCardState);//保存票種核定信息oJsCardState.Pzxxs.saveToDb(Sksblx, SksbNo);//4 逐個核定票種判斷是否能抄報//4.1 判斷是否已經抄報bReported := True;oHdFplxdmLst := spliteStr(hdfplxdm, ',');for idx := 0 to oHdFplxdmLst.Count - 1 dobeginsFplxdm := oHdFplxdmLst.Strings[idx];if not oDBAPzxx.isReport(Sksblx, SksbNo, sFplxdm, sCardClock) thenbeginbReported := False;Break;end;end;if bReported thenbegin//已抄報updateCzzt(oDBAJxsb, CZZT_CG, '已抄報,無需再執行抄報操作。');result := true;Exit;end;//4.2 是否到抄報期if oTaxCardRespOpenCard.IsRepReached <> '1' thenbeginerrMsg := '未到抄稅期';updateCzzt(oDBAJxsb, CZZT_SB, errMsg);Exit;end;//5 執行抄稅操作oJsCardRespRep := OJsCardComponent.taxReport;if not oJsCardRespRep.reportIsSucc thenbegin//抄稅失敗errMsg := Format('抄報失敗:%s-%s', [oJsCardRespRep.Code, oJsCardRespRep.Mess]);updateCzzt(oDBAJxsb, CZZT_SB, errMsg);Exit;endelsebegin//抄稅成功updateCzzt(oDBAJxsb, CZZT_CG, '');//更新金稅盤狀態if Assigned(taskThread) thentaskThread.setThirdPrompt('正在更新金稅盤狀態信息...');updateCzlb(oDBAJxsb, CZLB_HQJSPZT);oJsCardState := oJsCardComponent.queryTaxcardState(errMsg);if not Assigned(oJsCardState) thenbeginupdateCzzt(oDBAJxsb, CZZT_SB, errMsg);Exit;end;updateToDb(oDBAJxsb);//更新設備狀態信息assignFromJsCardState(oJsCardState);//保存票種核定信息oJsCardState.Pzxxs.saveToDb(Sksblx, SksbNo);result := true;end;finallyif Assigned(oJsCardComponent) then FreeAndNil(oJsCardComponent);if Assigned(oTaxCardRespOpenCard) thenFreeAndNil(oTaxCardRespOpenCard);if Assigned(oJsCardRespGetClock) thenFreeAndNil(oJsCardRespGetClock);if Assigned(oJsCardState) thenFreeAndNil(oJsCardState);if Assigned(oHdFplxdmLst) thenFreeAndNil(oHdFplxdmLst);if Assigned(oJsCardRespRep) thenFreeAndNil(oJsCardRespRep);if Assigned(oDBAJxsb) thenFreeAndNil(oDBAJxsb);if Assigned(oDBAPzxx) then FreeAndNil(oDBAPzxx);end; end;? ?4.1.2?清卡操作核心代碼
{**********************************************************功能:金稅盤清卡操作參數:errMsg 輸出參數,錯誤信息返回值:ture - 成功false - 失敗date: 2021-05-0430author: 海之邊 qq-3094353627流程簡述:1. 打開金稅盤2. 獲取金稅盤盤狀態3. 如果尚未清卡則進行清卡4. 清卡成功后獲取金稅盤狀態,并緩存到本地數據庫**********************************************************} function TCtrlItem.reportTaxJSP(var errMsg: string): boolean; var idx: integer;bCleared: boolean;sFplxdm, sCardClock: string;oJsCardComponent: TJsCardComponent;oTaxCardRespOpenCard: TJsCardResponse_OpenCard;oJsCardRespGetClock: TJsCardResponse_getClock;oJsCardState: TJsCardState;oJsCardRespClearCard: TJsCardResponse_clearReport;oHdFplxdmLst: TStrings;oDBAJxsb: TDBAJxsb;oDBAPzxx: TDBAPzxx; beginresult := false;oJsCardComponent := TJsCardComponent.Create;oTaxCardRespOpenCard := nil;oJsCardState := nil;oJsCardRespClearCard := nil;oHdFplxdmLst := nil;oDBAJxsb := nil;oDBAPzxx := nil;tryoDBAJxsb := TDBAJssb.Create;oDBAPzxx := TDBAPzxx.Create;//1 打開金稅盤oTaxCardRespOpenCard := oJsCardComponent.openTaxCard(Zsmm);if not oTaxCardRespOpenCard.isSucc thenbeginerrMsg := Format('打開金稅盤%s.%s失敗: %s。', [FNsrsbh, FMachineNo, errMsg]);updateCzzt(oDBAJxsb, CZZT_SB, errMsg);Exit;end;if oTaxCardRespOpenCard.isCertNotPass thenbegin//證書密碼錯誤,設置密碼校驗不通過狀態Zsmmjyjg := untGolbalConst.JYJG_NotPass;updateToDb(oDBAJxsb);Exit;end;//2. 判斷是否已經清卡//讀取金稅盤時鐘oJsCardRespGetClock := OJsCardComponent.getClock;if not oJsCardRespGetClock.isSucc thenbeginerrMsg := Format('讀取金稅盤時鐘失敗:%d-%s', [oJsCardRespGetClock.retCode,oJsCardRespGetClock.retMsg]);updateCzzt(oDBAJxsb, CZZT_SB, errMsg);Exit;end;sCardClock := oJsCardRespGetClock.Clock;updateLastClock(sCardClock);//讀取金稅盤狀態oJsCardState := oJsCardComponent.queryTaxcardState(errMsg);if not Assigned(oJsCardState) thenbeginupdateCzzt(oDBAJxsb, CZZT_SB, errMsg);Exit;end;updateToDb(oDBAJxsb);//更新設備狀態信息,保存票種核定信息assignFromJsCardState(oJsCardState);oJsCardState.Pzxxs.saveToDb(Sksblx, SksbNo);//這個核定票種判斷是否已經抄報bCleared := True;oHdFplxdmLst := spliteStr(hdfplxdm, ',');for idx := 0 to oHdFplxdmLst.Count - 1 dobeginsFplxdm := oHdFplxdmLst.Strings[idx];if not oDBAPzxx.isClearCard(Sksblx, SksbNo, sFplxdm, sCardClock) thenbeginbCleared := False;Break;end;end;if bCleared thenbegin//已經清卡WriteLog('金稅盤%s.%s已清卡,無需再次清卡。', [FNsrsbh, FMachineNo]);updateCzzt(oDBAJxsb, CZZT_CG, '已清卡,無需再執行清卡操作。');result := true;Exit;end;//3 執行清卡操作oJsCardRespClearCard := OJsCardComponent.clearCard;if not oJsCardRespClearCard.reportIsSucc thenbegin//清卡失敗errMsg := Format('清卡失敗:%s-%s', [oJsCardRespClearCard.Code, oJsCardRespClearCard.Mess]);updateCzzt(oDBAJxsb, CZZT_SB, errMsg);Exit;endelsebegin//清卡成功updateCzzt(oDBAJxsb, CZZT_CG, '');//更新金稅盤狀態if Assigned(oJsCardState) thenFreeAndNil(oJsCardState);oJsCardState := oJsCardComponent.queryTaxcardState(errMsg);if not Assigned(oJsCardState) thenbeginWriteLogError('更新金稅盤狀態信息失敗: %s', [errMsg]);updateCzzt(oDBAJxsb, CZZT_SB, errMsg);Exit;end;updateToDb(oDBAJxsb);oJsCardState.Pzxxs.saveToDb(Sksblx, SksbNo);result := true;end;finallyif Assigned(oJsCardComponent) then FreeAndNil(oJsCardComponent);if Assigned(oTaxCardRespOpenCard) thenFreeAndNil(oTaxCardRespOpenCard);if Assigned(oJsCardRespGetClock) thenFreeAndNil(oJsCardRespGetClock);if Assigned(oJsCardState) thenFreeAndNil(oJsCardState);if Assigned(oHdFplxdmLst) thenFreeAndNil(oHdFplxdmLst);if Assigned(oJsCardRespRep) thenFreeAndNil(oJsCardRespRep);if Assigned(oDBAJxsb) thenFreeAndNil(oDBAJxsb);if Assigned(oDBAPzxx) then FreeAndNil(oDBAPzxx);end; end;4.2 稅控盤組件
?????? 稅控盤組件的抄稅和清卡相關的核心代碼,和金稅盤類似,筆者不再一一貼出代碼,有興趣或需求的朋友可以和筆者做進一步的溝通交流。
4.3 稅務UKey組件
????????稅務UKey組件的抄稅和清卡相關的核心代碼,和金稅盤類似,筆者不再一一貼出代碼,有興趣或需求的朋友可以和筆者做進一步的溝通交流。????????
5. 后記
????????上文中筆者所述的組件,抄稅和清卡只是其諸多功能之一,同時支持發票開具,作廢,沖紅,庫存查詢,發票上傳,領購,版式文件下載,發票查詢等功能。有興趣的朋友可以和筆者進一步的溝通交流,同時筆者認知、技術水平有限,若文中有錯誤或不當之處,歡迎各位批評指正。
??
總結
以上是生活随笔為你收集整理的金税盘、税控盘、税务UKey快速批量抄税清卡的一种方法分享的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 开票接口系统能够解决的十大问题
- 下一篇: Java商城 架构演化