fix
This commit is contained in:
Generated
+1
@@ -1175,6 +1175,7 @@ version = "0.1.0"
|
|||||||
dependencies = [
|
dependencies = [
|
||||||
"che-orm",
|
"che-orm",
|
||||||
"che-tauri",
|
"che-tauri",
|
||||||
|
"reqwest 0.12.28",
|
||||||
"serde",
|
"serde",
|
||||||
"serde_json",
|
"serde_json",
|
||||||
"tauri",
|
"tauri",
|
||||||
|
|||||||
@@ -26,3 +26,4 @@ tauri-plugin-opener = "2"
|
|||||||
serde = { version = "1", features = ["derive"] }
|
serde = { version = "1", features = ["derive"] }
|
||||||
serde_json = "1"
|
serde_json = "1"
|
||||||
tokio = { version = "1", features = ["macros", "rt-multi-thread"] }
|
tokio = { version = "1", features = ["macros", "rt-multi-thread"] }
|
||||||
|
reqwest = { version = "0.12", features = ["json"] }
|
||||||
|
|||||||
+2
-1
@@ -2,4 +2,5 @@
|
|||||||
url = "sqlite://ewa-mobile.sqlite?mode=rwc"
|
url = "sqlite://ewa-mobile.sqlite?mode=rwc"
|
||||||
|
|
||||||
[remote]
|
[remote]
|
||||||
base_url = "http://localhost:3000"
|
base_url = "http://127.0.0.1:8000"
|
||||||
|
auth_path = "/api-token-auth/"
|
||||||
|
|||||||
@@ -0,0 +1,39 @@
|
|||||||
|
use che_tauri::{Filter, FilterSet};
|
||||||
|
|
||||||
|
use super::models::{Contract, Counterparty};
|
||||||
|
|
||||||
|
static CONTRACTAPP_FILTERS: &[Filter] = &[
|
||||||
|
Filter::exact("id"),
|
||||||
|
Filter::exact("name"),
|
||||||
|
Filter::contains("name"),
|
||||||
|
Filter::exact("number"),
|
||||||
|
Filter::contains("number"),
|
||||||
|
Filter::exact("date"),
|
||||||
|
Filter::exact("status"),
|
||||||
|
Filter::exact("status_name"),
|
||||||
|
Filter::contains("status_name"),
|
||||||
|
Filter::exact("contract_type"),
|
||||||
|
Filter::contains("contract_type"),
|
||||||
|
Filter::exact("category"),
|
||||||
|
Filter::contains("category"),
|
||||||
|
Filter::contains("counterparty_name"),
|
||||||
|
Filter::contains("name_of_product"),
|
||||||
|
Filter::exact("counterparty_id"),
|
||||||
|
Filter::exact("project_id"),
|
||||||
|
Filter::exact("frc_id"),
|
||||||
|
Filter::exact("employee_id"),
|
||||||
|
];
|
||||||
|
|
||||||
|
static COUNTERPARTY_FILTERS: &[Filter] = &[
|
||||||
|
Filter::exact("id"),
|
||||||
|
Filter::exact("name"),
|
||||||
|
Filter::contains("name"),
|
||||||
|
];
|
||||||
|
|
||||||
|
pub fn contractapp_filterset() -> FilterSet<Contract> {
|
||||||
|
FilterSet::new(CONTRACTAPP_FILTERS)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn counterparty_filterset() -> FilterSet<Counterparty> {
|
||||||
|
FilterSet::new(COUNTERPARTY_FILTERS)
|
||||||
|
}
|
||||||
@@ -0,0 +1,5 @@
|
|||||||
|
CREATE TABLE IF NOT EXISTS contractapp (
|
||||||
|
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||||
|
name TEXT NOT NULL,
|
||||||
|
number TEXT NOT NULL
|
||||||
|
);
|
||||||
@@ -0,0 +1,6 @@
|
|||||||
|
CREATE TABLE IF NOT EXISTS counterparty (
|
||||||
|
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||||
|
name TEXT NOT NULL
|
||||||
|
);
|
||||||
|
|
||||||
|
ALTER TABLE contractapp ADD COLUMN counterparty_id INTEGER REFERENCES counterparty(id);
|
||||||
@@ -0,0 +1,5 @@
|
|||||||
|
ALTER TABLE contractapp ADD COLUMN project_id INTEGER REFERENCES project(id);
|
||||||
|
|
||||||
|
ALTER TABLE contractapp ADD COLUMN frc_id INTEGER REFERENCES frc(id);
|
||||||
|
|
||||||
|
ALTER TABLE contractapp ADD COLUMN employee_id INTEGER REFERENCES employee(id);
|
||||||
@@ -0,0 +1,43 @@
|
|||||||
|
ALTER TABLE contractapp ADD COLUMN absolute_url TEXT NOT NULL DEFAULT '';
|
||||||
|
|
||||||
|
ALTER TABLE contractapp ADD COLUMN contract_type TEXT NOT NULL DEFAULT '';
|
||||||
|
|
||||||
|
ALTER TABLE contractapp ADD COLUMN category TEXT NOT NULL DEFAULT '';
|
||||||
|
|
||||||
|
ALTER TABLE contractapp ADD COLUMN amount_total_display TEXT NOT NULL DEFAULT '';
|
||||||
|
|
||||||
|
ALTER TABLE contractapp ADD COLUMN amount_by_ds TEXT NOT NULL DEFAULT '';
|
||||||
|
|
||||||
|
ALTER TABLE contractapp ADD COLUMN comment TEXT NOT NULL DEFAULT '';
|
||||||
|
|
||||||
|
ALTER TABLE contractapp ADD COLUMN date TEXT NOT NULL DEFAULT '';
|
||||||
|
|
||||||
|
ALTER TABLE contractapp ADD COLUMN status_name TEXT NOT NULL DEFAULT '';
|
||||||
|
|
||||||
|
ALTER TABLE contractapp ADD COLUMN status TEXT NOT NULL DEFAULT '';
|
||||||
|
|
||||||
|
ALTER TABLE contractapp ADD COLUMN nds TEXT NOT NULL DEFAULT '';
|
||||||
|
|
||||||
|
ALTER TABLE contractapp ADD COLUMN name_of_product TEXT NOT NULL DEFAULT '';
|
||||||
|
|
||||||
|
ALTER TABLE contractapp ADD COLUMN counterparty_name TEXT NOT NULL DEFAULT '';
|
||||||
|
|
||||||
|
ALTER TABLE contractapp ADD COLUMN amount REAL NOT NULL DEFAULT 0;
|
||||||
|
|
||||||
|
ALTER TABLE contractapp ADD COLUMN month_pay REAL;
|
||||||
|
|
||||||
|
ALTER TABLE contractapp ADD COLUMN avans_pay TEXT NOT NULL DEFAULT '';
|
||||||
|
|
||||||
|
ALTER TABLE contractapp ADD COLUMN amount_total REAL NOT NULL DEFAULT 0;
|
||||||
|
|
||||||
|
ALTER TABLE contractapp ADD COLUMN estimate_nds_cost REAL NOT NULL DEFAULT 0;
|
||||||
|
|
||||||
|
ALTER TABLE contractapp ADD COLUMN estimate_nds_cert REAL NOT NULL DEFAULT 0;
|
||||||
|
|
||||||
|
ALTER TABLE contractapp ADD COLUMN bill_cost_sum REAL;
|
||||||
|
|
||||||
|
ALTER TABLE contractapp ADD COLUMN bill_paid_sum REAL;
|
||||||
|
|
||||||
|
ALTER TABLE contractapp ADD COLUMN income_total REAL NOT NULL DEFAULT 0;
|
||||||
|
|
||||||
|
ALTER TABLE contractapp ADD COLUMN arrears REAL NOT NULL DEFAULT 0;
|
||||||
@@ -0,0 +1,367 @@
|
|||||||
|
{
|
||||||
|
"models": [
|
||||||
|
{
|
||||||
|
"table": "contractapp",
|
||||||
|
"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": "number",
|
||||||
|
"ty": "text",
|
||||||
|
"primary_key": false,
|
||||||
|
"nullable": false,
|
||||||
|
"auto": false,
|
||||||
|
"unique": false,
|
||||||
|
"max_length": null,
|
||||||
|
"default": null,
|
||||||
|
"foreign_key": null
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "absolute_url",
|
||||||
|
"ty": "text",
|
||||||
|
"primary_key": false,
|
||||||
|
"nullable": false,
|
||||||
|
"auto": false,
|
||||||
|
"unique": false,
|
||||||
|
"max_length": null,
|
||||||
|
"default": null,
|
||||||
|
"foreign_key": null
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "contract_type",
|
||||||
|
"ty": "text",
|
||||||
|
"primary_key": false,
|
||||||
|
"nullable": false,
|
||||||
|
"auto": false,
|
||||||
|
"unique": false,
|
||||||
|
"max_length": null,
|
||||||
|
"default": null,
|
||||||
|
"foreign_key": null
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "category",
|
||||||
|
"ty": "text",
|
||||||
|
"primary_key": false,
|
||||||
|
"nullable": false,
|
||||||
|
"auto": false,
|
||||||
|
"unique": false,
|
||||||
|
"max_length": null,
|
||||||
|
"default": null,
|
||||||
|
"foreign_key": null
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "amount_total_display",
|
||||||
|
"ty": "text",
|
||||||
|
"primary_key": false,
|
||||||
|
"nullable": false,
|
||||||
|
"auto": false,
|
||||||
|
"unique": false,
|
||||||
|
"max_length": null,
|
||||||
|
"default": null,
|
||||||
|
"foreign_key": null
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "amount_by_ds",
|
||||||
|
"ty": "text",
|
||||||
|
"primary_key": false,
|
||||||
|
"nullable": false,
|
||||||
|
"auto": false,
|
||||||
|
"unique": false,
|
||||||
|
"max_length": null,
|
||||||
|
"default": null,
|
||||||
|
"foreign_key": null
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "comment",
|
||||||
|
"ty": "text",
|
||||||
|
"primary_key": false,
|
||||||
|
"nullable": false,
|
||||||
|
"auto": false,
|
||||||
|
"unique": false,
|
||||||
|
"max_length": null,
|
||||||
|
"default": null,
|
||||||
|
"foreign_key": null
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "date",
|
||||||
|
"ty": "text",
|
||||||
|
"primary_key": false,
|
||||||
|
"nullable": false,
|
||||||
|
"auto": false,
|
||||||
|
"unique": false,
|
||||||
|
"max_length": null,
|
||||||
|
"default": null,
|
||||||
|
"foreign_key": null
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "status_name",
|
||||||
|
"ty": "text",
|
||||||
|
"primary_key": false,
|
||||||
|
"nullable": false,
|
||||||
|
"auto": false,
|
||||||
|
"unique": false,
|
||||||
|
"max_length": null,
|
||||||
|
"default": null,
|
||||||
|
"foreign_key": null
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "status",
|
||||||
|
"ty": "text",
|
||||||
|
"primary_key": false,
|
||||||
|
"nullable": false,
|
||||||
|
"auto": false,
|
||||||
|
"unique": false,
|
||||||
|
"max_length": null,
|
||||||
|
"default": null,
|
||||||
|
"foreign_key": null
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "nds",
|
||||||
|
"ty": "text",
|
||||||
|
"primary_key": false,
|
||||||
|
"nullable": false,
|
||||||
|
"auto": false,
|
||||||
|
"unique": false,
|
||||||
|
"max_length": null,
|
||||||
|
"default": null,
|
||||||
|
"foreign_key": null
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "name_of_product",
|
||||||
|
"ty": "text",
|
||||||
|
"primary_key": false,
|
||||||
|
"nullable": false,
|
||||||
|
"auto": false,
|
||||||
|
"unique": false,
|
||||||
|
"max_length": null,
|
||||||
|
"default": null,
|
||||||
|
"foreign_key": null
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "counterparty_name",
|
||||||
|
"ty": "text",
|
||||||
|
"primary_key": false,
|
||||||
|
"nullable": false,
|
||||||
|
"auto": false,
|
||||||
|
"unique": false,
|
||||||
|
"max_length": null,
|
||||||
|
"default": null,
|
||||||
|
"foreign_key": null
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "amount",
|
||||||
|
"ty": "real",
|
||||||
|
"primary_key": false,
|
||||||
|
"nullable": false,
|
||||||
|
"auto": false,
|
||||||
|
"unique": false,
|
||||||
|
"max_length": null,
|
||||||
|
"default": null,
|
||||||
|
"foreign_key": null
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "month_pay",
|
||||||
|
"ty": "real",
|
||||||
|
"primary_key": false,
|
||||||
|
"nullable": true,
|
||||||
|
"auto": false,
|
||||||
|
"unique": false,
|
||||||
|
"max_length": null,
|
||||||
|
"default": null,
|
||||||
|
"foreign_key": null
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "avans_pay",
|
||||||
|
"ty": "text",
|
||||||
|
"primary_key": false,
|
||||||
|
"nullable": false,
|
||||||
|
"auto": false,
|
||||||
|
"unique": false,
|
||||||
|
"max_length": null,
|
||||||
|
"default": null,
|
||||||
|
"foreign_key": null
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "amount_total",
|
||||||
|
"ty": "real",
|
||||||
|
"primary_key": false,
|
||||||
|
"nullable": false,
|
||||||
|
"auto": false,
|
||||||
|
"unique": false,
|
||||||
|
"max_length": null,
|
||||||
|
"default": null,
|
||||||
|
"foreign_key": null
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "estimate_nds_cost",
|
||||||
|
"ty": "real",
|
||||||
|
"primary_key": false,
|
||||||
|
"nullable": false,
|
||||||
|
"auto": false,
|
||||||
|
"unique": false,
|
||||||
|
"max_length": null,
|
||||||
|
"default": null,
|
||||||
|
"foreign_key": null
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "estimate_nds_cert",
|
||||||
|
"ty": "real",
|
||||||
|
"primary_key": false,
|
||||||
|
"nullable": false,
|
||||||
|
"auto": false,
|
||||||
|
"unique": false,
|
||||||
|
"max_length": null,
|
||||||
|
"default": null,
|
||||||
|
"foreign_key": null
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "bill_cost_sum",
|
||||||
|
"ty": "real",
|
||||||
|
"primary_key": false,
|
||||||
|
"nullable": true,
|
||||||
|
"auto": false,
|
||||||
|
"unique": false,
|
||||||
|
"max_length": null,
|
||||||
|
"default": null,
|
||||||
|
"foreign_key": null
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "bill_paid_sum",
|
||||||
|
"ty": "real",
|
||||||
|
"primary_key": false,
|
||||||
|
"nullable": true,
|
||||||
|
"auto": false,
|
||||||
|
"unique": false,
|
||||||
|
"max_length": null,
|
||||||
|
"default": null,
|
||||||
|
"foreign_key": null
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "income_total",
|
||||||
|
"ty": "real",
|
||||||
|
"primary_key": false,
|
||||||
|
"nullable": false,
|
||||||
|
"auto": false,
|
||||||
|
"unique": false,
|
||||||
|
"max_length": null,
|
||||||
|
"default": null,
|
||||||
|
"foreign_key": null
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "arrears",
|
||||||
|
"ty": "real",
|
||||||
|
"primary_key": false,
|
||||||
|
"nullable": false,
|
||||||
|
"auto": false,
|
||||||
|
"unique": false,
|
||||||
|
"max_length": null,
|
||||||
|
"default": null,
|
||||||
|
"foreign_key": null
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "counterparty_id",
|
||||||
|
"ty": "integer",
|
||||||
|
"primary_key": false,
|
||||||
|
"nullable": true,
|
||||||
|
"auto": false,
|
||||||
|
"unique": false,
|
||||||
|
"max_length": null,
|
||||||
|
"default": null,
|
||||||
|
"foreign_key": {
|
||||||
|
"table": "counterparty",
|
||||||
|
"column": "id"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "project_id",
|
||||||
|
"ty": "integer",
|
||||||
|
"primary_key": false,
|
||||||
|
"nullable": true,
|
||||||
|
"auto": false,
|
||||||
|
"unique": false,
|
||||||
|
"max_length": null,
|
||||||
|
"default": null,
|
||||||
|
"foreign_key": {
|
||||||
|
"table": "project",
|
||||||
|
"column": "id"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "frc_id",
|
||||||
|
"ty": "integer",
|
||||||
|
"primary_key": false,
|
||||||
|
"nullable": true,
|
||||||
|
"auto": false,
|
||||||
|
"unique": false,
|
||||||
|
"max_length": null,
|
||||||
|
"default": null,
|
||||||
|
"foreign_key": {
|
||||||
|
"table": "frc",
|
||||||
|
"column": "id"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "employee_id",
|
||||||
|
"ty": "integer",
|
||||||
|
"primary_key": false,
|
||||||
|
"nullable": true,
|
||||||
|
"auto": false,
|
||||||
|
"unique": false,
|
||||||
|
"max_length": null,
|
||||||
|
"default": null,
|
||||||
|
"foreign_key": {
|
||||||
|
"table": "employee",
|
||||||
|
"column": "id"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"table": "counterparty",
|
||||||
|
"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
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
@@ -0,0 +1,30 @@
|
|||||||
|
pub mod filters;
|
||||||
|
pub mod models;
|
||||||
|
pub mod serializers;
|
||||||
|
|
||||||
|
use che_tauri::{AppModule, ModuleContext};
|
||||||
|
|
||||||
|
pub fn module() -> ContractappModule {
|
||||||
|
ContractappModule
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct ContractappModule;
|
||||||
|
|
||||||
|
impl AppModule for ContractappModule {
|
||||||
|
fn name(&self) -> &'static str {
|
||||||
|
"contractapp"
|
||||||
|
}
|
||||||
|
|
||||||
|
fn init(&self, ctx: &mut ModuleContext) {
|
||||||
|
ctx.resource::<models::Contract>(
|
||||||
|
"contract",
|
||||||
|
serializers::contractapp_serializer(),
|
||||||
|
filters::contractapp_filterset(),
|
||||||
|
);
|
||||||
|
ctx.resource::<models::Counterparty>(
|
||||||
|
"counterparty",
|
||||||
|
serializers::counterparty_serializer(),
|
||||||
|
filters::counterparty_filterset(),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,58 @@
|
|||||||
|
use che_orm::Model;
|
||||||
|
|
||||||
|
use crate::apps::{
|
||||||
|
frcapp::models::Frc, personemanagment::models::Employee, projectapp::models::Project,
|
||||||
|
};
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, Model)]
|
||||||
|
#[model(table = "contractapp")]
|
||||||
|
pub struct Contract {
|
||||||
|
#[field(primary_key)]
|
||||||
|
pub id: i64,
|
||||||
|
|
||||||
|
pub name: String,
|
||||||
|
pub number: String,
|
||||||
|
pub absolute_url: String,
|
||||||
|
pub contract_type: String,
|
||||||
|
pub category: String,
|
||||||
|
pub amount_total_display: String,
|
||||||
|
pub amount_by_ds: String,
|
||||||
|
pub comment: String,
|
||||||
|
pub date: String,
|
||||||
|
pub status_name: String,
|
||||||
|
pub status: String,
|
||||||
|
pub nds: String,
|
||||||
|
pub name_of_product: String,
|
||||||
|
pub counterparty_name: String,
|
||||||
|
pub amount: f64,
|
||||||
|
pub month_pay: Option<f64>,
|
||||||
|
pub avans_pay: String,
|
||||||
|
pub amount_total: f64,
|
||||||
|
pub estimate_nds_cost: f64,
|
||||||
|
pub estimate_nds_cert: f64,
|
||||||
|
pub bill_cost_sum: Option<f64>,
|
||||||
|
pub bill_paid_sum: Option<f64>,
|
||||||
|
pub income_total: f64,
|
||||||
|
pub arrears: f64,
|
||||||
|
|
||||||
|
#[field(foreign_key = Counterparty)]
|
||||||
|
pub counterparty_id: Option<i64>,
|
||||||
|
|
||||||
|
#[field(foreign_key = Project)]
|
||||||
|
pub project_id: Option<i64>,
|
||||||
|
|
||||||
|
#[field(foreign_key = Frc)]
|
||||||
|
pub frc_id: Option<i64>,
|
||||||
|
|
||||||
|
#[field(foreign_key = Employee)]
|
||||||
|
pub employee_id: Option<i64>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, Model)]
|
||||||
|
#[model(table = "counterparty")]
|
||||||
|
pub struct Counterparty {
|
||||||
|
#[field(primary_key)]
|
||||||
|
pub id: i64,
|
||||||
|
|
||||||
|
pub name: String,
|
||||||
|
}
|
||||||
@@ -0,0 +1,60 @@
|
|||||||
|
use che_tauri::{Field, ModelSerializer, RelatedModel};
|
||||||
|
|
||||||
|
use crate::apps::{
|
||||||
|
frcapp::{models::Frc, serializers::frc_serializer},
|
||||||
|
personemanagment::{models::Employee, serializers::employee_serializer},
|
||||||
|
projectapp::{models::Project, serializers::project_serializer},
|
||||||
|
};
|
||||||
|
|
||||||
|
use super::models::{Contract, Counterparty};
|
||||||
|
|
||||||
|
static CONTRACT_FIELDS: &[Field] = &[
|
||||||
|
Field::new("id").read_only(),
|
||||||
|
Field::new("name"),
|
||||||
|
Field::new("number"),
|
||||||
|
Field::new("absolute_url"),
|
||||||
|
Field::new("contract_type"),
|
||||||
|
Field::new("category"),
|
||||||
|
Field::new("amount_total_display"),
|
||||||
|
Field::new("amount_by_ds"),
|
||||||
|
Field::new("comment"),
|
||||||
|
Field::new("date"),
|
||||||
|
Field::new("status_name"),
|
||||||
|
Field::new("status"),
|
||||||
|
Field::new("nds"),
|
||||||
|
Field::new("name_of_product"),
|
||||||
|
Field::new("counterparty_name"),
|
||||||
|
Field::new("amount"),
|
||||||
|
Field::new("month_pay").required(false).nullable(),
|
||||||
|
Field::new("avans_pay"),
|
||||||
|
Field::new("amount_total"),
|
||||||
|
Field::new("estimate_nds_cost"),
|
||||||
|
Field::new("estimate_nds_cert"),
|
||||||
|
Field::new("bill_cost_sum").required(false).nullable(),
|
||||||
|
Field::new("bill_paid_sum").required(false).nullable(),
|
||||||
|
Field::new("income_total"),
|
||||||
|
Field::new("arrears"),
|
||||||
|
Field::new("counterparty_id").required(false).nullable(),
|
||||||
|
Field::new("project_id").required(false).nullable(),
|
||||||
|
Field::new("frc_id").required(false).nullable(),
|
||||||
|
Field::new("employee_id").required(false).nullable(),
|
||||||
|
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 COUNTERPARTY_FIELDS: &[Field] = &[Field::new("id").read_only(), Field::new("name")];
|
||||||
|
static COUNTERPARTY_RELATION: RelatedModel<Counterparty> =
|
||||||
|
RelatedModel::new(counterparty_serializer);
|
||||||
|
static PROJECT_RELATION: RelatedModel<Project> = RelatedModel::new(project_serializer);
|
||||||
|
static FRC_RELATION: RelatedModel<Frc> = RelatedModel::new(frc_serializer);
|
||||||
|
static EMPLOYEE_RELATION: RelatedModel<Employee> = RelatedModel::new(employee_serializer);
|
||||||
|
|
||||||
|
pub fn contractapp_serializer() -> ModelSerializer<Contract> {
|
||||||
|
ModelSerializer::new(CONTRACT_FIELDS)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn counterparty_serializer() -> ModelSerializer<Counterparty> {
|
||||||
|
ModelSerializer::new(COUNTERPARTY_FIELDS)
|
||||||
|
}
|
||||||
@@ -0,0 +1,13 @@
|
|||||||
|
use che_tauri::{Filter, FilterSet};
|
||||||
|
|
||||||
|
use super::models::Frc;
|
||||||
|
|
||||||
|
static FRC_FILTERS: &[Filter] = &[
|
||||||
|
Filter::exact("id"),
|
||||||
|
Filter::exact("name"),
|
||||||
|
Filter::contains("name"),
|
||||||
|
];
|
||||||
|
|
||||||
|
pub fn frc_filterset() -> FilterSet<Frc> {
|
||||||
|
FilterSet::new(FRC_FILTERS)
|
||||||
|
}
|
||||||
@@ -0,0 +1,6 @@
|
|||||||
|
CREATE TABLE IF NOT EXISTS frc (
|
||||||
|
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||||
|
name TEXT NOT NULL,
|
||||||
|
icon TEXT NOT NULL,
|
||||||
|
balance REAL NOT NULL
|
||||||
|
);
|
||||||
@@ -0,0 +1,53 @@
|
|||||||
|
{
|
||||||
|
"models": [
|
||||||
|
{
|
||||||
|
"table": "frc",
|
||||||
|
"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": "icon",
|
||||||
|
"ty": "text",
|
||||||
|
"primary_key": false,
|
||||||
|
"nullable": false,
|
||||||
|
"auto": false,
|
||||||
|
"unique": false,
|
||||||
|
"max_length": null,
|
||||||
|
"default": null,
|
||||||
|
"foreign_key": null
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "balance",
|
||||||
|
"ty": "real",
|
||||||
|
"primary_key": false,
|
||||||
|
"nullable": false,
|
||||||
|
"auto": false,
|
||||||
|
"unique": false,
|
||||||
|
"max_length": null,
|
||||||
|
"default": null,
|
||||||
|
"foreign_key": null
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
@@ -0,0 +1,25 @@
|
|||||||
|
pub mod filters;
|
||||||
|
pub mod models;
|
||||||
|
pub mod serializers;
|
||||||
|
|
||||||
|
use che_tauri::{AppModule, ModuleContext};
|
||||||
|
|
||||||
|
pub fn module() -> FrcModule {
|
||||||
|
FrcModule
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct FrcModule;
|
||||||
|
|
||||||
|
impl AppModule for FrcModule {
|
||||||
|
fn name(&self) -> &'static str {
|
||||||
|
"frcapp"
|
||||||
|
}
|
||||||
|
|
||||||
|
fn init(&self, ctx: &mut ModuleContext) {
|
||||||
|
ctx.resource::<models::Frc>(
|
||||||
|
"frc",
|
||||||
|
serializers::frc_serializer(),
|
||||||
|
filters::frc_filterset(),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,12 @@
|
|||||||
|
use che_orm::Model;
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, Model)]
|
||||||
|
#[model(table = "frc")]
|
||||||
|
pub struct Frc {
|
||||||
|
#[field(primary_key)]
|
||||||
|
pub id: i64,
|
||||||
|
|
||||||
|
pub name: String,
|
||||||
|
pub icon: String,
|
||||||
|
pub balance: f64,
|
||||||
|
}
|
||||||
@@ -0,0 +1,14 @@
|
|||||||
|
use che_tauri::{Field, ModelSerializer};
|
||||||
|
|
||||||
|
use super::models::Frc;
|
||||||
|
|
||||||
|
static FRC_FIELDS: &[Field] = &[
|
||||||
|
Field::new("id").read_only(),
|
||||||
|
Field::new("name"),
|
||||||
|
Field::new("icon"),
|
||||||
|
Field::new("balance"),
|
||||||
|
];
|
||||||
|
|
||||||
|
pub fn frc_serializer() -> ModelSerializer<Frc> {
|
||||||
|
ModelSerializer::new(FRC_FIELDS)
|
||||||
|
}
|
||||||
@@ -1,4 +1,6 @@
|
|||||||
|
pub mod frcapp;
|
||||||
pub mod personemanagment;
|
pub mod personemanagment;
|
||||||
|
pub mod projectapp;
|
||||||
pub mod users;
|
pub mod users;
|
||||||
|
|
||||||
use che_tauri::InstalledApps;
|
use che_tauri::InstalledApps;
|
||||||
@@ -6,5 +8,9 @@ use che_tauri::InstalledApps;
|
|||||||
pub fn installed_apps() -> InstalledApps {
|
pub fn installed_apps() -> InstalledApps {
|
||||||
InstalledApps::new()
|
InstalledApps::new()
|
||||||
.add(users::module())
|
.add(users::module())
|
||||||
|
.add(frcapp::module())
|
||||||
|
.add(projectapp::module())
|
||||||
.add(personemanagment::module())
|
.add(personemanagment::module())
|
||||||
|
.add(contractapp::module())
|
||||||
}
|
}
|
||||||
|
pub mod contractapp;
|
||||||
|
|||||||
@@ -6,6 +6,8 @@ static EMPLOYEE_FILTERS: &[Filter] = &[
|
|||||||
Filter::exact("id"),
|
Filter::exact("id"),
|
||||||
Filter::exact("name"),
|
Filter::exact("name"),
|
||||||
Filter::contains("name"),
|
Filter::contains("name"),
|
||||||
|
Filter::exact("short_name"),
|
||||||
|
Filter::contains("short_name"),
|
||||||
];
|
];
|
||||||
|
|
||||||
static TASK_FILTERS: &[Filter] = &[
|
static TASK_FILTERS: &[Filter] = &[
|
||||||
|
|||||||
@@ -0,0 +1,5 @@
|
|||||||
|
ALTER TABLE employee ADD COLUMN short_name TEXT NOT NULL DEFAULT '';
|
||||||
|
|
||||||
|
ALTER TABLE employee ADD COLUMN avatar_small TEXT;
|
||||||
|
|
||||||
|
ALTER TABLE employee DROP COLUMN avatar;
|
||||||
@@ -26,7 +26,7 @@
|
|||||||
"foreign_key": null
|
"foreign_key": null
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "avatar",
|
"name": "short_name",
|
||||||
"ty": "text",
|
"ty": "text",
|
||||||
"primary_key": false,
|
"primary_key": false,
|
||||||
"nullable": false,
|
"nullable": false,
|
||||||
@@ -35,6 +35,17 @@
|
|||||||
"max_length": null,
|
"max_length": null,
|
||||||
"default": null,
|
"default": null,
|
||||||
"foreign_key": null
|
"foreign_key": null
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "avatar_small",
|
||||||
|
"ty": "text",
|
||||||
|
"primary_key": false,
|
||||||
|
"nullable": true,
|
||||||
|
"auto": false,
|
||||||
|
"unique": false,
|
||||||
|
"max_length": null,
|
||||||
|
"default": null,
|
||||||
|
"foreign_key": null
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -8,7 +8,9 @@ pub struct Employee {
|
|||||||
|
|
||||||
pub name: String,
|
pub name: String,
|
||||||
|
|
||||||
pub avatar: String,
|
pub short_name: String,
|
||||||
|
|
||||||
|
pub avatar_small: Option<String>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone, Model)]
|
#[derive(Debug, Clone, Model)]
|
||||||
|
|||||||
@@ -5,7 +5,8 @@ use super::models::{Employee, Message, Task};
|
|||||||
static EMPLOYEE_FIELDS: &[Field] = &[
|
static EMPLOYEE_FIELDS: &[Field] = &[
|
||||||
Field::new("id").read_only(),
|
Field::new("id").read_only(),
|
||||||
Field::new("name"),
|
Field::new("name"),
|
||||||
Field::new("avatar"),
|
Field::new("short_name"),
|
||||||
|
Field::new("avatar_small").required(false).nullable(),
|
||||||
];
|
];
|
||||||
|
|
||||||
static TASK_FIELDS: &[Field] = &[
|
static TASK_FIELDS: &[Field] = &[
|
||||||
|
|||||||
@@ -0,0 +1,15 @@
|
|||||||
|
use che_tauri::{Filter, FilterSet};
|
||||||
|
|
||||||
|
use super::models::Project;
|
||||||
|
|
||||||
|
static PROJECT_FILTERS: &[Filter] = &[
|
||||||
|
Filter::exact("id"),
|
||||||
|
Filter::exact("name"),
|
||||||
|
Filter::contains("name"),
|
||||||
|
Filter::exact("short_name"),
|
||||||
|
Filter::contains("short_name"),
|
||||||
|
];
|
||||||
|
|
||||||
|
pub fn project_filterset() -> FilterSet<Project> {
|
||||||
|
FilterSet::new(PROJECT_FILTERS)
|
||||||
|
}
|
||||||
@@ -0,0 +1,8 @@
|
|||||||
|
CREATE TABLE IF NOT EXISTS project (
|
||||||
|
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||||
|
name TEXT NOT NULL,
|
||||||
|
full_name TEXT NOT NULL,
|
||||||
|
short_name TEXT NOT NULL,
|
||||||
|
locality_id INTEGER,
|
||||||
|
locality_name TEXT
|
||||||
|
);
|
||||||
@@ -0,0 +1,75 @@
|
|||||||
|
{
|
||||||
|
"models": [
|
||||||
|
{
|
||||||
|
"table": "project",
|
||||||
|
"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": "full_name",
|
||||||
|
"ty": "text",
|
||||||
|
"primary_key": false,
|
||||||
|
"nullable": false,
|
||||||
|
"auto": false,
|
||||||
|
"unique": false,
|
||||||
|
"max_length": null,
|
||||||
|
"default": null,
|
||||||
|
"foreign_key": null
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "short_name",
|
||||||
|
"ty": "text",
|
||||||
|
"primary_key": false,
|
||||||
|
"nullable": false,
|
||||||
|
"auto": false,
|
||||||
|
"unique": false,
|
||||||
|
"max_length": null,
|
||||||
|
"default": null,
|
||||||
|
"foreign_key": null
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "locality_id",
|
||||||
|
"ty": "integer",
|
||||||
|
"primary_key": false,
|
||||||
|
"nullable": true,
|
||||||
|
"auto": false,
|
||||||
|
"unique": false,
|
||||||
|
"max_length": null,
|
||||||
|
"default": null,
|
||||||
|
"foreign_key": null
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "locality_name",
|
||||||
|
"ty": "text",
|
||||||
|
"primary_key": false,
|
||||||
|
"nullable": true,
|
||||||
|
"auto": false,
|
||||||
|
"unique": false,
|
||||||
|
"max_length": null,
|
||||||
|
"default": null,
|
||||||
|
"foreign_key": null
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
@@ -0,0 +1,25 @@
|
|||||||
|
pub mod filters;
|
||||||
|
pub mod models;
|
||||||
|
pub mod serializers;
|
||||||
|
|
||||||
|
use che_tauri::{AppModule, ModuleContext};
|
||||||
|
|
||||||
|
pub fn module() -> ProjectModule {
|
||||||
|
ProjectModule
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct ProjectModule;
|
||||||
|
|
||||||
|
impl AppModule for ProjectModule {
|
||||||
|
fn name(&self) -> &'static str {
|
||||||
|
"projectapp"
|
||||||
|
}
|
||||||
|
|
||||||
|
fn init(&self, ctx: &mut ModuleContext) {
|
||||||
|
ctx.resource::<models::Project>(
|
||||||
|
"project",
|
||||||
|
serializers::project_serializer(),
|
||||||
|
filters::project_filterset(),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,14 @@
|
|||||||
|
use che_orm::Model;
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, Model)]
|
||||||
|
#[model(table = "project")]
|
||||||
|
pub struct Project {
|
||||||
|
#[field(primary_key)]
|
||||||
|
pub id: i64,
|
||||||
|
|
||||||
|
pub name: String,
|
||||||
|
pub full_name: String,
|
||||||
|
pub short_name: String,
|
||||||
|
pub locality_id: Option<i64>,
|
||||||
|
pub locality_name: Option<String>,
|
||||||
|
}
|
||||||
@@ -0,0 +1,16 @@
|
|||||||
|
use che_tauri::{Field, ModelSerializer};
|
||||||
|
|
||||||
|
use super::models::Project;
|
||||||
|
|
||||||
|
static PROJECT_FIELDS: &[Field] = &[
|
||||||
|
Field::new("id").read_only(),
|
||||||
|
Field::new("name"),
|
||||||
|
Field::new("full_name"),
|
||||||
|
Field::new("short_name"),
|
||||||
|
Field::new("locality_id").required(false).nullable(),
|
||||||
|
Field::new("locality_name").required(false).nullable(),
|
||||||
|
];
|
||||||
|
|
||||||
|
pub fn project_serializer() -> ModelSerializer<Project> {
|
||||||
|
ModelSerializer::new(PROJECT_FIELDS)
|
||||||
|
}
|
||||||
+49
-2
@@ -1,6 +1,9 @@
|
|||||||
pub mod apps;
|
pub mod apps;
|
||||||
|
pub mod sync;
|
||||||
|
|
||||||
use che_tauri::{ApiError, ApiRequest, AppState, TauriApi};
|
use che_tauri::{ApiError, ApiRequest, AppState, AuthTokenResponse, TauriApi};
|
||||||
|
|
||||||
|
use crate::sync::SyncContractsResult;
|
||||||
|
|
||||||
#[tauri::command]
|
#[tauri::command]
|
||||||
fn greet(name: &str) -> String {
|
fn greet(name: &str) -> String {
|
||||||
@@ -15,8 +18,44 @@ async fn che_api(
|
|||||||
api.dispatch(request).await
|
api.dispatch(request).await
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[tauri::command]
|
||||||
|
async fn auth_login(
|
||||||
|
api: tauri::State<'_, TauriApi>,
|
||||||
|
username: String,
|
||||||
|
password: String,
|
||||||
|
) -> Result<AuthTokenResponse, ApiError> {
|
||||||
|
api.login(username, password).await
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tauri::command]
|
||||||
|
fn auth_set_token(api: tauri::State<'_, TauriApi>, token: String) {
|
||||||
|
api.set_auth_token(token);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tauri::command]
|
||||||
|
fn auth_logout(api: tauri::State<'_, TauriApi>) {
|
||||||
|
api.logout();
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tauri::command]
|
||||||
|
fn auth_status(api: tauri::State<'_, TauriApi>) -> bool {
|
||||||
|
api.is_authenticated()
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tauri::command]
|
||||||
|
async fn sync_contracts(api: tauri::State<'_, TauriApi>) -> Result<SyncContractsResult, ApiError> {
|
||||||
|
sync::sync_contracts(&api).await
|
||||||
|
}
|
||||||
|
|
||||||
#[cfg_attr(mobile, tauri::mobile_entry_point)]
|
#[cfg_attr(mobile, tauri::mobile_entry_point)]
|
||||||
pub fn run() {
|
pub fn run() {
|
||||||
|
#[cfg(target_os = "linux")]
|
||||||
|
if std::env::var_os("WAYLAND_DISPLAY").is_some()
|
||||||
|
&& std::env::var_os("WEBKIT_DISABLE_DMABUF_RENDERER").is_none()
|
||||||
|
{
|
||||||
|
std::env::set_var("WEBKIT_DISABLE_DMABUF_RENDERER", "1");
|
||||||
|
}
|
||||||
|
|
||||||
let api = tauri::async_runtime::block_on(async {
|
let api = tauri::async_runtime::block_on(async {
|
||||||
let state = AppState::from_config_file("app.toml").await?;
|
let state = AppState::from_config_file("app.toml").await?;
|
||||||
TauriApi::new(state)
|
TauriApi::new(state)
|
||||||
@@ -29,7 +68,15 @@ pub fn run() {
|
|||||||
tauri::Builder::default()
|
tauri::Builder::default()
|
||||||
.manage(api)
|
.manage(api)
|
||||||
.plugin(tauri_plugin_opener::init())
|
.plugin(tauri_plugin_opener::init())
|
||||||
.invoke_handler(tauri::generate_handler![greet, che_api])
|
.invoke_handler(tauri::generate_handler![
|
||||||
|
greet,
|
||||||
|
che_api,
|
||||||
|
auth_login,
|
||||||
|
auth_set_token,
|
||||||
|
auth_logout,
|
||||||
|
auth_status,
|
||||||
|
sync_contracts
|
||||||
|
])
|
||||||
.run(tauri::generate_context!())
|
.run(tauri::generate_context!())
|
||||||
.expect("error while running tauri application");
|
.expect("error while running tauri application");
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,321 @@
|
|||||||
|
use che_orm::__private::sqlx;
|
||||||
|
use che_tauri::{ApiError, TauriApi};
|
||||||
|
use serde::Deserialize;
|
||||||
|
|
||||||
|
#[derive(Debug, serde::Serialize)]
|
||||||
|
pub struct SyncContractsResult {
|
||||||
|
pub synced: usize,
|
||||||
|
pub pages: usize,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Deserialize)]
|
||||||
|
struct ContractPage {
|
||||||
|
links: PageLinks,
|
||||||
|
results: Vec<RemoteContract>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Deserialize)]
|
||||||
|
struct PageLinks {
|
||||||
|
next: Option<String>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Deserialize)]
|
||||||
|
struct RemoteContract {
|
||||||
|
id: i64,
|
||||||
|
name: String,
|
||||||
|
number: String,
|
||||||
|
#[serde(rename = "get_absolute_url")]
|
||||||
|
absolute_url: String,
|
||||||
|
#[serde(rename = "get_type")]
|
||||||
|
contract_type: String,
|
||||||
|
#[serde(rename = "get_category")]
|
||||||
|
category: String,
|
||||||
|
#[serde(rename = "get_amount_total")]
|
||||||
|
amount_total_display: String,
|
||||||
|
#[serde(rename = "get_amount_by_ds")]
|
||||||
|
amount_by_ds: String,
|
||||||
|
comment: String,
|
||||||
|
date: String,
|
||||||
|
#[serde(rename = "get_status")]
|
||||||
|
status_name: String,
|
||||||
|
status: String,
|
||||||
|
nds: String,
|
||||||
|
name_of_product: String,
|
||||||
|
counterparty: String,
|
||||||
|
amount: f64,
|
||||||
|
month_pay: Option<f64>,
|
||||||
|
avans_pay: String,
|
||||||
|
amount_total: f64,
|
||||||
|
estimate_nds_cost: f64,
|
||||||
|
estimate_nds_cert: f64,
|
||||||
|
#[serde(rename = "get_bill_cost")]
|
||||||
|
bill_cost: Option<RemoteBillCost>,
|
||||||
|
income_total: f64,
|
||||||
|
arrears: f64,
|
||||||
|
company: Option<RemoteCompany>,
|
||||||
|
project: Option<RemoteProject>,
|
||||||
|
frc: Option<RemoteFrc>,
|
||||||
|
get_employee: Option<RemoteEmployee>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Deserialize)]
|
||||||
|
struct RemoteBillCost {
|
||||||
|
#[serde(rename = "cost__sum")]
|
||||||
|
cost_sum: Option<f64>,
|
||||||
|
#[serde(rename = "paid__sum")]
|
||||||
|
paid_sum: Option<f64>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Deserialize)]
|
||||||
|
struct RemoteCompany {
|
||||||
|
id: i64,
|
||||||
|
name: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Deserialize)]
|
||||||
|
struct RemoteProject {
|
||||||
|
id: i64,
|
||||||
|
name: String,
|
||||||
|
full_name: String,
|
||||||
|
#[serde(rename = "get_short_name")]
|
||||||
|
short_name: String,
|
||||||
|
locality: Option<RemoteLocality>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Deserialize)]
|
||||||
|
#[serde(untagged)]
|
||||||
|
enum RemoteLocality {
|
||||||
|
Object { id: i64, name: String },
|
||||||
|
Id(i64),
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Deserialize)]
|
||||||
|
struct RemoteFrc {
|
||||||
|
id: i64,
|
||||||
|
name: String,
|
||||||
|
icon: Option<String>,
|
||||||
|
#[serde(rename = "get_balance")]
|
||||||
|
balance: Option<f64>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Deserialize)]
|
||||||
|
struct RemoteEmployee {
|
||||||
|
id: i64,
|
||||||
|
name: String,
|
||||||
|
short_name: String,
|
||||||
|
avatar_small: Option<String>,
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn sync_contracts(api: &TauriApi) -> Result<SyncContractsResult, ApiError> {
|
||||||
|
let state = api.state();
|
||||||
|
let token = state
|
||||||
|
.auth_token()
|
||||||
|
.ok_or_else(|| ApiError::new("not_authenticated", "authentication token is missing"))?;
|
||||||
|
let remote = state
|
||||||
|
.config
|
||||||
|
.remote
|
||||||
|
.as_ref()
|
||||||
|
.ok_or_else(|| ApiError::bad_request("sync requires [remote].base_url config"))?;
|
||||||
|
let client = reqwest::Client::new();
|
||||||
|
let mut next_url = Some(format!(
|
||||||
|
"{}/api/contract/?page_size=100",
|
||||||
|
remote.base_url.trim_end_matches('/')
|
||||||
|
));
|
||||||
|
let mut synced = 0;
|
||||||
|
let mut pages = 0;
|
||||||
|
|
||||||
|
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 request failed with {status}: {detail}"),
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
|
let page = response.json::<ContractPage>().await?;
|
||||||
|
pages += 1;
|
||||||
|
|
||||||
|
for contract in &page.results {
|
||||||
|
upsert_contract_dependencies(api, contract).await?;
|
||||||
|
upsert_contract(api, contract).await?;
|
||||||
|
synced += 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
next_url = page.links.next;
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(SyncContractsResult { synced, pages })
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn upsert_contract_dependencies(
|
||||||
|
api: &TauriApi,
|
||||||
|
contract: &RemoteContract,
|
||||||
|
) -> Result<(), ApiError> {
|
||||||
|
let pool = api.state().db().pool();
|
||||||
|
|
||||||
|
if let Some(company) = &contract.company {
|
||||||
|
sqlx::query(
|
||||||
|
"INSERT INTO counterparty (id, name) VALUES (?1, ?2)
|
||||||
|
ON CONFLICT(id) DO UPDATE SET name = excluded.name",
|
||||||
|
)
|
||||||
|
.bind(company.id)
|
||||||
|
.bind(&company.name)
|
||||||
|
.execute(pool)
|
||||||
|
.await
|
||||||
|
.map_err(database_error)?;
|
||||||
|
}
|
||||||
|
|
||||||
|
if let Some(project) = &contract.project {
|
||||||
|
let (locality_id, locality_name) = match &project.locality {
|
||||||
|
Some(RemoteLocality::Object { id, name }) => (Some(*id), Some(name.as_str())),
|
||||||
|
Some(RemoteLocality::Id(id)) => (Some(*id), None),
|
||||||
|
None => (None, None),
|
||||||
|
};
|
||||||
|
sqlx::query(
|
||||||
|
"INSERT INTO project (id, name, full_name, short_name, locality_id, locality_name)
|
||||||
|
VALUES (?1, ?2, ?3, ?4, ?5, ?6)
|
||||||
|
ON CONFLICT(id) DO UPDATE SET
|
||||||
|
name = excluded.name,
|
||||||
|
full_name = excluded.full_name,
|
||||||
|
short_name = excluded.short_name,
|
||||||
|
locality_id = excluded.locality_id,
|
||||||
|
locality_name = excluded.locality_name",
|
||||||
|
)
|
||||||
|
.bind(project.id)
|
||||||
|
.bind(&project.name)
|
||||||
|
.bind(&project.full_name)
|
||||||
|
.bind(&project.short_name)
|
||||||
|
.bind(locality_id)
|
||||||
|
.bind(locality_name)
|
||||||
|
.execute(pool)
|
||||||
|
.await
|
||||||
|
.map_err(database_error)?;
|
||||||
|
}
|
||||||
|
|
||||||
|
if let Some(frc) = &contract.frc {
|
||||||
|
sqlx::query(
|
||||||
|
"INSERT INTO frc (id, name, icon, balance) VALUES (?1, ?2, ?3, ?4)
|
||||||
|
ON CONFLICT(id) DO UPDATE SET
|
||||||
|
name = excluded.name,
|
||||||
|
icon = excluded.icon,
|
||||||
|
balance = excluded.balance",
|
||||||
|
)
|
||||||
|
.bind(frc.id)
|
||||||
|
.bind(&frc.name)
|
||||||
|
.bind(frc.icon.as_deref().unwrap_or_default())
|
||||||
|
.bind(frc.balance.unwrap_or_default())
|
||||||
|
.execute(pool)
|
||||||
|
.await
|
||||||
|
.map_err(database_error)?;
|
||||||
|
}
|
||||||
|
|
||||||
|
if let Some(employee) = &contract.get_employee {
|
||||||
|
sqlx::query(
|
||||||
|
"INSERT INTO employee (id, name, short_name, avatar_small) VALUES (?1, ?2, ?3, ?4)
|
||||||
|
ON CONFLICT(id) DO UPDATE SET
|
||||||
|
name = excluded.name,
|
||||||
|
short_name = excluded.short_name,
|
||||||
|
avatar_small = excluded.avatar_small",
|
||||||
|
)
|
||||||
|
.bind(employee.id)
|
||||||
|
.bind(&employee.name)
|
||||||
|
.bind(&employee.short_name)
|
||||||
|
.bind(employee.avatar_small.as_deref())
|
||||||
|
.execute(pool)
|
||||||
|
.await
|
||||||
|
.map_err(database_error)?;
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
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,
|
||||||
|
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,
|
||||||
|
counterparty_id, project_id, frc_id, employee_id
|
||||||
|
)
|
||||||
|
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
|
||||||
|
)
|
||||||
|
ON CONFLICT(id) DO UPDATE SET
|
||||||
|
name = excluded.name,
|
||||||
|
number = excluded.number,
|
||||||
|
absolute_url = excluded.absolute_url,
|
||||||
|
contract_type = excluded.contract_type,
|
||||||
|
category = excluded.category,
|
||||||
|
amount_total_display = excluded.amount_total_display,
|
||||||
|
amount_by_ds = excluded.amount_by_ds,
|
||||||
|
comment = excluded.comment,
|
||||||
|
date = excluded.date,
|
||||||
|
status_name = excluded.status_name,
|
||||||
|
status = excluded.status,
|
||||||
|
nds = excluded.nds,
|
||||||
|
name_of_product = excluded.name_of_product,
|
||||||
|
counterparty_name = excluded.counterparty_name,
|
||||||
|
amount = excluded.amount,
|
||||||
|
month_pay = excluded.month_pay,
|
||||||
|
avans_pay = excluded.avans_pay,
|
||||||
|
amount_total = excluded.amount_total,
|
||||||
|
estimate_nds_cost = excluded.estimate_nds_cost,
|
||||||
|
estimate_nds_cert = excluded.estimate_nds_cert,
|
||||||
|
bill_cost_sum = excluded.bill_cost_sum,
|
||||||
|
bill_paid_sum = excluded.bill_paid_sum,
|
||||||
|
income_total = excluded.income_total,
|
||||||
|
arrears = excluded.arrears,
|
||||||
|
counterparty_id = excluded.counterparty_id,
|
||||||
|
project_id = excluded.project_id,
|
||||||
|
frc_id = excluded.frc_id,
|
||||||
|
employee_id = excluded.employee_id",
|
||||||
|
)
|
||||||
|
.bind(contract.id)
|
||||||
|
.bind(&contract.name)
|
||||||
|
.bind(&contract.number)
|
||||||
|
.bind(&contract.absolute_url)
|
||||||
|
.bind(&contract.contract_type)
|
||||||
|
.bind(&contract.category)
|
||||||
|
.bind(&contract.amount_total_display)
|
||||||
|
.bind(&contract.amount_by_ds)
|
||||||
|
.bind(&contract.comment)
|
||||||
|
.bind(&contract.date)
|
||||||
|
.bind(&contract.status_name)
|
||||||
|
.bind(&contract.status)
|
||||||
|
.bind(&contract.nds)
|
||||||
|
.bind(&contract.name_of_product)
|
||||||
|
.bind(&contract.counterparty)
|
||||||
|
.bind(contract.amount)
|
||||||
|
.bind(contract.month_pay)
|
||||||
|
.bind(&contract.avans_pay)
|
||||||
|
.bind(contract.amount_total)
|
||||||
|
.bind(contract.estimate_nds_cost)
|
||||||
|
.bind(contract.estimate_nds_cert)
|
||||||
|
.bind(contract.bill_cost.as_ref().and_then(|bill_cost| bill_cost.cost_sum))
|
||||||
|
.bind(contract.bill_cost.as_ref().and_then(|bill_cost| bill_cost.paid_sum))
|
||||||
|
.bind(contract.income_total)
|
||||||
|
.bind(contract.arrears)
|
||||||
|
.bind(contract.company.as_ref().map(|company| company.id))
|
||||||
|
.bind(contract.project.as_ref().map(|project| project.id))
|
||||||
|
.bind(contract.frc.as_ref().map(|frc| frc.id))
|
||||||
|
.bind(contract.get_employee.as_ref().map(|employee| employee.id))
|
||||||
|
.execute(api.state().db().pool())
|
||||||
|
.await
|
||||||
|
.map_err(database_error)?;
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn database_error(error: sqlx::Error) -> ApiError {
|
||||||
|
ApiError::new("database_error", error.to_string())
|
||||||
|
}
|
||||||
+23
-26
@@ -1,12 +1,18 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { computed } from "vue";
|
import { computed } from "vue";
|
||||||
import { useRoute, useRouter } from "vue-router";
|
import { useRoute, useRouter } from "vue-router";
|
||||||
|
import { useAuth } from "./composables/useAuth";
|
||||||
|
|
||||||
const route = useRoute();
|
const route = useRoute();
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
|
const { logout } = useAuth();
|
||||||
|
|
||||||
const activeTab = computed({
|
const activeTab = computed({
|
||||||
get() {
|
get() {
|
||||||
|
if (route.path.startsWith("/contracts")) {
|
||||||
|
return "/contracts";
|
||||||
|
}
|
||||||
|
|
||||||
return route.path.startsWith("/users") ? "/users" : "/tasks";
|
return route.path.startsWith("/users") ? "/users" : "/tasks";
|
||||||
},
|
},
|
||||||
set(path: string) {
|
set(path: string) {
|
||||||
@@ -18,17 +24,32 @@ const activeTab = computed({
|
|||||||
|
|
||||||
const title = computed(() => String(route.meta.title ?? "EWA Mobile"));
|
const title = computed(() => String(route.meta.title ?? "EWA Mobile"));
|
||||||
const showBack = computed(() => Boolean(route.meta.back));
|
const showBack = computed(() => Boolean(route.meta.back));
|
||||||
|
const showShellNavigation = computed(() => route.path !== "/login");
|
||||||
|
|
||||||
|
async function logoutAndRedirect() {
|
||||||
|
await logout();
|
||||||
|
router.replace("/login");
|
||||||
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<van-nav-bar :title="title" :left-arrow="showBack" fixed placeholder @click-left="router.back()" />
|
<van-nav-bar
|
||||||
|
:title="title"
|
||||||
|
:left-arrow="showBack"
|
||||||
|
:right-text="showShellNavigation ? 'Выйти' : ''"
|
||||||
|
fixed
|
||||||
|
placeholder
|
||||||
|
@click-left="router.back()"
|
||||||
|
@click-right="logoutAndRedirect"
|
||||||
|
/>
|
||||||
|
|
||||||
<main class="page">
|
<main class="page">
|
||||||
<router-view />
|
<router-view />
|
||||||
</main>
|
</main>
|
||||||
|
|
||||||
<van-tabbar v-model="activeTab" route placeholder safe-area-inset-bottom>
|
<van-tabbar v-if="showShellNavigation" v-model="activeTab" route placeholder safe-area-inset-bottom>
|
||||||
<van-tabbar-item to="/tasks" name="/tasks" icon="todo-list-o">Задачи</van-tabbar-item>
|
<van-tabbar-item to="/tasks" name="/tasks" icon="todo-list-o">Задачи</van-tabbar-item>
|
||||||
|
<van-tabbar-item to="/contracts" name="/contracts" icon="orders-o">Контракты</van-tabbar-item>
|
||||||
<van-tabbar-item to="/users" name="/users" icon="friends-o">Пользователи</van-tabbar-item>
|
<van-tabbar-item to="/users" name="/users" icon="friends-o">Пользователи</van-tabbar-item>
|
||||||
</van-tabbar>
|
</van-tabbar>
|
||||||
</template>
|
</template>
|
||||||
@@ -65,30 +86,6 @@ body {
|
|||||||
padding: 24px 14px 40px;
|
padding: 24px 14px 40px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.hero {
|
|
||||||
margin: 12px 2px 18px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.eyebrow {
|
|
||||||
margin: 0 0 6px;
|
|
||||||
color: #1989fa;
|
|
||||||
font-size: 0.75rem;
|
|
||||||
font-weight: 800;
|
|
||||||
letter-spacing: 0.1em;
|
|
||||||
text-transform: uppercase;
|
|
||||||
}
|
|
||||||
|
|
||||||
h1 {
|
|
||||||
margin: 0;
|
|
||||||
font-size: clamp(2rem, 9vw, 4rem);
|
|
||||||
line-height: 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
.subtitle {
|
|
||||||
margin: 12px 0 0;
|
|
||||||
color: #646566;
|
|
||||||
}
|
|
||||||
|
|
||||||
.notice {
|
.notice {
|
||||||
margin-bottom: 12px;
|
margin-bottom: 12px;
|
||||||
border-radius: 14px;
|
border-radius: 14px;
|
||||||
|
|||||||
@@ -0,0 +1,135 @@
|
|||||||
|
<script setup lang="ts">
|
||||||
|
import { computed, ref, watch } from "vue";
|
||||||
|
import { counterpartyApi } from "../generated/api";
|
||||||
|
import type { CounterpartyListParams } from "../generated/models";
|
||||||
|
import { useModelApi } from "../composables/useModelApi";
|
||||||
|
|
||||||
|
const PAGE_SIZE = 20;
|
||||||
|
|
||||||
|
const selectedCounterpartyId = defineModel<number>({ required: true });
|
||||||
|
const search = ref("");
|
||||||
|
const showSelector = ref(false);
|
||||||
|
|
||||||
|
const {
|
||||||
|
items: counterparties,
|
||||||
|
filters,
|
||||||
|
loading,
|
||||||
|
} = useModelApi(counterpartyApi, {
|
||||||
|
defaultListParams: { ordering: "id", limit: PAGE_SIZE, offset: 0 } as CounterpartyListParams,
|
||||||
|
loadErrorMessage: "Не удалось загрузить контрагентов",
|
||||||
|
cleanListParams: (params) => {
|
||||||
|
if (!params.name__contains?.trim()) {
|
||||||
|
delete params.name__contains;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
const selectedCounterparty = computed(() =>
|
||||||
|
counterparties.value.find((counterparty) => counterparty.id === selectedCounterpartyId.value),
|
||||||
|
);
|
||||||
|
|
||||||
|
const selectedCounterpartyName = computed(() => selectedCounterparty.value?.name ?? "все");
|
||||||
|
|
||||||
|
function openSelector() {
|
||||||
|
search.value = "";
|
||||||
|
showSelector.value = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
function selectCounterparty(id: number) {
|
||||||
|
selectedCounterpartyId.value = id;
|
||||||
|
showSelector.value = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
watch(search, (value) => {
|
||||||
|
filters.name__contains = value.trim();
|
||||||
|
filters.offset = 0;
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<button class="counterparty-select" type="button" @click="openSelector">
|
||||||
|
<span>{{ selectedCounterpartyName }}</span>
|
||||||
|
<van-icon name="arrow" />
|
||||||
|
</button>
|
||||||
|
|
||||||
|
<van-popup v-model:show="showSelector" round position="bottom" class="counterparty-popup">
|
||||||
|
<div class="counterparty-popup-header">
|
||||||
|
<h2>Выберите контрагента</h2>
|
||||||
|
<van-button size="small" type="primary" plain @click="selectCounterparty(0)">Все</van-button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<van-search v-model="search" placeholder="Поиск по имени" />
|
||||||
|
|
||||||
|
<van-loading v-if="loading" class="counterparty-state" type="spinner">Загрузка...</van-loading>
|
||||||
|
|
||||||
|
<template v-else>
|
||||||
|
<van-cell-group inset>
|
||||||
|
<van-cell
|
||||||
|
v-for="counterparty in counterparties"
|
||||||
|
:key="counterparty.id"
|
||||||
|
:title="counterparty.name"
|
||||||
|
:label="`ID: ${counterparty.id}`"
|
||||||
|
clickable
|
||||||
|
center
|
||||||
|
@click="selectCounterparty(counterparty.id)"
|
||||||
|
>
|
||||||
|
<template #right-icon>
|
||||||
|
<van-icon
|
||||||
|
v-if="selectedCounterpartyId === counterparty.id"
|
||||||
|
name="success"
|
||||||
|
color="#1989fa"
|
||||||
|
/>
|
||||||
|
</template>
|
||||||
|
</van-cell>
|
||||||
|
</van-cell-group>
|
||||||
|
|
||||||
|
<van-empty v-if="counterparties.length === 0" description="Контрагенты не найдены" />
|
||||||
|
</template>
|
||||||
|
</van-popup>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
.counterparty-popup {
|
||||||
|
min-height: 55vh;
|
||||||
|
padding: 18px 0 28px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.counterparty-popup-header {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: space-between;
|
||||||
|
gap: 12px;
|
||||||
|
padding: 0 18px 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.counterparty-popup-header h2 {
|
||||||
|
margin: 0;
|
||||||
|
font-size: 18px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.counterparty-state {
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
padding: 36px 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.counterparty-select {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: flex-end;
|
||||||
|
gap: 4px;
|
||||||
|
width: 100%;
|
||||||
|
border: 0;
|
||||||
|
padding: 0;
|
||||||
|
color: #323233;
|
||||||
|
background: transparent;
|
||||||
|
font: inherit;
|
||||||
|
line-height: 24px;
|
||||||
|
text-align: right;
|
||||||
|
}
|
||||||
|
|
||||||
|
.counterparty-select .van-icon {
|
||||||
|
color: #969799;
|
||||||
|
font-size: 16px;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
@@ -1,28 +1,27 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { computed, onMounted, ref } from "vue";
|
import { computed, ref, watch } from "vue";
|
||||||
import { employeeApi } from "../generated/api";
|
import { employeeApi } from "../generated/api";
|
||||||
|
import type { EmployeeListParams } from "../generated/models";
|
||||||
import { useModelApi } from "../composables/useModelApi";
|
import { useModelApi } from "../composables/useModelApi";
|
||||||
|
|
||||||
|
const PAGE_SIZE = 20;
|
||||||
|
|
||||||
const selectedEmployeeId = defineModel<number>({ required: true });
|
const selectedEmployeeId = defineModel<number>({ required: true });
|
||||||
const search = ref("");
|
const search = ref("");
|
||||||
const showSelector = ref(false);
|
const showSelector = ref(false);
|
||||||
|
|
||||||
const {
|
const {
|
||||||
items: employees,
|
items: employees,
|
||||||
|
filters,
|
||||||
loading,
|
loading,
|
||||||
load: loadEmployees,
|
|
||||||
} = useModelApi(employeeApi, {
|
} = useModelApi(employeeApi, {
|
||||||
defaultListParams: { ordering: "id" },
|
defaultListParams: { ordering: "id", limit: PAGE_SIZE, offset: 0 } as EmployeeListParams,
|
||||||
loadErrorMessage: "Не удалось загрузить сотрудников",
|
loadErrorMessage: "Не удалось загрузить сотрудников",
|
||||||
});
|
cleanListParams: (params) => {
|
||||||
|
if (!params.name__contains?.trim()) {
|
||||||
const filteredEmployees = computed(() => {
|
delete params.name__contains;
|
||||||
const value = search.value.trim().toLowerCase();
|
|
||||||
if (!value) {
|
|
||||||
return employees.value;
|
|
||||||
}
|
}
|
||||||
|
},
|
||||||
return employees.value.filter((employee) => employee.name.toLowerCase().includes(value));
|
|
||||||
});
|
});
|
||||||
|
|
||||||
const selectedEmployee = computed(() =>
|
const selectedEmployee = computed(() =>
|
||||||
@@ -41,7 +40,10 @@ function selectEmployee(id: number) {
|
|||||||
showSelector.value = false;
|
showSelector.value = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
onMounted(loadEmployees);
|
watch(search, (value) => {
|
||||||
|
filters.name__contains = value.trim();
|
||||||
|
filters.offset = 0;
|
||||||
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
@@ -63,7 +65,7 @@ onMounted(loadEmployees);
|
|||||||
<template v-else>
|
<template v-else>
|
||||||
<van-cell-group inset>
|
<van-cell-group inset>
|
||||||
<van-cell
|
<van-cell
|
||||||
v-for="employee in filteredEmployees"
|
v-for="employee in employees"
|
||||||
:key="employee.id"
|
:key="employee.id"
|
||||||
:title="employee.name"
|
:title="employee.name"
|
||||||
:label="`ID: ${employee.id}`"
|
:label="`ID: ${employee.id}`"
|
||||||
@@ -72,7 +74,7 @@ onMounted(loadEmployees);
|
|||||||
@click="selectEmployee(employee.id)"
|
@click="selectEmployee(employee.id)"
|
||||||
>
|
>
|
||||||
<template #icon>
|
<template #icon>
|
||||||
<van-image class="employee-avatar" round width="36" height="36" :src="employee.avatar" />
|
<van-image class="employee-avatar" round width="36" height="36" :src="employee.avatar_small ?? ''" />
|
||||||
</template>
|
</template>
|
||||||
<template #right-icon>
|
<template #right-icon>
|
||||||
<van-icon v-if="selectedEmployeeId === employee.id" name="success" color="#1989fa" />
|
<van-icon v-if="selectedEmployeeId === employee.id" name="success" color="#1989fa" />
|
||||||
@@ -80,7 +82,7 @@ onMounted(loadEmployees);
|
|||||||
</van-cell>
|
</van-cell>
|
||||||
</van-cell-group>
|
</van-cell-group>
|
||||||
|
|
||||||
<van-empty v-if="filteredEmployees.length === 0" description="Сотрудники не найдены" />
|
<van-empty v-if="employees.length === 0" description="Сотрудники не найдены" />
|
||||||
</template>
|
</template>
|
||||||
</van-popup>
|
</van-popup>
|
||||||
</template>
|
</template>
|
||||||
|
|||||||
@@ -0,0 +1,47 @@
|
|||||||
|
import { invoke } from "@tauri-apps/api/core";
|
||||||
|
import { ref } from "vue";
|
||||||
|
|
||||||
|
const TOKEN_STORAGE_KEY = "ewa-mobile.authToken";
|
||||||
|
const authToken = ref(localStorage.getItem(TOKEN_STORAGE_KEY) ?? "");
|
||||||
|
let restored = false;
|
||||||
|
|
||||||
|
interface AuthTokenResponse {
|
||||||
|
token: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function useAuth() {
|
||||||
|
async function login(username: string, password: string) {
|
||||||
|
const response = await invoke<AuthTokenResponse>("auth_login", { username, password });
|
||||||
|
authToken.value = response.token;
|
||||||
|
localStorage.setItem(TOKEN_STORAGE_KEY, response.token);
|
||||||
|
restored = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
async function logout() {
|
||||||
|
authToken.value = "";
|
||||||
|
localStorage.removeItem(TOKEN_STORAGE_KEY);
|
||||||
|
restored = false;
|
||||||
|
await invoke("auth_logout");
|
||||||
|
}
|
||||||
|
|
||||||
|
async function restoreToken() {
|
||||||
|
if (restored || !authToken.value) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
await invoke("auth_set_token", { token: authToken.value });
|
||||||
|
restored = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
authToken,
|
||||||
|
isAuthenticated,
|
||||||
|
login,
|
||||||
|
logout,
|
||||||
|
restoreToken,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
export function isAuthenticated() {
|
||||||
|
return Boolean(authToken.value);
|
||||||
|
}
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
import { ref, shallowRef } from "vue";
|
import { onMounted, reactive, ref, shallowRef, watch } from "vue";
|
||||||
import { showToast } from "vant";
|
import { showToast } from "vant";
|
||||||
import type { BaseEntity, ListParams, ModelApi } from "../generated/api_client";
|
import type { BaseEntity, ListParams, ModelApi } from "../generated/api_client";
|
||||||
|
|
||||||
@@ -8,6 +8,10 @@ interface UseModelApiOptions<Params> {
|
|||||||
retrieveErrorMessage?: string;
|
retrieveErrorMessage?: string;
|
||||||
createErrorMessage?: string;
|
createErrorMessage?: string;
|
||||||
showErrorToast?: boolean;
|
showErrorToast?: boolean;
|
||||||
|
autoLoad?: boolean;
|
||||||
|
autoLoadOnFilterChange?: boolean;
|
||||||
|
debounceMs?: number;
|
||||||
|
cleanListParams?: (params: Params) => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function useModelApi<
|
export function useModelApi<
|
||||||
@@ -22,14 +26,23 @@ export function useModelApi<
|
|||||||
const loadingItem = ref(false);
|
const loadingItem = ref(false);
|
||||||
const creating = ref(false);
|
const creating = ref(false);
|
||||||
const error = ref("");
|
const error = ref("");
|
||||||
|
const count = ref(0);
|
||||||
|
const filters = reactive({ ...(options.defaultListParams ?? {}) } as Params) as Params;
|
||||||
|
let loadTimer: ReturnType<typeof setTimeout> | undefined;
|
||||||
|
const autoLoad = options.autoLoad ?? true;
|
||||||
|
const autoLoadOnFilterChange = options.autoLoadOnFilterChange ?? true;
|
||||||
|
const debounceMs = options.debounceMs ?? 250;
|
||||||
|
|
||||||
async function load(params?: Params) {
|
async function load(params?: Params) {
|
||||||
loading.value = true;
|
loading.value = true;
|
||||||
error.value = "";
|
error.value = "";
|
||||||
|
const listParams = params ?? filters;
|
||||||
|
options.cleanListParams?.(listParams);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const response = await api.list(params ?? options.defaultListParams);
|
const response = await api.list(listParams);
|
||||||
items.value = response.results;
|
items.value = response.results;
|
||||||
|
count.value = response.count;
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
handleError(err, options.loadErrorMessage ?? "Не удалось загрузить данные");
|
handleError(err, options.loadErrorMessage ?? "Не удалось загрузить данные");
|
||||||
} finally {
|
} finally {
|
||||||
@@ -77,9 +90,24 @@ export function useModelApi<
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (autoLoadOnFilterChange) {
|
||||||
|
watch(filters, () => {
|
||||||
|
clearTimeout(loadTimer);
|
||||||
|
loadTimer = setTimeout(load, debounceMs);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
if (autoLoad) {
|
||||||
|
onMounted(() => {
|
||||||
|
load();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
items,
|
items,
|
||||||
item,
|
item,
|
||||||
|
filters,
|
||||||
|
count,
|
||||||
loading,
|
loading,
|
||||||
loadingItem,
|
loadingItem,
|
||||||
creating,
|
creating,
|
||||||
|
|||||||
@@ -1,13 +1,29 @@
|
|||||||
import { createModelApi } from "./api_client";
|
import { createModelApi } from "./api_client";
|
||||||
import type {
|
import type {
|
||||||
|
Contract,
|
||||||
|
ContractCreate,
|
||||||
|
ContractUpdate,
|
||||||
|
ContractListParams,
|
||||||
|
Counterparty,
|
||||||
|
CounterpartyCreate,
|
||||||
|
CounterpartyUpdate,
|
||||||
|
CounterpartyListParams,
|
||||||
Employee,
|
Employee,
|
||||||
EmployeeCreate,
|
EmployeeCreate,
|
||||||
EmployeeUpdate,
|
EmployeeUpdate,
|
||||||
EmployeeListParams,
|
EmployeeListParams,
|
||||||
|
Frc,
|
||||||
|
FrcCreate,
|
||||||
|
FrcUpdate,
|
||||||
|
FrcListParams,
|
||||||
Message,
|
Message,
|
||||||
MessageCreate,
|
MessageCreate,
|
||||||
MessageUpdate,
|
MessageUpdate,
|
||||||
MessageListParams,
|
MessageListParams,
|
||||||
|
Project,
|
||||||
|
ProjectCreate,
|
||||||
|
ProjectUpdate,
|
||||||
|
ProjectListParams,
|
||||||
Task,
|
Task,
|
||||||
TaskCreate,
|
TaskCreate,
|
||||||
TaskUpdate,
|
TaskUpdate,
|
||||||
@@ -18,7 +34,11 @@ import type {
|
|||||||
UserListParams,
|
UserListParams,
|
||||||
} from "./models";
|
} from "./models";
|
||||||
|
|
||||||
|
export const contractApi = createModelApi<Contract, ContractCreate, ContractUpdate, ContractListParams>("contract");
|
||||||
|
export const counterpartyApi = createModelApi<Counterparty, CounterpartyCreate, CounterpartyUpdate, CounterpartyListParams>("counterparty");
|
||||||
export const employeeApi = createModelApi<Employee, EmployeeCreate, EmployeeUpdate, EmployeeListParams>("employee");
|
export const employeeApi = createModelApi<Employee, EmployeeCreate, EmployeeUpdate, EmployeeListParams>("employee");
|
||||||
|
export const frcApi = createModelApi<Frc, FrcCreate, FrcUpdate, FrcListParams>("frc");
|
||||||
export const messageApi = createModelApi<Message, MessageCreate, MessageUpdate, MessageListParams>("message");
|
export const messageApi = createModelApi<Message, MessageCreate, MessageUpdate, MessageListParams>("message");
|
||||||
|
export const projectApi = createModelApi<Project, ProjectCreate, ProjectUpdate, ProjectListParams>("project");
|
||||||
export const taskApi = createModelApi<Task, TaskCreate, TaskUpdate, TaskListParams>("task");
|
export const taskApi = createModelApi<Task, TaskCreate, TaskUpdate, TaskListParams>("task");
|
||||||
export const userApi = createModelApi<User, UserCreate, UserUpdate, UserListParams>("users");
|
export const userApi = createModelApi<User, UserCreate, UserUpdate, UserListParams>("users");
|
||||||
|
|||||||
+205
-3
@@ -1,25 +1,194 @@
|
|||||||
import type { ListParams } from "./api_client";
|
import type { ListParams } from "./api_client";
|
||||||
|
|
||||||
|
export interface Contract {
|
||||||
|
id: number;
|
||||||
|
name: string;
|
||||||
|
number: string;
|
||||||
|
absolute_url: string;
|
||||||
|
contract_type: string;
|
||||||
|
category: string;
|
||||||
|
amount_total_display: string;
|
||||||
|
amount_by_ds: string;
|
||||||
|
comment: string;
|
||||||
|
date: string;
|
||||||
|
status_name: string;
|
||||||
|
status: string;
|
||||||
|
nds: string;
|
||||||
|
name_of_product: string;
|
||||||
|
counterparty_name: string;
|
||||||
|
amount: number;
|
||||||
|
month_pay: number | null;
|
||||||
|
avans_pay: string;
|
||||||
|
amount_total: number;
|
||||||
|
estimate_nds_cost: number;
|
||||||
|
estimate_nds_cert: number;
|
||||||
|
bill_cost_sum: number | null;
|
||||||
|
bill_paid_sum: number | null;
|
||||||
|
income_total: number;
|
||||||
|
arrears: number;
|
||||||
|
counterparty_id: number | null;
|
||||||
|
project_id: number | null;
|
||||||
|
frc_id: number | null;
|
||||||
|
employee_id: number | null;
|
||||||
|
counterparty: Counterparty | null;
|
||||||
|
project: Project | null;
|
||||||
|
frc: Frc | null;
|
||||||
|
employee: Employee | null;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface ContractCreate {
|
||||||
|
name: string;
|
||||||
|
number: string;
|
||||||
|
absolute_url: string;
|
||||||
|
contract_type: string;
|
||||||
|
category: string;
|
||||||
|
amount_total_display: string;
|
||||||
|
amount_by_ds: string;
|
||||||
|
comment: string;
|
||||||
|
date: string;
|
||||||
|
status_name: string;
|
||||||
|
status: string;
|
||||||
|
nds: string;
|
||||||
|
name_of_product: string;
|
||||||
|
counterparty_name: string;
|
||||||
|
amount: number;
|
||||||
|
month_pay?: number | null;
|
||||||
|
avans_pay: string;
|
||||||
|
amount_total: number;
|
||||||
|
estimate_nds_cost: number;
|
||||||
|
estimate_nds_cert: number;
|
||||||
|
bill_cost_sum?: number | null;
|
||||||
|
bill_paid_sum?: number | null;
|
||||||
|
income_total: number;
|
||||||
|
arrears: number;
|
||||||
|
counterparty_id?: number | null;
|
||||||
|
project_id?: number | null;
|
||||||
|
frc_id?: number | null;
|
||||||
|
employee_id?: number | null;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface ContractUpdate {
|
||||||
|
name?: string;
|
||||||
|
number?: string;
|
||||||
|
absolute_url?: string;
|
||||||
|
contract_type?: string;
|
||||||
|
category?: string;
|
||||||
|
amount_total_display?: string;
|
||||||
|
amount_by_ds?: string;
|
||||||
|
comment?: string;
|
||||||
|
date?: string;
|
||||||
|
status_name?: string;
|
||||||
|
status?: string;
|
||||||
|
nds?: string;
|
||||||
|
name_of_product?: string;
|
||||||
|
counterparty_name?: string;
|
||||||
|
amount?: number;
|
||||||
|
month_pay?: number | null;
|
||||||
|
avans_pay?: string;
|
||||||
|
amount_total?: number;
|
||||||
|
estimate_nds_cost?: number;
|
||||||
|
estimate_nds_cert?: number;
|
||||||
|
bill_cost_sum?: number | null;
|
||||||
|
bill_paid_sum?: number | null;
|
||||||
|
income_total?: number;
|
||||||
|
arrears?: number;
|
||||||
|
counterparty_id?: number | null;
|
||||||
|
project_id?: number | null;
|
||||||
|
frc_id?: number | null;
|
||||||
|
employee_id?: number | null;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface ContractListParams extends ListParams {
|
||||||
|
id?: number;
|
||||||
|
name?: string;
|
||||||
|
name__contains?: string;
|
||||||
|
number?: string;
|
||||||
|
number__contains?: string;
|
||||||
|
date?: string;
|
||||||
|
status?: string;
|
||||||
|
status_name?: string;
|
||||||
|
status_name__contains?: string;
|
||||||
|
contract_type?: string;
|
||||||
|
contract_type__contains?: string;
|
||||||
|
category?: string;
|
||||||
|
category__contains?: string;
|
||||||
|
counterparty_name__contains?: string;
|
||||||
|
name_of_product__contains?: string;
|
||||||
|
counterparty_id?: number | null;
|
||||||
|
project_id?: number | null;
|
||||||
|
frc_id?: number | null;
|
||||||
|
employee_id?: number | null;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface Counterparty {
|
||||||
|
id: number;
|
||||||
|
name: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface CounterpartyCreate {
|
||||||
|
name: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface CounterpartyUpdate {
|
||||||
|
name?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface CounterpartyListParams extends ListParams {
|
||||||
|
id?: number;
|
||||||
|
name?: string;
|
||||||
|
name__contains?: string;
|
||||||
|
}
|
||||||
|
|
||||||
export interface Employee {
|
export interface Employee {
|
||||||
id: number;
|
id: number;
|
||||||
name: string;
|
name: string;
|
||||||
avatar: string;
|
short_name: string;
|
||||||
|
avatar_small: string | null;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface EmployeeCreate {
|
export interface EmployeeCreate {
|
||||||
name: string;
|
name: string;
|
||||||
avatar: string;
|
short_name: string;
|
||||||
|
avatar_small?: string | null;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface EmployeeUpdate {
|
export interface EmployeeUpdate {
|
||||||
name?: string;
|
name?: string;
|
||||||
avatar?: string;
|
short_name?: string;
|
||||||
|
avatar_small?: string | null;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface EmployeeListParams extends ListParams {
|
export interface EmployeeListParams extends ListParams {
|
||||||
id?: number;
|
id?: number;
|
||||||
name?: string;
|
name?: string;
|
||||||
name__contains?: string;
|
name__contains?: string;
|
||||||
|
short_name?: string;
|
||||||
|
short_name__contains?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface Frc {
|
||||||
|
id: number;
|
||||||
|
name: string;
|
||||||
|
icon: string;
|
||||||
|
balance: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface FrcCreate {
|
||||||
|
name: string;
|
||||||
|
icon: string;
|
||||||
|
balance: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface FrcUpdate {
|
||||||
|
name?: string;
|
||||||
|
icon?: string;
|
||||||
|
balance?: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface FrcListParams extends ListParams {
|
||||||
|
id?: number;
|
||||||
|
name?: string;
|
||||||
|
name__contains?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface Message {
|
export interface Message {
|
||||||
@@ -47,6 +216,39 @@ export interface MessageListParams extends ListParams {
|
|||||||
employee_id?: number;
|
employee_id?: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface Project {
|
||||||
|
id: number;
|
||||||
|
name: string;
|
||||||
|
full_name: string;
|
||||||
|
short_name: string;
|
||||||
|
locality_id: number | null;
|
||||||
|
locality_name: string | null;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface ProjectCreate {
|
||||||
|
name: string;
|
||||||
|
full_name: string;
|
||||||
|
short_name: string;
|
||||||
|
locality_id?: number | null;
|
||||||
|
locality_name?: string | null;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface ProjectUpdate {
|
||||||
|
name?: string;
|
||||||
|
full_name?: string;
|
||||||
|
short_name?: string;
|
||||||
|
locality_id?: number | null;
|
||||||
|
locality_name?: string | null;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface ProjectListParams extends ListParams {
|
||||||
|
id?: number;
|
||||||
|
name?: string;
|
||||||
|
name__contains?: string;
|
||||||
|
short_name?: string;
|
||||||
|
short_name__contains?: string;
|
||||||
|
}
|
||||||
|
|
||||||
export interface Task {
|
export interface Task {
|
||||||
id: number;
|
id: number;
|
||||||
name: string;
|
name: string;
|
||||||
|
|||||||
+39
-4
@@ -1,4 +1,8 @@
|
|||||||
import { createRouter, createWebHashHistory } from "vue-router";
|
import { createRouter, createWebHashHistory } from "vue-router";
|
||||||
|
import { isAuthenticated, useAuth } from "../composables/useAuth";
|
||||||
|
import ContractDetailView from "../views/ContractDetailView.vue";
|
||||||
|
import ContractsView from "../views/ContractsView.vue";
|
||||||
|
import LoginView from "../views/LoginView.vue";
|
||||||
import TaskCreateView from "../views/TaskCreateView.vue";
|
import TaskCreateView from "../views/TaskCreateView.vue";
|
||||||
import TaskDetailView from "../views/TaskDetailView.vue";
|
import TaskDetailView from "../views/TaskDetailView.vue";
|
||||||
import TasksView from "../views/TasksView.vue";
|
import TasksView from "../views/TasksView.vue";
|
||||||
@@ -15,25 +19,56 @@ export const router = createRouter({
|
|||||||
path: "/tasks",
|
path: "/tasks",
|
||||||
name: "tasks",
|
name: "tasks",
|
||||||
component: TasksView,
|
component: TasksView,
|
||||||
meta: { title: "Задачи" },
|
meta: { title: "Задачи", requiresAuth: true },
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
path: "/tasks/new",
|
path: "/tasks/new",
|
||||||
name: "task-create",
|
name: "task-create",
|
||||||
component: TaskCreateView,
|
component: TaskCreateView,
|
||||||
meta: { title: "Новая задача", back: true },
|
meta: { title: "Новая задача", back: true, requiresAuth: true },
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
path: "/tasks/:id",
|
path: "/tasks/:id",
|
||||||
name: "task-detail",
|
name: "task-detail",
|
||||||
component: TaskDetailView,
|
component: TaskDetailView,
|
||||||
meta: { title: "Детали задачи", back: true },
|
meta: { title: "Детали задачи", back: true, requiresAuth: true },
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
path: "/users",
|
path: "/users",
|
||||||
name: "users",
|
name: "users",
|
||||||
component: UsersView,
|
component: UsersView,
|
||||||
meta: { title: "Пользователи" },
|
meta: { title: "Пользователи", requiresAuth: true },
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: "/contracts",
|
||||||
|
name: "contracts",
|
||||||
|
component: ContractsView,
|
||||||
|
meta: { title: "Контракты", requiresAuth: true },
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: "/contracts/:id",
|
||||||
|
name: "contract-detail",
|
||||||
|
component: ContractDetailView,
|
||||||
|
meta: { title: "Детали контракта", back: true, requiresAuth: true },
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: "/login",
|
||||||
|
name: "login",
|
||||||
|
component: LoginView,
|
||||||
|
meta: { title: "Вход" },
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
});
|
});
|
||||||
|
|
||||||
|
router.beforeEach(async (to) => {
|
||||||
|
const { restoreToken } = useAuth();
|
||||||
|
await restoreToken();
|
||||||
|
|
||||||
|
if (to.meta.requiresAuth && !isAuthenticated()) {
|
||||||
|
return { path: "/login", query: { redirect: to.fullPath } };
|
||||||
|
}
|
||||||
|
|
||||||
|
if (to.path === "/login" && isAuthenticated()) {
|
||||||
|
return "/tasks";
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|||||||
@@ -0,0 +1,147 @@
|
|||||||
|
<script setup lang="ts">
|
||||||
|
import { computed, onMounted } from "vue";
|
||||||
|
import { useRoute, useRouter } from "vue-router";
|
||||||
|
import { contractApi } from "../generated/api";
|
||||||
|
import type { Contract } from "../generated/models";
|
||||||
|
import { useModelApi } from "../composables/useModelApi";
|
||||||
|
|
||||||
|
const route = useRoute();
|
||||||
|
const router = useRouter();
|
||||||
|
const contractId = computed(() => Number(route.params.id));
|
||||||
|
|
||||||
|
const {
|
||||||
|
item: contract,
|
||||||
|
loadingItem,
|
||||||
|
error,
|
||||||
|
retrieve: loadContract,
|
||||||
|
} = useModelApi(contractApi, {
|
||||||
|
retrieveErrorMessage: "Не удалось загрузить контракт",
|
||||||
|
autoLoad: false,
|
||||||
|
autoLoadOnFilterChange: false,
|
||||||
|
});
|
||||||
|
|
||||||
|
type DetailField = {
|
||||||
|
title: string;
|
||||||
|
key: keyof Contract;
|
||||||
|
};
|
||||||
|
|
||||||
|
const mainFields: DetailField[] = [
|
||||||
|
{ title: "ID", key: "id" },
|
||||||
|
{ title: "Название", key: "name" },
|
||||||
|
{ title: "Номер", key: "number" },
|
||||||
|
{ title: "Дата", key: "date" },
|
||||||
|
{ title: "Тип", key: "contract_type" },
|
||||||
|
{ title: "Категория", key: "category" },
|
||||||
|
{ title: "Статус", key: "status_name" },
|
||||||
|
{ title: "Код статуса", key: "status" },
|
||||||
|
{ title: "НДС", key: "nds" },
|
||||||
|
{ title: "Продукт", key: "name_of_product" },
|
||||||
|
{ title: "Комментарий", key: "comment" },
|
||||||
|
{ title: "URL", key: "absolute_url" },
|
||||||
|
];
|
||||||
|
|
||||||
|
const moneyFields: DetailField[] = [
|
||||||
|
{ title: "Сумма", key: "amount" },
|
||||||
|
{ title: "Сумма итого", key: "amount_total" },
|
||||||
|
{ title: "Сумма итого строкой", key: "amount_total_display" },
|
||||||
|
{ title: "Сумма по ДС", key: "amount_by_ds" },
|
||||||
|
{ title: "Платеж в месяц", key: "month_pay" },
|
||||||
|
{ title: "Аванс", key: "avans_pay" },
|
||||||
|
{ title: "Смета НДС стоимость", key: "estimate_nds_cost" },
|
||||||
|
{ title: "Смета НДС сертификат", key: "estimate_nds_cert" },
|
||||||
|
{ title: "Счета сумма", key: "bill_cost_sum" },
|
||||||
|
{ title: "Счета оплачено", key: "bill_paid_sum" },
|
||||||
|
{ title: "Доход всего", key: "income_total" },
|
||||||
|
{ title: "Задолженность", key: "arrears" },
|
||||||
|
];
|
||||||
|
|
||||||
|
const relationFields = computed(() => {
|
||||||
|
const item = contract.value;
|
||||||
|
|
||||||
|
if (!item) {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
|
||||||
|
return [
|
||||||
|
{ title: "Контрагент", value: item.counterparty?.name ?? item.counterparty_name },
|
||||||
|
{ title: "Контрагент ID", value: item.counterparty_id },
|
||||||
|
{ title: "Проект", value: item.project?.short_name ?? item.project?.name },
|
||||||
|
{ title: "Проект ID", value: item.project_id },
|
||||||
|
{ title: "ФРЦ", value: item.frc?.name },
|
||||||
|
{ title: "ФРЦ ID", value: item.frc_id },
|
||||||
|
{ title: "Баланс ФРЦ", value: item.frc?.balance },
|
||||||
|
{ title: "Сотрудник", value: item.employee?.short_name ?? item.employee?.name },
|
||||||
|
{ title: "Сотрудник ID", value: item.employee_id },
|
||||||
|
];
|
||||||
|
});
|
||||||
|
|
||||||
|
function fieldValue(field: DetailField) {
|
||||||
|
return formatValue(contract.value?.[field.key]);
|
||||||
|
}
|
||||||
|
|
||||||
|
function formatValue(value: unknown) {
|
||||||
|
if (value === null || value === undefined || value === "") {
|
||||||
|
return "не указано";
|
||||||
|
}
|
||||||
|
|
||||||
|
return String(value);
|
||||||
|
}
|
||||||
|
|
||||||
|
onMounted(() => {
|
||||||
|
if (Number.isFinite(contractId.value)) {
|
||||||
|
loadContract(contractId.value);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<van-notice-bar
|
||||||
|
v-if="error"
|
||||||
|
class="notice"
|
||||||
|
color="#991b1b"
|
||||||
|
background="#fee2e2"
|
||||||
|
left-icon="warning-o"
|
||||||
|
wrapable
|
||||||
|
:scrollable="false"
|
||||||
|
:text="error"
|
||||||
|
/>
|
||||||
|
|
||||||
|
<section class="card detail-card">
|
||||||
|
<van-loading v-if="loadingItem" class="state" type="spinner">Загрузка...</van-loading>
|
||||||
|
|
||||||
|
<van-empty v-else-if="!contract" description="Контракт не найден" />
|
||||||
|
|
||||||
|
<template v-else>
|
||||||
|
<van-cell-group inset>
|
||||||
|
<van-cell
|
||||||
|
v-for="field in mainFields"
|
||||||
|
:key="field.key"
|
||||||
|
:title="field.title"
|
||||||
|
:value="fieldValue(field)"
|
||||||
|
/>
|
||||||
|
</van-cell-group>
|
||||||
|
|
||||||
|
<van-cell-group inset title="Суммы">
|
||||||
|
<van-cell
|
||||||
|
v-for="field in moneyFields"
|
||||||
|
:key="field.key"
|
||||||
|
:title="field.title"
|
||||||
|
:value="fieldValue(field)"
|
||||||
|
/>
|
||||||
|
</van-cell-group>
|
||||||
|
|
||||||
|
<van-cell-group inset title="Связи">
|
||||||
|
<van-cell
|
||||||
|
v-for="field in relationFields"
|
||||||
|
:key="field.title"
|
||||||
|
:title="field.title"
|
||||||
|
:value="formatValue(field.value)"
|
||||||
|
/>
|
||||||
|
</van-cell-group>
|
||||||
|
|
||||||
|
<div class="detail-actions">
|
||||||
|
<van-button block round type="primary" plain @click="router.back()">Назад</van-button>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
</section>
|
||||||
|
</template>
|
||||||
@@ -0,0 +1,200 @@
|
|||||||
|
<script setup lang="ts">
|
||||||
|
import { invoke } from "@tauri-apps/api/core";
|
||||||
|
import { computed, ref, watch } from "vue";
|
||||||
|
import { useRouter } from "vue-router";
|
||||||
|
import { showToast } from "vant";
|
||||||
|
import { contractApi } from "../generated/api";
|
||||||
|
import type { ContractListParams } from "../generated/models";
|
||||||
|
import { useModelApi } from "../composables/useModelApi";
|
||||||
|
import CounterpartySelect from "../components/CounterpartySelect.vue";
|
||||||
|
|
||||||
|
const PAGE_SIZE = 10;
|
||||||
|
|
||||||
|
const router = useRouter();
|
||||||
|
const {
|
||||||
|
items: contracts,
|
||||||
|
filters,
|
||||||
|
count,
|
||||||
|
loading,
|
||||||
|
error,
|
||||||
|
load: loadContracts,
|
||||||
|
} = useModelApi(contractApi, {
|
||||||
|
defaultListParams: { ordering: "-id", limit: PAGE_SIZE, offset: 0 } as ContractListParams,
|
||||||
|
loadErrorMessage: "Не удалось загрузить контракты",
|
||||||
|
cleanListParams(params) {
|
||||||
|
params.name__contains = params.name__contains?.trim() || undefined;
|
||||||
|
params.number__contains = params.number__contains?.trim() || undefined;
|
||||||
|
params.counterparty_id = params.counterparty_id || undefined;
|
||||||
|
params.limit = PAGE_SIZE;
|
||||||
|
params.offset = params.offset ?? 0;
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
const selectedCounterpartyId = computed({
|
||||||
|
get() {
|
||||||
|
return filters.counterparty_id ?? 0;
|
||||||
|
},
|
||||||
|
set(id: number) {
|
||||||
|
filters.counterparty_id = id || undefined;
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
const hasActiveFilters = computed(() =>
|
||||||
|
Boolean(
|
||||||
|
filters.name__contains ||
|
||||||
|
filters.number__contains ||
|
||||||
|
filters.counterparty_id,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
|
||||||
|
const currentPage = computed({
|
||||||
|
get() {
|
||||||
|
return Math.floor((filters.offset ?? 0) / PAGE_SIZE) + 1;
|
||||||
|
},
|
||||||
|
set(page: number) {
|
||||||
|
filters.offset = (page - 1) * PAGE_SIZE;
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
interface SyncContractsResult {
|
||||||
|
synced: number;
|
||||||
|
pages: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
const syncing = ref(false);
|
||||||
|
|
||||||
|
async function syncContracts() {
|
||||||
|
syncing.value = true;
|
||||||
|
|
||||||
|
try {
|
||||||
|
const result = await invoke<SyncContractsResult>("sync_contracts");
|
||||||
|
showToast(`Синхронизировано: ${result.synced}`);
|
||||||
|
if (filters.offset) {
|
||||||
|
filters.offset = 0;
|
||||||
|
} else {
|
||||||
|
await loadContracts();
|
||||||
|
}
|
||||||
|
} catch (err) {
|
||||||
|
showToast(errorMessage(err, "Не удалось синхронизировать договоры"));
|
||||||
|
} finally {
|
||||||
|
syncing.value = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function errorMessage(err: unknown, fallback: string) {
|
||||||
|
if (typeof err === "object" && err && "detail" in err) {
|
||||||
|
return String((err as { detail: unknown }).detail);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (err instanceof Error) {
|
||||||
|
return err.message;
|
||||||
|
}
|
||||||
|
|
||||||
|
return fallback;
|
||||||
|
}
|
||||||
|
|
||||||
|
watch(
|
||||||
|
() => [filters.name__contains, filters.number__contains, filters.counterparty_id],
|
||||||
|
() => {
|
||||||
|
filters.offset = 0;
|
||||||
|
},
|
||||||
|
);
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<van-notice-bar
|
||||||
|
v-if="error"
|
||||||
|
class="notice"
|
||||||
|
color="#991b1b"
|
||||||
|
background="#fee2e2"
|
||||||
|
left-icon="warning-o"
|
||||||
|
wrapable
|
||||||
|
:scrollable="false"
|
||||||
|
:text="error"
|
||||||
|
/>
|
||||||
|
|
||||||
|
<section class="card list-card">
|
||||||
|
<van-search
|
||||||
|
v-model="filters.name__contains"
|
||||||
|
placeholder="Поиск по названию"
|
||||||
|
clearable
|
||||||
|
/>
|
||||||
|
|
||||||
|
<van-cell-group inset class="contract-filters">
|
||||||
|
<van-field
|
||||||
|
v-model="filters.number__contains"
|
||||||
|
label="Номер"
|
||||||
|
placeholder="Номер договора"
|
||||||
|
clearable
|
||||||
|
/>
|
||||||
|
<van-field label="Контрагент">
|
||||||
|
<template #input>
|
||||||
|
<CounterpartySelect v-model="selectedCounterpartyId" />
|
||||||
|
</template>
|
||||||
|
</van-field>
|
||||||
|
</van-cell-group>
|
||||||
|
|
||||||
|
<div class="list-title">
|
||||||
|
<h2>Список</h2>
|
||||||
|
<div class="list-actions">
|
||||||
|
<van-button size="small" plain type="primary" :loading="loading" @click="loadContracts()">
|
||||||
|
Обновить
|
||||||
|
</van-button>
|
||||||
|
<van-button size="small" type="primary" :loading="syncing" @click="syncContracts">
|
||||||
|
Синхронизация
|
||||||
|
</van-button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<van-loading
|
||||||
|
v-if="loading && contracts.length === 0"
|
||||||
|
class="state"
|
||||||
|
type="spinner"
|
||||||
|
>
|
||||||
|
Загрузка...
|
||||||
|
</van-loading>
|
||||||
|
|
||||||
|
<van-empty
|
||||||
|
v-else-if="contracts.length === 0"
|
||||||
|
:description="
|
||||||
|
hasActiveFilters ? 'Договоры не найдены' : 'Контрактов пока нет'
|
||||||
|
"
|
||||||
|
/>
|
||||||
|
|
||||||
|
<template v-else>
|
||||||
|
<van-cell-group inset>
|
||||||
|
<van-cell
|
||||||
|
v-for="contract in contracts"
|
||||||
|
:key="contract.id"
|
||||||
|
:title="contract.name"
|
||||||
|
:label="`№ ${contract.number} · ${contract.counterparty?.name ?? 'не указан'}`"
|
||||||
|
center
|
||||||
|
is-link
|
||||||
|
@click="router.push(`/contracts/${contract.id}`)"
|
||||||
|
>
|
||||||
|
<template #right-icon>
|
||||||
|
<van-tag plain type="primary">Contract</van-tag>
|
||||||
|
</template>
|
||||||
|
</van-cell>
|
||||||
|
</van-cell-group>
|
||||||
|
|
||||||
|
<van-pagination
|
||||||
|
v-model="currentPage"
|
||||||
|
class="contract-pagination"
|
||||||
|
:total-items="count"
|
||||||
|
:items-per-page="PAGE_SIZE"
|
||||||
|
mode="simple"
|
||||||
|
/>
|
||||||
|
</template>
|
||||||
|
</section>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
.contract-filters {
|
||||||
|
margin-bottom: 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.contract-pagination {
|
||||||
|
margin: 14px 16px 0;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
@@ -0,0 +1,87 @@
|
|||||||
|
<script setup lang="ts">
|
||||||
|
import { ref } from "vue";
|
||||||
|
import { useRoute, useRouter } from "vue-router";
|
||||||
|
import { showToast } from "vant";
|
||||||
|
import { useAuth } from "../composables/useAuth";
|
||||||
|
|
||||||
|
const route = useRoute();
|
||||||
|
const router = useRouter();
|
||||||
|
const { login } = useAuth();
|
||||||
|
|
||||||
|
const username = ref("");
|
||||||
|
const password = ref("");
|
||||||
|
const loading = ref(false);
|
||||||
|
const error = ref("");
|
||||||
|
|
||||||
|
async function submitLogin() {
|
||||||
|
const cleanUsername = username.value.trim();
|
||||||
|
if (!cleanUsername || !password.value) {
|
||||||
|
showToast("Введите логин и пароль");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
loading.value = true;
|
||||||
|
error.value = "";
|
||||||
|
|
||||||
|
try {
|
||||||
|
await login(cleanUsername, password.value);
|
||||||
|
await router.replace(String(route.query.redirect ?? "/tasks"));
|
||||||
|
} catch (err) {
|
||||||
|
error.value = errorMessage(err);
|
||||||
|
} finally {
|
||||||
|
loading.value = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function errorMessage(err: unknown) {
|
||||||
|
if (typeof err === "object" && err && "detail" in err) {
|
||||||
|
return String((err as { detail: unknown }).detail);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (err instanceof Error) {
|
||||||
|
return err.message;
|
||||||
|
}
|
||||||
|
|
||||||
|
return "Не удалось войти";
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<van-notice-bar
|
||||||
|
v-if="error"
|
||||||
|
class="notice"
|
||||||
|
color="#991b1b"
|
||||||
|
background="#fee2e2"
|
||||||
|
left-icon="warning-o"
|
||||||
|
wrapable
|
||||||
|
:scrollable="false"
|
||||||
|
:text="error"
|
||||||
|
/>
|
||||||
|
|
||||||
|
<van-form class="card" @submit="submitLogin">
|
||||||
|
<van-field
|
||||||
|
v-model="username"
|
||||||
|
name="username"
|
||||||
|
label="Логин"
|
||||||
|
placeholder="Введите логин"
|
||||||
|
autocomplete="username"
|
||||||
|
clearable
|
||||||
|
:disabled="loading"
|
||||||
|
/>
|
||||||
|
<van-field
|
||||||
|
v-model="password"
|
||||||
|
name="password"
|
||||||
|
label="Пароль"
|
||||||
|
type="password"
|
||||||
|
placeholder="Введите пароль"
|
||||||
|
autocomplete="current-password"
|
||||||
|
clearable
|
||||||
|
:disabled="loading"
|
||||||
|
/>
|
||||||
|
<div class="form-actions">
|
||||||
|
<van-button block round type="primary" native-type="submit" :loading="loading">
|
||||||
|
Войти
|
||||||
|
</van-button>
|
||||||
|
</div>
|
||||||
|
</van-form>
|
||||||
|
</template>
|
||||||
@@ -17,6 +17,8 @@ const {
|
|||||||
create: createTaskApi,
|
create: createTaskApi,
|
||||||
} = useModelApi(taskApi, {
|
} = useModelApi(taskApi, {
|
||||||
createErrorMessage: "Не удалось создать задачу",
|
createErrorMessage: "Не удалось создать задачу",
|
||||||
|
autoLoad: false,
|
||||||
|
autoLoadOnFilterChange: false,
|
||||||
});
|
});
|
||||||
|
|
||||||
async function createTask() {
|
async function createTask() {
|
||||||
@@ -41,12 +43,6 @@ async function createTask() {
|
|||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<section class="hero">
|
|
||||||
<p class="eyebrow">New task</p>
|
|
||||||
<h1>Создать задачу</h1>
|
|
||||||
<p class="subtitle">Заполните название и при необходимости выберите автора и ответственного.</p>
|
|
||||||
</section>
|
|
||||||
|
|
||||||
<van-notice-bar
|
<van-notice-bar
|
||||||
v-if="error"
|
v-if="error"
|
||||||
class="notice"
|
class="notice"
|
||||||
|
|||||||
@@ -19,6 +19,8 @@ const {
|
|||||||
retrieve: loadTask,
|
retrieve: loadTask,
|
||||||
} = useModelApi(taskApi, {
|
} = useModelApi(taskApi, {
|
||||||
retrieveErrorMessage: "Не удалось загрузить задачу",
|
retrieveErrorMessage: "Не удалось загрузить задачу",
|
||||||
|
autoLoad: false,
|
||||||
|
autoLoadOnFilterChange: false,
|
||||||
});
|
});
|
||||||
const {
|
const {
|
||||||
items: messages,
|
items: messages,
|
||||||
@@ -30,8 +32,10 @@ const {
|
|||||||
} = useModelApi(messageApi, {
|
} = useModelApi(messageApi, {
|
||||||
loadErrorMessage: "Не удалось загрузить сообщения",
|
loadErrorMessage: "Не удалось загрузить сообщения",
|
||||||
createErrorMessage: "Не удалось отправить сообщение",
|
createErrorMessage: "Не удалось отправить сообщение",
|
||||||
|
autoLoad: false,
|
||||||
|
autoLoadOnFilterChange: false,
|
||||||
});
|
});
|
||||||
const { items: employees, load: loadEmployees } = useModelApi(employeeApi, {
|
const { items: employees } = useModelApi(employeeApi, {
|
||||||
defaultListParams: { ordering: "id" },
|
defaultListParams: { ordering: "id" },
|
||||||
loadErrorMessage: "Не удалось загрузить сотрудников",
|
loadErrorMessage: "Не удалось загрузить сотрудников",
|
||||||
});
|
});
|
||||||
@@ -71,7 +75,7 @@ function employeeName(id: number) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function employeeAvatar(id: number) {
|
function employeeAvatar(id: number) {
|
||||||
return employees.value.find((employee) => employee.id === id)?.avatar ?? "";
|
return employees.value.find((employee) => employee.id === id)?.avatar_small ?? "";
|
||||||
}
|
}
|
||||||
|
|
||||||
onMounted(() => {
|
onMounted(() => {
|
||||||
@@ -79,17 +83,10 @@ onMounted(() => {
|
|||||||
loadTask(taskId.value);
|
loadTask(taskId.value);
|
||||||
loadMessages({ task_id: taskId.value });
|
loadMessages({ task_id: taskId.value });
|
||||||
}
|
}
|
||||||
loadEmployees();
|
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<section class="hero detail-hero">
|
|
||||||
<p class="eyebrow">Task details</p>
|
|
||||||
<h1>Детали</h1>
|
|
||||||
<p class="subtitle">Просмотр задачи и диалог сотрудников.</p>
|
|
||||||
</section>
|
|
||||||
|
|
||||||
<van-notice-bar
|
<van-notice-bar
|
||||||
v-if="error || messageError"
|
v-if="error || messageError"
|
||||||
class="notice"
|
class="notice"
|
||||||
|
|||||||
+1
-13
@@ -1,5 +1,4 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { onMounted } from "vue";
|
|
||||||
import { useRouter } from "vue-router";
|
import { useRouter } from "vue-router";
|
||||||
import { employeeApi, taskApi } from "../generated/api";
|
import { employeeApi, taskApi } from "../generated/api";
|
||||||
import { useModelApi } from "../composables/useModelApi";
|
import { useModelApi } from "../composables/useModelApi";
|
||||||
@@ -14,7 +13,7 @@ const {
|
|||||||
defaultListParams: { ordering: "id" },
|
defaultListParams: { ordering: "id" },
|
||||||
loadErrorMessage: "Не удалось загрузить задачи",
|
loadErrorMessage: "Не удалось загрузить задачи",
|
||||||
});
|
});
|
||||||
const { items: employees, load: loadEmployees } = useModelApi(employeeApi, {
|
const { items: employees } = useModelApi(employeeApi, {
|
||||||
defaultListParams: { ordering: "id" },
|
defaultListParams: { ordering: "id" },
|
||||||
loadErrorMessage: "Не удалось загрузить сотрудников",
|
loadErrorMessage: "Не удалось загрузить сотрудников",
|
||||||
});
|
});
|
||||||
@@ -26,20 +25,9 @@ function employeeName(id: number | null) {
|
|||||||
|
|
||||||
return employees.value.find((employee) => employee.id === id)?.name ?? `#${id}`;
|
return employees.value.find((employee) => employee.id === id)?.name ?? `#${id}`;
|
||||||
}
|
}
|
||||||
|
|
||||||
onMounted(() => {
|
|
||||||
loadTasks();
|
|
||||||
loadEmployees();
|
|
||||||
});
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<section class="hero">
|
|
||||||
<p class="eyebrow">Task manager</p>
|
|
||||||
<h1>Мои задачи</h1>
|
|
||||||
<p class="subtitle">Данные загружаются через `che_api` и отображаются компонентами Vant.</p>
|
|
||||||
</section>
|
|
||||||
|
|
||||||
<van-notice-bar
|
<van-notice-bar
|
||||||
v-if="error"
|
v-if="error"
|
||||||
class="notice"
|
class="notice"
|
||||||
|
|||||||
@@ -1,5 +1,4 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { onMounted } from "vue";
|
|
||||||
import { userApi } from "../generated/api";
|
import { userApi } from "../generated/api";
|
||||||
import { useModelApi } from "../composables/useModelApi";
|
import { useModelApi } from "../composables/useModelApi";
|
||||||
|
|
||||||
@@ -12,17 +11,9 @@ const {
|
|||||||
defaultListParams: { ordering: "id" },
|
defaultListParams: { ordering: "id" },
|
||||||
loadErrorMessage: "Не удалось загрузить пользователей",
|
loadErrorMessage: "Не удалось загрузить пользователей",
|
||||||
});
|
});
|
||||||
|
|
||||||
onMounted(loadUsers);
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<section class="hero">
|
|
||||||
<p class="eyebrow">Directory</p>
|
|
||||||
<h1>Пользователи</h1>
|
|
||||||
<p class="subtitle">Вторая страница для проверки маршрутизации и панели навигации.</p>
|
|
||||||
</section>
|
|
||||||
|
|
||||||
<van-notice-bar
|
<van-notice-bar
|
||||||
v-if="error"
|
v-if="error"
|
||||||
class="notice"
|
class="notice"
|
||||||
|
|||||||
Reference in New Issue
Block a user