44 votes

Conception de base de données pour les factures, les lignes de facturation et les révisions

Je suis de la conception de la 2ème version majeure d'une base de données relationnelle pour une franchise de CRM (avec beaucoup de refactoring) et j'ai besoin d'aide sur la meilleure base de données de conception pratiques pour le stockage de l'emploi des factures et des lignes de facture avec une forte piste de vérification de tous les changements apportés à chaque facture.

Schéma actuel

Invoices Tableau

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 Tableau

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

Révision du schéma

InvoiceRevisions Tableau

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)

Schéma des considérations de conception

1. Est-il judicieux de stocker une facture Payées ou en Attente de statut?

Tous les paiements reçus pour une facture sont stockées dans une Payments tableau (par exemple. Espèces, Carte De Crédit, Chèque, Banque De Dépôt). Est-il significatif pour stocker un "Payé" dans le statut de Invoices tableau si tous les revenus liés à un emploi de factures peut être déduit de l' Payments tableau?

2. Comment garder une trace de la ligne de la facture, élément de révisions?

Je peux suivre des révisions à une facture en stockant les changements de statut avec le total de la facture et la vérification de l'utilisateur dans une facture de révision de la table (voir InvoiceRevisions ci-dessus), mais de garder la trace d'une ligne de facture de révision de la table se sent mal à maintenir. Pensées? Edit: ligne les articles doivent être immuable. Cela s'applique à un "projet" de la facture.

3. L'impôt

Comment dois-je incorporer la taxe de vente (ou de la TVA de 14% en afrique du sud) lors du stockage de données de facturation?


Edit: Bon retour, les gars. Les factures et les lignes de la facture sont par définition immuable, de sorte que le suivi des modifications n'est pas raisonnable. Cependant, un "projet" de la facture doit être modifiable par plus d'une personne (par exemple. gestionnaire de rabais s'applique après le technicien crée facture) avant l'émission...

4. La meilleure façon de définir et suivre le statut des factures?

  1. Projet
  2. Publié
  3. Annulé

...contraint de modifier dans un sens?

55voto

Aaronaught Points 73049

Mon conseil à environ 4 ans d'avoir à travailler avec le back-end d'un système de facturation que quelqu'un d'autre conçu: vous N'avez pas un état "en attente" sur les factures. Il va vous conduire fou.

Le problème avec l'enregistrement des factures ordinaires factures (avec un "en attente" drapeau de l'état/) c'est qu'il y aura des centaines d'opérations/les rapports qui sont censés prendre en compte posté factures, qui signifie littéralement chaque état , sauf pour l'attente. Ce qui signifie que cet état doit être vérifié tous les. unique. temps. Et quelqu'un va l'oublier. Et il en sera de semaines avant que quelqu'un s'en rend compte.

Vous pouvez créer un ActiveInvoices vue avec l'attente de filtre intégré, mais qui déplace simplement le problème, quelqu'un en aura oubliez pas d'utiliser la vue au lieu de la table.

En attente de la facture n'est pas une facture. Il est bien dit dans la question des commentaires comme un projet (ou un ordre, une demande, etc., tous le même concept). Le besoin d'être en mesure de modifier ces projets est compréhensible, certainement. Voici donc ma recommandation.

Tout d'abord, créez un projet de table (que nous appellerons 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 (...)

Ce sont de base de votre "projet" des tables. Ils peuvent être modifiés. Pour suivre les modifications, vous devez créer l'histoire des tableaux, dont toutes les colonnes qui sont à l'origine, Orders et OrderDetails tables, en plus de la vérification des colonnes de la dernière modification de l'utilisateur, la date et le type de modification (insert, update ou delete).

Comme Cade mentionne, vous pouvez utiliser AutoAudit pour automatiser la plupart de ces processus.

Ce que vous voudrez également un déclencheur pour prévenir des mises à jour pour les projets qui ne sont plus actifs (en particulier les projets qui sont affichés, et sont devenus des factures). Il est important de garder ces données cohérentes:

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

Car les factures sont une hiérarchie à deux niveaux, vous avez besoin d'un semblable et un peu plus compliqué de déclenchement pour les détails:

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

Cela peut sembler beaucoup de travail, mais maintenant vous pouvez faire ceci:

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

Voir ce que j'ai fait ici? Nos factures sont vierges, sacré entités des nations unies-entachée par les changements arbitraires par quelques-premier-jour-sur-le-emploi de service à la clientèle gars. Il n'y a pas de risque de vissage jusqu'ici. Mais, si nous en avons besoin, nous pouvons encore trouver de l'ensemble de "l'histoire" d'une facture, car il des liens de retour à son origine, en Order - ce qui, si vous vous souvenez, nous ne sommes pas la possibilité de changer de après avoir quitté le statut de membre actif.

Cela représente correctement ce qui se passe dans le monde réel. Une fois qu'une facture est envoyée/posté, il ne peut pas être repris. C'est là-bas. Si vous souhaitez annuler, vous devez poster un renversement, soit pour un A/R (si votre système prend en charge ce genre de chose) ou comme un négatif de la facture pour satisfaire votre information financière. Et si cela est fait, vous pouvez réellement voir ce qui s'est passé , sans avoir à fouiller dans l'historique d'audit pour chaque facture; vous avez juste à regarder les factures elles-mêmes.

