payout runs/payout periods split

This commit is contained in:
aecsocket
2026-08-17 19:08:18 +09:00
parent 9e07617173
commit 760a1adbe4
12 changed files with 342 additions and 5 deletions
+8 -3
View File
@@ -4,9 +4,10 @@ use crate::models::ids::{
AffiliateCodeId, AnalyticsEventId, AttributionGroupId, CampaignDonationId,
ChargeId, CollectionId, FileId, ImageId, NotificationId,
OAuthAccessTokenId, OAuthClientAuthorizationId, OAuthClientId,
OAuthRedirectUriId, OrganizationId, PasskeyId, PatId, PayoutId, ProductId,
ProductPriceId, ProjectId, ReportId, SessionId, TeamId, TeamMemberId,
ThreadId, ThreadMessageId, UserSubscriptionId, VersionId,
OAuthRedirectUriId, OrganizationId, PasskeyId, PatId, PayoutId,
PayoutRunId, ProductId, ProductPriceId, ProjectId, ReportId, SessionId,
TeamId, TeamMemberId, ThreadId, ThreadMessageId, UserSubscriptionId,
VersionId,
};
use ariadne::ids::base62_impl::to_base62;
use ariadne::ids::{UserId, random_base62_rng, random_base62_rng_range};
@@ -217,6 +218,10 @@ db_id_interface!(
PayoutId,
generator: generate_payout_id @ "payouts",
);
db_id_interface!(
PayoutRunId,
generator: generate_payout_run_id @ "payout_runs",
);
db_id_interface!(
ProductId,
generator: generate_product_id @ "products",
+2
View File
@@ -27,6 +27,8 @@ pub mod organization_item;
pub mod passkey_item;
pub mod pat_item;
pub mod payout_item;
pub mod payout_run_item;
pub mod payout_variance_item;
pub mod payouts_values_notifications;
pub mod product_item;
pub mod products_tax_identifier_item;
@@ -0,0 +1,112 @@
use chrono::{DateTime, NaiveDate, Utc};
use serde::{Deserialize, Serialize};
use sqlx::types::Json;
use strum::{EnumString, IntoStaticStr};
use super::{DBPayoutRunId, DBUserId, DatabaseError};
#[derive(
Debug,
Clone,
Copy,
PartialEq,
Eq,
Serialize,
Deserialize,
EnumString,
IntoStaticStr,
)]
#[serde(rename_all = "snake_case")]
#[strum(serialize_all = "snake_case")]
pub enum PayoutRunStatus {
Scheduled,
Running,
Cancelled,
Failed,
Succeeded,
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct PayoutRunError {
pub error: String,
pub description: String,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub details: Option<serde_json::Value>,
}
impl From<crate::models::error::ApiError<'_>> for PayoutRunError {
fn from(error: crate::models::error::ApiError<'_>) -> Self {
Self {
error: error.error.to_owned(),
description: error.description,
details: error.details,
}
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct DBPayoutRun {
pub id: DBPayoutRunId,
pub period: NaiveDate,
pub status: PayoutRunStatus,
pub started_at: DateTime<Utc>,
pub started_by: DBUserId,
pub execute_at: DateTime<Utc>,
pub processing_started_at: Option<DateTime<Utc>>,
pub finished_at: Option<DateTime<Utc>>,
pub cancelled_at: Option<DateTime<Utc>>,
pub cancelled_by: Option<DBUserId>,
pub error: Option<PayoutRunError>,
}
impl DBPayoutRun {
pub async fn upsert(
&self,
exec: impl crate::database::Executor<'_, Database = sqlx::Postgres>,
) -> Result<(), DatabaseError> {
sqlx::query!(
r#"
INSERT INTO payout_runs (
id,
period,
status,
started_at,
started_by,
execute_at,
processing_started_at,
finished_at,
cancelled_at,
cancelled_by,
error
)
VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11)
ON CONFLICT (id) DO UPDATE SET
period = EXCLUDED.period,
status = EXCLUDED.status,
started_at = EXCLUDED.started_at,
started_by = EXCLUDED.started_by,
execute_at = EXCLUDED.execute_at,
processing_started_at = EXCLUDED.processing_started_at,
finished_at = EXCLUDED.finished_at,
cancelled_at = EXCLUDED.cancelled_at,
cancelled_by = EXCLUDED.cancelled_by,
error = EXCLUDED.error
"#,
self.id.0,
self.period,
<&'static str>::from(self.status),
self.started_at,
self.started_by.0,
self.execute_at,
self.processing_started_at,
self.finished_at,
self.cancelled_at,
self.cancelled_by.map(|id| id.0),
self.error.as_ref().map(Json) as Option<Json<&PayoutRunError>>,
)
.execute(exec)
.await?;
Ok(())
}
}
@@ -0,0 +1,50 @@
use chrono::NaiveDate;
use rust_decimal::Decimal;
use serde::{Deserialize, Serialize};
use super::DatabaseError;
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct DBPayoutVariance {
pub applied_on: NaiveDate,
pub variance: Decimal,
}
impl DBPayoutVariance {
pub async fn get_all(
exec: impl crate::database::Executor<'_, Database = sqlx::Postgres>,
) -> Result<Vec<Self>, DatabaseError> {
let variances = sqlx::query_as!(
Self,
r#"
SELECT applied_on, variance
FROM payouts_variance
ORDER BY applied_on
"#,
)
.fetch_all(exec)
.await?;
Ok(variances)
}
pub async fn upsert(
&self,
exec: impl crate::database::Executor<'_, Database = sqlx::Postgres>,
) -> Result<(), DatabaseError> {
sqlx::query!(
r#"
INSERT INTO payouts_variance (applied_on, variance)
VALUES ($1, $2)
ON CONFLICT (applied_on) DO UPDATE
SET variance = EXCLUDED.variance
"#,
self.applied_on,
self.variance,
)
.execute(exec)
.await?;
Ok(())
}
}