发票,发票行和修订版的数据库devise

我正在devise一个关于特许经营CRM的关系数据库的第二个主要迭代(有许多重构),我需要帮助最好的数据库devise实践来存储作业发票发票行 ,并对每一个更改进行强大的审计logging发票。

当前模式

Invoices

 InvoiceId (int) // Primary key JobId (int) StatusId (tinyint) // Pending, Paid or Deleted UserId (int) // auditing user Reference (nvarchar(256)) // unique natural string key with invoice number Date (datetime) Comments (nvarchar(MAX)) 

InvoiceLines

 LineId (int) // Primary key InvoiceId (int) // related to Invoices above Quantity (decimal(9,4)) Title (nvarchar(512)) Comment (nvarchar(512)) UnitPrice (smallmoney) 

修订架构

InvoiceRevisions

 RevisionId (int) // Primary key InvoiceId (int) JobId (int) StatusId (tinyint) // Pending, Paid or Deleted UserId (int) // auditing user Reference (nvarchar(256)) // unique natural string key with invoice number Date (datetime) Total (smallmoney) 

架构devise考虑

1.是否明智地存储发票的付款或待定状态?

收到的所有发票Payments都存储在Payments表(例如现金,信用卡,支票,银行存款)中。 如果从“ Payments表中可以推出与给定工作发票相关的所有收入,那么将“已付”状态存储在Invoices表中是否有意义?

2.如何跟踪发票行项目修订?

通过将发票总额和审计用户存储在发票修订表 (请参阅上面的发票修订版 )中,可以跟踪发票的修订,但跟踪发票行修订表感觉很难维护。 思考? 修改:订单项应该是不可变的。 这适用于“草稿”发票。

3.税收

在存储发票数据时,我应该如何纳入销售税(SA中的14%增值税)?


编辑:良好的反馈,家伙。 发票和发票行在定义上是不可变的 ,所以跟踪更改是不明智的。 然而,“发票”发票必须由多人(例如,技术员在创build发票后应用折扣)可编辑,然后才能发出。

4.定义和跟踪发票状态的最佳方式?

  1. 草案
  2. 发行
  3. 作废

…被迫改变一个方向?

我的build议是,大约4年来不得不与其他人devise的发票系统的后端一起工作:在发票上没有“挂起”状态。 它会让你疯狂。

将待处理的发票作为普通发票(具有“待处理”标志/状态)存储的问题是将会有数百个操作/报告仅被考虑到已发票的发票,这实际上意味着除了待处理之外的每个状态。 这意味着这个状态必须被检查 单。 时间。 有人会忘记。 而任何人都会意识到这一点。

您可以使用内置的未决filter创buildActiveInvoices视图,但这只是改变了问题; 有人会忘记使用视图而不是表格。

待处理的发票不是发票。 在问题评论中正确地表述为草案 (或者命令,请求等,所有相同的概念)。 能够修改这些草案的必要性是可以理解的。 所以这是我的build议。

首先,创build一个草稿表(我们称之为“ Orders ):

 CREATE TABLE Orders ( OrderID int NOT NULL IDENTITY(1, 1) CONSTRAINT PK_Orders PRIMARY KEY CLUSTERED, OrderDate datetime NOT NULL CONSTRAINT DF_Orders_OrderDate DEFAULT GETDATE(), OrderStatus tinyint NOT NULL, -- 0 = Active, 1 = Canceled, 2 = Invoiced ... ) CREATE TABLE OrderDetails ( -- Optional, if individual details need to be referenced OrderDetailID int NOT NULL IDENTITY(1, 1) CONSTRAINT PK_OrderDetails PRIMARY KEY CLUSTERED, OrderID int NOT NULL CONSTRAINT FK_OrderDetails_Orders FOREIGN KEY REFERENCES Orders (OrderID) ON UPDATE CASCADE ON DELETE CASCADE, ... ) CREATE INDEX IX_OrderDetails ON OrderDetails (OrderID) INCLUDE (...) 

这些是您的基本“草稿”表格。 他们可以改变。 要跟踪更改,应该创build历史logging表,其中包含原始OrdersOrderDetails表中的所有列,以及最后修改的用户,date和修改types(插入,更新或删除)的审计列。

正如Cade提到的,您可以使用AutoAudit来自动执行大部分此过程。

你还想要的是一个触发器,以防止更新不再活动的草稿(尤其是已经过帐并已经成为发票的草稿)。 保持这个数据一致是很重要的:

 CREATE TRIGGER tr_Orders_ActiveUpdatesOnly ON Orders FOR UPDATE, DELETE AS IF EXISTS ( SELECT 1 FROM deleted WHERE OrderStatus <> 0 ) BEGIN RAISERROR('Cannot modify a posted/canceled order.', 16, 1) ROLLBACK END 

由于发票是两级分层结构,因此您需要一个类似的稍微复杂的细节触发器:

 CREATE TRIGGER tr_OrderDetails_ActiveUpdatesOnly ON OrderDetails FOR INSERT, UPDATE, DELETE AS IF EXISTS ( SELECT 1 FROM ( SELECT OrderID FROM deleted UNION ALL SELECT OrderID FROM inserted ) d INNER JOIN Orders o ON o.OrderID = d.OrderID WHERE o.OrderStatus <> 0 ) BEGIN RAISERROR('Cannot change details for a posted/canceled order.', 16, 1) ROLLBACK END 

