Description
Background
In the current logic we tracking amount
inside payments. The same time we can process several payments for the same Work Period using different memberRate
. Because of this we cannot understand if the payment is fully done for the week, underpaid or overpaid. To make sure we can correctly track the payment status we have to track days
which has been paid in each payment. Also, we have to move memberRate
and customerRate
from Work Periods to payments for tracking purposes.
Work Periods
Work Period Model
- Remove fields
memberRate
andcustomerRate
from the model and endpoints. Because 1 Work Period could have multiple payments with different rates, so we would move these fields to the WorkPeriodPayment model - Add the next fields to the Work Periods model, but don't add them to the endpoints, they would be calculated automatically:
- add field
daysPaid
- integer, required, 0 by default - this value would hold how many days we already paid for the Work Period out ofdaysWorked
- add field
paymentTotal
- float, required, 0 by default
- add field
Work Period Endpoints (this section updated on June 11)
- Remove endpoints to create WP and delete WP, but keep services. All WPs would be created and deleted automatically only. Update Swagger and Postman accordingly.
- Endpoints to update WPs should ONLY allow us to update
daysWorked
and nothing else. All other fields would be calculated by automation logic.
Work Period Payments
Work Period Payment Model
Add fields:
memberRate
- optional floatcustomerRate
- optional floatdaysPaid
- required, integer, 0 by default
Work Period Payment Endpoints
Create (schedule) payment
POST /work-period-payments
should accept payment body with the next fields only:
workPeriodId
(required) - associate payment with WPdays
(optional) - if provided we should validate that this value is integer more 1 or more, and not more than(WP.daysWorked - WP.daysPaid)
or return error. If not provided then calculate automatically as(WP.daysWorked - WP.daysPaid)
, if the value is less than 1, then return an error, because there are no days to pay.
The next values should NOT be allowed in body during payment creation and should be calculated automatically:
memberRate
- should be populated fromRB.memberRate
, if it'snull
or0
then return Error.customerRate
- should be populated fromRB.customerRate
(if not provided, thennull
)billingAccountId
- should be populated fromRB.billingAccountId
, if it's not there then return Error.amount=WPP.memberRate * WPP.days / 5
status=scheduled
statusDetails
- would be only updated via SchedulerchallengeId
- would be only updated via Scheduler
NOTE:
- This logic should be followed when we crate single payment using endpoint
POST /work-period-payments
or multiple payments using the same endpoint (it supports arrays) - This logic should be also followed by the
POST /work-period-payments/query
endpoint. The only difference, we cannot passdays
for payments, so in this casedays
should be always calculated automatically as shown above.
Update payment
PUT/PATCH /work-period-payments/:wppId
should be updated to only allow changing status
and nothing else
status
- can be changed to
cancelled
if the current status is NOTin-progress
- any other can cancel - can be changed to
scheduled
if the current status isfailed
- so the scheduler would try to process payment one more time - cannot change status in any other case
- can be changed to
We should NOT be able to change other fields.
Automation logic
Each Work Period might have several payments, though we would like to be able to do sorting and filtering WPs by payment information.
When we create or update any payment inside WP or update dependant fields of the WP, we have to re-calculate fields automatically (inside event handlers). We should only update WP if any calculated value is really changed.
General Idea
If payment is in-progress
, shceduled
or completed
- we count such payments as if we have them. If payment is cancelled
or failed
we don't count such payments.
Calcualtion Fields
daysPaid
- how many ofdaysWorked
are already paid, scheduled or in progress- it should get all
scheduled
,in-progress
andcompleted
payments of the WP and sumdays
of the payments, sodaysToPay = sum(WP.payments, 'days')
- note that if we cancel some payment, or some payment is failed, this value has to be re-calculated, as we don't count cancelled/failed payments as paid
- the same thing if we
schedule
previousfailed
payment this value should be re-calculated again as we countscheduled
payments as paid
- it should get all
paymentStatus
- what is the current payments status
Should be set as per next logic (the first status which matches the current situation is used):no-days
ifdaysWorked === 0
in-progress
if any of the payments are inscheduled
orin-progress
statuscompleted
ifdaysWorked - daysPaid === 0
and there are noscheduled
orin-progress
paymentspartially-completed
(if we need to pay, but we have some payments completed and nothing is in progress) - i. e. ifdaysWorked - daysPaid > 0
and there are some payments in statuscompleted
and there are noscheduled
orin-progress
paymentspending
(if we need to pay, but there are no payments processed at all) - I. e. ifdaysWorked - daysPaid > 0
and there are no ,completed
payments and noscheduled
orin-progress
payments
paymentAmount
- the total amount of payments that are NOTcancelled
orfailed
This logic would be triggered by multiple events like Payment creation, Payment update, and WP update. So make sure that all these updates happen if any of the dependant values changed.
Migration
As we don't use Work Periods and Payments on production yet we can keep it simple and don't think how to migrate existent data.
Create a migration script in the migrations
folder which would do the next thing:
- remove all existent Work Periods and Work Period Payments
- regenerate Work Periods for all existent Resource Bookings using new logic, similar way as it was done before via https://github.com/topcoder-platform/taas-apis/blob/9ac2bdc6793d17d293cf3149ebd08bf6a6ae8fa0/migrations/2021-04-22-3-populate-work-periods-for-resource-bookings.js
- NOTE, that it has to be a new migration script (you can copy existent one) because the previous one was already run and we cannot run it again with the same name
General Requirements
- Follow Code Guideline in the README.
Verification Steps
- Create a RB with
memberRate=1000
- Get any WP with
daysWorked=5
,WP.daysPaid=0
,WP.paymentStatus="pending"
- Update
WP.daysWorked=3
Result: updated:WP.daysWorked=3
, same as before:WP.paymentStatus="pending"
,WP.daysPaid=0
,WP.paymentTotal=0
- Schedule payment 1
[POST { workPeriodId: WP.id }]
Result:- Payment scheduled:
days = WP.daysWorked - WP.daysPaid
(3),amount = WPP.memberRate * WPP.days / 5
($600),memberRate=RB.memberRate
($1000) - WP automatically updated:
WP.paymentStatus="in-progress"
,WP.daysPaid=3
,WP.paymentTotal=600
, same as before:WP.daysWorked=3
- Payment scheduled:
- Try to schedule one more payment immediately after that again.
Result: Cannot process one more payment because all 3 days Worked has been already scheduled to be paid by the previous payment. - After some time scheduler processed the payment.
Result:- Payment completed:
WPP.status="completed"
- WP automatically updated:
WP.paymentStatus="completed"
, same as before:WP.daysWorked=3
,WP.daysPaid=3
,WP.paymentTotal=600
- Payment completed:
- Update
WP.daysWorked = 2
.
Result: Error - cannot updatedaysWorked
to the value less thandaysPiad
- Try to create one payment
[POST { workPeriodId: WP.id } ]
.
Result: Cannot process one more payment because all 3 days Worked has been already paid. - Update
WP.daysWorked = 4
Result:WP.paymentStatus="partially-completed"
,WP.daysWorked=4
, same as before:WP.daysPaid=3
,WP.paymentTotal=600
- Update
RB.memberRate=2000
- Process payment 2
[POST { workPeriodId: WP.id } ]
.
Result:- 2nd payment created:
days = WP.daysWorked - WP.daysPaid
(1),amount = RB.memberRate / 5 * days
($400),memberRate=RB.memberRate
($2000) - WP automatically updated:
WP.paymentStatus="in-progress"
,WP.daysPaid=4
,WP.paymentTotal=1000
, same as before:WP.daysWorked=4
- 2nd payment created:
- After some time scheduler processed the payment.
Result:- Payment completed:
WPP.status="completed"
- WP automatically updated:
WP.paymentStatus="completed"
, same as before:WP.daysWorked=4
,WP.daysPaid=4
,WP.paymentTotal=1000
- Payment completed:
- Try to create one payment
[POST { workPeriodId: WP.id } ]
.
Result: Cannot process one more payment because all 4 days Worked has been already paid. - Set
WP.daysWorked = 5
.
Result:WP.paymentStatus="partially-completed"
,WP.daysWorked=5
, same as before:,WP.daysPaid=4
, WP.paymentTotal=1000 - Process payment 3
[POST { workPeriodId: WP.id } ]
.
Result:- 3rd payment created:
days = WP.daysWorked - WP.daysPaid
(1),amount = RB.memberRate / 5 * days
($400),memberRate=RB.memberRate
($2000) - WP automatically updated:
WP.paymentStatus="in-progress"
,WP.daysPaid=5
,WP.paymentTotal=1400
, same as before:WP.daysWorked=5
- 3rd payment created:
- Imagine that after some time the scheduler tried to process the payment, but it failed.
Result:- Payment failed:
WPP.status="failed"
- WP automatically updated:
WP.paymentStatus="partially-completed"
,WP.daysPaid=4
,WP.paymentTotal=1000
, same as before:WP.daysWorked=5
,
NOTE, that failed payment is not counted indaysPaid
and inpaymentTotal
.
- Payment failed:
- Cancel payment 1 (days = 3).
Result:WP.paymentStatus="partially-completed"
,WP.daysPaid=1
,WP.paymentTotal=400
(because we still have the 2nd payment complete),WP.daysWorked=5
, - Cancel payment 2 (days = 1).
Result:WP.paymentStatus="pending"
,WP.daysPaid=0
,WP.paymentTotal=0
(because all the payments are cancelled or failed and not counted),WP.daysWorked=5