web-dev-qa-db-ja.com

請求書の生成と追跡

2週間ごとに、システムは会社の請求書を生成します。

会社は毎月1日と16日に請求書を受け取ります。 (2週間ごとにCron Jobを介して実行されます。注文テーブルをスキャンし、「請求書」テーブルに追加します。別の方法はありますか?)

ordersテーブルには顧客の注文のリストがあり、それが属する会社も示しています(orders.company_id

invoiceテーブルは、ordersテーブルからの注文の合計コストを計算します。

私は、合理的な請求書追跡を設計する方法を理解しようとしています。いつか会社から料金を送ってもらうか、料金を送ってくれることがあります(invoice.amount

以下を使用して請求書を追跡する必要があります:

  • 会社が私に金額を送ったとき
  • いつ会社に送金しましたか
  • 会社から受け取った金額
  • 会社にいくら送ったか
  • 全額を受け取りましたか(受け取っていない場合、DBで何を更新する必要がありますか?)
  • 請求書のステータス(送信された請求書、キャンセル済み、受け取った金額、送信した金額)

ここに私が思いついたデータベース設計があります:

会社テーブル

mysql> select * from company;
+----+-----------+
| id | name      |
+----+-----------+
|  1 | Company A |
|  2 | Company B |
+----+-----------+

顧客は私のウェブサイトから会社を選択できます。

注文表

mysql> select * from orders;
+----+---------+------------+------------+---------------------+-----------+
| id | user_id | company_id | total_cost | order_date          | status_id |
+----+---------+------------+------------+---------------------+-----------+
|  1 |       5 |          2 |      25.00 | 2012-02-03 23:30:24 |         1 |
|  2 |       7 |          2 |      30.00 | 2012-02-13 18:06:12 |         1 |
+----+---------+------------+------------+---------------------+-----------+

2人の顧客がB社orders.company_id = 2)に製品を注文しました。 Ordersフィールドは十分ではなく、単純化されているだけです。

orders_productsテーブル

mysql> select * from orders_products;
+----+----------+------------+--------------+-------+
| id | order_id | product_id | product_name | cost  |
+----+----------+------------+--------------+-------+
|  1 |        1 |         34 | Chair        | 10.00 |
|  2 |        1 |         25 | TV           | 10.00 |
|  3 |        1 |         27 | Desk         |  2.50 |
|  4 |        1 |         36 | Laptop       |  2.50 |
|  5 |        2 |         75 | PHP Book     | 25.00 |
|  6 |        2 |         74 | MySQL Book   |  5.00 |
+----+----------+------------+--------------+-------+

顧客が注文した製品のリスト。

請求書テーブル

mysql> select * from invoice;
+----+------------+------------+---------------------+--------+-----------+
| id | company_id | invoice_no | invoice_date        | amount | status_id |
+----+------------+------------+---------------------+--------+-----------+
|  7 |          2 |        123 | 2012-02-16 23:59:59 |  55.00 |         1 |
+----+------------+------------+---------------------+--------+-----------+

ここで、請求書テーブルのデザインにかなりこだわっています。どうすればいいのかわかりません。請求書は2週間ごとに生成されます。結果の例から、invoice.amountorders.company_id = 2テーブルから計算されているため、55.00になります。

invoice.amountが-50.00(マイナス)の場合、会社が手数料の金額を送金する必要があることを意味します。

invoice.amountが50.00の場合、会社に手数料を送る必要があることを意味します。

Status_idは次のようになります:(1)送信済み請求書、(2)キャンセル済み、(3)完了済み

ordersテーブルにinvoice_idフィールドを追加する必要がありますか?行が「invoice」テーブルに挿入されたら、orders.invoice_idフィールドを更新します。

invoice_payment table

mysql> select * from invoice_payment;
+----+------------+-----------------+-------------+---------------------+---------------------+
| id | invoice_id | amount_received | amount_sent | date_received       | date_sent           |
+----+------------+-----------------+-------------+---------------------+---------------------+
|  1 |          1 |            0.00 |       55.00 | 0000-00-00 00:00:00 | 2012-02-18 22:20:53 |
+----+------------+-----------------+-------------+---------------------+---------------------+

ここでトランザクションを追跡および更新できます。支払いはBACS経由で行われます。

これは良いテーブルデザインですか、それとも改善する必要があるのですか?追加する必要があるフィールドとテーブルは?

請求書が生成され、後でorders_productsまたはordersテーブルに変更を加える必要がある場合、invoice.amountフィールドを再計算する必要がありますか? (PHP/MySQLを使用します)。

SQLダンプ

CREATE TABLE IF NOT EXISTS `company` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `name` varchar(25) NOT NULL,
  PRIMARY KEY (`id`)
) ENGINE=MyISAM  DEFAULT CHARSET=latin1 AUTO_INCREMENT=3 ;

