Farseer

导航

瞎子摸象---销售开票(一)

一直在想怎么才能更清晰地描述一个过程的代码,如果采用类似时序图的方式描述,看到的只是这个方法调用了另一个方法,然后一次调用下去,这样写了感觉用处不大,下次要做客制化还是要重新去跟踪一遍,没有起到梳理代码的目的。
细细想想,不管代码怎么编排,它的目的都是要实现业务的需求,而代码用各种模式去设计,无非也就是为了便于修改,更加有效地从数据库里读取和写入数据。于是可以把整个过程分解为三个阶段,就像把大象关到冰箱里拢共分几步一样。前段是业务,后端是表结构,中间是代码。变化最大的是中间的代码,如果一头钻进代码里,后果很有可能是云深不知处。于是我想可能更好的分析代码方式是抓住两头,梳理中间。首先要搞清楚这段代码在业务上要处理哪些问题,然后处理这些业务问题需要用哪些表结构来实现,再想一想如果让自己去写代码会怎么去做,然后就可以看它是怎么实现的了。
ERP要处理什么问题?从不同的角度看有不同的答案。按照我目前的认识,它有一个方面的任务是梳理公司的资金流,物流和信息流。按照这个思路去看销售开票的这个过程,它要处理的也无非就是这三个方面的问题。我们分别从这三个方面去考察它,这一篇首先看AX是如何处理资金流的。
这里的资金流,简单地说成成销售开票时凭证的生成。
业务需求
在业务上,销售开票主要生成哪些凭证?我的理解应该主要生成两对凭证:
1.借:主营业务成本
   贷:库存商品
2.借:应收账款
   贷:主营业务收入
         应缴税金
当然这只是最简单的情况。要生成这两对凭证,要解决几个问题:
1.科目设置
2.金额计算
科目设置相对简单,只要提供一个地方让用户选择相应的会计科目,然后在代码中找到指定的会计科目就可以了。
金额的计算相对麻烦些:
对于第一对凭证要计算其成本价,这就涉及到成本价的计算问题。
对于第二对凭证一方面要处理现金折扣和折扣现金,又要处理税的计算。当然还有可能要处理杂项收费和佣金。
数据表设计
上面是前面提到的两头中的一头,业务上的需求,另一头就是表设计的问题了,对于凭证的生成只要有一个表来存储凭证和凭证对应的交易就可以了,AX把这张表的名字取名叫做LedgerTrans。
代码分析
OK,了解了业务需求和数据表的设计,我们就要去看AX是怎么去玩的了。为了方便,我们不可能每生成一张凭证都要自己去给LedgerTrans这张表的每个字段赋值,这样要求每个coder都要明白LedgerTrans的每个字段的含义,是会死人的,从这种角度讲也应该有个类去封装一哈子。我们知道一个凭证可能会一借一贷,也可能是多借多贷,总之至少要有两条交易,也就是至少要对应LedgerTrans的两条记录,这个没什么异议,如果出于读写LedgerTrans表方便的目的最起码也要把LedgerTrans封装一下,于是需要这么一个类,OK,AX里的这个类取名叫做LedgerVoucherTransOjbect。紧接下来的问题就是,一个凭证会对应多条LedgerTrans记录,当然要有这么一个类来把这些LedgerTrans弄成一张凭证吧?正常的思维过程是,我先创建一个凭证,然后根据需要向这个凭证中添加交易记录,于是需要一个类来处理单个凭证,AX里的这个类取名叫做LedgerVoucherOjbect。对于一个过程(比如销售开票过程)可能有多张凭证生成,于是需要一个对象来管理这多张凭证的生成和过账,于是又一个对象应运而生,这就是类LedgerVoucher(当然它还有一些子类来处理特定的情形,这里简化之)。于是为了处理凭证的生成就有了三个类,根据距离表LedgerTrans的距离从近到远依次为:
1.LedgerVoucherTransObject:是对单条LegderTrans记录的封装;
2.LedgerVoucherOjbect:是对一个凭证的封装,对应多条LedgerTrans记录;
3.LedgerVoucher:是对多个凭证的封装,对应多个凭证。
可以看出从1-3整个过程都是被包含和包含的过程,需要一个容器来存储,比如LedgerVoucher要处理多个凭证,总得有个容器来存才好,可以用多种容器对象来存储,AX采用的是对Map的封装LedgerVoucherList和LedgerVoucherTransList,Map很像C#里的HashTable,那么在这里它用的键值是什么那?可以看这两个类对应的add方法。LedgerVoucherList采用的键值是凭证号,而LedgerVoucherTransList要储存的是LedgerTrans,它的键值就采用了顺序号。
使用过程
上面简单介绍了AX凭证系统的类图设计,但咱可不能做天桥的把式,光说不练,这东西得run起来才算有用。
我们就以销售开票凭证的生成过程,金额的计算逻辑为主线介绍介绍一下AX处理现金流的过程。
第一对凭证
1.借:主营业务成本
   贷:库存商品
