Receivables Aging Buckets
MediumData Processing · Stripe
Apply customer payments to open invoices, then report how much is still owed in each aging bucket.
invoices: rows[customer_id, invoice_id, due_day, amount]. Invoice ids are unique; each invoice starts withamountoutstanding.payments: rows[customer_id, invoice_id, amount], applied in order.invoice_idmay beNone.
Applying one payment:
- If
invoice_idis notNone: if it is unknown or belongs to a different customer, ignore the whole payment. Otherwise pay down that invoice by up to its outstanding amount. - Any money left over (or the whole payment when
invoice_idisNone) goes to the same customer's other invoices that still have a balance, oldest first: bydue_day, then byinvoice_id. - Money left after every invoice of the customer is paid off is discarded.
Then age each invoice that still has a balance, using age = as_of_day - due_day:
current:age <= 0overdue_1_30:1 <= age <= 30overdue_31_60:31 <= age <= 60overdue_61_plus:age >= 61
Return [["current", x], ["overdue_1_30", y], ["overdue_31_60", z], ["overdue_61_plus", w]] with the total outstanding in each bucket, always in this order.
Example 1
- Input:
- invoices = [["acme", "in_1", 10, 500], ["acme", "in_2", 50, 300], ["acme", "in_3", 90, 400], ["bolt", "in_4", 70, 200]], payments = [["acme", "in_3", 600], ["bolt", None, 50]], as_of_day = 100
- Output:
- [["current", 0], ["overdue_1_30", 150], ["overdue_31_60", 300], ["overdue_61_plus", 300]]
- Explanation
- acme's 600 pays off in_3 (400) and the extra 200 goes to acme's oldest open invoice, in_1, leaving 300 there. bolt's unapplied 50 reduces in_4 to 150. At day 100, in_1 is 90 days overdue, in_2 is 50 days, and in_4 is 30 days.
Example 2
- Input:
- invoices = [["c1", "a", 5, 100], ["c2", "b", 5, 100]], payments = [["c1", "b", 100], ["c2", "zz", 40]], as_of_day = 20
- Output:
- [["current", 0], ["overdue_1_30", 200], ["overdue_31_60", 0], ["overdue_61_plus", 0]]
- Explanation
- c1 cannot pay c2's invoice
b, andzzdoes not exist, so both payments are ignored. Both invoices are 15 days overdue.
Example 3
- Input:
- invoices = [["c", "i1", 30, 100], ["c", "i2", 30, 100]], payments = [["c", None, 250]], as_of_day = 30
- Output:
- [["current", 0], ["overdue_1_30", 0], ["overdue_31_60", 0], ["overdue_61_plus", 0]]
- Explanation
- The oldest-first order breaks the due-day tie by id, so i1 then i2 are paid off and the extra 50 is discarded. Nothing is left outstanding.
Constraints
1 <= invoices.length <= 10^40 <= payments.length <= 10^40 <= amount <= 10^9for invoices and payments0 <= due_day, as_of_day <= 10^6