請求書の生成と追跡


11

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.amount50.00の場合、会社に手数料を送信する必要があることを意味します。

status_idは次のいずれかになります:(1)Invoice Sent、(2)Cancelled、(3)Completed

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

invoice_paymentテーブル

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');

ここで回答するためにテーブルを更新/追加することをご自由にしてください。

ありがとう

回答:


8

現金マッチング

これは現金照合問題です。これは、次の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

鉱山には、請求書と支払い用に別々のテーブルがあります。内部リンケージのある共通テーブルを使用できます。キャッシュマッチングは、会計システムでそのように実装されることがよくあります。
ConcernedOfTunbridgeWells

SQL Serverの例をMySQLに翻訳しました。私は通り抜けました、そして私は今とてもよく理解しました。何を考えAllocationType、私はクライアントのお金を送りたい場合でしょうか?CashPaymentテーブルにも挿入する必要がありますか(BACSで支払うとしましょう)?
私は

1
はい、入金と入金の両方の現金支払い記録が必要です。現金照合取引の実際の取引タイプはあなた次第です。
ConcernedOfTunbridgeWells

1
必要に応じて、請求書のトランザクションを両方向の単一の決済支払いと照合できます。例:$ 100の送信請求書、$ 50(-50)の受信請求書、および$ 50の受信入金のバランスは、両方の請求書と照合されます。
ConcernedOfTunbridgeWells
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.