43个版本
0.11.4 | 2024年6月21日 |
---|---|
0.11.3 | 2024年3月6日 |
0.11.2 | 2023年11月10日 |
0.9.0 | 2023年7月19日 |
0.5.8 | 2023年3月31日 |
#1431 在 数据库接口
每月 129 次下载
125KB
3K SLoC
Sqlx-Ledger
这个crate在sqlx Postgres集成之上提供了双边会计原语。
它具有以下特性:
- 账户可以在多个日记账上具有余额
- 多货币/多层支持
- 基于模板的交易输入以提高一致性
- 基于CEL的模板插值
- 事件流以通知更改
CEL解释器尚不完整,但提供了足够的功能来支持基本用例。随着需求的出现,将添加更多功能。
要使用它,请将迁移复制到您的项目中,并使用以下命令添加crate:cargo add sqlx-ledger
.
查看文档以获取使用示例
lib.rs
:
sqlx-ledger
这个crate在sqlx crate的基础上构建,提供了一套实现SQL兼容双边会计系统的原语。这个系统专门设计用于处理金钱和构建金融产品。
快速开始
在开始使用之前,请添加并执行迁移目录中的迁移。
cp ./migrations/* <path/to/your/projects/migrations>
# in your project
cargo sqlx migrate
以下是初始化账簿、创建基本模板和提交交易的方法。这是一个端到端结合所有组件的玩具示例。不推荐实际使用。
use uuid::uuid;
use rust_decimal::Decimal;
use sqlx_ledger::{*, journal::*, account::*, tx_template::*};
async fn init_ledger(journal_id: JournalId) -> SqlxLedger {
let pg_con =
std::env::var("PG_CON").unwrap_or(format!("postgres://user:password@localhost:5432/pg"));
let pool = sqlx::PgPool::connect(&pg_con).await.unwrap();
let ledger = SqlxLedger::new(&pool);
// Initialize the journal - all entities are constructed via builders
let new_journal = NewJournal::builder()
.id(journal_id)
.description("General ledger".to_string())
.name("Ledger")
.build()
.expect("Couldn't build NewJournal");
let _ = ledger.journals().create(new_journal).await;
// Initialize an income omnibus account
let main_account_id = uuid!("00000000-0000-0000-0000-000000000001");
let new_account = NewAccount::builder()
.id(main_account_id)
.name("Income")
.code("Income")
.build()
.unwrap();
let _ = ledger.accounts().create(new_account).await;
// Create the trivial 'income' template
//
// Here are the 'parameters' that the template will require as inputs.
let params = vec![
ParamDefinition::builder()
.name("sender_account_id")
.r#type(ParamDataType::UUID)
.build()
.unwrap(),
ParamDefinition::builder()
.name("units")
.r#type(ParamDataType::DECIMAL)
.build()
.unwrap()
];
// The templates for the Entries that will be created as part of the transaction.
let entries = vec![
EntryInput::builder()
.entry_type("'INCOME_DR'")
// Reference the input parameters via CEL syntax
.account_id("params.sender_account_id")
.layer("SETTLED")
.direction("DEBIT")
.units("params.units")
.currency("'BTC'")
.build()
.unwrap(),
EntryInput::builder()
.entry_type("'INCOME_CR'")
.account_id(format!("uuid('{main_account_id}')"))
.layer("SETTLED")
.direction("CREDIT")
.units("params.units")
.currency("'BTC'")
.build()
.unwrap(),
];
let tx_code = "GENERAL_INCOME";
let new_template = NewTxTemplate::builder()
.id(uuid::Uuid::new_v4())
.code(tx_code)
.params(params)
.tx_input(
// Template for the Transaction metadata.
TxInput::builder()
.effective("date()")
.journal_id(format!("uuid('{journal_id}')"))
.build()
.unwrap(),
)
.entries(entries)
.build()
.unwrap();
let _ = ledger.tx_templates().create(new_template).await;
ledger
}
tokio_test::block_on(async {
let journal_id = JournalId::from(uuid!("00000000-0000-0000-0000-000000000001"));
let ledger = init_ledger(journal_id).await;
// The account that is sending to the general income account
let sender_account_id = AccountId::new();
let sender_account = NewAccount::builder()
.id(sender_account_id)
.name(format!("Sender-{sender_account_id}"))
.code(format!("Sender-{sender_account_id}"))
.build()
.unwrap();
ledger.accounts().create(sender_account).await.unwrap();
// Prepare the input parameters that the template requires
let mut params = TxParams::new();
params.insert("sender_account_id", sender_account_id);
params.insert("units", Decimal::ONE);
// Create the transaction via the template
ledger
.post_transaction(TransactionId::new(), "GENERAL_INCOME", Some(params))
.await
.unwrap();
// Check the resulting balance
let account_balance = ledger
.balances()
.find(journal_id, sender_account_id, "BTC".parse().unwrap())
.await
.unwrap();
assert_eq!(account_balance.unwrap().settled(), Decimal::NEGATIVE_ONE);
});
依赖关系
~41–56MB
~1M SLoC