INSERT INTO `company` (`id`, `name`) VALUES
(1, 'Company A'),
(2, 'Company B');

CREATE TABLE IF NOT EXISTS `invoice` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `company_id` int(11) NOT NULL,
  `invoice_no` int(11) NOT NULL,
  `invoice_date` datetime NOT NULL,
  `amount` decimal(6,2) NOT NULL,
  `status_id` tinyint(1) NOT NULL,
  PRIMARY KEY (`id`)
) ENGINE=MyISAM  DEFAULT CHARSET=latin1 AUTO_INCREMENT=8 ;


INSERT INTO `invoice` (`id`, `company_id`, `invoice_no`, `invoice_date`, `amount`, `status_id`) VALUES
(7, 2, 123, '2012-02-16 23:59:59', '55.00', 1);


CREATE TABLE IF NOT EXISTS `invoice_payment` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `invoice_id` int(11) NOT NULL,
  `amount_received` decimal(6,2) NOT NULL,
  `amount_sent` decimal(6,2) NOT NULL,
  `date_received` datetime NOT NULL,
  `date_sent` datetime NOT NULL,
  PRIMARY KEY (`id`)
) ENGINE=MyISAM  DEFAULT CHARSET=latin1 AUTO_INCREMENT=2 ;

INSERT INTO `invoice_payment` (`id`, `invoice_id`, `amount_received`, `amount_sent`, `date_received`, `date_sent`) VALUES
(1, 1, '0.00', '55.00', '0000-00-00 00:00:00', '2012-02-18 22:20:53');


CREATE TABLE IF NOT EXISTS `orders` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `user_id` int(11) NOT NULL,
  `company_id` int(11) NOT NULL,
  `total_cost` decimal(6,2) NOT NULL,
  `order_date` datetime NOT NULL,
  `status_id` int(11) NOT NULL,
  PRIMARY KEY (`id`)
) ENGINE=MyISAM  DEFAULT CHARSET=latin1 AUTO_INCREMENT=3 ;


INSERT INTO `orders` (`id`, `user_id`, `company_id`, `total_cost`, `order_date`, `status_id`) VALUES
(1, 5, 2, '25.00', '2012-02-03 23:30:24', 1),
(2, 7, 2, '30.00', '2012-02-13 18:06:12', 1);


CREATE TABLE IF NOT EXISTS `orders_products` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `order_id` int(11) NOT NULL,
  `product_id` int(11) NOT NULL,
  `product_name` varchar(100) NOT NULL,
  `cost` decimal(6,2) NOT NULL,
  PRIMARY KEY (`id`)
) ENGINE=MyISAM  DEFAULT CHARSET=latin1 AUTO_INCREMENT=7 ;

INSERT INTO `orders_products` (`id`, `order_id`, `product_id`, `product_name`, `cost`) VALUES
(1, 1, 34, 'Chair', '10.00'),
(2, 1, 25, 'TV', '10.00'),
(3, 1, 27, 'Desk', '2.50'),
(4, 1, 36, 'Laptop', '2.50'),
(5, 2, 75, 'PHP Book', '25.00'),
(6, 2, 74, 'MySQL Book', '5.00');

ここで回答するためにテーブルを更新/追加することをご自由にどうぞ.

ありがとう

11
I'll-Be-Back

現金照合

これは現金照合問題です。これは、次の2つのレベルのいずれかで追跡できます。

  • 請求額と現金の数値を比較します(多少ずさんですが、実際には、多くのロイドシンジケートが対内取引でこれを行う方法であり、しばしば「書面対署名済み」レポートと呼ばれます)。

  • 請求書で分類された現金支払いからの明示的な現金配分を維持します。

あなたの質問から私はあなたが後者をしたいと思います。

通常、これは、個別の一連の現金トランザクションと、請求書への現金支払いの割り当てを持つブリッジテーブルを使用して行われます。値が等しい場合、または現金支払いに単一の請求書参照が付属している場合は、割り当てを自動的に行うことができます。請求書と支払いの間にM:Mの関係がある場合は、手動の照合プロセスを実行する必要があります(これを自動的に行うことは、実際には ナップサック問題 の変形です)。

基本的な現金照合システム

請求書テーブル、現金支払いテーブル、および割り当てテーブルがあるとします。請求書を発行するときは、請求書テーブルに請求書レコードを設定し、割り当てテーブルに「売掛金」または「支払い可能」レコードを設定します。

  • 請求書#1、100ドル

  • 割り当て:請求書#1、「売掛金」の取引タイプ、および$ 100の未払いへの参照を含むレコード。このレコードの現金支払いへの参照はありません。

