web-dev-qa-db-ja.com

複数の支払いを請求書明細に分配する

請求明細への支払いの割り当てに問題があります。 MsSql 2014を使用しています

データは次のようになります。

請求書明細表(販売):

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

この問題は以下の投稿で解決されていますが、負の請求書値がある場合、または請求書明細を2つの支払いに分割する必要がある場合、解決策は機能しません。
クエリで累積合計を使用して財務累積を出力するにはどうすればよいですか?

これは、上記の記事に基づいてこれまでに行ったことです。

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
Claudiu Radu

結局、私は非常にシンプルで自然な解決策を見つけました-請求書の合計額における各支払いの割合に基づいて支払いを割り当てることです。

                    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;

そして、resuntは次のようになります。

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
0
Claudiu Radu