Подтвердить что ты не робот

Разработка базы данных для счетов-фактур, счетов-фактур и изменений

Я разрабатываю вторую значительную итерацию реляционной базы данных для 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. Налог

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


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

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

  • Проект
  • Выпущен
  • аннулирована

... ограничено изменение в одном направлении?

4b9b3361

Ответ 1

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

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

Ответ 2

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

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

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

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

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

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

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

Ответ 3

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

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

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

Ответ 4

Я согласен с приведенным выше замечанием Aaronaught относительно "неизменности" счета-фактуры.

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

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

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