今、あなたは100ドルの現金支払いを得ます

  • 現金支払い(chq#12345):$ 100

  • 割り当て:請求書#1とchq#12345への参照、「現金」トランザクションタイプ、および-100の支払い($ 100支払)のレコード。

これをM:M関係に一般化して、単一の請求書に対して複数の支払いを取得するか、複数の請求書をカバーする支払いを取得できます。この構造により、与信管理レポートの作成も非常に簡単になります。レポートでは、未処理の残高がある(たとえば)180日より古い請求書を見つける必要があります。

これは、スキーマに加えて、いくつかのシナリオと古い債務クエリの例です。残念ながら、手元に実行中のmysqlインスタンスがないので、これはSQL Server用です。

-- ==============================================================
-- === CashMatch.sql ============================================
-- ==============================================================
--


-- === Invoices =================================================
--
create table Invoice (
       InvoiceID        int identity (1,1) not null
      ,InvoiceRef       varchar (20)
      ,Amount           money
      ,InvoiceDate      datetime
)
go

alter table Invoice
  add constraint PK_Invoice 
      primary key nonclustered (InvoiceID)
go


-- === Cash Payments ============================================
--
create table CashPayment (
       CashPaymentID    int identity (1,1) not null
      ,CashPaymentRef   varchar (20)
      ,Amount           money
      ,PaidDate         datetime
)
go

alter table CashPayment
  add constraint PK_CashPayment
      primary key nonclustered (CashPaymentID)
go




-- === Allocations ==============================================
--
create table Allocation (
       AllocationID       int identity (1,1) not null
      ,CashPaymentID      int  -- Note that some records are not
      ,InvoiceID          int  -- on one side.
      ,AllocatedAmount    money
      ,AllocationType     varchar (20)
      ,TransactionDate    datetime
)
go

alter table Allocation
  add constraint PK_Allocation
      primary key nonclustered (AllocationID)
go


-- ==============================================================
-- === Scenarios ================================================
-- ==============================================================
--
declare @Invoice1ID int
       ,@Invoice2ID int
       ,@PaymentID int


-- === Raise a new invoice ======================================
--
insert Invoice (InvoiceRef, Amount, InvoiceDate)
values ('001', 100, '2012-01-01')

set @Invoice1ID = @@identity

insert Allocation (
       InvoiceID
      ,AllocatedAmount
      ,TransactionDate
      ,AllocationType
) values (@Invoice1ID, 100, '2012-01-01', 'receivable')


-- === Receive a payment ========================================
--
insert CashPayment (CashPaymentRef, Amount, PaidDate)
values ('12345', 100, getdate())

set @PaymentID = @@identity

insert Allocation (
       InvoiceID
      ,CashPaymentID
      ,AllocatedAmount
      ,TransactionDate
      ,AllocationType
) values (@Invoice1ID, @PaymentID, -100, getdate(), 'paid')



-- === Raise two invoices =======================================
--
insert Invoice (InvoiceRef, Amount, InvoiceDate)
values ('002', 75, '2012-01-01')

set @Invoice1ID = @@identity

insert Allocation (
       InvoiceID
      ,AllocatedAmount
      ,TransactionDate
      ,AllocationType
) values (@Invoice1ID, 75, '2012-01-01', 'receivable')


insert Invoice (InvoiceRef, Amount, InvoiceDate)
values ('003', 75, '2012-01-01')

set @Invoice2ID = @@identity

insert Allocation (
       InvoiceID
      ,AllocatedAmount
      ,TransactionDate
      ,AllocationType
) values (@Invoice2ID, 75, '2012-01-01', 'receivable')


-- === Receive a payment ========================================
-- The payment covers one invoice in full and part of the other.
--
insert CashPayment (CashPaymentRef, Amount, PaidDate)
values ('23456', 120, getdate()) 

set @PaymentID = @@identity

insert Allocation (
       InvoiceID
      ,CashPaymentID
      ,AllocatedAmount
      ,TransactionDate
      ,AllocationType
) values (@Invoice1ID, @PaymentID, -75, getdate(), 'paid')

insert Allocation (
       InvoiceID
      ,CashPaymentID
      ,AllocatedAmount
      ,TransactionDate
      ,AllocationType
) values (@Invoice2ID, @PaymentID, -45, getdate(), 'paid')



-- === Aged debt report ========================================
--
select i.InvoiceRef
      ,sum (a.AllocatedAmount)                 as Owing
      ,datediff (dd, i.InvoiceDate, getdate()) as Age
  from Invoice i
  join Allocation a
    on a.InvoiceID = i.InvoiceID
 group by i.InvoiceRef
         ,datediff (dd, i.InvoiceDate, getdate())
having sum (a.AllocatedAmount) > 0