Разработка базы данных для счетов-фактур, строк счетов-фактур и ревизий - PullRequest
41 голосов
/ 21 апреля 2010

Я проектирую вторую основную итерацию реляционной базы данных для CRM франшизы (с большим количеством рефакторинга), и мне нужна помощь по лучшим методам проектирования баз данных для хранения счетов-фактур за работу и счета строки со строгим контрольным журналом любых изменений, внесенных в каждый счет.

Текущая схема

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)

Схема проектирования

1. Целесообразно ли сохранять статус оплаченного или отложенного счета?

Все платежи, полученные по счету, хранятся в таблице Payments (например, наличные, кредитная карта, чек, банковский депозит). Имеет ли смысл сохранять статус «Оплачено» в таблице Invoices, если из таблицы Payments можно вывести весь доход, связанный со счетами данной работы?

2. Как отслеживать изменения позиции в счете-фактуре?

Я могу отслеживать изменения в счете , сохраняя изменения статуса вместе с итоговой суммой счета и пользователя-аудитора в таблице изменений счета (см. InvoiceRevisions выше), но сохраняя трек таблицы пересмотра строки счета-фактуры трудно поддерживать. Мысли? Редактировать: позиции должны быть неизменными. Это относится к «черновому» счету.

3. Tax

Как мне включить налог с продаж (или 14% НДС в SA) при хранении данных счета?


Редактировать: Хорошие отзывы, ребята. Счета-фактуры и строки счетов-фактур по определению неизменны , поэтому отслеживание изменений не имеет смысла. Однако «черновик» счета-фактуры должен быть доступен для редактирования более чем одному человеку (например, менеджер применяет скидку после того, как техник создает счет-фактуру) до его выставления ...

4. Лучший способ определить и отследить статус счета?

  1. Проект
  2. Выпускается
  3. аннулирована

... вынуждены меняться в одном направлении?

Ответы [ 4 ]

51 голосов
/ 21 апреля 2010

Мой совет от 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, а также столбцы аудита для последнего измененного пользователя, даты и типа изменения (вставка, обновление, или удалить).

Как упоминает Кейд, вы можете использовать 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% уверенностью, что это подходит для вашего конкретного применения. Я могу только повторить гнездо проблем шершня, которое я видел в результате представления «ожидающих» счетов, из-за смешивания данных состояния с данными транзакций.

Как и в случае с любым другим дизайном, который вы найдете в Интернете, вы должны изучить этот вариант как один из возможных вариантов и оценить, действительно ли он может работать для вас.

7 голосов
/ 21 апреля 2010

Обычно строки счета-фактуры не изменяются. то есть заказ (заказ на покупку или заказ на работу) становится счетом. После выставления счета-фактуры он может быть аннулирован или могут быть применены платежи и кредитовые авизо, но обычно это так.

Ваша ситуация может немного отличаться, но я считаю, что это обычное соглашение - в конце концов, когда вы получаете счет-фактуру xyz, вы не ожидаете, что данные, на которых основан документ, будут каким-либо образом изменены.

Что касается налога, обычно по моему опыту, который хранится на уровне накладной и определяется в момент разноски накладной.

Что касается изменения заказов до того, как они стали счетами, обычно я не видел ничего более сложного, чем базовый аудит на уровне базы данных - обычно приложение не предоставляет эту историю пользователям.

Если вам нужен простой контрольный журнал, относительно независимый от домена, вы можете изучить AutoAudit - контрольный журнал на основе триггера.

У нас обычно нет "черновиков счетов". Это заманчиво, потому что у вас есть много общего между заказами и счетами. Но на самом деле лучше иметь заказы, которые не стали счетами, в отдельной таблице. Счета, как правило, имеют некоторые различия (то есть изменение состояния на самом деле является преобразованием из одной сущности в другую), и при ссылочной целостности иногда вам действительно нужно только, чтобы вещи соединялись с «настоящими» накладными.

Таким образом, у нас обычно всегда есть PurchaseOrder, PurchaseOrderLine, Invoice и InvoiceLine. В некоторых случаях я вел себя, чтобы сторона заказа на поставку вела себя больше как корзина покупок - где цена не сохраняется и плавает вместе с таблицей продуктов, а в других случаях они больше похожи на ценовые котировки, которые должны соблюдаться после их передачи в клиент. Эти тонкости могут быть важны при рассмотрении бизнес-процессов и требований.

3 голосов
/ 21 апреля 2010

Я согласен с приведенным выше комментарием Аарона, касающимся «неизменности» счета.

Если вы воспользуетесь этим советом, то я буду рассматривать статусы «Ожидание проверки», «Одобрено» и «Аннулировано». «Ожидание обзора» это как раз то. «Утверждено» считается правильным и оплачивается клиентом. «Аннулировано» - это просто: счет больше не действителен и не оплачивается клиентом. Затем вы можете определить, полностью ли оплачен счет, из записей в Payments, и вы не повторяете информацию.

Кроме этого, нет никаких реальных проблем с вашей идеей ревизии.

Вы можете включить налог в качестве еще одной записи в InvoiceLines.

3 голосов
/ 21 апреля 2010

Почему бы просто не создать копии таблиц, которые вы хотите проверять, а затем на исходных таблицах создать триггеры, которые будут копировать строки в копии таблиц при каждой вставке, обновлении, удалении?

Триггер обычно выглядит примерно так:

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
...