Skip to content

Commit

Permalink
Merge pull request #280 from zhang-accounting/277-support-multiple-op…
Browse files Browse the repository at this point in the history
…erating-currency

277 support multiple operating currency
  • Loading branch information
Kilerd authored Mar 28, 2024
2 parents 3b2dd0e + cc1f216 commit 5bcef00
Show file tree
Hide file tree
Showing 9 changed files with 134 additions and 71 deletions.
62 changes: 30 additions & 32 deletions frontend/public/locales/en/translation.json
Original file line number Diff line number Diff line change
@@ -1,34 +1,32 @@
{
"Modify": "Modify",
"ASSET_BLANACE": "Asset Balance",
"LIABILITY": "Liability",
"CURRENT_MONTH_INCOME":"Montly Income",
"CURRENT_MONTH_EXPENSE":"Monthly Expenses",
"RESET": "Reset",
"REFRESH": "Refresh",

"NEW_TRANSACTION_PAYEE_CREATE": "Create",

"NAV_HOME": "Home",
"NAV_JOURNALS": "Journals",
"NAV_ACCOUNTS": "Accounts",
"NAV_COMMDOITIES": "Commodities",
"NAV_DOCUMENTS": "Documents",
"NAV_REPORT": "Report",
"NAV_LIABILITY": "Liability",
"NAV_RAW_EDITING": "Raw Editing",
"NAV_TOOLS": "Tools",
"NAV_SETTING": "Setting",
"NAV_BUDGETS": "Budget",

"AccountDoesNotExist": "Account does not exist",
"AccountBalanceCheckError": " Account does not pass the balance check",
"AccountClosed": "Try to operate a closed account",
"TransactionDoesNotBalance": "Transaction does not balance",
"CommodityDoesNotDefine": "Try to use a undefined commodity",
"TransactionHasMultipleImplicitPosting": "Transaction has more than one implicit posting unit",
"CloseNonZeroAccount": "Trying to close an account with non zero balance",

"ACCOUNT_FILTER_PLACEHOLDER": "filter by keyword...",
"ACCOUNT_FILTER_CLOSE_BUTTON_ARIA": "clean account filter keyword"
"Modify": "Modify",
"ASSET_BLANACE": "Asset Balance",
"LIABILITY": "Liability",
"CURRENT_MONTH_INCOME": "Montly Income",
"CURRENT_MONTH_EXPENSE": "Monthly Expenses",
"RESET": "Reset",
"SAVE": "Save",
"REFRESH": "Refresh",
"NEW_TRANSACTION_PAYEE_CREATE": "Create",
"NAV_HOME": "Home",
"NAV_JOURNALS": "Journals",
"NAV_ACCOUNTS": "Accounts",
"NAV_COMMDOITIES": "Commodities",
"NAV_DOCUMENTS": "Documents",
"NAV_REPORT": "Report",
"NAV_LIABILITY": "Liability",
"NAV_RAW_EDITING": "Raw Editing",
"NAV_TOOLS": "Tools",
"NAV_SETTING": "Setting",
"NAV_BUDGETS": "Budget",
"ERROR.AccountDoesNotExist": "Account does not exist",
"ERROR.AccountBalanceCheckError": " Account does not pass the balance check",
"ERROR.AccountClosed": "Try to operate a closed account",
"ERROR.TransactionDoesNotBalance": "Transaction does not balance",
"ERROR.CommodityDoesNotDefine": "Try to use a undefined commodity",
"ERROR.TransactionHasMultipleImplicitPosting": "Transaction has more than one implicit posting unit",
"ERROR.CloseNonZeroAccount": "Trying to close an account with non zero balance",
"ERROR.MultipleOperatingCurrencyDetect": "Ledger contains multiple operating currency options, which is not recommended in zhang",
"ACCOUNT_FILTER_PLACEHOLDER": "filter by keyword...",
"ACCOUNT_FILTER_CLOSE_BUTTON_ARIA": "clean account filter keyword"
}
53 changes: 30 additions & 23 deletions frontend/public/locales/zh/translation.json
Original file line number Diff line number Diff line change
@@ -1,25 +1,32 @@
{
"Modify": "修改",
"ASSET_BLANACE": "账户余额",
"LIABILITY": "负债",
"CURRENT_MONTH_INCOME":"本月收入",
"CURRENT_MONTH_EXPENSE":"本月支出",
"RESET": "还原",
"REFRESH": "刷新",

"NEW_TRANSACTION_PAYEE_CREATE": "新增",

"NAV_HOME": "仪表台",
"NAV_JOURNALS": "流水",
"NAV_ACCOUNTS": "账户",
"NAV_COMMDOITIES": "货币",
"NAV_DOCUMENTS": "文档",
"NAV_REPORT": "报表",
"NAV_LIABILITY": "负债",
"NAV_RAW_EDITING": "编辑",
"NAV_TOOLS": "工具",
"NAV_SETTING": "设置",
"NAV_BUDGETS": "预算",
"ACCOUNT_FILTER_PLACEHOLDER": "按关键字过滤...",
"ACCOUNT_FILTER_CLOSE_BUTTON_ARIA": "清空过滤关键字"
"Modify": "修改",
"ASSET_BLANACE": "账户余额",
"LIABILITY": "负债",
"CURRENT_MONTH_INCOME": "本月收入",
"CURRENT_MONTH_EXPENSE": "本月支出",
"RESET": "还原",
"SAVE": "保存",
"REFRESH": "刷新",
"NEW_TRANSACTION_PAYEE_CREATE": "新增",
"NAV_HOME": "仪表台",
"NAV_JOURNALS": "流水",
"NAV_ACCOUNTS": "账户",
"NAV_COMMDOITIES": "货币",
"NAV_DOCUMENTS": "文档",
"NAV_REPORT": "报表",
"NAV_LIABILITY": "负债",
"NAV_RAW_EDITING": "编辑",
"NAV_TOOLS": "工具",
"NAV_SETTING": "设置",
"NAV_BUDGETS": "预算",
"ACCOUNT_FILTER_PLACEHOLDER": "按关键字过滤...",
"ACCOUNT_FILTER_CLOSE_BUTTON_ARIA": "清空过滤关键字",
"ERROR.AccountDoesNotExist": "对应账户不存在",
"ERROR.AccountBalanceCheckError": "账户定期对账不通过",
"ERROR.AccountClosed": "尝试使用一个已经关闭的账户",
"ERROR.TransactionDoesNotBalance": "该交易行间金额不平衡",
"ERROR.CommodityDoesNotDefine": "尝试使用一个未定义的货币",
"ERROR.TransactionHasMultipleImplicitPosting": "该交易存在多条隐形/需推倒金额的行",
"ERROR.CloseNonZeroAccount": "尝试关闭一个余额非零的账户",
"ERROR.MultipleOperatingCurrencyDetect": "账本中存在多项 operating currency 的配置,这是 zhang 中不推荐的用法"
}
8 changes: 4 additions & 4 deletions frontend/src/components/ErrorBox.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -61,7 +61,7 @@ export default function ErrorBox() {
onClose={() => setIsOpen(false)}
title={`${selectError?.span.filename}:${selectError?.span.start}:${selectError?.span.end}`}
>
<Text>{t(selectError?.error_type || '')}</Text>
<Text>{t(`ERROR.${selectError?.error_type || ''}`)}</Text>
<Textarea
value={selectErrorContent}
onChange={(event) => {
Expand All @@ -70,17 +70,17 @@ export default function ErrorBox() {
/>
<Group position="right">
<Button onClick={onModalReset} variant="default">
{t('Reset')}
{t('RESET')}
</Button>
<Button onClick={saveErrorModfiyData} variant="default">
{t('Save')}
{t('SAVE')}
</Button>
</Group>
</Modal>
<Stack>
{items.map((error, idx) => (
<Text key={idx} onClick={() => toggleError(error)}>
{t(error.error_type)}
{t(`ERROR.${error.error_type}`)}
</Text>
))}

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
option "title" "My Accounting"
option "operating_currency" "CNY"
option "operating_currency" "USD"
option "operating_currency" "EUR"

1970-01-01 open Assets:BankCard CNY,USD
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
[
{
"uri": "/api/store",
"validations": [
[
"$.data.errors.length()",
2
],
[
"$.data.errors[0].error_type",
"MultipleOperatingCurrencyDetect"
],
[
"$.data.errors[1].error_type",
"MultipleOperatingCurrencyDetect"
],
[
"$.data.options.operating_currency",
"EUR"
],
[
"$.data.commodities.CNY.name",
"CNY"
],
[
"$.data.commodities.USD.name",
"USD"
],
[
"$.data.commodities.EUR.name",
"EUR"
]
]
}
]
2 changes: 2 additions & 0 deletions zhang-ast/src/error.rs
Original file line number Diff line number Diff line change
Expand Up @@ -17,4 +17,6 @@ pub enum ErrorKind {
CloseNonZeroAccount,

BudgetDoesNotExist,

MultipleOperatingCurrencyDetect,
}
17 changes: 12 additions & 5 deletions zhang-core/src/ledger.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
use std::borrow::Cow;
use std::cmp::Ordering;
use std::collections::HashSet;
use std::path::PathBuf;
use std::sync::atomic::AtomicI32;
use std::sync::{Arc, RwLock};
Expand Down Expand Up @@ -65,14 +67,19 @@ impl Ledger {
store: Default::default(),
trx_counter: AtomicI32::new(1),
};
let mut merged_metas = BuiltinOption::default_options()

let options_key: HashSet<Cow<str>> = meta_directives
.iter()
.filter_map(|it| match &it.data {
Directive::Option(option) => Some(Cow::Borrowed(option.key.as_str())),
_ => None,
})
.collect();

let mut merged_metas = BuiltinOption::default_options(options_key)
.into_iter()
.chain(meta_directives)
.rev()
.dedup_by(|x, y| match (&x.data, &y.data) {
(Directive::Option(option_x), Directive::Option(option_y)) => option_x.key.eq(&option_y.key),
_ => false,
})
.collect_vec();
for directive in merged_metas.iter_mut().rev().chain(directives.iter_mut()) {
match &mut directive.data {
Expand Down
18 changes: 13 additions & 5 deletions zhang-core/src/options.rs
Original file line number Diff line number Diff line change
@@ -1,9 +1,12 @@
use std::borrow::Cow;
use std::collections::{HashMap, HashSet};
use std::str::FromStr;

use chrono_tz::Tz;
use itertools::Itertools;
use log::{error, info, warn};
use strum::{AsRefStr, EnumIter, EnumString, IntoEnumIterator};
use zhang_ast::error::ErrorKind;
use zhang_ast::{Directive, Options, Rounding, SpanInfo, Spanned, ZhangString};

use crate::constants::{
Expand Down Expand Up @@ -53,13 +56,14 @@ impl BuiltinOption {
pub fn key(&self) -> &str {
self.as_ref()
}
pub fn default_options() -> Vec<Spanned<Directive>> {
pub fn default_options(options_key: HashSet<Cow<str>>) -> Vec<Spanned<Directive>> {
BuiltinOption::iter()
.map(|key| {
.filter(|it| !options_key.contains(it.key()))
.map(|it| {
Spanned::new(
Directive::Option(Options {
key: ZhangString::quote(key.as_ref()),
value: ZhangString::quote(key.default_value()),
key: ZhangString::quote(it.as_ref()),
value: ZhangString::quote(it.default_value()),
}),
SpanInfo::default(),
)
Expand All @@ -69,7 +73,7 @@ impl BuiltinOption {
}

impl InMemoryOptions {
pub fn parse(&mut self, key: impl Into<String>, value: impl Into<String>, operation: &mut Operations) -> ZhangResult<String> {
pub fn parse(&mut self, key: impl Into<String>, value: impl Into<String>, operation: &mut Operations, span: &SpanInfo) -> ZhangResult<String> {
let value = value.into();
let key = key.into();
if let Ok(option) = BuiltinOption::from_str(&key) {
Expand All @@ -80,6 +84,10 @@ impl InMemoryOptions {
let suffix: Option<String> = None;
let rounding = self.default_rounding;

let has_operating_currency = operation.option(key)?.is_some();
if has_operating_currency {
operation.new_error(ErrorKind::MultipleOperatingCurrencyDetect, span, HashMap::default())?;
}
operation.insert_commodity(&value, precision, prefix, suffix, rounding)?;

value.clone_into(&mut self.operating_currency);
Expand Down
4 changes: 2 additions & 2 deletions zhang-core/src/process.rs
Original file line number Diff line number Diff line change
Expand Up @@ -62,9 +62,9 @@ fn check_commodity_define(commodity_name: &str, ledger: &mut Ledger, span: &Span
}

impl DirectiveProcess for Options {
fn process(&mut self, ledger: &mut Ledger, _span: &SpanInfo) -> ZhangResult<()> {
fn process(&mut self, ledger: &mut Ledger, span: &SpanInfo) -> ZhangResult<()> {
let mut operations = ledger.operations();
let option_value = ledger.options.parse(self.key.as_str(), self.value.as_str(), &mut operations)?;
let option_value = ledger.options.parse(self.key.as_str(), self.value.as_str(), &mut operations, span)?;
operations.insert_or_update_options(self.key.as_str(), option_value.as_str())?;
Ok(())
}
Expand Down

0 comments on commit 5bcef00

Please sign in to comment.