这可能看起来像很多工作,但现在你可以做到这一点:

 CREATE TABLE Invoices ( InvoiceID int NOT NULL IDENTITY(1, 1) CONSTRAINT PK_Invoices PRIMARY KEY CLUSTERED, OrderID int NOT NULL CONSTRAINT FK_Invoices_Orders FOREIGN KEY REFERENCES Orders (OrderID), InvoiceDate datetime NOT NULL CONSTRAINT DF_Invoices_Date DEFAULT GETDATE(), IsPaid bit NOT NULL CONSTRAINT DF_Invoices_IsPaid DEFAULT 0, ... ) 

看看我在这里做了什么? 我们的发票是原始的,神圣的实体,由一些首日在线的客户服务人员任意改变, 这里没有风险 。 但是,如果我们需要的话,我们仍然可以找出发票的全部“历史”,因为它与其原始Order链接 – 如果您记得,我们不允许在离开活动状态之后进行更改。

这正确地代表了现实世界中正在发生的事情。 一旦发票发送/发送,它不能被收回。 它在那里。 如果你想取消它,你必须发送一个逆转,要么到A / R(如果你的系统支持这样的事情),要么作为负面的发票来满足你的财务报告。 如果这样做,你可以真正看到发生了什么,而不必深入每个发票的审计历史; 你只需要看自己的发票。

还有一个问题,开发人员必须记住要将订单状态发布为发票后才能更改,但是我们可以通过触发器进行修正:

 CREATE TRIGGER tr_Invoices_UpdateOrderStatus ON Invoices FOR INSERT AS UPDATE Orders SET OrderStatus = 2 WHERE OrderID IN (SELECT OrderID FROM inserted) 

现在您的数据是安全的,从粗心的用户,甚至粗心的开发人员。 发票不再含糊不清; 你不必担心因为有人忘记检查发票状态而出现错误,因为没有状态

所以只是为了重新总结和解释一下这个问题:为什么我只是为了一些发票历史而烦恼了?

因为尚未发布的发票不是真实的交易 。 他们是交易“国家” – 交易正在进行中。 他们不属于您的交易数据。 通过像这样保持它们分离,你将解决很多潜在的未来问题。

免责声明:这一切都是从个人的经历来讲, 没有看到世界上所有的发票系统。 我不能保证100%确定这是适合您的特定应用程序。 我只能重申,从“待定”发票的概念,我看到的马蜂窝问题,从混合状态数据与交易数据。

就像你在网上find的其他devise一样,你应该调查这个作为一个可能的select,并评估它是否真的可以为你工作。

通常,发票行不会更改。 即订单(采购订单或工​​单)成为发票。 发票一旦发出,可以作废,也可以使用付款和贷记凭证,但这通常是关于它的。

您的情况可能会有所不同,但我相信这是惯例 – 毕竟,当您收到xyz发票时,您不希望文档所依据的数据以任何方式被更改。

就通常以我的经验而言,税收存储在发票级别并在发票发布时确定。

就成为发票之前的订单而言,通常我没有看到比基本的数据库级审计更复杂的事情 – 通常应用程序不会向用户公开该历史logging。

如果你想要一个直接的审计跟踪,这是相对不可知论的,你可以看看AutoAudit – 一个基于触发器的审计跟踪。

我们通常没有“草稿发票”。 这很诱人,因为订单和发票之间有很多相似之处。 但事实上,最好是在单独的表格中有没有成为发票的订单。 发票往往有一些差异(即,国家变化实际上是从一个实体到另一个实体的转变),并且在参照完整性的情况下,有时您只希望事物join“真正的”发票。

所以我们通常总是有PurchaseOrder,PurchaseOrderLine,Invoice和InvoiceLine。 在某些情况下,我有购物车的行为更像一个购物车 – 价格不存储和价格浮动与产品表和其他情况下,他们更像是价格报价,必须兑现后,一旦传输到客户。 在考虑业务工作stream程和需求时,这些细微之处可能很重要。

为什么不只是创build你想要审计的表的副本,而是创build在每个插入,更新,删除操作中将行复制到表副本的triggres?

触发器通常看起来像这样:

 CREATE TRIGGER Trg_MyTrigger ON MyTable AFTER UPDATE,DELETE AS BEGIN -- SET NOCOUNT ON added to prevent extra result sets from -- interfering with SELECT statements. SET NOCOUNT ON; INSERT INTO [DB].[dbo].[MyTable_Audit] (Field1, Field2) SELECT Field1, Field2 FROM DELETED END GO 

我同意上面关于发票的“不变性”的Aaronaught的评论。

如果你接受这个build议,那么我会考虑将“待审核”,“批准”和“空洞”作为状态。 “待审核”就是这样。 “批准”被认为是正确的,并由客户支付。 “无效”就是:发票不再有效,不能由客户支付。 然后,您可以推断发票是否从Paymentslogging中全部Payments ,而不是重复信息。

除此之外,你的修改想法,但没有真正的问题。

您可以在InvoiceLines包含税额作为另一个logging。

Interesting Posts