feat(htykc): add course_package CRUD with pagination and org scoping
Introduce course_package table and API for selling course templates. Supports teacher-owned and org-wide active package queries with keyword search, sort order, and pagination. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
@@ -18,8 +18,14 @@ use tracing::{debug, error};
|
||||
mod notifications;
|
||||
mod ws_clazz;
|
||||
mod ws_clazz_repeat;
|
||||
mod ws_course_package;
|
||||
mod ws_xiaoke;
|
||||
|
||||
use crate::ws_course_package::{
|
||||
create_course_package, delete_course_package, find_all_active_course_packages_by_org_with_page,
|
||||
find_all_course_packages_by_created_by_with_page, find_course_package_by_id,
|
||||
update_course_package,
|
||||
};
|
||||
use crate::ws_xiaoke::{batch_save_clazz_attendance, find_clazz_attendance_by_clazz_id};
|
||||
use crate::ws_xiaoke::{
|
||||
approve_clazz_leave, create_clazz_leave, list_clazz_leave, supervisor_teacher_stats,
|
||||
@@ -97,6 +103,13 @@ pub fn clazz_router(db_url: &str) -> Router {
|
||||
.route("/api/v1/clazz/leave/create", post(create_clazz_leave))
|
||||
.route("/api/v1/clazz/leave/approve", post(approve_clazz_leave))
|
||||
.route("/api/v1/clazz/leave/list", get(list_clazz_leave))
|
||||
// 课包(course_package)
|
||||
.route("/api/v1/clazz/course-package/create", post(create_course_package))
|
||||
.route("/api/v1/clazz/course-package/update", post(update_course_package))
|
||||
.route("/api/v1/clazz/course-package/delete/{id}", post(delete_course_package))
|
||||
.route("/api/v1/clazz/course-package/{id}", get(find_course_package_by_id))
|
||||
.route("/api/v1/clazz/course-package/my-packages", get(find_all_course_packages_by_created_by_with_page))
|
||||
.route("/api/v1/clazz/course-package/org-packages", get(find_all_active_course_packages_by_org_with_page))
|
||||
.layer(TraceLayer::new_for_http())
|
||||
.with_state(shared_db_state);
|
||||
|
||||
|
||||
@@ -0,0 +1,214 @@
|
||||
//! 课包(course_package)相关 API — 用于卖课的产品模板
|
||||
|
||||
use anyhow::anyhow;
|
||||
use axum::extract::{Path, Query, State};
|
||||
use axum::Json;
|
||||
use htycommons::common::{current_local_datetime, get_some_from_query_params, HtyErr, HtyErrCode, HtyResponse};
|
||||
use htycommons::jwt::jwt_decode_token;
|
||||
use htycommons::web::{
|
||||
wrap_json_anyhow_err, wrap_json_ok_resp, AuthorizationHeader, HtyHostHeader,
|
||||
HtySudoerTokenHeader,
|
||||
};
|
||||
use htykc_models::models::{CoursePackage, ReqCoursePackage};
|
||||
use htycommons::uuid;
|
||||
use std::collections::HashMap;
|
||||
use std::ops::DerefMut;
|
||||
use std::sync::Arc;
|
||||
use tracing::error;
|
||||
|
||||
use htycommons::db::{extract_conn, fetch_db_conn, DbState};
|
||||
|
||||
fn required_org_id_from_auth(auth: &AuthorizationHeader) -> anyhow::Result<String> {
|
||||
jwt_decode_token(&(*auth).clone())?
|
||||
.current_org_id
|
||||
.ok_or_else(|| anyhow!("current_org_id is required"))
|
||||
}
|
||||
|
||||
fn required_hty_id_from_auth(auth: &AuthorizationHeader) -> anyhow::Result<String> {
|
||||
jwt_decode_token(&(*auth).clone())?
|
||||
.hty_id
|
||||
.ok_or_else(|| anyhow!("hty_id is required"))
|
||||
}
|
||||
|
||||
pub async fn create_course_package(
|
||||
_sudoer: HtySudoerTokenHeader,
|
||||
_host: HtyHostHeader,
|
||||
auth: AuthorizationHeader,
|
||||
State(db_pool): State<Arc<DbState>>,
|
||||
Json(body): Json<ReqCoursePackage>,
|
||||
) -> Json<HtyResponse<CoursePackage>> {
|
||||
let result = (|| -> anyhow::Result<CoursePackage> {
|
||||
let token = jwt_decode_token(&(*auth).clone())?;
|
||||
let org_id = token.current_org_id.ok_or_else(|| anyhow!("current_org_id is required"))?;
|
||||
let hty_id = token.hty_id.ok_or_else(|| anyhow!("hty_id is required"))?;
|
||||
let now = current_local_datetime();
|
||||
let payload = CoursePackage {
|
||||
id: body.id.unwrap_or_else(uuid),
|
||||
package_name: body.package_name.ok_or_else(|| anyhow!("package_name is required"))?,
|
||||
description: body.description,
|
||||
ws_course_id: body.ws_course_id,
|
||||
total_lessons: body.total_lessons,
|
||||
original_price: body.original_price,
|
||||
selling_price: body.selling_price,
|
||||
validity_days: body.validity_days,
|
||||
package_status: body.package_status.unwrap_or_else(|| "ACTIVE".to_string()),
|
||||
cover_image_url: body.cover_image_url,
|
||||
sort_order: body.sort_order,
|
||||
created_by: Some(hty_id),
|
||||
created_at: now,
|
||||
updated_at: None,
|
||||
is_delete: Some(false),
|
||||
org_id: Some(org_id),
|
||||
};
|
||||
CoursePackage::create(&payload, extract_conn(fetch_db_conn(&db_pool)?).deref_mut())
|
||||
})();
|
||||
match result {
|
||||
Ok(ok) => wrap_json_ok_resp(ok),
|
||||
Err(e) => {
|
||||
error!("create_course_package -> failed, e: {}", e);
|
||||
wrap_json_anyhow_err(e)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn update_course_package(
|
||||
_sudoer: HtySudoerTokenHeader,
|
||||
_host: HtyHostHeader,
|
||||
auth: AuthorizationHeader,
|
||||
State(db_pool): State<Arc<DbState>>,
|
||||
Json(body): Json<ReqCoursePackage>,
|
||||
) -> Json<HtyResponse<CoursePackage>> {
|
||||
let result = (|| -> anyhow::Result<CoursePackage> {
|
||||
let token = jwt_decode_token(&(*auth).clone())?;
|
||||
let package_id = body.id.clone().ok_or_else(|| anyhow!("id is required"))?;
|
||||
let mut conn_holder = extract_conn(fetch_db_conn(&db_pool)?);
|
||||
let conn = conn_holder.deref_mut();
|
||||
|
||||
let existing = CoursePackage::find_by_id(&package_id, conn)?;
|
||||
|
||||
// Only creator or same org can update
|
||||
if let Some(org_id) = &token.current_org_id {
|
||||
if existing.org_id.as_ref() != Some(org_id) {
|
||||
return Err(anyhow!(HtyErr {
|
||||
code: HtyErrCode::AuthenticationFailed,
|
||||
reason: Some("course package does not belong to current organization".to_string()),
|
||||
}));
|
||||
}
|
||||
}
|
||||
|
||||
let now = current_local_datetime();
|
||||
let payload = CoursePackage {
|
||||
id: package_id,
|
||||
package_name: body.package_name.unwrap_or(existing.package_name),
|
||||
description: body.description.or(existing.description),
|
||||
ws_course_id: body.ws_course_id.or(existing.ws_course_id),
|
||||
total_lessons: body.total_lessons.or(existing.total_lessons),
|
||||
original_price: body.original_price.or(existing.original_price),
|
||||
selling_price: body.selling_price.or(existing.selling_price),
|
||||
validity_days: body.validity_days.or(existing.validity_days),
|
||||
package_status: body.package_status.unwrap_or(existing.package_status),
|
||||
cover_image_url: body.cover_image_url.or(existing.cover_image_url),
|
||||
sort_order: body.sort_order.or(existing.sort_order),
|
||||
created_by: existing.created_by,
|
||||
created_at: existing.created_at,
|
||||
updated_at: Some(now),
|
||||
is_delete: existing.is_delete,
|
||||
org_id: existing.org_id,
|
||||
};
|
||||
CoursePackage::update(&payload, conn)
|
||||
})();
|
||||
match result {
|
||||
Ok(ok) => wrap_json_ok_resp(ok),
|
||||
Err(e) => {
|
||||
error!("update_course_package -> failed, e: {}", e);
|
||||
wrap_json_anyhow_err(e)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn delete_course_package(
|
||||
_sudoer: HtySudoerTokenHeader,
|
||||
_host: HtyHostHeader,
|
||||
auth: AuthorizationHeader,
|
||||
State(db_pool): State<Arc<DbState>>,
|
||||
Path(package_id): Path<String>,
|
||||
) -> Json<HtyResponse<usize>> {
|
||||
let result = (|| -> anyhow::Result<usize> {
|
||||
let _token = jwt_decode_token(&(*auth).clone())?;
|
||||
CoursePackage::logic_delete_by_id(&package_id, extract_conn(fetch_db_conn(&db_pool)?).deref_mut())
|
||||
})();
|
||||
match result {
|
||||
Ok(ok) => wrap_json_ok_resp(ok),
|
||||
Err(e) => {
|
||||
error!("delete_course_package -> failed, e: {}", e);
|
||||
wrap_json_anyhow_err(e)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn find_course_package_by_id(
|
||||
_sudoer: HtySudoerTokenHeader,
|
||||
_host: HtyHostHeader,
|
||||
_auth: AuthorizationHeader,
|
||||
State(db_pool): State<Arc<DbState>>,
|
||||
Path(package_id): Path<String>,
|
||||
) -> Json<HtyResponse<CoursePackage>> {
|
||||
let result = (|| -> anyhow::Result<CoursePackage> {
|
||||
let mut conn_holder = extract_conn(fetch_db_conn(&db_pool)?);
|
||||
CoursePackage::find_by_id(&package_id, conn_holder.deref_mut())
|
||||
})();
|
||||
match result {
|
||||
Ok(ok) => wrap_json_ok_resp(ok),
|
||||
Err(e) => {
|
||||
error!("find_course_package_by_id -> failed, e: {}", e);
|
||||
wrap_json_anyhow_err(e)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn find_all_course_packages_by_created_by_with_page(
|
||||
_sudoer: HtySudoerTokenHeader,
|
||||
_host: HtyHostHeader,
|
||||
auth: AuthorizationHeader,
|
||||
State(db_pool): State<Arc<DbState>>,
|
||||
Query(params): Query<HashMap<String, String>>,
|
||||
) -> Json<HtyResponse<(Vec<CoursePackage>, i64, i64)>> {
|
||||
let result = (|| -> anyhow::Result<(Vec<CoursePackage>, i64, i64)> {
|
||||
let hty_id = required_hty_id_from_auth(&auth)?;
|
||||
let org_id = required_org_id_from_auth(&auth)?;
|
||||
let page = get_some_from_query_params::<i64>("page", ¶ms).unwrap_or(1);
|
||||
let page_size = get_some_from_query_params::<i64>("page_size", ¶ms).unwrap_or(20);
|
||||
let keyword = get_some_from_query_params::<String>("keyword", ¶ms);
|
||||
CoursePackage::find_all_by_teacher_id_with_page(&hty_id, &org_id, &keyword, page, page_size, extract_conn(fetch_db_conn(&db_pool)?).deref_mut())
|
||||
})();
|
||||
match result {
|
||||
Ok(ok) => wrap_json_ok_resp(ok),
|
||||
Err(e) => {
|
||||
error!("find_all_course_packages_by_created_by_with_page -> failed, e: {}", e);
|
||||
wrap_json_anyhow_err(e)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn find_all_active_course_packages_by_org_with_page(
|
||||
_sudoer: HtySudoerTokenHeader,
|
||||
_host: HtyHostHeader,
|
||||
auth: AuthorizationHeader,
|
||||
State(db_pool): State<Arc<DbState>>,
|
||||
Query(params): Query<HashMap<String, String>>,
|
||||
) -> Json<HtyResponse<(Vec<CoursePackage>, i64, i64)>> {
|
||||
let result = (|| -> anyhow::Result<(Vec<CoursePackage>, i64, i64)> {
|
||||
let org_id = required_org_id_from_auth(&auth)?;
|
||||
let page = get_some_from_query_params::<i64>("page", ¶ms).unwrap_or(1);
|
||||
let page_size = get_some_from_query_params::<i64>("page_size", ¶ms).unwrap_or(20);
|
||||
let keyword = get_some_from_query_params::<String>("keyword", ¶ms);
|
||||
CoursePackage::find_all_active_by_org_with_page(&org_id, &keyword, page, page_size, extract_conn(fetch_db_conn(&db_pool)?).deref_mut())
|
||||
})();
|
||||
match result {
|
||||
Ok(ok) => wrap_json_ok_resp(ok),
|
||||
Err(e) => {
|
||||
error!("find_all_active_course_packages_by_org_with_page -> failed, e: {}", e);
|
||||
wrap_json_anyhow_err(e)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
DROP TABLE IF EXISTS course_package;
|
||||
@@ -0,0 +1,23 @@
|
||||
-- 课包(用于卖课的产品模板)
|
||||
CREATE TABLE course_package (
|
||||
id VARCHAR NOT NULL PRIMARY KEY,
|
||||
package_name VARCHAR NOT NULL,
|
||||
description TEXT,
|
||||
ws_course_id VARCHAR,
|
||||
total_lessons INT4 DEFAULT 0,
|
||||
original_price INT8 DEFAULT 0,
|
||||
selling_price INT8 DEFAULT 0,
|
||||
validity_days INT4 DEFAULT 0,
|
||||
package_status VARCHAR NOT NULL DEFAULT 'ACTIVE',
|
||||
cover_image_url VARCHAR,
|
||||
sort_order INT4 DEFAULT 0,
|
||||
created_by VARCHAR,
|
||||
created_at TIMESTAMP NOT NULL DEFAULT NOW(),
|
||||
updated_at TIMESTAMP,
|
||||
is_delete BOOL DEFAULT FALSE,
|
||||
org_id VARCHAR
|
||||
);
|
||||
|
||||
CREATE INDEX idx_course_package_org_id ON course_package (org_id);
|
||||
CREATE INDEX idx_course_package_created_by ON course_package (created_by);
|
||||
CREATE INDEX idx_course_package_status ON course_package (package_status);
|
||||
+196
-1
@@ -3,7 +3,7 @@ use chrono::NaiveDateTime;
|
||||
use diesel::{
|
||||
delete, insert_into, sql_query, update,
|
||||
BoolExpressionMethods, Connection, ExpressionMethods, OptionalExtension, PgConnection,
|
||||
QueryDsl, RunQueryDsl,
|
||||
QueryDsl, RunQueryDsl, TextExpressionMethods,
|
||||
};
|
||||
use log::debug;
|
||||
use string_builder::Builder;
|
||||
@@ -716,6 +716,201 @@ pub struct ReqClazzUser {
|
||||
pub user_type: Option<String>,
|
||||
}
|
||||
|
||||
// --- 课包(course_package,用于卖课)---
|
||||
|
||||
#[derive(
|
||||
AsChangeset,
|
||||
Identifiable,
|
||||
PartialEq,
|
||||
Serialize,
|
||||
Deserialize,
|
||||
Queryable,
|
||||
Debug,
|
||||
Insertable,
|
||||
Clone,
|
||||
)]
|
||||
#[diesel(table_name = course_package)]
|
||||
pub struct CoursePackage {
|
||||
pub id: String,
|
||||
pub package_name: String,
|
||||
pub description: Option<String>,
|
||||
pub ws_course_id: Option<String>,
|
||||
pub total_lessons: Option<i32>,
|
||||
pub original_price: Option<i64>,
|
||||
pub selling_price: Option<i64>,
|
||||
pub validity_days: Option<i32>,
|
||||
pub package_status: String,
|
||||
pub cover_image_url: Option<String>,
|
||||
pub sort_order: Option<i32>,
|
||||
pub created_by: Option<String>,
|
||||
pub created_at: NaiveDateTime,
|
||||
pub updated_at: Option<NaiveDateTime>,
|
||||
pub is_delete: Option<bool>,
|
||||
pub org_id: Option<String>,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug, Clone)]
|
||||
pub struct ReqCoursePackage {
|
||||
pub id: Option<String>,
|
||||
pub package_name: Option<String>,
|
||||
pub description: Option<String>,
|
||||
pub ws_course_id: Option<String>,
|
||||
pub total_lessons: Option<i32>,
|
||||
pub original_price: Option<i64>,
|
||||
pub selling_price: Option<i64>,
|
||||
pub validity_days: Option<i32>,
|
||||
pub package_status: Option<String>,
|
||||
pub cover_image_url: Option<String>,
|
||||
pub sort_order: Option<i32>,
|
||||
pub created_by: Option<String>,
|
||||
pub created_at: Option<NaiveDateTime>,
|
||||
pub updated_at: Option<NaiveDateTime>,
|
||||
pub is_delete: Option<bool>,
|
||||
pub org_id: Option<String>,
|
||||
}
|
||||
|
||||
impl CoursePackage {
|
||||
pub fn find_by_id(id: &String, conn: &mut PgConnection) -> anyhow::Result<CoursePackage> {
|
||||
use crate::schema::course_package::dsl::{
|
||||
course_package as packages, id as pkg_id, is_delete,
|
||||
};
|
||||
match packages
|
||||
.filter(pkg_id.eq(id))
|
||||
.filter(is_delete.is_null().or(is_delete.eq(false)))
|
||||
.first::<CoursePackage>(conn)
|
||||
{
|
||||
Ok(q) => Ok(q),
|
||||
Err(e) => Err(anyhow!(HtyErr {
|
||||
code: HtyErrCode::DbErr,
|
||||
reason: Some(e.to_string()),
|
||||
})),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn create(in_package: &CoursePackage, conn: &mut PgConnection) -> anyhow::Result<CoursePackage> {
|
||||
use crate::schema::course_package::dsl::course_package as packages;
|
||||
match insert_into(packages).values(in_package).get_result(conn) {
|
||||
Ok(res) => Ok(res),
|
||||
Err(e) => Err(anyhow!(HtyErr {
|
||||
code: HtyErrCode::DbErr,
|
||||
reason: Some(e.to_string()),
|
||||
})),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn update(in_package: &CoursePackage, conn: &mut PgConnection) -> anyhow::Result<CoursePackage> {
|
||||
use crate::schema::course_package::dsl::{course_package as packages, id as pkg_id};
|
||||
let result = update(packages)
|
||||
.filter(pkg_id.eq(in_package.clone().id))
|
||||
.set(in_package)
|
||||
.get_result(conn)
|
||||
.map_err(|e| {
|
||||
anyhow!(HtyErr {
|
||||
code: HtyErrCode::DbErr,
|
||||
reason: Some(e.to_string()),
|
||||
})
|
||||
});
|
||||
result
|
||||
}
|
||||
|
||||
pub fn logic_delete_by_id(id: &String, conn: &mut PgConnection) -> anyhow::Result<usize> {
|
||||
use crate::schema::course_package::dsl::{
|
||||
course_package as packages, id as pkg_id, is_delete, updated_at,
|
||||
};
|
||||
let now = current_local_datetime();
|
||||
update(packages)
|
||||
.filter(pkg_id.eq(id))
|
||||
.set((
|
||||
is_delete.eq(true),
|
||||
updated_at.eq(Some(now)),
|
||||
))
|
||||
.execute(conn)
|
||||
.map_err(|e| {
|
||||
anyhow!(HtyErr {
|
||||
code: HtyErrCode::DbErr,
|
||||
reason: Some(e.to_string()),
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
pub fn find_all_active_by_org_with_page(
|
||||
org: &String,
|
||||
keyword: &Option<String>,
|
||||
page: i64,
|
||||
page_size: i64,
|
||||
conn: &mut PgConnection,
|
||||
) -> anyhow::Result<(Vec<CoursePackage>, i64, i64)> {
|
||||
use crate::schema::course_package::dsl::{
|
||||
course_package as packages, is_delete, org_id, package_name, package_status,
|
||||
sort_order, created_at,
|
||||
};
|
||||
let total: i64 = packages
|
||||
.filter(org_id.eq(org))
|
||||
.filter(package_status.eq("ACTIVE"))
|
||||
.filter(is_delete.is_null().or(is_delete.eq(false)))
|
||||
.count()
|
||||
.get_result(conn)?;
|
||||
let pages = (total + page_size - 1) / page_size;
|
||||
let offset = (page - 1) * page_size;
|
||||
let kws = keyword.clone().unwrap_or_default();
|
||||
let mut query = packages
|
||||
.filter(org_id.eq(org))
|
||||
.filter(package_status.eq("ACTIVE"))
|
||||
.filter(is_delete.is_null().or(is_delete.eq(false)))
|
||||
.into_boxed();
|
||||
if !kws.is_empty() {
|
||||
let pattern = format!("%{}%", kws);
|
||||
query = query.filter(package_name.like(pattern));
|
||||
}
|
||||
let items = query
|
||||
.order(sort_order.asc())
|
||||
.then_order_by(created_at.desc())
|
||||
.offset(offset)
|
||||
.limit(page_size)
|
||||
.load::<CoursePackage>(conn)?;
|
||||
Ok((items, pages, total))
|
||||
}
|
||||
|
||||
pub fn find_all_by_teacher_id_with_page(
|
||||
teacher_id: &String,
|
||||
org: &String,
|
||||
keyword: &Option<String>,
|
||||
page: i64,
|
||||
page_size: i64,
|
||||
conn: &mut PgConnection,
|
||||
) -> anyhow::Result<(Vec<CoursePackage>, i64, i64)> {
|
||||
use crate::schema::course_package::dsl::{
|
||||
course_package as packages, created_by, is_delete, org_id, package_name, sort_order,
|
||||
created_at,
|
||||
};
|
||||
let total: i64 = packages
|
||||
.filter(org_id.eq(org))
|
||||
.filter(created_by.eq(teacher_id))
|
||||
.filter(is_delete.is_null().or(is_delete.eq(false)))
|
||||
.count()
|
||||
.get_result(conn)?;
|
||||
let pages = (total + page_size - 1) / page_size;
|
||||
let offset = (page - 1) * page_size;
|
||||
let kws = keyword.clone().unwrap_or_default();
|
||||
let mut query = packages
|
||||
.filter(org_id.eq(org))
|
||||
.filter(created_by.eq(teacher_id))
|
||||
.filter(is_delete.is_null().or(is_delete.eq(false)))
|
||||
.into_boxed();
|
||||
if !kws.is_empty() {
|
||||
let pattern = format!("%{}%", kws);
|
||||
query = query.filter(package_name.like(pattern));
|
||||
}
|
||||
let items = query
|
||||
.order(sort_order.asc())
|
||||
.then_order_by(created_at.desc())
|
||||
.offset(offset)
|
||||
.limit(page_size)
|
||||
.load::<CoursePackage>(conn)?;
|
||||
Ok((items, pages, total))
|
||||
}
|
||||
}
|
||||
|
||||
// --- 消课 / 课时包(xiaoke)---
|
||||
|
||||
#[derive(Queryable, Serialize, Deserialize, Debug, Clone)]
|
||||
|
||||
+41
-19
@@ -44,6 +44,24 @@ diesel::table! {
|
||||
}
|
||||
}
|
||||
|
||||
diesel::table! {
|
||||
clazz_leave_request (id) {
|
||||
id -> Varchar,
|
||||
org_id -> Varchar,
|
||||
clazz_id -> Varchar,
|
||||
student_id -> Varchar,
|
||||
teacher_id -> Nullable<Varchar>,
|
||||
leave_type -> Varchar,
|
||||
reason -> Nullable<Varchar>,
|
||||
request_status -> Varchar,
|
||||
created_at -> Timestamp,
|
||||
created_by -> Nullable<Varchar>,
|
||||
reviewed_at -> Nullable<Timestamp>,
|
||||
reviewed_by -> Nullable<Varchar>,
|
||||
is_delete -> Bool,
|
||||
}
|
||||
}
|
||||
|
||||
diesel::table! {
|
||||
clazz_repeat (id) {
|
||||
id -> Varchar,
|
||||
@@ -77,6 +95,27 @@ diesel::table! {
|
||||
}
|
||||
}
|
||||
|
||||
diesel::table! {
|
||||
course_package (id) {
|
||||
id -> Varchar,
|
||||
package_name -> Varchar,
|
||||
description -> Nullable<Text>,
|
||||
ws_course_id -> Nullable<Varchar>,
|
||||
total_lessons -> Nullable<Int4>,
|
||||
original_price -> Nullable<Int8>,
|
||||
selling_price -> Nullable<Int8>,
|
||||
validity_days -> Nullable<Int4>,
|
||||
package_status -> Varchar,
|
||||
cover_image_url -> Nullable<Varchar>,
|
||||
sort_order -> Nullable<Int4>,
|
||||
created_by -> Nullable<Varchar>,
|
||||
created_at -> Timestamp,
|
||||
updated_at -> Nullable<Timestamp>,
|
||||
is_delete -> Nullable<Bool>,
|
||||
org_id -> Nullable<Varchar>,
|
||||
}
|
||||
}
|
||||
|
||||
diesel::table! {
|
||||
hour_transaction (id) {
|
||||
id -> Varchar,
|
||||
@@ -92,27 +131,9 @@ diesel::table! {
|
||||
}
|
||||
}
|
||||
|
||||
diesel::table! {
|
||||
clazz_leave_request (id) {
|
||||
id -> Varchar,
|
||||
org_id -> Varchar,
|
||||
clazz_id -> Varchar,
|
||||
student_id -> Varchar,
|
||||
teacher_id -> Nullable<Varchar>,
|
||||
leave_type -> Varchar,
|
||||
reason -> Nullable<Varchar>,
|
||||
request_status -> Varchar,
|
||||
created_at -> Timestamp,
|
||||
created_by -> Nullable<Varchar>,
|
||||
reviewed_at -> Nullable<Timestamp>,
|
||||
reviewed_by -> Nullable<Varchar>,
|
||||
is_delete -> Bool,
|
||||
}
|
||||
}
|
||||
|
||||
diesel::joinable!(clazz_attendance -> clazz (clazz_id));
|
||||
diesel::joinable!(clazz_leave_request -> clazz (clazz_id));
|
||||
diesel::joinable!(clazz_attendance -> course_hour_package (course_hour_package_id));
|
||||
diesel::joinable!(clazz_leave_request -> clazz (clazz_id));
|
||||
diesel::joinable!(clazz_repeat -> clazz (clazz_id));
|
||||
diesel::joinable!(hour_transaction -> clazz (clazz_id));
|
||||
diesel::joinable!(hour_transaction -> course_hour_package (package_id));
|
||||
@@ -123,5 +144,6 @@ diesel::allow_tables_to_appear_in_same_query!(
|
||||
clazz_leave_request,
|
||||
clazz_repeat,
|
||||
course_hour_package,
|
||||
course_package,
|
||||
hour_transaction,
|
||||
);
|
||||
|
||||
Reference in New Issue
Block a user