mirror of
https://github.com/modrinth/code.git
synced 2026-08-27 01:54:47 +00:00
add net-60 in-review payout rows
This commit is contained in:
@@ -40,7 +40,7 @@ pub enum PayoutRunStatus {
|
||||
Pending,
|
||||
/// The ad provider should have issued payouts to us by now, and we will
|
||||
/// soon run the payouts.
|
||||
Review,
|
||||
InReview,
|
||||
/// Payouts run is currently being performed.
|
||||
Running,
|
||||
/// Payouts run is complete and payouts have been distributed to users.
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
use crate::database::PgPool;
|
||||
use crate::env::ENV;
|
||||
use chrono::{Datelike, Duration, TimeZone, Utc};
|
||||
use eyre::{Context, Result, eyre};
|
||||
use rust_decimal::{Decimal, dec};
|
||||
use tracing::warn;
|
||||
|
||||
use crate::database::models::{DBAffiliateCodeId, DBUserId};
|
||||
use crate::util::time::{YearMonth, net_60_payout_available_at};
|
||||
|
||||
pub async fn process_affiliate_payouts(postgres: &PgPool) -> Result<()> {
|
||||
// process:
|
||||
@@ -91,21 +91,10 @@ pub async fn process_affiliate_payouts(postgres: &PgPool) -> Result<()> {
|
||||
continue;
|
||||
};
|
||||
|
||||
// affiliate payouts are Net 60 from the end of the month
|
||||
// this is net 60 relative to the time of the charge's last attempt, not from now
|
||||
let available = {
|
||||
let year = last_attempt.year();
|
||||
let month = last_attempt.month();
|
||||
|
||||
// get the first day of the next month
|
||||
let last_day_of_month = if month == 12 {
|
||||
Utc.with_ymd_and_hms(year + 1, 1, 1, 0, 0, 0).unwrap()
|
||||
} else {
|
||||
Utc.with_ymd_and_hms(year, month + 1, 1, 0, 0, 0).unwrap()
|
||||
};
|
||||
|
||||
last_day_of_month + Duration::days(59)
|
||||
};
|
||||
let available = net_60_payout_available_at(YearMonth::from_day1(
|
||||
last_attempt.date_naive(),
|
||||
))
|
||||
.ok_or_else(|| eyre!("failed to calculate affiliate payout date"))?;
|
||||
|
||||
let revenue_split = row
|
||||
.revenue_split
|
||||
|
||||
@@ -9,12 +9,13 @@ use crate::models::payouts::{
|
||||
use crate::models::projects::MonetizationStatus;
|
||||
use crate::routes::ApiError;
|
||||
use crate::util::error::Context;
|
||||
use crate::util::time::{YearMonth, net_60_payout_available_at};
|
||||
use crate::util::webhook::{
|
||||
PayoutSourceAlertType, send_slack_payout_source_alert_webhook,
|
||||
};
|
||||
use arc_swap::ArcSwapOption;
|
||||
use base64::Engine;
|
||||
use chrono::{DateTime, Datelike, Duration, NaiveTime, TimeZone, Utc};
|
||||
use chrono::{DateTime, Duration, NaiveTime, Utc};
|
||||
use dashmap::DashMap;
|
||||
use eyre::Result;
|
||||
use futures::TryStreamExt;
|
||||
@@ -1132,22 +1133,10 @@ pub async fn process_payout(
|
||||
|
||||
let payout = net_revenue * (Decimal::from(1) - modrinth_cut);
|
||||
|
||||
// Ad payouts are Net 60 from the end of the month
|
||||
let available = {
|
||||
let now = Utc::now().date_naive();
|
||||
|
||||
let year = now.year();
|
||||
let month = now.month();
|
||||
|
||||
// Get the first day of the next month
|
||||
let last_day_of_month = if month == 12 {
|
||||
Utc.with_ymd_and_hms(year + 1, 1, 1, 0, 0, 0).unwrap()
|
||||
} else {
|
||||
Utc.with_ymd_and_hms(year, month + 1, 1, 0, 0, 0).unwrap()
|
||||
};
|
||||
|
||||
last_day_of_month + Duration::days(59)
|
||||
};
|
||||
let available = net_60_payout_available_at(YearMonth::from_day1(
|
||||
Utc::now().date_naive(),
|
||||
))
|
||||
.wrap_internal_err("failed to calculate creator payout date")?;
|
||||
|
||||
let (
|
||||
mut insert_user_ids,
|
||||
|
||||
@@ -12,7 +12,7 @@ use crate::models::payout_runs::{Adjustment, PayoutRun, PayoutRunStatus};
|
||||
use crate::queue::session::AuthQueue;
|
||||
use crate::routes::ApiError;
|
||||
use crate::util::error::Context;
|
||||
use crate::util::time::YearMonth;
|
||||
use crate::util::time::{YearMonth, net_60_payout_available_at};
|
||||
use xredis::RedisPool;
|
||||
|
||||
pub fn config(cfg: &mut web::ServiceConfig) {
|
||||
@@ -100,9 +100,19 @@ pub async fn get(
|
||||
|
||||
while period <= newest_period {
|
||||
if !stored_periods.contains(&period) {
|
||||
let status =
|
||||
if net_60_payout_available_at(period).wrap_internal_err(
|
||||
"failed to calculate payout review date",
|
||||
)? <= newest_created
|
||||
{
|
||||
PayoutRunStatus::InReview
|
||||
} else {
|
||||
PayoutRunStatus::Pending
|
||||
};
|
||||
|
||||
runs.push(PayoutRun {
|
||||
period_start: period,
|
||||
status: PayoutRunStatus::Pending,
|
||||
status,
|
||||
started_at: None,
|
||||
started_by: None,
|
||||
completed_at: None,
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
use std::{fmt, str::FromStr};
|
||||
|
||||
use chrono::{Datelike, NaiveDate};
|
||||
use chrono::{DateTime, Datelike, Days, Months, NaiveDate, Utc};
|
||||
use serde::{Deserialize, Deserializer, Serialize, Serializer, de::Error};
|
||||
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
|
||||
@@ -28,6 +28,16 @@ impl YearMonth {
|
||||
}
|
||||
}
|
||||
|
||||
/// Calculate when a payout period becomes available under Net-60 terms.
|
||||
pub fn net_60_payout_available_at(period: YearMonth) -> Option<DateTime<Utc>> {
|
||||
period
|
||||
.date()
|
||||
.checked_add_months(Months::new(1))?
|
||||
.and_hms_opt(0, 0, 0)?
|
||||
.and_utc()
|
||||
.checked_add_days(Days::new(59))
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, thiserror::Error)]
|
||||
#[error("a year-month must use the first day of its month")]
|
||||
pub struct InvalidYearMonth;
|
||||
@@ -171,4 +181,31 @@ mod tests {
|
||||
assert!(serde_json::from_str::<YearMonth>(value).is_err());
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn calculates_net_60_payout_availability() {
|
||||
let august = YearMonth::from_year_month(2026, 8).unwrap();
|
||||
let december = YearMonth::from_year_month(2026, 12).unwrap();
|
||||
|
||||
assert_eq!(
|
||||
net_60_payout_available_at(august),
|
||||
Some(
|
||||
NaiveDate::from_ymd_opt(2026, 10, 30)
|
||||
.unwrap()
|
||||
.and_hms_opt(0, 0, 0)
|
||||
.unwrap()
|
||||
.and_utc()
|
||||
)
|
||||
);
|
||||
assert_eq!(
|
||||
net_60_payout_available_at(december),
|
||||
Some(
|
||||
NaiveDate::from_ymd_opt(2027, 3, 1)
|
||||
.unwrap()
|
||||
.and_hms_opt(0, 0, 0)
|
||||
.unwrap()
|
||||
.and_utc()
|
||||
)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user