From 5fbff39c4fdbb93d3b549126e709083474f20391 Mon Sep 17 00:00:00 2001 From: Zhengchen Tao Date: Sun, 5 Apr 2026 17:04:16 +0800 Subject: [PATCH] =?UTF-8?q?=E6=B7=BB=E5=8A=A0=E4=BF=A1=E7=94=A8=E9=A2=9D?= =?UTF-8?q?=E5=BA=A6=E5=8A=9F=E8=83=BD=EF=BC=9A=E5=9C=A8=E8=B4=A6=E6=88=B7?= =?UTF-8?q?=E6=A8=A1=E5=9E=8B=E4=B8=AD=E6=96=B0=E5=A2=9E=E4=BF=A1=E7=94=A8?= =?UTF-8?q?=E9=A2=9D=E5=BA=A6=E5=AD=97=E6=AE=B5=EF=BC=8C=E6=9B=B4=E6=96=B0?= =?UTF-8?q?=E7=9B=B8=E5=85=B3=E8=AF=B7=E6=B1=82=E5=92=8C=E5=93=8D=E5=BA=94?= =?UTF-8?q?=E7=BB=93=E6=9E=84=EF=BC=8C=E4=BF=AE=E6=94=B9=E8=B4=A6=E6=88=B7?= =?UTF-8?q?=E5=88=9B=E5=BB=BA=E5=92=8C=E4=BF=AE=E6=94=B9=E9=80=BB=E8=BE=91?= =?UTF-8?q?=EF=BC=8C=E6=9B=B4=E6=96=B0=E7=95=8C=E9=9D=A2=E4=BB=A5=E6=94=AF?= =?UTF-8?q?=E6=8C=81=E4=BF=A1=E7=94=A8=E9=A2=9D=E5=BA=A6=E7=9A=84=E6=98=BE?= =?UTF-8?q?=E7=A4=BA=E5=92=8C=E7=BC=96=E8=BE=91=E3=80=82?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- pkg/api/accounts.go | 12 +++++++ pkg/models/account.go | 11 ++++++- src/locales/en.json | 2 ++ src/locales/zh_Hans.json | 2 ++ src/models/account.ts | 27 ++++++++++++---- src/views/mobile/accounts/EditPage.vue | 45 ++++++++++++++++++++++++++ src/views/mobile/accounts/ListPage.vue | 11 ++++++- 7 files changed, 102 insertions(+), 8 deletions(-) diff --git a/pkg/api/accounts.go b/pkg/api/accounts.go index c0bb32b4..0c860e6a 100644 --- a/pkg/api/accounts.go +++ b/pkg/api/accounts.go @@ -713,6 +713,9 @@ func (a *AccountsApi) createNewAccountModel(uid int64, accountCreateReq *models. if !isSubAccount && accountCreateReq.Category == models.ACCOUNT_CATEGORY_CREDIT_CARD { accountExtend.CreditCardStatementDate = &accountCreateReq.CreditCardStatementDate + if accountCreateReq.CreditLimit > 0 { + accountExtend.CreditLimit = &accountCreateReq.CreditLimit + } } return &models.Account{ @@ -769,6 +772,9 @@ func (a *AccountsApi) getToUpdateAccount(uid int64, accountModifyReq *models.Acc if !isSubAccount && accountModifyReq.Category == models.ACCOUNT_CATEGORY_CREDIT_CARD { newAccountExtend.CreditCardStatementDate = &accountModifyReq.CreditCardStatementDate + if accountModifyReq.CreditLimit > 0 { + newAccountExtend.CreditLimit = &accountModifyReq.CreditLimit + } } newAccount := &models.Account{ @@ -804,6 +810,12 @@ func (a *AccountsApi) getToUpdateAccount(uid int64, accountModifyReq *models.Acc return newAccount } + if newAccountExtend.CreditLimit != oldAccountExtend.CreditLimit { + if newAccountExtend.CreditLimit == nil || oldAccountExtend.CreditLimit == nil || *newAccountExtend.CreditLimit != *oldAccountExtend.CreditLimit { + return newAccount + } + } + return nil } diff --git a/pkg/models/account.go b/pkg/models/account.go index dd8ec37a..41838719 100644 --- a/pkg/models/account.go +++ b/pkg/models/account.go @@ -90,7 +90,8 @@ type Account struct { // AccountExtend represents account extend data stored in database type AccountExtend struct { - CreditCardStatementDate *int `json:"creditCardStatementDate"` + CreditCardStatementDate *int `json:"creditCardStatementDate"` + CreditLimit *int64 `json:"creditLimit"` } // AccountCreateRequest represents all parameters of account creation request @@ -105,6 +106,7 @@ type AccountCreateRequest struct { BalanceTime int64 `json:"balanceTime"` Comment string `json:"comment" binding:"max=255"` CreditCardStatementDate int `json:"creditCardStatementDate" binding:"min=0,max=28"` + CreditLimit int64 `json:"creditLimit" binding:"min=0"` SubAccounts []*AccountCreateRequest `json:"subAccounts" binding:"omitempty"` ClientSessionId string `json:"clientSessionId"` } @@ -121,6 +123,7 @@ type AccountModifyRequest struct { BalanceTime *int64 `json:"balanceTime" binding:"omitempty"` Comment string `json:"comment" binding:"max=255"` CreditCardStatementDate int `json:"creditCardStatementDate" binding:"min=0,max=28"` + CreditLimit int64 `json:"creditLimit" binding:"min=0"` Hidden bool `json:"hidden"` SubAccounts []*AccountModifyRequest `json:"subAccounts" binding:"omitempty"` ClientSessionId string `json:"clientSessionId"` @@ -171,6 +174,7 @@ type AccountInfoResponse struct { Balance int64 `json:"balance"` Comment string `json:"comment"` CreditCardStatementDate *int `json:"creditCardStatementDate,omitempty"` + CreditLimit *int64 `json:"creditLimit,omitempty"` DisplayOrder int32 `json:"displayOrder"` IsAsset bool `json:"isAsset,omitempty"` IsLiability bool `json:"isLiability,omitempty"` @@ -181,10 +185,14 @@ type AccountInfoResponse struct { // ToAccountInfoResponse returns a view-object according to database model func (a *Account) ToAccountInfoResponse() *AccountInfoResponse { var creditCardStatementDate *int + var creditLimit *int64 if a.ParentAccountId == LevelOneAccountParentId && a.Category == ACCOUNT_CATEGORY_CREDIT_CARD { if a.Extend != nil { creditCardStatementDate = a.Extend.CreditCardStatementDate + if a.Extend.CreditLimit != nil && *a.Extend.CreditLimit > 0 { + creditLimit = a.Extend.CreditLimit + } } else { creditCardStatementDate = &defaultCreditCardAccountStatementDate } @@ -202,6 +210,7 @@ func (a *Account) ToAccountInfoResponse() *AccountInfoResponse { Balance: a.Balance, Comment: a.Comment, CreditCardStatementDate: creditCardStatementDate, + CreditLimit: creditLimit, DisplayOrder: a.DisplayOrder, IsAsset: assetAccountCategory[a.Category], IsLiability: liabilityAccountCategory[a.Category], diff --git a/src/locales/en.json b/src/locales/en.json index d9c1fcc6..71398223 100644 --- a/src/locales/en.json +++ b/src/locales/en.json @@ -1860,6 +1860,8 @@ "Balance Time": "Balance Time", "Sub-account Balance Time": "Sub-account Balance Time", "Statement Date": "Statement Date", + "Credit Limit": "Credit Limit", + "Available": "Available", "Description": "Description", "Your account description (optional)": "Your account description (optional)", "Your sub-account description (optional)": "Your sub-account description (optional)", diff --git a/src/locales/zh_Hans.json b/src/locales/zh_Hans.json index 27ebbabf..8da91b57 100644 --- a/src/locales/zh_Hans.json +++ b/src/locales/zh_Hans.json @@ -1860,6 +1860,8 @@ "Balance Time": "余额时间", "Sub-account Balance Time": "子账户余额时间", "Statement Date": "账单日", + "Credit Limit": "信用额度", + "Available": "可用额度", "Description": "描述", "Your account description (optional)": "你的账户描述 (可选)", "Your sub-account description (optional)": "你的子账户描述 (可选)", diff --git a/src/models/account.ts b/src/models/account.ts index 830ff2c0..00a4b23b 100644 --- a/src/models/account.ts +++ b/src/models/account.ts @@ -17,6 +17,7 @@ export class Account implements AccountInfoResponse { public balanceTime?: number; public comment: string; public creditCardStatementDate?: number; + public creditLimit?: number; public displayOrder: number; public visible: boolean; public subAccounts?: Account[]; @@ -24,7 +25,7 @@ export class Account implements AccountInfoResponse { private readonly _isAsset?: boolean; private readonly _isLiability?: boolean; - protected constructor(id: string, name: string, parentId: string, category: number, type: number, icon: string, color: string, currency: string, balance: number, comment: string, displayOrder: number, visible: boolean, balanceTime?: number, creditCardStatementDate?: number, isAsset?: boolean, isLiability?: boolean, subAccounts?: Account[]) { + protected constructor(id: string, name: string, parentId: string, category: number, type: number, icon: string, color: string, currency: string, balance: number, comment: string, displayOrder: number, visible: boolean, balanceTime?: number, creditCardStatementDate?: number, isAsset?: boolean, isLiability?: boolean, subAccounts?: Account[], creditLimit?: number) { this.id = id; this.name = name; this.parentId = parentId; @@ -39,6 +40,7 @@ export class Account implements AccountInfoResponse { this.displayOrder = displayOrder; this.visible = visible; this.creditCardStatementDate = creditCardStatementDate; + this.creditLimit = creditLimit; this._isAsset = isAsset; this._isLiability = isLiability; @@ -95,7 +97,8 @@ export class Account implements AccountInfoResponse { this.comment === other.comment && this.displayOrder === other.displayOrder && this.visible === other.visible && - this.creditCardStatementDate === other.creditCardStatementDate; + this.creditCardStatementDate === other.creditCardStatementDate && + this.creditLimit === other.creditLimit; if (!isEqual) { return false; @@ -130,6 +133,7 @@ export class Account implements AccountInfoResponse { this.balanceTime = other.balanceTime; this.comment = other.comment; this.creditCardStatementDate = other.creditCardStatementDate; + this.creditLimit = other.creditLimit; this.visible = other.visible; } @@ -181,6 +185,7 @@ export class Account implements AccountInfoResponse { balanceTime: (parentAccount || this.type === AccountType.SingleAccount.type) && this.balanceTime ? this.balanceTime : 0, comment: this.comment, creditCardStatementDate: !parentAccount && this.category === AccountCategory.CreditCard.type ? this.creditCardStatementDate : undefined, + creditLimit: !parentAccount && this.category === AccountCategory.CreditCard.type ? (this.creditLimit ?? 0) : undefined, subAccounts: !parentAccount ? subAccountCreateRequests : undefined, clientSessionId: !parentAccount ? clientSessionId : undefined }; @@ -214,6 +219,7 @@ export class Account implements AccountInfoResponse { balanceTime: parentAccount && (!this.id || this.id === '0') ? this.balanceTime : undefined, comment: this.comment, creditCardStatementDate: !parentAccount && this.category === AccountCategory.CreditCard.type ? this.creditCardStatementDate : undefined, + creditLimit: !parentAccount && this.category === AccountCategory.CreditCard.type ? (this.creditLimit ?? 0) : undefined, hidden: !this.visible, subAccounts: !parentAccount ? subAccountModifyRequests : undefined, clientSessionId: !parentAccount ? clientSessionId : undefined @@ -365,7 +371,9 @@ export class Account implements AccountInfoResponse { this.balanceTime, this.creditCardStatementDate, this.isAsset, - this.isLiability + this.isLiability, + undefined, + this.creditLimit ); } @@ -387,7 +395,9 @@ export class Account implements AccountInfoResponse { this.creditCardStatementDate, this.isAsset, this.isLiability, - typeof(this.subAccounts) !== 'undefined' ? Account.cloneAccounts(this.subAccounts) : undefined); + typeof(this.subAccounts) !== 'undefined' ? Account.cloneAccounts(this.subAccounts) : undefined, + this.creditLimit + ); } public createNewSubAccount(currency: string, balanceTime: number): Account { @@ -446,7 +456,8 @@ export class Account implements AccountInfoResponse { accountResponse.creditCardStatementDate, accountResponse.isAsset, accountResponse.isLiability, - accountResponse.subAccounts ? Account.ofMulti(accountResponse.subAccounts) : undefined + accountResponse.subAccounts ? Account.ofMulti(accountResponse.subAccounts) : undefined, + accountResponse.creditLimit ); } @@ -560,7 +571,8 @@ export class AccountWithDisplayBalance extends Account { account.creditCardStatementDate, account.isAsset, account.isLiability, - account.subAccounts + account.subAccounts, + account.creditLimit ); this.displayBalance = displayBalance; @@ -582,6 +594,7 @@ export interface AccountCreateRequest { readonly balanceTime: number; readonly comment: string; readonly creditCardStatementDate?: number; + readonly creditLimit?: number; readonly subAccounts?: AccountCreateRequest[]; readonly clientSessionId?: string; } @@ -597,6 +610,7 @@ export interface AccountModifyRequest { readonly balanceTime?: number; readonly comment: string; readonly creditCardStatementDate?: number; + readonly creditLimit?: number; readonly hidden: boolean; readonly subAccounts?: AccountModifyRequest[]; readonly clientSessionId?: string; @@ -614,6 +628,7 @@ export interface AccountInfoResponse { readonly balance: number; readonly comment: string; readonly creditCardStatementDate?: number; + readonly creditLimit?: number; readonly displayOrder: number; readonly isAsset?: boolean; readonly isLiability?: boolean; diff --git a/src/views/mobile/accounts/EditPage.vue b/src/views/mobile/accounts/EditPage.vue index e2823c09..874bc351 100644 --- a/src/views/mobile/accounts/EditPage.vue +++ b/src/views/mobile/accounts/EditPage.vue @@ -201,6 +201,24 @@ + + + + + + + + @@ -550,6 +586,7 @@ interface AccountContext { showColorSelectionSheet: boolean; showCurrencyPopup: boolean; showCreditCardStatementDatePopup: boolean; + showCreditLimitSheet: boolean; showBalanceSheet: boolean; showBalanceDateTimeSheet: boolean; balanceDateTimeSheetMode: string; @@ -600,6 +637,7 @@ const DEFAULT_ACCOUNT_CONTEXT: AccountContext = { showColorSelectionSheet: false, showCurrencyPopup: false, showCreditCardStatementDatePopup: false, + showCreditLimitSheet: false, showBalanceSheet: false, showBalanceDateTimeSheet: false, balanceDateTimeSheetMode: 'time' @@ -621,6 +659,13 @@ function formatAccountDisplayBalance(selectedAccount: Account): string { return formatAmountToLocalizedNumeralsWithCurrency(balance, selectedAccount.currency); } +function formatCreditLimitDisplay(selectedAccount: Account): string { + if (!selectedAccount.creditLimit) { + return tt('Not set'); + } + return formatAmountToLocalizedNumeralsWithCurrency(selectedAccount.creditLimit, selectedAccount.currency); +} + function formatAccountBalanceDate(account: Account): string { if (!isDefined(account.balanceTime)) { return ''; diff --git a/src/views/mobile/accounts/ListPage.vue b/src/views/mobile/accounts/ListPage.vue index 942779ad..eedff00d 100644 --- a/src/views/mobile/accounts/ListPage.vue +++ b/src/views/mobile/accounts/ListPage.vue @@ -113,6 +113,7 @@
{{ account.name }} +
{{ accountBalance(account) }} @@ -241,7 +242,7 @@ const props = defineProps<{ f7router: Router.Router; }>(); -const { tt, getCurrentLanguageTextDirection } = useI18n(); +const { tt, getCurrentLanguageTextDirection, formatAmountToLocalizedNumeralsWithCurrency } = useI18n(); const { showAlert, showToast, routeBackOnError } = useI18nUIComponents(); const { @@ -288,6 +289,14 @@ const noAvailableAccount = computed(() => { } }); +function getRemainingCredit(account: Account): string { + if (!account.creditLimit) { + return ''; + } + const remaining = account.creditLimit + account.balance; // balance is negative for credit cards + return formatAmountToLocalizedNumeralsWithCurrency(remaining, account.currency); +} + function hasAccount(accountCategory: AccountCategory, visibleOnly: boolean): boolean { return accountsStore.hasAccount(accountCategory, visibleOnly); }