将多个付款分配到发票行

时间:2018-10-15 04:40:43

标签: sql-server cursor common-table-expression window-functions running-total

我在将付款分配到发票行时遇到问题。

数据如下:

发票行表(销售):

lineId   invoiceId   value
 1          1         100
 2          1         -50
 3          1          40
 4          2         500

付款表(付款):

paymentId   invoiceId   amount
     1          1          50
     2          1          40
     3          2          300

现在,我想知道每个发票行的付款明细。付款应首先分配到最小值(即第2行-50)

输出应如下所示:

  lineId   invoiceId   value   paymentId   valuePaid   valueUnpaid
    2           1        -50        1          -50        0
    3           1        40         1          40         0
    1           1        100        1          60         40
    1           1        100        2          40         0
    4           2        500        3          300        200

问题在下面的帖子中得到了解决,但是如果发票值为负或必须将发票行分为两笔付款,则该解决方案将不起作用。
https://dba.stackexchange.com/questions/58474/how-can-i-use-running-total-aggregates-in-a-query-to-output-financial-accumulati/219925?noredirect=1#comment431486_219925

根据以上文章,这是我到目前为止所做的:

drop table dbo.#sales
drop table dbo.#payments 
            CREATE TABLE dbo.#sales
            (   lineId       int primary key,           -- unique line id
                invoiceId         int not null ,  -- InvoiceId foreign key
                itemValue      money not null  )       -- value of invoice line.


            CREATE TABLE dbo.#payments 
            (   paymentId       int primary key,        -- Unique payment id
                InvoiceId       int not null,           -- InvoiceId foreign key
                PayAmount          money not null
            )

            -- Example invoice, id #1, with 3 lines, total ammount = 90; id #2, with one line, value 500 

            INSERT dbo.#sales VALUES 
                (1, 1, 100),
                (2, 1, -50), 
                (3, 1, 40),
                (4, 2, 500) ;

            -- Two payments paid towards invoice id#1, 50+40 = 90
            -- One payment paid towards invoice id#2, 300


            INSERT dbo.#Payments
            VALUES  (1, 1, 50),
                    (2, 1, 40),

                    (3, 2, 300);

            -- Expected output should be as follows, for reporting purposes.
            /* lineId, invoiceId, value, paymentId, valuePaid, valueUnpaid
            2, 1, -50, 1, -50, 0
            3, 1, 40, 1, 40, 0
            1, 1, 100, 1, 60, 40
            1, 1, 100, 2, 40, 0
            4, 2, 500, 3, 300, 200 */


            WITH inv AS
              ( SELECT lineId, invoiceId, 
                    itemValue, 
                    SumItemValue = SUM(itemValue) OVER 
                    (PARTITION BY InvoiceId 
                     ORDER BY ItemValue Asc
                     ROWS BETWEEN UNBOUNDED PRECEDING
                              AND CURRENT ROW)
                FROM dbo.#Sales 
                )
            ,  pay AS
              ( SELECT 
                  PaymentId, InvoiceId, PayAmount as PayAmt,
                  SumPayAmt = SUM(PayAmount) OVER 
                    (PARTITION BY InvoiceId 
                     ORDER BY PaymentId
                     ROWS BETWEEN UNBOUNDED PRECEDING
                              AND CURRENT ROW)
                FROM dbo.#payments 
              )



                SELECT 
                inv.lineId,
                inv.InvoiceId,
                inv.itemValue,
                pay.PaymentId,
                PaymentAllocated = 
                  CASE WHEN SumPayAmt <= SumItemValue - itemValue
                         OR SumItemValue <= SumPayAmt - PayAmt
                  THEN 0
                  ELSE
                      CASE WHEN SumPayAmt <= SumItemValue THEN SumPayAmt      
                           ELSE SumItemValue END                             
                    - CASE WHEN SumPayAmt-PayAmt <= SumItemValue-itemValue        
                           THEN SumItemValue-itemValue                          
                           ELSE SumPayAmt-PayAmt END
                  END
            FROM inv JOIN pay
              ON inv.InvoiceId = pay.InvoiceId
            ORDER BY 
                inv.InvoiceId,
                pay.PaymentId;