这一对凭证相对简单,因为是成本端的东西,各种折扣,税之类的都与这个无关(当然库存成本的复杂度更大了,至于AX的成本是怎么计算的,月底是怎么执行库存关帐的,以后有时间再慢慢分析,本文不涉及这方面的内容),只要得到物料的成本价*数量就可以算出金额了。这一对凭证在哪里计算出来比较好?当然是库存那边好了,在扣减物料的时候,就准确地知道多少物料出库了,然后顺便得到成本价,一乘就得到金额了。AX也确实这这么做的,我前面分析库存模块涉及到的两个类InventUpdate和InventMovement的时候介绍了这两个类各自的职责,也介绍了在销售开票的时候具体是怎么调用这两个类去做库存方面的处理的。其实在处理库存的时候同时也就把与库存紧密相关的财务凭证生成了,当时说了这些是由InventMovement这个类去做的。在销售开票的时候生成凭证涉及到两个方法updateLedgerAdjust和updateLedgerPhysical。这两个方法是在InventUpd_Financial的updateFinancialIssue方法调用的,其实就是在处理完数量以后顺带把相应的凭证生成了。其中updateLedgerPhysical实际上是把暂估凭证借贷相反反向冲回了,如果在发货的时候生成了暂估凭证的话,updateLedgerAdjust是生成财务凭证的。
业务上可能有这样的需求,对于某些物料在某些情况下我们不产生这个凭证,该怎么办那?我们看一下updateLedgerAdjust这个方法不难发现并不是所有情况下都会生成这个凭证,AX预留了一个开关,这个开关在 库存管理->设置->库存->库存模型组->设置 过账财务库存。
这一对凭证过账科目那?就在updateLedgerAdjust这个方法里,在这个方法里正是对应出库的一对凭证的生成:

Code

我们看到第一个凭证交易,对应的交易类型是SalesIssue,也就是销售订单-发货。我们需要一个枚举类型来标记过账的类型,在AX里这个枚举类型是LedgerPostingType。对于要过账到哪个科目里,需要写一个类来集中处理,AX里的这个类叫做InventPosting,从这里也可以看出AX称第一个凭证交易为BalanceSheet,国外称资产负债表为Balance Sheet,我想它这里指的应该是资产负债类科目,而第二个凭证交易叫做Operations,应该就是它对应的科目,Operations对应的交易类型是SalesConsump,也就是销售订单-消耗量。在这个方法里costAmount是负数的,所以第一个凭证交易是贷方,第二个凭证交易是借方。所以说到这里就很明白了
贷:库存商品
借:主营业务成本
就是这对科目了。通过accountBalanceSheet和accountOperations方法可以找到具体在什么地方设置这些科目,也就是在 库存商品->设置->物料组->销售订单->发票,发货和消耗额。发货科目就对应库存商品,而消耗额就是主营业务成本了。
OK,第一对凭证的过账科目已经解决了,那金额那?也就是上面代码中的costAmount是怎么算出来的?
成本额的计算也是在InventMovement,方法是financialIssueCostValue,具体的计算逻辑可以去看它的代码,比用文字描述起来更简单。该方法被类InventUpd_Financial的updateFinancialIssue方法调用。它这样设计也很正常和直接,出入库的过账科目和金额都扔给InventMovement去处理,因为不同类型的出入库交易的过账科目可能不一样,封到InventMovement里比较好。
第二对凭证
借:应收账款
贷:主营业务收入
      应交税金
