Skip to content

Commit 0c4dc23

Browse files
committed
inclusive
1 parent e50b9e3 commit 0c4dc23

3 files changed

Lines changed: 62 additions & 44 deletions

File tree

modules/meteroid/crates/meteroid-store/src/services/lifecycle/period_transitions.rs

Lines changed: 16 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -174,12 +174,17 @@ impl Services {
174174

175175
fn activate_subscription(&self, subscription: &SubscriptionRow) -> StoreResult<NextCycle> {
176176
if subscription.trial_duration.is_some() {
177-
let new_period_start = subscription
177+
let current_period_end = subscription
178178
.current_period_end
179179
.unwrap_or_else(|| Utc::now().naive_utc().date());
180+
181+
let new_period_start = current_period_end.succ_opt().unwrap_or(current_period_end);
182+
183+
let trial_duration_days = subscription.trial_duration.unwrap() as u64;
180184
let new_period_end = new_period_start
181-
.checked_add_days(Days::new(subscription.trial_duration.unwrap() as u64))
182-
.unwrap_or_else(|| new_period_start + Duration::days(7));
185+
.checked_add_days(Days::new(trial_duration_days - 1)) // subtract 1 because we use inclusive dates
186+
.unwrap_or_else(|| new_period_start + Duration::days(6));
187+
183188
Ok(NextCycle {
184189
status: SubscriptionStatusEnum::TrialActive,
185190
next_cycle_action: Some(CycleActionEnum::EndTrial),
@@ -242,10 +247,13 @@ impl Services {
242247

243248
fn renew_subscription(&self, subscription: &SubscriptionRow) -> StoreResult<NextCycle> {
244249
// Calculate new period
245-
let new_period_start = subscription
250+
// current_period_end is inclusive
251+
let current_period_end = subscription
246252
.current_period_end // cannot be null in this context
247253
.unwrap_or_else(|| Utc::now().naive_utc().date());
248254

255+
let new_period_start = current_period_end.succ_opt().unwrap_or(current_period_end);
256+
249257
let period = calculate_advance_period_range(
250258
new_period_start,
251259
subscription.billing_day_anchor as u32,
@@ -263,10 +271,13 @@ impl Services {
263271
}
264272

265273
fn end_subscription(&self, subscription: &SubscriptionRow) -> StoreResult<NextCycle> {
266-
let new_period_start = subscription
274+
// current_period_end is inclusive
275+
let current_period_end = subscription
267276
.current_period_end // cannot be null in this context
268277
.unwrap_or_else(|| Utc::now().naive_utc().date());
269278

279+
let new_period_start = current_period_end.succ_opt().unwrap_or(current_period_end);
280+
270281
Ok(NextCycle {
271282
status: SubscriptionStatusEnum::Completed,
272283
next_cycle_action: None,

modules/meteroid/crates/meteroid-store/src/utils/periods.rs

Lines changed: 43 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -74,21 +74,23 @@ pub fn calculate_component_period_for_invoice_date(
7474
}
7575

7676
fn calculate_proration_factor(period: &Period) -> Option<f64> {
77-
let days_in_period = period.end.signed_duration_since(period.start).num_days() as u64; // +1 ?
77+
// +1 because inclusive
78+
let days_in_period = period.end.signed_duration_since(period.start).num_days() as u64 + 1;
7879
let days_in_month_from = period.start.days_in_month() as u64;
7980
let days_in_month_to = period.end.days_in_month() as u64;
8081

81-
// if from is end of month and from.day <= to.day. Ex: 2023-02-28 -> 2023-03-28+
82-
if period.start.day() == days_in_month_from as u32 && period.end.day() >= period.start.day() {
82+
// if from is end of month and from.day <= to.day. Ex: 2023-02-28 -> 2023-03-27+
83+
if period.start.day() == days_in_month_from as u32 && period.end.day() >= period.start.day() - 1
84+
{
8385
return None;
8486
}
8587

8688
if days_in_period >= days_in_month_from {
8789
return None;
8890
}
8991

90-
// if to is end of month and from.day >= to.day. Ex: 2023-01-28+ -> 2023-02-28
91-
if period.end.day() == days_in_month_to as u32 && period.start.day() >= period.end.day() {
92+
// if to is end of month and from.day >= to.day. Ex: 2023-01-28+ -> 2023-02-27
93+
if period.end.day() == days_in_month_to as u32 && period.start.day() > period.end.day() {
9294
return None;
9395
}
9496

@@ -122,17 +124,19 @@ pub fn calculate_advance_period_range(
122124
let period_end = {
123125
let should_add_full_period = started_after_billing_day || !is_partial;
124126

125-
if should_add_full_period {
127+
let next_period_start = if should_add_full_period {
126128
add_months_at_billing_day(period_start, months_per_period, billing_day)
127129
.expect("Failed to calculate period end date")
128130
} else {
129131
// For the first period when billing started before the billing day,
130-
// end on the billing day of the current month
132+
// end on the day before the billing day of the current month
131133
let target_day = period_start.days_in_month().min(billing_day);
132134
period_start
133135
.with_day(target_day)
134136
.expect("Failed to set period end day")
135-
}
137+
};
138+
// subtract one day to make the end date inclusive
139+
next_period_start.pred_opt().unwrap_or(next_period_start)
136140
};
137141

138142
Period {
@@ -150,9 +154,10 @@ pub fn calculate_arrear_period_range(
150154
let months_per_period = billing_period.as_months();
151155

152156
// For arrear billing, we're billing for a period that has already ended
153-
let period_end = invoice_date;
157+
// The period ends the day before the invoice date (inclusive)
158+
let period_end = invoice_date.pred_opt().unwrap_or(invoice_date);
154159

155-
let period_start = subtract_months_at_billing_day(period_end, months_per_period, billing_day)
160+
let period_start = subtract_months_at_billing_day(invoice_date, months_per_period, billing_day)
156161
.expect("Failed to calculate arrear period start")
157162
.max(billing_start_or_resume_date);
158163

@@ -200,143 +205,143 @@ mod test {
200205
1,
201206
true, // is_first_period
202207
"2021-01-01",
203-
"2021-02-01"
208+
"2021-01-31"
204209
)]
205210
#[case(
206211
BillingPeriodEnum::Monthly,
207212
"2021-02-01", // invoice_date (start of second period)
208213
1,
209214
false, // not first period
210215
"2021-02-01",
211-
"2021-03-01"
216+
"2021-02-28"
212217
)]
213218
#[case(
214219
BillingPeriodEnum::Monthly,
215220
"2021-03-01", // invoice_date (start of third period)
216221
1,
217222
false,
218223
"2021-03-01",
219-
"2021-04-01"
224+
"2021-03-31"
220225
)]
221226
#[case(
222227
BillingPeriodEnum::Monthly,
223228
"2021-01-10", // invoice_date
224229
1,
225230
true, // first period
226231
"2021-01-10",
227-
"2021-02-01"
232+
"2021-01-31"
228233
)]
229234
#[case(
230235
BillingPeriodEnum::Monthly,
231236
"2021-02-01", // invoice_date
232237
1,
233238
false,
234239
"2021-02-01",
235-
"2021-03-01"
240+
"2021-02-28"
236241
)]
237242
#[case(
238243
BillingPeriodEnum::Monthly,
239244
"2021-03-01", // invoice_date
240245
1,
241246
false,
242247
"2021-03-01",
243-
"2021-04-01"
248+
"2021-03-31"
244249
)]
245250
#[case(
246251
BillingPeriodEnum::Monthly,
247252
"2021-01-01", // invoice_date
248253
10,
249254
true, // first period
250255
"2021-01-01",
251-
"2021-01-10"
256+
"2021-01-09"
252257
)]
253258
#[case(
254259
BillingPeriodEnum::Monthly,
255260
"2021-01-10", // invoice_date
256261
10,
257262
false,
258263
"2021-01-10",
259-
"2021-02-10"
264+
"2021-02-09"
260265
)]
261266
#[case(
262267
BillingPeriodEnum::Monthly,
263268
"2021-02-10", // invoice_date
264269
10,
265270
false,
266271
"2021-02-10",
267-
"2021-03-10"
272+
"2021-03-09"
268273
)]
269274
#[case(
270275
BillingPeriodEnum::Quarterly,
271276
"2021-01-10", // invoice_date
272277
1,
273278
true,
274279
"2021-01-10",
275-
"2021-04-01"
280+
"2021-03-31"
276281
)]
277282
#[case(
278283
BillingPeriodEnum::Quarterly,
279284
"2021-04-01", // invoice_date
280285
1,
281286
false,
282287
"2021-04-01",
283-
"2021-07-01"
288+
"2021-06-30"
284289
)]
285290
#[case(
286291
BillingPeriodEnum::Quarterly,
287292
"2021-07-01", // invoice_date
288293
1,
289294
false,
290295
"2021-07-01",
291-
"2021-10-01"
296+
"2021-09-30"
292297
)]
293298
#[case(
294299
BillingPeriodEnum::Quarterly,
295300
"2021-01-01", // invoice_date
296301
10,
297302
true,
298303
"2021-01-01",
299-
"2021-01-10"
304+
"2021-01-09"
300305
)]
301306
#[case(
302307
BillingPeriodEnum::Quarterly,
303308
"2021-01-10", // invoice_date
304309
10,
305310
false,
306311
"2021-01-10",
307-
"2021-04-10"
312+
"2021-04-09"
308313
)]
309314
#[case(
310315
BillingPeriodEnum::Quarterly,
311316
"2021-04-10", // invoice_date
312317
10,
313318
false,
314319
"2021-04-10",
315-
"2021-07-10"
320+
"2021-07-09"
316321
)]
317322
#[case(
318323
BillingPeriodEnum::Annual,
319324
"2021-01-10", // invoice_date
320325
1,
321326
true,
322327
"2021-01-10",
323-
"2022-01-01"
328+
"2021-12-31"
324329
)]
325330
#[case(
326331
BillingPeriodEnum::Annual,
327332
"2022-01-01", // invoice_date
328333
1,
329334
false,
330335
"2022-01-01",
331-
"2023-01-01"
336+
"2022-12-31"
332337
)]
333338
#[case(
334339
BillingPeriodEnum::Annual,
335340
"2023-01-01", // invoice_date
336341
1,
337342
false,
338343
"2023-01-01",
339-
"2024-01-01"
344+
"2023-12-31"
340345
)]
341346
fn test_calculate_advance_period_range(
342347
#[case] billing_period: BillingPeriodEnum,
@@ -364,79 +369,79 @@ mod test {
364369
"2021-01-01", // billing_start_date
365370
1,
366371
"2021-01-01", // max(calculated_start, billing_start) = max(2021-01-01, 2021-01-01)
367-
"2021-02-01"
372+
"2021-01-31"
368373
)]
369374
#[case(
370375
BillingPeriodEnum::Monthly,
371376
"2021-03-01", // invoice_date
372377
"2021-01-01", // billing_start_date
373378
1,
374379
"2021-02-01", // calculated back 1 month from 2021-03-01
375-
"2021-03-01"
380+
"2021-02-28"
376381
)]
377382
#[case(
378383
BillingPeriodEnum::Monthly,
379384
"2021-04-01", // invoice_date
380385
"2021-01-01", // billing_start_date
381386
1,
382387
"2021-03-01", // calculated back 1 month from 2021-04-01
383-
"2021-04-01"
388+
"2021-03-31"
384389
)]
385390
#[case(
386391
BillingPeriodEnum::Monthly,
387392
"2021-02-01", // invoice_date
388393
"2021-01-10", // billing_start_date (service started mid-month)
389394
1,
390395
"2021-01-10", // max(2021-01-01, 2021-01-10) = 2021-01-10
391-
"2021-02-01"
396+
"2021-01-31"
392397
)]
393398
#[case(
394399
BillingPeriodEnum::Monthly,
395400
"2021-01-10", // invoice_date
396401
"2021-01-01", // billing_start_date
397402
10,
398403
"2021-01-01", // max(2020-12-10, 2021-01-01) = 2021-01-01
399-
"2021-01-10"
404+
"2021-01-09"
400405
)]
401406
#[case(
402407
BillingPeriodEnum::Monthly,
403408
"2021-02-10", // invoice_date
404409
"2021-01-01", // billing_start_date
405410
10,
406411
"2021-01-10", // calculated back 1 month from 2021-02-10
407-
"2021-02-10"
412+
"2021-02-09"
408413
)]
409414
#[case(
410415
BillingPeriodEnum::Quarterly,
411416
"2021-04-01", // invoice_date
412417
"2021-01-10", // billing_start_date
413418
1,
414419
"2021-01-10", // max(2021-01-01, 2021-01-10) = 2021-01-10
415-
"2021-04-01"
420+
"2021-03-31"
416421
)]
417422
#[case(
418423
BillingPeriodEnum::Quarterly,
419424
"2021-07-01", // invoice_date
420425
"2021-01-10", // billing_start_date
421426
1,
422427
"2021-04-01", // calculated back 3 months from 2021-07-01
423-
"2021-07-01"
428+
"2021-06-30"
424429
)]
425430
#[case(
426431
BillingPeriodEnum::Annual,
427432
"2022-01-01", // invoice_date
428433
"2021-01-10", // billing_start_date
429434
1,
430435
"2021-01-10", // max(2021-01-01, 2021-01-10) = 2021-01-10
431-
"2022-01-01"
436+
"2021-12-31"
432437
)]
433438
#[case(
434439
BillingPeriodEnum::Annual,
435440
"2023-01-01", // invoice_date
436441
"2021-01-10", // billing_start_date
437442
1,
438443
"2022-01-01", // calculated back 12 months from 2023-01-01
439-
"2023-01-01"
444+
"2022-12-31"
440445
)]
441446
fn test_calculate_arrear_period_range(
442447
#[case] billing_period: BillingPeriodEnum,

modules/meteroid/src/clients/usage.rs

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -135,14 +135,16 @@ impl UsageClient for MeteringUsageClient {
135135
None => vec![],
136136
};
137137

138+
let exclusive_end = period.end.succ_opt().unwrap_or(period.end);
139+
138140
let request = QueryMeterRequest {
139141
tenant_id: tenant_id.as_proto(),
140142
meter_slug: metric.id.to_string(),
141143
code: metric.code.clone(),
142144
meter_aggregation_type: aggregation_type,
143145
customer_ids: vec![customer_id.to_string()],
144146
from: Some(date_to_timestamp(period.start)),
145-
to: Some(date_to_timestamp(period.end)), // exclusive TODO check
147+
to: Some(date_to_timestamp(exclusive_end)),
146148
// not used here, defaults to customer_id
147149
group_by_properties: vec![],
148150
// the segmentation dimensions TODO

0 commit comments

Comments
 (0)