mirror of
https://github.com/modrinth/code.git
synced 2026-08-30 11:36:05 +00:00
openapi, payout run start only if none are running
This commit is contained in:
Generated
-52
@@ -1,52 +0,0 @@
|
||||
{
|
||||
"db_name": "PostgreSQL",
|
||||
"query": "\n\t\t\tSELECT\n\t\t\t\tpayout_periods.period,\n\t\t\t\tpayout_periods.raw_actual_aditude_revenue_usd,\n\t\t\t\tpayout_periods.adjustments,\n\t\t\t\tEXISTS (\n\t\t\t\t\tSELECT 1\n\t\t\t\t\tFROM payout_runs\n\t\t\t\t\tWHERE payout_runs.period = payout_periods.period\n\t\t\t\t\t\tAND payout_runs.status = 'scheduled'\n\t\t\t\t) AS \"has_scheduled_run!\",\n\t\t\t\tEXISTS (\n\t\t\t\t\tSELECT 1\n\t\t\t\t\tFROM payout_runs\n\t\t\t\t\tWHERE payout_runs.period = payout_periods.period\n\t\t\t\t\t\tAND payout_runs.status = 'running'\n\t\t\t\t) AS \"has_running_run!\",\n\t\t\t\tEXISTS (\n\t\t\t\t\tSELECT 1\n\t\t\t\t\tFROM payout_runs\n\t\t\t\t\tWHERE payout_runs.period = payout_periods.period\n\t\t\t\t\t\tAND payout_runs.status = 'succeeded'\n\t\t\t\t) AS \"has_succeeded_run!\"\n\t\t\tFROM payout_periods\n\t\t\tWHERE payout_periods.period = ANY($1)\n\t\t\t",
|
||||
"describe": {
|
||||
"columns": [
|
||||
{
|
||||
"ordinal": 0,
|
||||
"name": "period",
|
||||
"type_info": "Date"
|
||||
},
|
||||
{
|
||||
"ordinal": 1,
|
||||
"name": "raw_actual_aditude_revenue_usd",
|
||||
"type_info": "Numeric"
|
||||
},
|
||||
{
|
||||
"ordinal": 2,
|
||||
"name": "adjustments",
|
||||
"type_info": "Jsonb"
|
||||
},
|
||||
{
|
||||
"ordinal": 3,
|
||||
"name": "has_scheduled_run!",
|
||||
"type_info": "Bool"
|
||||
},
|
||||
{
|
||||
"ordinal": 4,
|
||||
"name": "has_running_run!",
|
||||
"type_info": "Bool"
|
||||
},
|
||||
{
|
||||
"ordinal": 5,
|
||||
"name": "has_succeeded_run!",
|
||||
"type_info": "Bool"
|
||||
}
|
||||
],
|
||||
"parameters": {
|
||||
"Left": [
|
||||
"DateArray"
|
||||
]
|
||||
},
|
||||
"nullable": [
|
||||
false,
|
||||
false,
|
||||
false,
|
||||
null,
|
||||
null,
|
||||
null
|
||||
]
|
||||
},
|
||||
"hash": "066e14c739f3467fbeef939879074a4816912b069894c4b06972300f19ebd191"
|
||||
}
|
||||
Generated
+58
@@ -0,0 +1,58 @@
|
||||
{
|
||||
"db_name": "PostgreSQL",
|
||||
"query": "\n\t\t\tSELECT\n\t\t\t\tpayout_periods.period,\n\t\t\t\tpayout_periods.raw_actual_aditude_revenue_usd,\n\t\t\t\tpayout_periods.adjustments AS \"adjustments: Json<Vec<Adjustment>>\",\n\t\t\t\t(\n\t\t\t\t\tSELECT payout_runs.payload\n\t\t\t\t\tFROM payout_runs\n\t\t\t\t\tWHERE payout_runs.period = payout_periods.period\n\t\t\t\t\t\tAND payout_runs.status IN ('scheduled', 'running')\n\t\t\t\t\tLIMIT 1\n\t\t\t\t) AS \"active_run_payload: Json<PayoutRunPayload>\",\n\t\t\t\tEXISTS (\n\t\t\t\t\tSELECT 1\n\t\t\t\t\tFROM payout_runs\n\t\t\t\t\tWHERE payout_runs.period = payout_periods.period\n\t\t\t\t\t\tAND payout_runs.status = 'scheduled'\n\t\t\t\t) AS \"has_scheduled_run!\",\n\t\t\t\tEXISTS (\n\t\t\t\t\tSELECT 1\n\t\t\t\t\tFROM payout_runs\n\t\t\t\t\tWHERE payout_runs.period = payout_periods.period\n\t\t\t\t\t\tAND payout_runs.status = 'running'\n\t\t\t\t) AS \"has_running_run!\",\n\t\t\t\tEXISTS (\n\t\t\t\t\tSELECT 1\n\t\t\t\t\tFROM payout_runs\n\t\t\t\t\tWHERE payout_runs.period = payout_periods.period\n\t\t\t\t\t\tAND payout_runs.status = 'succeeded'\n\t\t\t\t) AS \"has_succeeded_run!\"\n\t\t\tFROM payout_periods\n\t\t\tWHERE payout_periods.period = ANY($1)\n\t\t\t",
|
||||
"describe": {
|
||||
"columns": [
|
||||
{
|
||||
"ordinal": 0,
|
||||
"name": "period",
|
||||
"type_info": "Date"
|
||||
},
|
||||
{
|
||||
"ordinal": 1,
|
||||
"name": "raw_actual_aditude_revenue_usd",
|
||||
"type_info": "Numeric"
|
||||
},
|
||||
{
|
||||
"ordinal": 2,
|
||||
"name": "adjustments: Json<Vec<Adjustment>>",
|
||||
"type_info": "Jsonb"
|
||||
},
|
||||
{
|
||||
"ordinal": 3,
|
||||
"name": "active_run_payload: Json<PayoutRunPayload>",
|
||||
"type_info": "Jsonb"
|
||||
},
|
||||
{
|
||||
"ordinal": 4,
|
||||
"name": "has_scheduled_run!",
|
||||
"type_info": "Bool"
|
||||
},
|
||||
{
|
||||
"ordinal": 5,
|
||||
"name": "has_running_run!",
|
||||
"type_info": "Bool"
|
||||
},
|
||||
{
|
||||
"ordinal": 6,
|
||||
"name": "has_succeeded_run!",
|
||||
"type_info": "Bool"
|
||||
}
|
||||
],
|
||||
"parameters": {
|
||||
"Left": [
|
||||
"DateArray"
|
||||
]
|
||||
},
|
||||
"nullable": [
|
||||
false,
|
||||
false,
|
||||
false,
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
null
|
||||
]
|
||||
},
|
||||
"hash": "1d68f3585ac26a38955abf04703e26538972770ea7454fab132da87dad747669"
|
||||
}
|
||||
Generated
-24
@@ -1,24 +0,0 @@
|
||||
{
|
||||
"db_name": "PostgreSQL",
|
||||
"query": "\n\t\t\tINSERT INTO payout_runs (\n\t\t\t\tid,\n\t\t\t\tperiod,\n\t\t\t\tstatus,\n\t\t\t\tstarted_at,\n\t\t\t\tstarted_by,\n\t\t\t\texecute_at,\n\t\t\t\tprocessing_started_at,\n\t\t\t\tfinished_at,\n\t\t\t\tcancelled_at,\n\t\t\t\tcancelled_by,\n\t\t\t\terror\n\t\t\t)\n\t\t\tVALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11)\n\t\t\tON CONFLICT (id) DO UPDATE SET\n\t\t\t\tperiod = EXCLUDED.period,\n\t\t\t\tstatus = EXCLUDED.status,\n\t\t\t\tstarted_at = EXCLUDED.started_at,\n\t\t\t\tstarted_by = EXCLUDED.started_by,\n\t\t\t\texecute_at = EXCLUDED.execute_at,\n\t\t\t\tprocessing_started_at = EXCLUDED.processing_started_at,\n\t\t\t\tfinished_at = EXCLUDED.finished_at,\n\t\t\t\tcancelled_at = EXCLUDED.cancelled_at,\n\t\t\t\tcancelled_by = EXCLUDED.cancelled_by,\n\t\t\t\terror = EXCLUDED.error\n\t\t\t",
|
||||
"describe": {
|
||||
"columns": [],
|
||||
"parameters": {
|
||||
"Left": [
|
||||
"Int8",
|
||||
"Date",
|
||||
"Text",
|
||||
"Timestamptz",
|
||||
"Int8",
|
||||
"Timestamptz",
|
||||
"Timestamptz",
|
||||
"Timestamptz",
|
||||
"Timestamptz",
|
||||
"Int8",
|
||||
"Jsonb"
|
||||
]
|
||||
},
|
||||
"nullable": []
|
||||
},
|
||||
"hash": "3de025bb99952ac0ba5d1bbf270a42b68c675c0f38cb760c42acd1786a20c241"
|
||||
}
|
||||
Generated
+15
@@ -0,0 +1,15 @@
|
||||
{
|
||||
"db_name": "PostgreSQL",
|
||||
"query": "\n INSERT INTO payout_periods (\n period,\n raw_actual_aditude_revenue_usd,\n adjustments\n )\n VALUES ($1, $2, '[]'::jsonb)\n ON CONFLICT (period) DO UPDATE SET\n raw_actual_aditude_revenue_usd =\n EXCLUDED.raw_actual_aditude_revenue_usd\n ",
|
||||
"describe": {
|
||||
"columns": [],
|
||||
"parameters": {
|
||||
"Left": [
|
||||
"Date",
|
||||
"Numeric"
|
||||
]
|
||||
},
|
||||
"nullable": []
|
||||
},
|
||||
"hash": "4798f2780f28c4329d96af43e4902d97972573a8f4954ceab284b8d8989641a7"
|
||||
}
|
||||
Generated
+25
@@ -0,0 +1,25 @@
|
||||
{
|
||||
"db_name": "PostgreSQL",
|
||||
"query": "\n\t\t\tINSERT INTO payout_runs (\n\t\t\t\tid,\n\t\t\t\tperiod,\n\t\t\t\tpayload,\n\t\t\t\tstatus,\n\t\t\t\tstarted_at,\n\t\t\t\tstarted_by,\n\t\t\t\texecute_at,\n\t\t\t\tprocessing_started_at,\n\t\t\t\tfinished_at,\n\t\t\t\tcancelled_at,\n\t\t\t\tcancelled_by,\n\t\t\t\terror\n\t\t\t)\n\t\t\tVALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12)\n\t\t\tON CONFLICT (id) DO UPDATE SET\n\t\t\t\tperiod = EXCLUDED.period,\n\t\t\t\tpayload = EXCLUDED.payload,\n\t\t\t\tstatus = EXCLUDED.status,\n\t\t\t\tstarted_at = EXCLUDED.started_at,\n\t\t\t\tstarted_by = EXCLUDED.started_by,\n\t\t\t\texecute_at = EXCLUDED.execute_at,\n\t\t\t\tprocessing_started_at = EXCLUDED.processing_started_at,\n\t\t\t\tfinished_at = EXCLUDED.finished_at,\n\t\t\t\tcancelled_at = EXCLUDED.cancelled_at,\n\t\t\t\tcancelled_by = EXCLUDED.cancelled_by,\n\t\t\t\terror = EXCLUDED.error\n\t\t\t",
|
||||
"describe": {
|
||||
"columns": [],
|
||||
"parameters": {
|
||||
"Left": [
|
||||
"Int8",
|
||||
"Date",
|
||||
"Jsonb",
|
||||
"Text",
|
||||
"Timestamptz",
|
||||
"Int8",
|
||||
"Timestamptz",
|
||||
"Timestamptz",
|
||||
"Timestamptz",
|
||||
"Timestamptz",
|
||||
"Int8",
|
||||
"Jsonb"
|
||||
]
|
||||
},
|
||||
"nullable": []
|
||||
},
|
||||
"hash": "5236bdf4ab31a7a49ae274e965b7310b757083d0c7870a3e5ae11ab5eb57d1aa"
|
||||
}
|
||||
Generated
-16
@@ -1,16 +0,0 @@
|
||||
{
|
||||
"db_name": "PostgreSQL",
|
||||
"query": "\n INSERT INTO payout_periods (\n period,\n raw_actual_aditude_revenue_usd,\n adjustments\n )\n VALUES ($1, $2, $3)\n ON CONFLICT (period) DO UPDATE SET\n raw_actual_aditude_revenue_usd =\n EXCLUDED.raw_actual_aditude_revenue_usd,\n adjustments = EXCLUDED.adjustments\n ",
|
||||
"describe": {
|
||||
"columns": [],
|
||||
"parameters": {
|
||||
"Left": [
|
||||
"Date",
|
||||
"Numeric",
|
||||
"Jsonb"
|
||||
]
|
||||
},
|
||||
"nullable": []
|
||||
},
|
||||
"hash": "df7d47467f7344cb4538ffddaf3805bc9666c9651152f6ed648434b6e2b7ee61"
|
||||
}
|
||||
@@ -20,6 +20,7 @@ CREATE TABLE payout_period_days (
|
||||
CREATE TABLE payout_runs (
|
||||
id BIGINT PRIMARY KEY,
|
||||
period DATE NOT NULL REFERENCES payout_periods(period),
|
||||
payload JSONB NOT NULL,
|
||||
status TEXT NOT NULL,
|
||||
started_at TIMESTAMPTZ NOT NULL,
|
||||
started_by BIGINT NOT NULL REFERENCES users(id) ON DELETE CASCADE,
|
||||
|
||||
@@ -2,14 +2,17 @@ use std::collections::HashMap;
|
||||
|
||||
use chrono::NaiveDate;
|
||||
use rust_decimal::Decimal;
|
||||
use sqlx::types::Json;
|
||||
|
||||
use super::DatabaseError;
|
||||
use crate::queue::payout_run::{Adjustment, PayoutRunPayload};
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct DBPayoutPeriod {
|
||||
pub period: NaiveDate,
|
||||
pub raw_actual_aditude_revenue_usd: Decimal,
|
||||
pub adjustments: serde_json::Value,
|
||||
pub adjustments: Vec<Adjustment>,
|
||||
pub active_run_payload: Option<PayoutRunPayload>,
|
||||
pub days: Vec<DBPayoutPeriodDay>,
|
||||
pub has_scheduled_run: bool,
|
||||
pub has_running_run: bool,
|
||||
@@ -37,7 +40,14 @@ impl DBPayoutPeriod {
|
||||
SELECT
|
||||
payout_periods.period,
|
||||
payout_periods.raw_actual_aditude_revenue_usd,
|
||||
payout_periods.adjustments,
|
||||
payout_periods.adjustments AS "adjustments: Json<Vec<Adjustment>>",
|
||||
(
|
||||
SELECT payout_runs.payload
|
||||
FROM payout_runs
|
||||
WHERE payout_runs.period = payout_periods.period
|
||||
AND payout_runs.status IN ('scheduled', 'running')
|
||||
LIMIT 1
|
||||
) AS "active_run_payload: Json<PayoutRunPayload>",
|
||||
EXISTS (
|
||||
SELECT 1
|
||||
FROM payout_runs
|
||||
@@ -73,7 +83,10 @@ impl DBPayoutPeriod {
|
||||
period: row.period,
|
||||
raw_actual_aditude_revenue_usd: row
|
||||
.raw_actual_aditude_revenue_usd,
|
||||
adjustments: row.adjustments,
|
||||
adjustments: row.adjustments.0,
|
||||
active_run_payload: row
|
||||
.active_run_payload
|
||||
.map(|payload| payload.0),
|
||||
days: Vec::new(),
|
||||
has_scheduled_run: row.has_scheduled_run,
|
||||
has_running_run: row.has_running_run,
|
||||
|
||||
@@ -4,6 +4,7 @@ use sqlx::types::Json;
|
||||
use strum::{EnumString, IntoStaticStr};
|
||||
|
||||
use super::{DBPayoutRunId, DBUserId, DatabaseError};
|
||||
use crate::queue::payout_run::PayoutRunPayload;
|
||||
|
||||
#[derive(
|
||||
Debug,
|
||||
@@ -48,6 +49,7 @@ impl From<crate::models::error::ApiError<'_>> for PayoutRunError {
|
||||
pub struct DBPayoutRun {
|
||||
pub id: DBPayoutRunId,
|
||||
pub period: NaiveDate,
|
||||
pub payload: PayoutRunPayload,
|
||||
pub status: PayoutRunStatus,
|
||||
pub started_at: DateTime<Utc>,
|
||||
pub started_by: DBUserId,
|
||||
@@ -69,6 +71,7 @@ impl DBPayoutRun {
|
||||
INSERT INTO payout_runs (
|
||||
id,
|
||||
period,
|
||||
payload,
|
||||
status,
|
||||
started_at,
|
||||
started_by,
|
||||
@@ -79,9 +82,10 @@ impl DBPayoutRun {
|
||||
cancelled_by,
|
||||
error
|
||||
)
|
||||
VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11)
|
||||
VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12)
|
||||
ON CONFLICT (id) DO UPDATE SET
|
||||
period = EXCLUDED.period,
|
||||
payload = EXCLUDED.payload,
|
||||
status = EXCLUDED.status,
|
||||
started_at = EXCLUDED.started_at,
|
||||
started_by = EXCLUDED.started_by,
|
||||
@@ -94,6 +98,7 @@ impl DBPayoutRun {
|
||||
"#,
|
||||
self.id.0,
|
||||
self.period,
|
||||
Json(&self.payload) as Json<&PayoutRunPayload>,
|
||||
<&'static str>::from(self.status),
|
||||
self.started_at,
|
||||
self.started_by.0,
|
||||
|
||||
@@ -71,13 +71,36 @@ mod estimate;
|
||||
|
||||
pub use estimate::*;
|
||||
|
||||
/// Inputs supplied by an admin when scheduling a payout run.
|
||||
#[derive(Debug, Clone, Serialize, Deserialize, utoipa::ToSchema)]
|
||||
pub struct PayoutRunPayload {
|
||||
/// Actual raw revenue received from the ad provider for the period.
|
||||
#[serde(with = "rust_decimal::serde::float")]
|
||||
pub raw_actual_revenue_usd: Decimal,
|
||||
/// Manual adjustments to apply on top of actual revenue.
|
||||
pub adjustments: Vec<Adjustment>,
|
||||
}
|
||||
|
||||
/// Manual admin-input adjustment to a payout period.
|
||||
#[derive(Debug, Clone, Serialize, Deserialize, utoipa::ToSchema)]
|
||||
pub struct Adjustment {
|
||||
/// Total value of the adjustment.
|
||||
#[serde(with = "rust_decimal::serde::float")]
|
||||
pub amount_usd: Decimal,
|
||||
/// Why this adjustment was applied.
|
||||
///
|
||||
/// Only visible to admins.
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub description: Option<String>,
|
||||
}
|
||||
|
||||
/// Fraction defining much of the net revenue goes to the platform.
|
||||
const PLATFORM_REVENUE_SPLIT: Decimal = dec!(0.25);
|
||||
|
||||
/// How input revenue is distributed for a specific day.
|
||||
///
|
||||
/// This may refer to either estimated or actual revenue.
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
#[derive(Debug, Clone, Serialize, Deserialize, utoipa::ToSchema)]
|
||||
pub struct DayDistribution {
|
||||
/// Amount of revenue input into the algorithm.
|
||||
#[serde(with = "rust_decimal::serde::float")]
|
||||
|
||||
@@ -185,6 +185,9 @@ pub fn config(cfg: &mut web::ServiceConfig) {
|
||||
analytics_event::analytics_event_create,
|
||||
analytics_event::analytics_event_edit,
|
||||
analytics_event::analytics_event_delete,
|
||||
payout_runs::get_runs,
|
||||
payout_runs::start_run,
|
||||
payout_runs::cancel_runs,
|
||||
),
|
||||
modifiers(&InternalPathModifier, &SecurityAddon)
|
||||
)]
|
||||
|
||||
@@ -4,7 +4,7 @@ use rust_decimal::Decimal;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use xredis::RedisPool;
|
||||
|
||||
use super::Adjustment;
|
||||
use super::{Adjustment, PayoutRunPayload};
|
||||
use crate::{
|
||||
auth::{
|
||||
AuthenticationError, get_user_from_headers, two_factor::verify_2fa_code,
|
||||
@@ -25,7 +25,7 @@ use crate::{
|
||||
},
|
||||
};
|
||||
|
||||
#[derive(Debug, Deserialize)]
|
||||
#[derive(Debug, Deserialize, utoipa::ToSchema)]
|
||||
pub struct StartPayoutRun {
|
||||
pub period: YearMonth,
|
||||
pub two_factor_code: Option<String>,
|
||||
@@ -40,12 +40,26 @@ pub struct StartPayoutRun {
|
||||
pub adjustments: Vec<Adjustment>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Serialize)]
|
||||
#[derive(Debug, Serialize, utoipa::ToSchema)]
|
||||
pub struct StartPayoutRunResponse {
|
||||
pub id: PayoutRunId,
|
||||
pub execute_at: DateTime<Utc>,
|
||||
}
|
||||
|
||||
/// Start a payout run.
|
||||
///
|
||||
/// Admin-only.
|
||||
#[utoipa::path(
|
||||
tag = "payout runs",
|
||||
request_body = StartPayoutRun,
|
||||
responses(
|
||||
(status = OK, body = StartPayoutRunResponse),
|
||||
(status = BAD_REQUEST, description = "Invalid payout run input"),
|
||||
(status = UNAUTHORIZED, description = "Invalid authentication or TOTP code"),
|
||||
(status = CONFLICT, description = "Payout period is unavailable or another run is active"),
|
||||
),
|
||||
security(("bearer_auth" = ["SESSION_ACCESS"])),
|
||||
)]
|
||||
#[post("/start")]
|
||||
pub async fn start_run(
|
||||
req: HttpRequest,
|
||||
@@ -126,8 +140,10 @@ pub async fn start_run(
|
||||
let estimate = estimates
|
||||
.pop()
|
||||
.wrap_internal_err("missing requested payout estimate")?;
|
||||
let adjustments = serde_json::to_value(&body.adjustments)
|
||||
.wrap_internal_err("serializing payout adjustments")?;
|
||||
let payload = PayoutRunPayload {
|
||||
raw_actual_revenue_usd: body.raw_actual_revenue_usd,
|
||||
adjustments: body.adjustments.clone(),
|
||||
};
|
||||
|
||||
let mut transaction = pool
|
||||
.begin()
|
||||
@@ -141,15 +157,13 @@ pub async fn start_run(
|
||||
raw_actual_aditude_revenue_usd,
|
||||
adjustments
|
||||
)
|
||||
VALUES ($1, $2, $3)
|
||||
VALUES ($1, $2, '[]'::jsonb)
|
||||
ON CONFLICT (period) DO UPDATE SET
|
||||
raw_actual_aditude_revenue_usd =
|
||||
EXCLUDED.raw_actual_aditude_revenue_usd,
|
||||
adjustments = EXCLUDED.adjustments
|
||||
EXCLUDED.raw_actual_aditude_revenue_usd
|
||||
"#,
|
||||
body.period.date(),
|
||||
body.raw_actual_revenue_usd,
|
||||
&adjustments,
|
||||
)
|
||||
.execute(&mut transaction)
|
||||
.await
|
||||
@@ -175,9 +189,14 @@ pub async fn start_run(
|
||||
.fetch_one(&mut transaction)
|
||||
.await
|
||||
.wrap_internal_err("checking payout period run status")?;
|
||||
if run_state.has_active_run || run_state.has_succeeded_run {
|
||||
if run_state.has_active_run {
|
||||
return Err(ApiError::Conflict(eyre::eyre!(
|
||||
"payout period already has an active or succeeded run",
|
||||
"another payout run is already scheduled or running",
|
||||
)));
|
||||
}
|
||||
if run_state.has_succeeded_run {
|
||||
return Err(ApiError::Conflict(eyre::eyre!(
|
||||
"payout period already has a succeeded run",
|
||||
)));
|
||||
}
|
||||
|
||||
@@ -231,6 +250,7 @@ pub async fn start_run(
|
||||
let run = DBPayoutRun {
|
||||
id,
|
||||
period: body.period.date(),
|
||||
payload,
|
||||
status: PayoutRunStatus::Scheduled,
|
||||
started_at: timing.started_at,
|
||||
started_by: user_id,
|
||||
@@ -256,11 +276,22 @@ pub async fn start_run(
|
||||
}))
|
||||
}
|
||||
|
||||
#[derive(Debug, Serialize)]
|
||||
#[derive(Debug, Serialize, utoipa::ToSchema)]
|
||||
pub struct CancelPayoutRunsResponse {
|
||||
pub cancelled: u64,
|
||||
}
|
||||
|
||||
/// Cancel all scheduled payout runs.
|
||||
///
|
||||
/// Admin-only.
|
||||
#[utoipa::path(
|
||||
tag = "payout runs",
|
||||
responses(
|
||||
(status = OK, body = CancelPayoutRunsResponse),
|
||||
(status = UNAUTHORIZED, description = "Invalid authentication"),
|
||||
),
|
||||
security(("bearer_auth" = ["SESSION_ACCESS"])),
|
||||
)]
|
||||
#[post("/cancel")]
|
||||
pub async fn cancel_runs(
|
||||
req: HttpRequest,
|
||||
|
||||
@@ -30,12 +30,12 @@ use crate::{
|
||||
},
|
||||
};
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize)]
|
||||
#[derive(Debug, Serialize, Deserialize, utoipa::ToSchema)]
|
||||
pub struct PayoutRuns {
|
||||
pub periods: Vec<PayoutRunPeriod>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize)]
|
||||
#[derive(Debug, Serialize, Deserialize, utoipa::ToSchema)]
|
||||
pub struct PayoutRunPeriod {
|
||||
pub period: YearMonth,
|
||||
pub status: PayoutPeriodStatus,
|
||||
@@ -44,7 +44,9 @@ pub struct PayoutRunPeriod {
|
||||
}
|
||||
|
||||
/// Has revenue been distributed for a specific payout period month yet?
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
|
||||
#[derive(
|
||||
Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, utoipa::ToSchema,
|
||||
)]
|
||||
#[serde(rename_all = "snake_case")]
|
||||
pub enum PayoutPeriodStatus {
|
||||
/// We are still waiting on the NET 60 cycle to complete for this month;
|
||||
@@ -61,13 +63,18 @@ pub enum PayoutPeriodStatus {
|
||||
Paid,
|
||||
}
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize)]
|
||||
#[derive(Debug, Serialize, Deserialize, utoipa::ToSchema)]
|
||||
pub struct PayoutRunDay {
|
||||
pub date: NaiveDate,
|
||||
pub estimated: DayDistribution,
|
||||
pub actual: Option<DayDistribution>,
|
||||
}
|
||||
|
||||
/// Fetch all payout runs.
|
||||
#[utoipa::path(
|
||||
tag = "payout runs",
|
||||
responses((status = OK, body = PayoutRuns)),
|
||||
)]
|
||||
#[get("")]
|
||||
pub async fn get_runs(
|
||||
req: HttpRequest,
|
||||
@@ -150,10 +157,11 @@ pub async fn get_runs(
|
||||
.map(|estimate| -> Result<_, ApiError> {
|
||||
if let Some(period) = stored_periods.get(&estimate.period.date()) {
|
||||
let mut adjustments =
|
||||
serde_json::from_value::<Vec<Adjustment>>(
|
||||
period.adjustments.clone(),
|
||||
)
|
||||
.wrap_internal_err("deserializing payout adjustments")?;
|
||||
if let Some(payload) = &period.active_run_payload {
|
||||
payload.adjustments.clone()
|
||||
} else {
|
||||
period.adjustments.clone()
|
||||
};
|
||||
if !show_adjustment_descriptions {
|
||||
for adjustment in &mut adjustments {
|
||||
adjustment.description = None;
|
||||
|
||||
@@ -1,25 +1,12 @@
|
||||
use actix_web::web;
|
||||
use rust_decimal::Decimal;
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
mod admin;
|
||||
mod fetch;
|
||||
|
||||
pub fn config(cfg: &mut web::ServiceConfig) {
|
||||
cfg.service(fetch::get_runs)
|
||||
.service(admin::start_run)
|
||||
.service(admin::cancel_runs);
|
||||
}
|
||||
pub use crate::queue::payout_run::{Adjustment, PayoutRunPayload};
|
||||
pub use admin::*;
|
||||
pub use fetch::*;
|
||||
|
||||
/// Manual admin-input adjustment to a payout period.
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
pub struct Adjustment {
|
||||
/// Total value of the adjustment.
|
||||
#[serde(with = "rust_decimal::serde::float")]
|
||||
pub amount_usd: Decimal,
|
||||
/// Why this adjustment was applied.
|
||||
///
|
||||
/// Only visible to admins.
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub description: Option<String>,
|
||||
pub fn config(cfg: &mut actix_web::web::ServiceConfig) {
|
||||
cfg.service(get_runs)
|
||||
.service(start_run)
|
||||
.service(cancel_runs);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user