从业务上来看生成这对凭证涉及到哪些内容那?从最基本的来看,要处理这对凭证的过账科目和过账到各个科目的金额分别是多少。过账科目比较好弄,无非就是要给用户预留一个地方设置过账的科目,后面在分析代码的时候会逐一说明。金额的计算相对复杂些,对于应收账款,并不是简单的销售金额+税就是应收账款了,要处理如下内容:
1.折扣销售
参考CPA2008年税法教程,折扣销售的定义如下:折扣销售是指销货方在销售货物或应税劳务时,因购货方购货数量较大等原因而给予购货方的价格优惠。税法规定,如果销售额和折扣额在同一张发票上分别注明的,可按折扣后的余额做为销售额计算增值税;如果将折扣另开发票,不论其在财务上如何处理,均不得从销售额中扣除折扣额。
2.销售折扣(现金折扣)
销售折扣是指在销货方在销售货物货或应税劳务后,为了鼓励购货方及早偿还货款而协议许诺给予购货方的一种折扣优待。销售折扣发生在销货之后,是一种融资性质的理财费用,因此,销售折扣不得从销售额中扣除。销售折扣的会计处理方式有总价法和净价法两种处理方式,从CPA2008税法规定来看,我国只允许是用总价法,在生成第二对凭证的时候必须按照销售额入账。
3.杂项收费
一些运费之类的杂项费用如何处理。
对于其中的折扣销售,AX可谓考虑的周全,以至于我参与实施的几个项目一点都没用到,或许对于大批量批发的时候有点用,说实在的,对于一般的企业根本用不着这么复杂的东西,设置那么复杂的东西,到最后设置的人自己都会搞不清楚设置了些什么,AX的设计有点违背简单就是美的原则,不过还好,如果不是出于研究的目的,大可不必理会它。

说一下AX是怎么分别处理以上几个业务问题的:
销售折扣
AX的折扣销售分为单行折扣,多行折扣和总折扣几种,单行折扣和多行折扣的处理方式类似,它直接在行的销售额减去折扣额就可以了,总折扣跟这两个有些区别,是销售额达到一定的金额就享受一定的折扣。对于过账科目,单行折扣和多行折扣用户可以选择是否过账到销售费用科目,具体为,如果在 库存管理->设置->物料组->销售订单->发票 组的折扣选择了科目,则会将折扣金额记录在该科目的借方。生成分录类似如下:
借:应收账款
     销售费用
贷:主营业务收入
     应交税金
