This commit is contained in:
che
2026-07-25 10:31:32 +05:00
parent 42029533fd
commit 860ba21dd0
15 changed files with 1362 additions and 230 deletions
+14 -1
View File
@@ -1,6 +1,6 @@
use che_tauri::{Filter, FilterSet};
use super::models::{Contract, ContractApplicationFile, Counterparty};
use super::models::{Contract, ContractApplicationFile, ContractCategory, Counterparty};
static CONTRACTAPP_FILTERS: &[Filter] = &[
Filter::exact("id"),
@@ -16,6 +16,7 @@ static CONTRACTAPP_FILTERS: &[Filter] = &[
Filter::contains("contract_type"),
Filter::exact("category"),
Filter::contains("category"),
Filter::exact("category_id"),
Filter::contains("counterparty_name"),
Filter::contains("name_of_product"),
Filter::exact("counterparty_id"),
@@ -24,6 +25,14 @@ static CONTRACTAPP_FILTERS: &[Filter] = &[
Filter::exact("employee_id"),
];
static CONTRACT_CATEGORY_FILTERS: &[Filter] = &[
Filter::exact("id"),
Filter::exact("name"),
Filter::contains("name"),
Filter::exact("name_group"),
Filter::contains("name_group"),
];
static COUNTERPARTY_FILTERS: &[Filter] = &[
Filter::exact("id"),
Filter::exact("name"),
@@ -43,6 +52,10 @@ pub fn contractapp_filterset() -> FilterSet<Contract> {
FilterSet::new(CONTRACTAPP_FILTERS)
}
pub fn contract_category_filterset() -> FilterSet<ContractCategory> {
FilterSet::new(CONTRACT_CATEGORY_FILTERS)
}
pub fn counterparty_filterset() -> FilterSet<Counterparty> {
FilterSet::new(COUNTERPARTY_FILTERS)
}
@@ -0,0 +1,11 @@
CREATE TABLE IF NOT EXISTS contract_category (
id INTEGER PRIMARY KEY AUTOINCREMENT,
name TEXT NOT NULL,
name_group TEXT NOT NULL,
template TEXT,
is_questionnair BOOLEAN NOT NULL,
parent INTEGER,
secure_group INTEGER
);
ALTER TABLE contractapp ADD COLUMN category_id INTEGER REFERENCES contract_category(id);
@@ -173,6 +173,88 @@
}
]
},
{
"table": "contract_category",
"fields": [
{
"name": "id",
"ty": "integer",
"primary_key": true,
"nullable": false,
"auto": true,
"unique": false,
"max_length": null,
"default": null,
"foreign_key": null
},
{
"name": "name",
"ty": "text",
"primary_key": false,
"nullable": false,
"auto": false,
"unique": false,
"max_length": null,
"default": null,
"foreign_key": null
},
{
"name": "name_group",
"ty": "text",
"primary_key": false,
"nullable": false,
"auto": false,
"unique": false,
"max_length": null,
"default": null,
"foreign_key": null
},
{
"name": "template",
"ty": "text",
"primary_key": false,
"nullable": true,
"auto": false,
"unique": false,
"max_length": null,
"default": null,
"foreign_key": null
},
{
"name": "is_questionnair",
"ty": "boolean",
"primary_key": false,
"nullable": false,
"auto": false,
"unique": false,
"max_length": null,
"default": null,
"foreign_key": null
},
{
"name": "parent",
"ty": "integer",
"primary_key": false,
"nullable": true,
"auto": false,
"unique": false,
"max_length": null,
"default": null,
"foreign_key": null
},
{
"name": "secure_group",
"ty": "integer",
"primary_key": false,
"nullable": true,
"auto": false,
"unique": false,
"max_length": null,
"default": null,
"foreign_key": null
}
]
},
{
"table": "contractapp",
"fields": [
@@ -242,6 +324,20 @@
"default": null,
"foreign_key": null
},
{
"name": "category_id",
"ty": "integer",
"primary_key": false,
"nullable": true,
"auto": false,
"unique": false,
"max_length": null,
"default": null,
"foreign_key": {
"table": "contract_category",
"column": "id"
}
},
{
"name": "amount_total_display",
"ty": "text",
+5
View File
@@ -21,6 +21,11 @@ impl AppModule for ContractappModule {
serializers::contractapp_serializer(),
filters::contractapp_filterset(),
);
ctx.resource::<models::ContractCategory>(
"contract_category",
serializers::contract_category_serializer(),
filters::contract_category_filterset(),
);
ctx.resource::<models::Counterparty>(
"counterparty",
serializers::counterparty_serializer(),
+18
View File
@@ -15,6 +15,10 @@ pub struct Contract {
pub absolute_url: String,
pub contract_type: String,
pub category: String,
#[field(foreign_key = ContractCategory)]
pub category_id: Option<i64>,
pub amount_total_display: String,
pub amount_by_ds: String,
pub comment: String,
@@ -48,6 +52,20 @@ pub struct Contract {
pub employee_id: Option<i64>,
}
#[derive(Debug, Clone, Model)]
#[model(table = "contract_category")]
pub struct ContractCategory {
#[field(primary_key)]
pub id: i64,
pub name: String,
pub name_group: String,
pub template: Option<String>,
pub is_questionnair: bool,
pub parent: Option<i64>,
pub secure_group: Option<i64>,
}
#[derive(Debug, Clone, Model)]
#[model(table = "counterparty")]
pub struct Counterparty {
+18 -1
View File
@@ -6,7 +6,7 @@ use crate::apps::{
projectapp::{models::Project, serializers::project_serializer},
};
use super::models::{Contract, ContractApplicationFile, Counterparty};
use super::models::{Contract, ContractApplicationFile, ContractCategory, Counterparty};
static CONTRACT_FIELDS: &[Field] = &[
Field::new("id").read_only(),
@@ -15,6 +15,7 @@ static CONTRACT_FIELDS: &[Field] = &[
Field::new("absolute_url"),
Field::new("contract_type"),
Field::new("category"),
Field::new("category_id").required(false).nullable(),
Field::new("amount_total_display"),
Field::new("amount_by_ds"),
Field::new("comment"),
@@ -38,12 +39,22 @@ static CONTRACT_FIELDS: &[Field] = &[
Field::new("project_id").required(false).nullable(),
Field::new("frc_id").required(false).nullable(),
Field::new("employee_id").required(false).nullable(),
Field::related("category_ref", "category_id", &CATEGORY_RELATION),
Field::related("counterparty", "counterparty_id", &COUNTERPARTY_RELATION),
Field::related("project", "project_id", &PROJECT_RELATION),
Field::related("frc", "frc_id", &FRC_RELATION),
Field::related("employee", "employee_id", &EMPLOYEE_RELATION),
];
static CONTRACT_CATEGORY_FIELDS: &[Field] = &[
Field::new("id").read_only(),
Field::new("name"),
Field::new("name_group"),
Field::new("template").required(false).nullable(),
Field::new("is_questionnair"),
Field::new("parent").required(false).nullable(),
Field::new("secure_group").required(false).nullable(),
];
static COUNTERPARTY_FIELDS: &[Field] = &[Field::new("id").read_only(), Field::new("name")];
static CONTRACT_APPLICATION_FILE_FIELDS: &[Field] = &[
Field::new("id").read_only(),
@@ -65,6 +76,8 @@ static CONTRACT_APPLICATION_FILE_FIELDS: &[Field] = &[
];
static COUNTERPARTY_RELATION: RelatedModel<Counterparty> =
RelatedModel::new(counterparty_serializer);
static CATEGORY_RELATION: RelatedModel<ContractCategory> =
RelatedModel::new(contract_category_serializer);
static CONTRACT_RELATION: RelatedModel<Contract> = RelatedModel::new(contractapp_serializer);
static PROJECT_RELATION: RelatedModel<Project> = RelatedModel::new(project_serializer);
static FRC_RELATION: RelatedModel<Frc> = RelatedModel::new(frc_serializer);
@@ -74,6 +87,10 @@ pub fn contractapp_serializer() -> ModelSerializer<Contract> {
ModelSerializer::new(CONTRACT_FIELDS)
}
pub fn contract_category_serializer() -> ModelSerializer<ContractCategory> {
ModelSerializer::new(CONTRACT_CATEGORY_FIELDS)
}
pub fn counterparty_serializer() -> ModelSerializer<Counterparty> {
ModelSerializer::new(COUNTERPARTY_FIELDS)
}
+102 -5
View File
@@ -18,6 +18,12 @@ struct ContractPage {
results: Vec<RemoteContract>,
}
#[derive(Debug, Deserialize)]
struct ContractCategoryPage {
links: PageLinks,
results: Vec<RemoteContractCategory>,
}
#[derive(Debug, Deserialize)]
struct PageLinks {
next: Option<String>,
@@ -34,6 +40,8 @@ struct RemoteContract {
contract_type: String,
#[serde(rename = "get_category")]
category: String,
#[serde(default)]
category_id: Option<i64>,
#[serde(rename = "get_amount_total")]
amount_total_display: String,
#[serde(rename = "get_amount_by_ds")]
@@ -62,6 +70,17 @@ struct RemoteContract {
get_employee: Option<RemoteEmployee>,
}
#[derive(Debug, Deserialize)]
struct RemoteContractCategory {
id: i64,
name: String,
name_group: String,
template: Option<String>,
is_questionnair: bool,
parent: Option<i64>,
secure_group: Option<i64>,
}
#[derive(Debug, Deserialize)]
struct RemoteBillCost {
#[serde(rename = "cost__sum")]
@@ -151,6 +170,8 @@ pub async fn sync_contracts(
.as_ref()
.ok_or_else(|| ApiError::bad_request("sync requires [remote].base_url config"))?;
let client = reqwest::Client::new();
sync_contract_categories(api, &client, &token, &remote.base_url).await?;
let mut next_url = Some(format!(
"{}/api/contract/?page_size=100",
remote.base_url.trim_end_matches('/')
@@ -182,12 +203,14 @@ pub async fn sync_contracts(
for contract in &page.results {
upsert_contract_dependencies(api, contract).await?;
upsert_contract(api, contract).await?;
let detail = fetch_contract_detail(&client, &token, &remote.base_url, contract.id).await?;
let detail =
fetch_contract_detail(&client, &token, &remote.base_url, contract.id).await?;
for application in &detail.contractapplicationfile_set {
let local_path =
download_application_file(&client, &token, &app_data_dir, application).await?;
upsert_contract_application_file(api, contract.id, application, &local_path).await?;
upsert_contract_application_file(api, contract.id, application, &local_path)
.await?;
applications += 1;
if !local_path.is_empty() {
files_downloaded += 1;
@@ -208,6 +231,74 @@ pub async fn sync_contracts(
})
}
async fn sync_contract_categories(
api: &TauriApi,
client: &reqwest::Client,
token: &str,
base_url: &str,
) -> Result<(), ApiError> {
let mut next_url = Some(format!(
"{}/api/cont/category/?page_size=100",
base_url.trim_end_matches('/')
));
while let Some(url) = next_url {
let response = client
.get(url)
.header(reqwest::header::AUTHORIZATION, format!("Token {token}"))
.send()
.await?;
if !response.status().is_success() {
let status = response.status();
let detail = response.text().await.unwrap_or_default();
return Err(ApiError::new(
"remote_error",
format!("remote category request failed with {status}: {detail}"),
));
}
let page = response.json::<ContractCategoryPage>().await?;
for category in &page.results {
upsert_contract_category(api, category).await?;
}
next_url = page.links.next;
}
Ok(())
}
async fn upsert_contract_category(
api: &TauriApi,
category: &RemoteContractCategory,
) -> Result<(), ApiError> {
sqlx::query(
"INSERT INTO contract_category (
id, name, name_group, template, is_questionnair, parent, secure_group
)
VALUES (?1, ?2, ?3, ?4, ?5, ?6, ?7)
ON CONFLICT(id) DO UPDATE SET
name = excluded.name,
name_group = excluded.name_group,
template = excluded.template,
is_questionnair = excluded.is_questionnair,
parent = excluded.parent,
secure_group = excluded.secure_group",
)
.bind(category.id)
.bind(&category.name)
.bind(&category.name_group)
.bind(category.template.as_deref())
.bind(category.is_questionnair)
.bind(category.parent)
.bind(category.secure_group)
.execute(api.state().db().pool())
.await
.map_err(database_error)?;
Ok(())
}
async fn fetch_contract_detail(
client: &reqwest::Client,
token: &str,
@@ -250,7 +341,11 @@ async fn download_application_file(
.await
.map_err(file_error)?;
let file_name = format!("{}_{}", application.id, sanitize_file_name(&application.scan_name));
let file_name = format!(
"{}_{}",
application.id,
sanitize_file_name(&application.scan_name)
);
let path = directory.join(file_name);
let response = client
@@ -379,7 +474,7 @@ async fn upsert_contract_dependencies(
async fn upsert_contract(api: &TauriApi, contract: &RemoteContract) -> Result<(), ApiError> {
sqlx::query(
"INSERT INTO contractapp (
id, name, number, absolute_url, contract_type, category, amount_total_display,
id, name, number, absolute_url, contract_type, category, category_id, amount_total_display,
amount_by_ds, comment, date, status_name, status, nds, name_of_product,
counterparty_name, amount, month_pay, avans_pay, amount_total, estimate_nds_cost,
estimate_nds_cert, bill_cost_sum, bill_paid_sum, income_total, arrears,
@@ -387,7 +482,7 @@ async fn upsert_contract(api: &TauriApi, contract: &RemoteContract) -> Result<()
)
VALUES (
?1, ?2, ?3, ?4, ?5, ?6, ?7, ?8, ?9, ?10, ?11, ?12, ?13, ?14, ?15,
?16, ?17, ?18, ?19, ?20, ?21, ?22, ?23, ?24, ?25, ?26, ?27, ?28, ?29
?16, ?17, ?18, ?19, ?20, ?21, ?22, ?23, ?24, ?25, ?26, ?27, ?28, ?29, ?30
)
ON CONFLICT(id) DO UPDATE SET
name = excluded.name,
@@ -395,6 +490,7 @@ async fn upsert_contract(api: &TauriApi, contract: &RemoteContract) -> Result<()
absolute_url = excluded.absolute_url,
contract_type = excluded.contract_type,
category = excluded.category,
category_id = excluded.category_id,
amount_total_display = excluded.amount_total_display,
amount_by_ds = excluded.amount_by_ds,
comment = excluded.comment,
@@ -425,6 +521,7 @@ async fn upsert_contract(api: &TauriApi, contract: &RemoteContract) -> Result<()
.bind(&contract.absolute_url)
.bind(&contract.contract_type)
.bind(&contract.category)
.bind(contract.category_id)
.bind(&contract.amount_total_display)
.bind(&contract.amount_by_ds)
.bind(&contract.comment)