生成发票和跟踪

I'l*_*ack 12 mysql normalization database-design transaction

每两周,系统将为公司生成发票。

公司将在每月 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 |
+----+-----------+
Run Code Online (Sandbox Code Playgroud)

Customers can select a company from my website.

orders table

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 |
+----+---------+------------+------------+---------------------+-----------+
Run Code Online (Sandbox Code Playgroud)

two customers have ordered the products from Company B (orders.company_id = 2). I know the orders fields is not enough, just simplified for you.

orders_products table

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 |
+----+----------+------------+--------------+-------+
Run Code Online (Sandbox Code Playgroud)

List of products what customers have ordered.

invoice table

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 |
+----+------------+------------+---------------------+--------+-----------+
Run Code Online (Sandbox Code Playgroud)

This is where I am quite stuck on invoice tables design. I am not sure how it should be done. Invoices will be generated every 2 weeks. From the result example invoice.amount is 55.00 because it has been calculated from orders.company_id = 2 table

If the invoice.amount is -50.00 (minus), it mean company will need to send me the fees amount.

If the invoice.amount is 50.00, it mean I need to send the company the fees.

The status_id could be: (1)Invoice Sent, (2)Cancelled, (3)Completed

Do I need to add invoice_id field in the orders table? Update the orders.invoice_id field when row has been inserted into 'invoice' table.

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 |
+----+------------+-----------------+-------------+---------------------+---------------------+
Run Code Online (Sandbox Code Playgroud)

This is where I can track and update transaction.. the payment will be made via BACS.

Is this good tables design or what do I need to improve? What fields and tables I should add?

If the invoice has been generated and later I need to make the changes in orders_products or orders tables - should it recalculate the invoice.amount field? (I will be using PHP / MySQL).

SQL Dump:

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');
Run Code Online (Sandbox Code Playgroud)

Feel free you want to updates/add tables to Answer here.

Thanks

Con*_*lls 9

现金匹配

这是一个现金匹配问题。您可以在以下两个级别之一进行跟踪:

  • 将发票与现金数字进行比较(有点草率,但这实际上是大多数劳合社辛迪加对内向业务的处理方式,通常称为“书面与签名”报告)。

  • 从按发票细分的现金支付中维护明确的现金分配。

从你的问题来看,我认为你想做后者。

通常,这是通过拥有一组单独的现金交易和一个将现金支付分配到发票的桥接表来完成的。如果值相等或现金付款带有单个发票参考,您可以自动进行分配。如果发票和付款之间存在 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
Run Code Online (Sandbox Code Playgroud)