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