global discount price

This commit is contained in:
Priec
2026-06-22 00:18:39 +02:00
parent d2b463135b
commit e98c70aa63
12 changed files with 413 additions and 125 deletions

View File

@@ -9,20 +9,22 @@
//! prices still layer on top (lowest price wins). Both are computed off the
//! regular price.
use std::collections::HashMap;
use std::collections::{HashMap, HashSet};
use axum_extra::extract::cookie::CookieJar;
use loco_rs::prelude::*;
use sea_orm::{ActiveModelTrait, EntityTrait, QueryOrder, Set};
use sea_orm::{
ActiveModelTrait, ColumnTrait, EntityTrait, QueryFilter, QueryOrder, Set, TransactionTrait,
};
use serde::Deserialize;
use serde_json::json;
use crate::{
controllers::i18n::current_lang,
models::products,
models::{audience_discount_profiles, discount_profiles, products},
shared::{
guard,
money::{format_price, parse_percent, parse_price_to_cents},
money::{format_bp, format_price, parse_percent, parse_price_to_cents},
},
};
@@ -122,13 +124,79 @@ async fn index(
.all(&ctx.db)
.await?;
let rows: Vec<serde_json::Value> = list.iter().map(list_row).collect();
// Profiles applied globally to this audience, plus all profiles to choose from.
let assigned: HashSet<i32> = audience_discount_profiles::Entity::find()
.filter(audience_discount_profiles::Column::Audience.eq(audience))
.all(&ctx.db)
.await?
.into_iter()
.map(|a| a.discount_profile_id)
.collect();
let all_profiles = discount_profiles::Entity::find()
.order_by_asc(discount_profiles::Column::Name)
.all(&ctx.db)
.await?;
let profiles: Vec<serde_json::Value> = all_profiles
.iter()
.map(|p| {
json!({
"id": p.id,
"name": p.name,
"percent": format_bp(p.percent_bp),
"scope_type": p.scope_type,
"assigned": assigned.contains(&p.id),
})
})
.collect();
format::view(
&v,
"admin/catalog/discounts.html",
json!({ "products": rows, "audience": audience, "lang": current_lang(&jar) }),
json!({
"products": rows,
"profiles": profiles,
"audience": audience,
"lang": current_lang(&jar),
}),
)
}
/// Replace the profiles applied to this audience with the submitted checkbox set
/// (`profile_ids`, a repeated field parsed directly from the body).
#[debug_handler]
async fn sync_profiles(
auth: auth::JWT,
Query(params): Query<HashMap<String, String>>,
State(ctx): State<AppContext>,
body: String,
) -> Result<Response> {
guard::current_admin(auth, &ctx).await?;
let audience = read_audience(&params);
let profile_ids: Vec<i32> = form_urlencoded::parse(body.as_bytes())
.filter(|(k, _)| k == "profile_ids")
.filter_map(|(_, value)| value.parse::<i32>().ok())
.collect();
let txn = ctx.db.begin().await?;
audience_discount_profiles::Entity::delete_many()
.filter(audience_discount_profiles::Column::Audience.eq(audience))
.exec(&txn)
.await?;
for profile_id in profile_ids {
audience_discount_profiles::ActiveModel {
audience: Set(audience.to_string()),
discount_profile_id: Set(profile_id),
..Default::default()
}
.insert(&txn)
.await?;
}
txn.commit().await?;
list_redirect(audience)
}
/// What to pre-fill the form with: the chosen input mode and the raw values for
/// each field, so a rejected submit (or a re-edit) shows what the admin had.
#[derive(Default)]
@@ -284,6 +352,7 @@ async fn remove(
pub fn routes() -> Routes {
Routes::new()
.add("/admin/catalog/discounts", get(index))
.add("/admin/catalog/discounts/profiles", post(sync_profiles))
.add("/admin/catalog/discounts/{id}/edit", get(edit))
.add("/admin/catalog/discounts/{id}", post(update))
.add("/admin/catalog/discounts/{id}/remove", post(remove))