如果这个地方没有设置折扣科目,则直接在主营业务收入收入扣减折扣金额,不会将折扣金额在财务凭证中体现。对于总折扣,AX没有提供一个选项让用户选择可以不过账到科目中,它始终过账到某个指定的科目中,这个科目在系统账户中设置,总账->设置->过账->系统账户 客户-发票折扣 设置相应的科目。
现金折扣
在AX中提供了一个选项,在计算税的时候是否要把现金折扣给去掉,这个选项在  总账->设置->参数设置->增值税->现金折扣 在计算增值税之前扣除现金折扣,正如前面提到的,按照我国税法的固定,不能扣除,所以这个选项不选就得了。另外有些用户在这个组里可能会看到一个选项 发票中含现金折扣,而有些用户看不到这个选项,是因为这个选项只有在启用了西班牙国家特性后才会启用,不知道是不是只有西班牙人才用这个选项,呵呵,这个选项的用途很明显,就是在开发票的时候就把现金折扣登记到财务费用科目而不是等到收钱对方享受折扣的时候才登记,一旦勾选了这个选项 发票中含现金折扣就自动勾选了。OK,所以这个选项跟我们也没什么关系了。我们就老老实实地啥选项都不勾就得了。
杂项费用
AX的杂项收费处理的还是比较复杂的,分为针对整张订单的杂项收费和针对行的杂项收费。不过AX用单独的表和类对其进行了合理的封装,所以看起来也不会那么困难。对于杂项收费,是小孩没娘,说来话长,放在这篇文章里叙述会把整篇文章拖得过长,显得过于臃肿,所以杂项收费和佣金再另起文章单独描述,本文只简单介绍一下通过哪些类去处理就完了。
从开始写这篇文章开始,我就在想,如果让我去写生成这对凭证的逻辑,我会怎么去写?或许我能想到的也就是像现在AX系统类SalesFormLetter_invoice的updateNow方法里写的那样,通过一个for循环处理所有事情,在遍历每一行的时候计算其单行折扣,多行折扣,销售额以及总折扣,税额等,然后通过类LedgerVoucher过账到相应的科目就OK了。的确在大多数情况下这样足够了,因为说到底,从业务的本质上也就这么点东西,把销售订单的SalesLine过一遍也就得了。
有时候经过设计的东西,跟我拍拍脑袋一下能想到的就是会有一些区别。AX为了计算这对凭证的应收账款这个科目的金额弄了一些列的类出来,从父类到子类依次为TradeTotals,SalesTotals,SalesTotals_Sales,SalesTotals_ParmTrans,当然这里只是列举了一个分支,TradeTotals还有采购分支,即便是销售分支也有其他的子分支,其实这也就是这一系列类之所以存在的理由,如果只是销售开票的过程才用到这个类的话,用那个for循环足够了。AX只是把这段计算逻辑抽象了一层,封装了一把,于是得到了这么一系列的类用来计算应收账款科目的金额。当然如果这些类只是计算了应收账款的金额,显然是有失公允的,但是我们可以认为其他金额都是计算应收账款金额的一段子逻辑,看一下类TradeTotals的totalAmount(应收账款科目的过账金额是通过这个方法获取的)方法就知道了,totalAmount方法就是计算逻辑就知道了:

Code

这个方法只是做一些舍入的计算,重点看totaoAmountUnRounded方法的内容:

Code

从这段代码中就可以很清楚地看到AX应收账款的计算逻辑了,totalBalance(销售单价*数量-单行和多行折扣),totalEndDisc就是现金折扣,totalMarkup行杂项收费和头杂项收费之和,totalTaxAmount税额。
从这里也可以看出要得到应收账款的金额,上面这几个必须要算出来,所以可以把这个类看做是计算应收账款金额的类,虽然设计者可能不是这么想的。
我们没有展开看各个金额的计算逻辑,这篇文章里我也不展开介绍各个金额是如何计算的了,要不然这篇文章就成老婆娘的裹脚布了。。。等有时间再单独介绍一下税和杂项收费的计算逻辑。
我们看一下AX是怎么使用这个类的,在AX里一共用了两次:
第一次在SalesFormLetter的createJournal方法,说句题外话,仔细分析一下AX的SalesFormLetter及其子类里的方法就会发现有几个**Journal的方法,比如insertJournal,createJournal,writeJournal,想了这么多Journal名字,我想也难为坏了给方法取名的人,呵呵。

Code
这次调用初步计算了应收账款的金额,并初步过滤了无效的行,后面在计算主营业务收入金额的时候用到了这些数据。
第二次调用发生在SalesFormLetter_Invoice的updateNow方法:
Code
因为在处理发货的时候可能不够了,所以根据现有量发货了,这样数量就更改了,所以需要重新计算一把。
OK,应收账款金额就这些了。
主营业务收入的金额跟我们一般的想法类似,它只是循环一下要计算的行,用行的销售额通过一定的计算得到,逻辑相对简单。
先说到这里了,过账科目前面已经提到过几个了,主营业务收入科目也是在物料组设置的,库存商品->设置->物料组->销售订单->发票组 收入。应收账款科目在过账模板处设置,通过应收账款->设置->过账模板设置,查找过账科目的方法是:CustLedgerAccounts::sumAccount(accountNum, postingProfile)。
为了处理方便,在处理应收模块的凭证生成时,AX又把LedgerVoucher封装了一层CustVendVoucher,CustVoucher之类,无非就是再给应收模块所特有的处理逻辑,到最后还是调用了LedgerVoucher对象,这里就不再赘述了。

OK,关于销售开票过程的现金流部分就说这些了,只是我个人的一些看法,不见得正确,还望高手指点。

posted on 2008-12-08 16:25  佛西亚  阅读(1433)  评论(1编辑  收藏  举报