decoupled module system with exchange rates
This commit is contained in:
@@ -2,7 +2,7 @@
|
||||
|
||||
- When creating a new Rust file, format ONLY that new file with the Cargo formatter (`rustfmt --edition 2024 --config skip_children=true path/to/new_file.rs`). Use the file’s actual Rust edition. Never run workspace-wide or package-wide `cargo fmt`, and never format existing files unless explicitly requested.
|
||||
- Do not run other automatic code-formatting commands unless explicitly requested.
|
||||
- Write new or changed SQL in a human-readable layout. Use indented multiline queries, put SQL clauses and JOIN conditions on separate lines, and indent subqueries. Rust formatting does not format SQL inside strings; review that SQL manually.
|
||||
- Keep SQL easy to scan and preserve existing readable layouts. Short queries and simple subqueries may stay on one line; for example, keep each simple `EXISTS (SELECT 1 FROM ... WHERE ...)` condition together, with successive `OR EXISTS` conditions on separate lines. Use multiline layout and indentation when query complexity or line length warrants it, especially for long SELECT lists, multiple JOINs, or nested logic. Do not mechanically put every clause on its own line or expand compact SQL just to satisfy a formatting rule. Review SQL inside Rust strings manually; do not reformat unrelated queries.
|
||||
- Preserve existing formatting. Make only the smallest hand-edited changes required for the task.
|
||||
- Do not run builds, checks, linters, or pre-existing tests. Only run tests that an agent wrote or modified as part of the current task; if the agent did not write or modify any tests, run no tests.
|
||||
- Check if what you are doing is running. Server can be running, tauri app might be running. No need to turn on redundant systems.
|
||||
|
||||
@@ -9,6 +9,7 @@ fn main() -> Result<(), Box<dyn std::error::Error>> {
|
||||
.type_attribute(".komp_ac.accounting", serde)
|
||||
.type_attribute(".komp_ac.analytics", serde)
|
||||
.type_attribute(".komp_ac.auth", serde)
|
||||
.type_attribute(".komp_ac.common", serde)
|
||||
.type_attribute(".komp_ac.document_data", serde)
|
||||
.type_attribute(
|
||||
".komp_ac.document_data.TypstTemplateVersion",
|
||||
|
||||
@@ -127,6 +127,8 @@ message PreviewDirectConversionResponse {
|
||||
ExchangeRateDateRule applied_date_rule = 23;
|
||||
ExchangeRateSource applied_source = 24;
|
||||
string selection_reason = 25;
|
||||
string module_id = 26;
|
||||
string provider_evidence_json = 27;
|
||||
}
|
||||
|
||||
message ListConversionEvidenceRequest {
|
||||
@@ -167,6 +169,8 @@ message ConversionEvidence {
|
||||
optional string custom_rate_note = 26;
|
||||
string selection_reason = 27;
|
||||
optional string selected_by_user_id = 28;
|
||||
string module_id = 29;
|
||||
string provider_evidence_json = 30;
|
||||
}
|
||||
|
||||
message ListConversionEvidenceResponse {
|
||||
|
||||
@@ -9,6 +9,9 @@ import "common.proto";
|
||||
// logical "profiles" (schemas). Each table has stored structure, links, and
|
||||
// validation rules.
|
||||
service TableDefinition {
|
||||
rpc GetModuleConfiguration(GetModuleConfigurationRequest) returns (ModuleConfiguration);
|
||||
rpc SetModuleConfiguration(SetModuleConfigurationRequest) returns (ModuleConfiguration);
|
||||
|
||||
// Creates a new table (and schema if missing) with system columns,
|
||||
// linked-table foreign keys, user-defined columns, and optional indexes.
|
||||
// Also inserts metadata and default validation rules. Entirely transactional.
|
||||
@@ -102,6 +105,30 @@ service TableDefinition {
|
||||
rpc DeleteProfile(DeleteProfileRequest) returns (DeleteProfileResponse);
|
||||
}
|
||||
|
||||
message ModuleScope {
|
||||
oneof target {
|
||||
komp_ac.common.Empty global = 1;
|
||||
string profile_name = 2;
|
||||
}
|
||||
}
|
||||
|
||||
message GetModuleConfigurationRequest {
|
||||
ModuleScope scope = 1;
|
||||
}
|
||||
|
||||
message SetModuleConfigurationRequest {
|
||||
ModuleScope scope = 1;
|
||||
repeated string enabled_modules = 2;
|
||||
}
|
||||
|
||||
message ModuleConfiguration {
|
||||
ModuleScope scope = 1;
|
||||
repeated string available_modules = 2;
|
||||
repeated string selected_modules = 3;
|
||||
repeated string inherited_modules = 4;
|
||||
repeated string effective_modules = 5;
|
||||
}
|
||||
|
||||
message CreateCustomExchangeRatesTableRequest {
|
||||
string profile_name = 1;
|
||||
}
|
||||
|
||||
Binary file not shown.
@@ -1,11 +1,14 @@
|
||||
// This file is @generated by prost-build.
|
||||
#[derive(serde::Serialize, serde::Deserialize)]
|
||||
#[derive(Clone, Copy, PartialEq, Eq, Hash, ::prost::Message)]
|
||||
pub struct Empty {}
|
||||
#[derive(serde::Serialize, serde::Deserialize)]
|
||||
#[derive(Clone, Copy, PartialEq, Eq, Hash, ::prost::Message)]
|
||||
pub struct CountResponse {
|
||||
#[prost(int64, tag = "1")]
|
||||
pub count: i64,
|
||||
}
|
||||
#[derive(serde::Serialize, serde::Deserialize)]
|
||||
#[derive(Clone, Copy, PartialEq, Eq, Hash, ::prost::Message)]
|
||||
pub struct PositionRequest {
|
||||
#[prost(int64, tag = "1")]
|
||||
|
||||
@@ -97,6 +97,10 @@ pub struct PreviewDirectConversionResponse {
|
||||
pub applied_source: i32,
|
||||
#[prost(string, tag = "25")]
|
||||
pub selection_reason: ::prost::alloc::string::String,
|
||||
#[prost(string, tag = "26")]
|
||||
pub module_id: ::prost::alloc::string::String,
|
||||
#[prost(string, tag = "27")]
|
||||
pub provider_evidence_json: ::prost::alloc::string::String,
|
||||
}
|
||||
#[derive(serde::Serialize, serde::Deserialize)]
|
||||
#[derive(Clone, PartialEq, Eq, Hash, ::prost::Message)]
|
||||
@@ -173,6 +177,10 @@ pub struct ConversionEvidence {
|
||||
pub selection_reason: ::prost::alloc::string::String,
|
||||
#[prost(string, optional, tag = "28")]
|
||||
pub selected_by_user_id: ::core::option::Option<::prost::alloc::string::String>,
|
||||
#[prost(string, tag = "29")]
|
||||
pub module_id: ::prost::alloc::string::String,
|
||||
#[prost(string, tag = "30")]
|
||||
pub provider_evidence_json: ::prost::alloc::string::String,
|
||||
}
|
||||
#[derive(serde::Serialize, serde::Deserialize)]
|
||||
#[derive(Clone, PartialEq, ::prost::Message)]
|
||||
|
||||
@@ -1,6 +1,51 @@
|
||||
// This file is @generated by prost-build.
|
||||
#[derive(serde::Serialize, serde::Deserialize)]
|
||||
#[derive(Clone, PartialEq, Eq, Hash, ::prost::Message)]
|
||||
pub struct ModuleScope {
|
||||
#[prost(oneof = "module_scope::Target", tags = "1, 2")]
|
||||
pub target: ::core::option::Option<module_scope::Target>,
|
||||
}
|
||||
/// Nested message and enum types in `ModuleScope`.
|
||||
pub mod module_scope {
|
||||
#[derive(serde::Serialize, serde::Deserialize)]
|
||||
#[derive(Clone, PartialEq, Eq, Hash, ::prost::Oneof)]
|
||||
pub enum Target {
|
||||
#[prost(message, tag = "1")]
|
||||
Global(super::super::common::Empty),
|
||||
#[prost(string, tag = "2")]
|
||||
ProfileName(::prost::alloc::string::String),
|
||||
}
|
||||
}
|
||||
#[derive(serde::Serialize, serde::Deserialize)]
|
||||
#[derive(Clone, PartialEq, Eq, Hash, ::prost::Message)]
|
||||
pub struct GetModuleConfigurationRequest {
|
||||
#[prost(message, optional, tag = "1")]
|
||||
pub scope: ::core::option::Option<ModuleScope>,
|
||||
}
|
||||
#[derive(serde::Serialize, serde::Deserialize)]
|
||||
#[derive(Clone, PartialEq, Eq, Hash, ::prost::Message)]
|
||||
pub struct SetModuleConfigurationRequest {
|
||||
#[prost(message, optional, tag = "1")]
|
||||
pub scope: ::core::option::Option<ModuleScope>,
|
||||
#[prost(string, repeated, tag = "2")]
|
||||
pub enabled_modules: ::prost::alloc::vec::Vec<::prost::alloc::string::String>,
|
||||
}
|
||||
#[derive(serde::Serialize, serde::Deserialize)]
|
||||
#[derive(Clone, PartialEq, Eq, Hash, ::prost::Message)]
|
||||
pub struct ModuleConfiguration {
|
||||
#[prost(message, optional, tag = "1")]
|
||||
pub scope: ::core::option::Option<ModuleScope>,
|
||||
#[prost(string, repeated, tag = "2")]
|
||||
pub available_modules: ::prost::alloc::vec::Vec<::prost::alloc::string::String>,
|
||||
#[prost(string, repeated, tag = "3")]
|
||||
pub selected_modules: ::prost::alloc::vec::Vec<::prost::alloc::string::String>,
|
||||
#[prost(string, repeated, tag = "4")]
|
||||
pub inherited_modules: ::prost::alloc::vec::Vec<::prost::alloc::string::String>,
|
||||
#[prost(string, repeated, tag = "5")]
|
||||
pub effective_modules: ::prost::alloc::vec::Vec<::prost::alloc::string::String>,
|
||||
}
|
||||
#[derive(serde::Serialize, serde::Deserialize)]
|
||||
#[derive(Clone, PartialEq, Eq, Hash, ::prost::Message)]
|
||||
pub struct CreateCustomExchangeRatesTableRequest {
|
||||
#[prost(string, tag = "1")]
|
||||
pub profile_name: ::prost::alloc::string::String,
|
||||
@@ -1314,6 +1359,64 @@ pub mod table_definition_client {
|
||||
self.inner = self.inner.max_encoding_message_size(limit);
|
||||
self
|
||||
}
|
||||
pub async fn get_module_configuration(
|
||||
&mut self,
|
||||
request: impl tonic::IntoRequest<super::GetModuleConfigurationRequest>,
|
||||
) -> std::result::Result<
|
||||
tonic::Response<super::ModuleConfiguration>,
|
||||
tonic::Status,
|
||||
> {
|
||||
self.inner
|
||||
.ready()
|
||||
.await
|
||||
.map_err(|e| {
|
||||
tonic::Status::unknown(
|
||||
format!("Service was not ready: {}", e.into()),
|
||||
)
|
||||
})?;
|
||||
let codec = tonic_prost::ProstCodec::default();
|
||||
let path = http::uri::PathAndQuery::from_static(
|
||||
"/komp_ac.table_definition.TableDefinition/GetModuleConfiguration",
|
||||
);
|
||||
let mut req = request.into_request();
|
||||
req.extensions_mut()
|
||||
.insert(
|
||||
GrpcMethod::new(
|
||||
"komp_ac.table_definition.TableDefinition",
|
||||
"GetModuleConfiguration",
|
||||
),
|
||||
);
|
||||
self.inner.unary(req, path, codec).await
|
||||
}
|
||||
pub async fn set_module_configuration(
|
||||
&mut self,
|
||||
request: impl tonic::IntoRequest<super::SetModuleConfigurationRequest>,
|
||||
) -> std::result::Result<
|
||||
tonic::Response<super::ModuleConfiguration>,
|
||||
tonic::Status,
|
||||
> {
|
||||
self.inner
|
||||
.ready()
|
||||
.await
|
||||
.map_err(|e| {
|
||||
tonic::Status::unknown(
|
||||
format!("Service was not ready: {}", e.into()),
|
||||
)
|
||||
})?;
|
||||
let codec = tonic_prost::ProstCodec::default();
|
||||
let path = http::uri::PathAndQuery::from_static(
|
||||
"/komp_ac.table_definition.TableDefinition/SetModuleConfiguration",
|
||||
);
|
||||
let mut req = request.into_request();
|
||||
req.extensions_mut()
|
||||
.insert(
|
||||
GrpcMethod::new(
|
||||
"komp_ac.table_definition.TableDefinition",
|
||||
"SetModuleConfiguration",
|
||||
),
|
||||
);
|
||||
self.inner.unary(req, path, codec).await
|
||||
}
|
||||
/// Creates a new table (and schema if missing) with system columns,
|
||||
/// linked-table foreign keys, user-defined columns, and optional indexes.
|
||||
/// Also inserts metadata and default validation rules. Entirely transactional.
|
||||
@@ -2044,6 +2147,20 @@ pub mod table_definition_server {
|
||||
/// Generated trait containing gRPC methods that should be implemented for use with TableDefinitionServer.
|
||||
#[async_trait]
|
||||
pub trait TableDefinition: std::marker::Send + std::marker::Sync + 'static {
|
||||
async fn get_module_configuration(
|
||||
&self,
|
||||
request: tonic::Request<super::GetModuleConfigurationRequest>,
|
||||
) -> std::result::Result<
|
||||
tonic::Response<super::ModuleConfiguration>,
|
||||
tonic::Status,
|
||||
>;
|
||||
async fn set_module_configuration(
|
||||
&self,
|
||||
request: tonic::Request<super::SetModuleConfigurationRequest>,
|
||||
) -> std::result::Result<
|
||||
tonic::Response<super::ModuleConfiguration>,
|
||||
tonic::Status,
|
||||
>;
|
||||
/// Creates a new table (and schema if missing) with system columns,
|
||||
/// linked-table foreign keys, user-defined columns, and optional indexes.
|
||||
/// Also inserts metadata and default validation rules. Entirely transactional.
|
||||
@@ -2332,6 +2449,104 @@ pub mod table_definition_server {
|
||||
}
|
||||
fn call(&mut self, req: http::Request<B>) -> Self::Future {
|
||||
match req.uri().path() {
|
||||
"/komp_ac.table_definition.TableDefinition/GetModuleConfiguration" => {
|
||||
#[allow(non_camel_case_types)]
|
||||
struct GetModuleConfigurationSvc<T: TableDefinition>(pub Arc<T>);
|
||||
impl<
|
||||
T: TableDefinition,
|
||||
> tonic::server::UnaryService<super::GetModuleConfigurationRequest>
|
||||
for GetModuleConfigurationSvc<T> {
|
||||
type Response = super::ModuleConfiguration;
|
||||
type Future = BoxFuture<
|
||||
tonic::Response<Self::Response>,
|
||||
tonic::Status,
|
||||
>;
|
||||
fn call(
|
||||
&mut self,
|
||||
request: tonic::Request<super::GetModuleConfigurationRequest>,
|
||||
) -> Self::Future {
|
||||
let inner = Arc::clone(&self.0);
|
||||
let fut = async move {
|
||||
<T as TableDefinition>::get_module_configuration(
|
||||
&inner,
|
||||
request,
|
||||
)
|
||||
.await
|
||||
};
|
||||
Box::pin(fut)
|
||||
}
|
||||
}
|
||||
let accept_compression_encodings = self.accept_compression_encodings;
|
||||
let send_compression_encodings = self.send_compression_encodings;
|
||||
let max_decoding_message_size = self.max_decoding_message_size;
|
||||
let max_encoding_message_size = self.max_encoding_message_size;
|
||||
let inner = self.inner.clone();
|
||||
let fut = async move {
|
||||
let method = GetModuleConfigurationSvc(inner);
|
||||
let codec = tonic_prost::ProstCodec::default();
|
||||
let mut grpc = tonic::server::Grpc::new(codec)
|
||||
.apply_compression_config(
|
||||
accept_compression_encodings,
|
||||
send_compression_encodings,
|
||||
)
|
||||
.apply_max_message_size_config(
|
||||
max_decoding_message_size,
|
||||
max_encoding_message_size,
|
||||
);
|
||||
let res = grpc.unary(method, req).await;
|
||||
Ok(res)
|
||||
};
|
||||
Box::pin(fut)
|
||||
}
|
||||
"/komp_ac.table_definition.TableDefinition/SetModuleConfiguration" => {
|
||||
#[allow(non_camel_case_types)]
|
||||
struct SetModuleConfigurationSvc<T: TableDefinition>(pub Arc<T>);
|
||||
impl<
|
||||
T: TableDefinition,
|
||||
> tonic::server::UnaryService<super::SetModuleConfigurationRequest>
|
||||
for SetModuleConfigurationSvc<T> {
|
||||
type Response = super::ModuleConfiguration;
|
||||
type Future = BoxFuture<
|
||||
tonic::Response<Self::Response>,
|
||||
tonic::Status,
|
||||
>;
|
||||
fn call(
|
||||
&mut self,
|
||||
request: tonic::Request<super::SetModuleConfigurationRequest>,
|
||||
) -> Self::Future {
|
||||
let inner = Arc::clone(&self.0);
|
||||
let fut = async move {
|
||||
<T as TableDefinition>::set_module_configuration(
|
||||
&inner,
|
||||
request,
|
||||
)
|
||||
.await
|
||||
};
|
||||
Box::pin(fut)
|
||||
}
|
||||
}
|
||||
let accept_compression_encodings = self.accept_compression_encodings;
|
||||
let send_compression_encodings = self.send_compression_encodings;
|
||||
let max_decoding_message_size = self.max_decoding_message_size;
|
||||
let max_encoding_message_size = self.max_encoding_message_size;
|
||||
let inner = self.inner.clone();
|
||||
let fut = async move {
|
||||
let method = SetModuleConfigurationSvc(inner);
|
||||
let codec = tonic_prost::ProstCodec::default();
|
||||
let mut grpc = tonic::server::Grpc::new(codec)
|
||||
.apply_compression_config(
|
||||
accept_compression_encodings,
|
||||
send_compression_encodings,
|
||||
)
|
||||
.apply_max_message_size_config(
|
||||
max_decoding_message_size,
|
||||
max_encoding_message_size,
|
||||
);
|
||||
let res = grpc.unary(method, req).await;
|
||||
Ok(res)
|
||||
};
|
||||
Box::pin(fut)
|
||||
}
|
||||
"/komp_ac.table_definition.TableDefinition/PostTableDefinition" => {
|
||||
#[allow(non_camel_case_types)]
|
||||
struct PostTableDefinitionSvc<T: TableDefinition>(pub Arc<T>);
|
||||
|
||||
12
generic_rust_todo.txt
Normal file
12
generic_rust_todo.txt
Normal file
@@ -0,0 +1,12 @@
|
||||
1. - Your Rust module can define a column type XX, which you can add through the table definition.
|
||||
- When a row is saved, the framework calls the registered modules’ row hooks.
|
||||
- You implement your module’s hook and choose whether its logic runs for every table, only tables containing XX, or
|
||||
another condition.
|
||||
|
||||
- If you want it to affect only the XX column, your hook selects and processes that column’s value from the supplied
|
||||
row.
|
||||
|
||||
- The framework doesn’t automatically make those choices. You implement the filtering and column-specific behavior;
|
||||
modules without a row-hook implementation default to doing nothing.
|
||||
|
||||
2.
|
||||
2
server
2
server
Submodule server updated: cc585a2a9c...1da33d6562
Reference in New Issue
Block a user