我不得不使用其他人设计的发票系统的后端大约 4 年的建议:不要在发票上具有“待定”状态。它会让你发疯。
将待处理发票存储为普通发票(带有“待处理”标志/状态)的问题在于,将有数百个操作/报告只应考虑已发布的发票,这实际上意味着除待处理之外的所有状态。这意味着必须每次检查此状态。单身的。时间。 有人会忘记。任何人都需要几周的时间才能意识到这一点。
您可以使用ActiveInvoices
内置的待处理过滤器创建一个视图,但这只是转移了问题;有人会忘记使用视图而不是表格。
待处理发票不是发票。它在问题评论中正确地表述为草稿(或命令、请求等,都是相同的概念)。能够修改这些草案的需要是可以理解的,当然。所以这是我的建议。
首先,创建一个草稿表(我们称之为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 (...)
这些是您的基本“草稿”表。它们可以更改。要跟踪更改,您应该创建历史表,其中包含原始Orders
和OrderDetails
表中的所有列,以及最后修改的用户、日期和修改类型(插入、更新或删除)的审计列。
正如 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
- 如果您还记得的话,我们不允许在发票离开活动状态后对其进行更改。
This correctly represents what's going on in the real world. Once an invoice is sent/posted, it can't be taken back. It's out there. If you want to cancel it, you have to post a reversal, either to an A/R (if your system supports that sort of thing) or as a negative invoice to satisfy your financial reporting. And if this is done, you can actually see what happened without having to dig into the audit history for each invoice; you just have to look at the invoices themselves.
There's still the problem that developers have to remember to change the order status after it's been posted as an invoice, but we can remedy that with a trigger:
CREATE TRIGGER tr_Invoices_UpdateOrderStatus
ON Invoices
FOR INSERT
AS
UPDATE Orders
SET OrderStatus = 2
WHERE OrderID IN (SELECT OrderID FROM inserted)
Now your data is safe from careless users and even careless developers. And invoices are no longer ambiguous; you don't have to be worry about bugs creeping in because somebody forgot to check the invoice status, because there is no status.
So just to re-summarize and paraphrase some of this: Why have I gone to all this trouble just for some invoice history?
Because invoices that haven't been posted yet aren't real transactions. They are transaction "state" - transactions in progress. They don't belong with your transactional data. By keeping them separate like this, you will solve a lot of potential future problems.
免责声明:这都是我个人的经验,我还没有见过世界上所有的发票系统。我不能 100% 保证这适用于您的特定应用。我只能重申我所看到的由“待处理”发票的概念、状态数据与交易数据混合所导致的大黄蜂巢问题。
与您在互联网上找到的所有其他设计一样,您应该将其作为一种可能的选择进行调查,并评估它是否真的适合您。