当前输出为:

lineId    InvoiceId    itemValue    PaymentId    PaymentAllocated    
  2           1        -50.00         1              0.00
  3           1        40.00          1              0.00
  1           1        100.00         1              50.00
  2           1        -50.00         2              0.00
  3           1        40.00          2              0.00
  1           1        100.00         2              40.00
  4           2        500.00         3              300.00

任何方向将不胜感激。谢谢。

有关分配规则的更多信息:

  • 将首笔付款分配给最小的销售额(即-50)只是一个 保证所有销售线都付款的惯例。如果我要分配 任意或其他规则,第1行(值100)将获得 第一次付款,我会将所有付款用于此行,其余的 发票将保持未分配状态。
  • 正如我所说,这只是一个约定。如果其他人来了 不同的规则行之有效。实际上,结构是 与生产表相比简化了:付款也有一个 付款日期,类型,…以及正确的分配方式应告诉我们 发票行在每个付款时间支付。
  • 付款受系统逻辑的限制小于 发票行的总和。好吧,可能是付款的情况 更大:总发票为负(即:-100)。在这种情况下 我们可以在付款表中插入-100范围内的金额:0 和“总付款”限制为-100

1 个答案:

答案 0 :(得分:0)

最后,我找到了一个非常简单自然的解决方案-根据每次付款在发票总值中所占的百分比来分配付款。

                    drop table dbo.#sales
        drop table dbo.#payments

        CREATE TABLE dbo.#sales
        (   lineId       int primary key,           -- unique line id
            invoiceId         int not null ,  -- InvoiceId foreign key
            itemValue      money not null  )       -- value of invoice line.


        CREATE TABLE dbo.#payments 
        (   paymentId       int primary key,        -- Unique payment id
            InvoiceId       int not null,           -- InvoiceId foreign key
            PayAmount          money not null
        )

        -- Example invoice, id #1, with 3 lines, total ammount = 90; id #2, with one line, value 500 

        INSERT dbo.#sales VALUES 
            (1, 1, 100),
            (2, 1, -50), 
            (3, 1, 40),
            (4, 2, 500) ;

        -- Two payments paid towards invoice id#1, 50+40 = 90
        -- One payment paid towards invoice id#2, 300

        INSERT dbo.#Payments
        VALUES  (1, 1, 50),
                (2, 1, 40),
                (3, 2, 300);

        SELECT 
            s.lineId,
            s.InvoiceId,
            s.itemValue,
            p.PayAmount,
            p.PaymentId,
            round(p.PayAmount / ts.SumItemValue,3) as PaymentPercent,
            s.ItemValue  * round(p.PayAmount  / ts.SumItemValue,3) as AllocatedPayment
        FROM dbo.#sales s 
        LEFT JOIN dbo.#payments p 
          ON s.InvoiceId = p.InvoiceId
        LEFT JOIN (SELECT invoiceId, sum(itemValue) as SumItemValue FROM dbo.#sales GROUP BY invoiceId) ts 
            ON s.invoiceId = ts.invoiceId
        ORDER BY 
            s.InvoiceId,
            p.PaymentId;

重新启动看起来像这样:

lineId  InvoiceId   itemValue   PayAmount   PaymentId   PaymentPercent  AllocatedPayment
1   1   100.00  50.00   1   0.556   55.60
2   1   -50.00  50.00   1   0.556   -27.80
3   1   40.00   50.00   1   0.556   22.24
3   1   40.00   40.00   2   0.444   17.76
2   1   -50.00  40.00   2   0.444   -22.20
1   1   100.00  40.00   2   0.444   44.40
4   2   500.00  300.00  3   0.60    300.00
相关问题