Il y a toujours le problème que les développeurs n'oubliez pas de modifier le statut de la commande après qu'il a été publié comme une facture, mais nous pouvons remédier à cela avec un déclencheur:

CREATE TRIGGER tr_Invoices_UpdateOrderStatus
ON Invoices
FOR INSERT
AS

UPDATE Orders
SET OrderStatus = 2
WHERE OrderID IN (SELECT OrderID FROM inserted)

Maintenant, vos données sont en sécurité à cause de utilisateurs et même imprudent de développeurs. Et les factures ne sont plus ambigus; vous n'avez pas à vous soucier des bugs rampant parce que quelqu'un a oublié de vérifier le statut de la facture, parce qu'il n'y est pas de statut.

Donc, juste pour re-résumer et paraphraser certains de ceci: Pourquoi ai-je pris cette peine, juste pour quelques la facture de l'histoire?

Parce que les factures qui n'ont pas été publiés mais ne sont pas des transactions réelles. Ils sont des transactions "état" - les opérations en cours. Ils n'appartiennent pas avec vos données transactionnelles. En les gardant séparées comme ceci, vous permettra de résoudre beaucoup de potentiel de futurs problèmes.

Avertissement: C'est tout en parlant de mon expérience personnelle et je n'ai pas vu tous les système de facturation dans le monde. Je ne peux pas garantir avec certitude à 100% que c'est approprié pour votre application particulière. Je ne peux que réitérer le nid de guêpes problèmes que j'ai vu résultant de la notion de "en attente", factures, à partir d'un mélange de l'état des données avec les données transactionnelles.

Comme avec tous les autres que vous trouverez sur l'internet, vous devez étudier cela comme une option possible et d'évaluer si oui ou non il peut vraiment travailler pour vous.

7voto

Cade Roux Points 53870

Généralement les lignes de la facture ne sont pas modifiées. c'est à dire une commande (bon de commande ou bon de travail) devient une facture. Une fois qu'une facture est émise, il peut être annulé ou les paiements et les notes de crédit peuvent être appliquées, mais c'est habituellement à ce sujet.

Votre situation peut être un peu différent, mais je crois que c'est la convention habituelle - après tout, lorsque vous recevez la facture xyz, vous ne comptez pas les données le document est basé sur d'être modifiés en aucune façon.

Aussi loin que l'impôt, généralement dans mon expérience, il est stocké au niveau de factures et déterminé au moment où la facture est validée.

Aussi loin que les ordres de modification avant de devenir factures, souvent, j'ai vu rien de plus complexe que la base de données de base au niveau de l'audit généralement de l'application n'expose pas que l'histoire pour les utilisateurs.

Si vous voulez un compliqué piste de vérification qui est relativement domaine indépendant, vous pouvez regarder dans AutoAudit - un déclencheur à base de piste de vérification.

En général, nous n'avons pas de "projet de factures". Il est tentant parce que vous avez beaucoup de similitude entre les commandes et les factures. Mais, en réalité, c'est mieux d'avoir des commandes qui ne sont pas devenus les factures dans un tableau distinct. Les factures ont tendance à avoir quelques différences (c'est à dire le changement d'état est en fait une transformation à partir d'une entité à l'autre) et avec intégrité référentielle parfois, vous ne voulez que les choses se joindre à des "vraies" des factures.

Donc, nous avons l'habitude de toujours avoir PurchaseOrder, PurchaseOrderLine, la Facture et les InvoiceLine. Dans certains cas, j'ai eu le PO côté se comportent plus comme un panier - si le prix n'est pas stockée et de la flotte avec la table produits et d'autres cas où ils sont plus comme des devis qui ont honoré une fois qu'ils sont transmis au client. Ces subtilités peuvent être importantes lorsque l'on regarde le flux de travail d'entreprise et les exigences.

3voto

Dean K. Points 5298

Pourquoi ne pas simplement créer des copies des tables que vous souhaitez auditer et que sur les tables d'origine créer des triggres qui copieront une ligne dans des copies de table à chaque insertion, mise à jour, suppression?

Le déclencheur ressemble généralement à ceci:

 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
 

3voto

John Points 12438

Je suis d'accord avec Aaronaught du commentaire ci-dessus concernant la "immutabilité" de la facture.

Si vous prenez ce conseil, alors je vous envisagez d'avoir "en Attente d'Examen, "Approuvé", et la mention "nul" comme les statuts. "En attendant l'Examen" est juste que. "Approuvé" est considéré comme correct, et payables par le client. La mention "nul" est juste que: la facture n'est plus valide, et ne sont dus par le client. Ensuite, vous pouvez déduire si la facture est payée en totalité à partir des enregistrements en Payments, et vous n'êtes pas répéter l'information.

Mis à part cela, pas de réels problèmes avec votre révision idée.

Vous pouvez inclure l'impôt comme un autre record en InvoiceLines.

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X