package models import ( "fmt" "strings" "time" "github.com/mayswind/ezbookkeeping/pkg/errs" "github.com/mayswind/ezbookkeeping/pkg/utils" ) // TransactionType represents transaction type type TransactionType byte // Transaction types const ( TRANSACTION_TYPE_MODIFY_BALANCE TransactionType = 1 TRANSACTION_TYPE_INCOME TransactionType = 2 TRANSACTION_TYPE_EXPENSE TransactionType = 3 TRANSACTION_TYPE_TRANSFER TransactionType = 4 ) // TransactionDbType represents transaction type in database type TransactionDbType byte // Transaction db types const ( TRANSACTION_DB_TYPE_MODIFY_BALANCE TransactionDbType = 1 TRANSACTION_DB_TYPE_INCOME TransactionDbType = 2 TRANSACTION_DB_TYPE_EXPENSE TransactionDbType = 3 TRANSACTION_DB_TYPE_TRANSFER_OUT TransactionDbType = 4 TRANSACTION_DB_TYPE_TRANSFER_IN TransactionDbType = 5 ) // Transaction represents transaction data stored in database type Transaction struct { TransactionId int64 `xorm:"PK"` Uid int64 `xorm:"UNIQUE(UQE_transaction_uid_time) INDEX(IDX_transaction_uid_deleted_time) INDEX(IDX_transaction_uid_deleted_type_time) INDEX(IDX_transaction_uid_deleted_category_id_time) INDEX(IDX_transaction_uid_deleted_account_id_time) NOT NULL"` Deleted bool `xorm:"INDEX(IDX_transaction_uid_deleted_time) INDEX(IDX_transaction_uid_deleted_type_time) INDEX(IDX_transaction_uid_deleted_category_id_time) INDEX(IDX_transaction_uid_deleted_account_id_time) NOT NULL"` Type TransactionDbType `xorm:"INDEX(IDX_transaction_uid_deleted_type_time) NOT NULL"` CategoryId int64 `xorm:"INDEX(IDX_transaction_uid_deleted_category_id_time) NOT NULL"` AccountId int64 `xorm:"INDEX(IDX_transaction_uid_deleted_account_id_time) NOT NULL"` TransactionTime int64 `xorm:"UNIQUE(UQE_transaction_uid_time) INDEX(IDX_transaction_uid_deleted_time) INDEX(IDX_transaction_uid_deleted_type_time) INDEX(IDX_transaction_uid_deleted_category_id_time) INDEX(IDX_transaction_uid_deleted_account_id_time) NOT NULL"` TimezoneUtcOffset int16 `xorm:"NOT NULL"` Amount int64 `xorm:"NOT NULL"` RelatedId int64 `xorm:"NOT NULL"` RelatedAccountId int64 `xorm:"NOT NULL"` RelatedAccountAmount int64 `xorm:"NOT NULL"` HideAmount bool `xorm:"NOT NULL"` Comment string `xorm:"VARCHAR(255) NOT NULL"` CreatedIp string `xorm:"VARCHAR(39)"` CreatedUnixTime int64 UpdatedUnixTime int64 DeletedUnixTime int64 } // TransactionCreateRequest represents all parameters of transaction creation request type TransactionCreateRequest struct { Type TransactionType `json:"type" binding:"required"` CategoryId int64 `json:"categoryId,string"` Time int64 `json:"time" binding:"required,min=1"` UtcOffset int16 `json:"utcOffset" binding:"min=-720,max=840"` SourceAccountId int64 `json:"sourceAccountId,string" binding:"required,min=1"` DestinationAccountId int64 `json:"destinationAccountId,string" binding:"min=0"` SourceAmount int64 `json:"sourceAmount" binding:"min=-99999999999,max=99999999999"` DestinationAmount int64 `json:"destinationAmount" binding:"min=-99999999999,max=99999999999"` HideAmount bool `json:"hideAmount"` TagIds []string `json:"tagIds"` Comment string `json:"comment" binding:"max=255"` } // TransactionModifyRequest represents all parameters of transaction modification request type TransactionModifyRequest struct { Id int64 `json:"id,string" binding:"required,min=1"` CategoryId int64 `json:"categoryId,string"` Time int64 `json:"time" binding:"required,min=1"` UtcOffset int16 `json:"utcOffset" binding:"min=-720,max=840"` SourceAccountId int64 `json:"sourceAccountId,string" binding:"required,min=1"` DestinationAccountId int64 `json:"destinationAccountId,string" binding:"min=0"` SourceAmount int64 `json:"sourceAmount" binding:"min=-99999999999,max=99999999999"` DestinationAmount int64 `json:"destinationAmount" binding:"min=-99999999999,max=99999999999"` HideAmount bool `json:"hideAmount"` TagIds []string `json:"tagIds"` Comment string `json:"comment" binding:"max=255"` } // TransactionCountRequest represents transaction count request type TransactionCountRequest struct { Type TransactionDbType `form:"type" binding:"min=0,max=4"` CategoryId int64 `form:"category_id" binding:"min=0"` AccountId int64 `form:"account_id" binding:"min=0"` Keyword string `form:"keyword"` MaxTime int64 `form:"max_time" binding:"min=0"` MinTime int64 `form:"min_time" binding:"min=0"` } // TransactionListByMaxTimeRequest represents all parameters of transaction listing by max time request type TransactionListByMaxTimeRequest struct { Type TransactionDbType `form:"type" binding:"min=0,max=4"` CategoryId int64 `form:"category_id" binding:"min=0"` AccountId int64 `form:"account_id" binding:"min=0"` Keyword string `form:"keyword"` MaxTime int64 `form:"max_time" binding:"min=0"` MinTime int64 `form:"min_time" binding:"min=0"` Count int32 `form:"count" binding:"required,min=1,max=50"` TrimAccount bool `form:"trim_account"` TrimCategory bool `form:"trim_category"` TrimTag bool `form:"trim_tag"` } // TransactionListInMonthByPageRequest represents all parameters of transaction listing by month request type TransactionListInMonthByPageRequest struct { Year int32 `form:"year" binding:"required,min=1"` Month int32 `form:"month" binding:"required,min=1"` Type TransactionDbType `form:"type" binding:"min=0,max=4"` CategoryId int64 `form:"category_id" binding:"min=0"` AccountId int64 `form:"account_id" binding:"min=0"` Keyword string `form:"keyword"` Page int32 `form:"page" binding:"required,min=1"` Count int32 `form:"count" binding:"required,min=1,max=50"` TrimAccount bool `form:"trim_account"` TrimCategory bool `form:"trim_category"` TrimTag bool `form:"trim_tag"` } // TransactionStatisticRequest represents all parameters of transaction statistic request type TransactionStatisticRequest struct { StartTime int64 `form:"start_time" binding:"min=0"` EndTime int64 `form:"end_time" binding:"min=0"` } // TransactionAmountsRequest represents all parameters of transaction amounts request type TransactionAmountsRequest struct { Query string `form:"query"` } // TransactionAmountsRequestItem represents an item of transaction amounts request type TransactionAmountsRequestItem struct { Name string StartTime int64 EndTime int64 } // TransactionMonthAmountsRequest represents all parameters of transaction month amounts request type TransactionMonthAmountsRequest struct { StartYearMonth string `form:"start_year_month"` EndYearMonth string `form:"end_year_month"` } // TransactionGetRequest represents all parameters of transaction getting request type TransactionGetRequest struct { Id int64 `form:"id,string" binding:"required,min=1"` TrimAccount bool `form:"trim_account"` TrimCategory bool `form:"trim_category"` TrimTag bool `form:"trim_tag"` } // TransactionDeleteRequest represents all parameters of transaction deleting request type TransactionDeleteRequest struct { Id int64 `json:"id,string" binding:"required,min=1"` } // TransactionAccountsAmount represents transaction accounts amount map type TransactionAccountsAmount map[int64]*TransactionAccountAmount // TransactionAccountAmount represents transaction account amount type TransactionAccountAmount struct { AccountId int64 TotalIncomeAmount int64 TotalExpenseAmount int64 } // TransactionInfoResponse represents a view-object of transaction type TransactionInfoResponse struct { Id int64 `json:"id,string"` TimeSequenceId int64 `json:"timeSequenceId,string"` Type TransactionType `json:"type"` CategoryId int64 `json:"categoryId,string"` Category *TransactionCategoryInfoResponse `json:"category,omitempty"` Time int64 `json:"time"` UtcOffset int16 `json:"utcOffset"` SourceAccountId int64 `json:"sourceAccountId,string"` SourceAccount *AccountInfoResponse `json:"sourceAccount,omitempty"` DestinationAccountId int64 `json:"destinationAccountId,string,omitempty"` DestinationAccount *AccountInfoResponse `json:"destinationAccount,omitempty"` SourceAmount int64 `json:"sourceAmount"` DestinationAmount int64 `json:"destinationAmount,omitempty"` HideAmount bool `json:"hideAmount"` TagIds []string `json:"tagIds"` Tags []*TransactionTagInfoResponse `json:"tags,omitempty"` Comment string `json:"comment"` Editable bool `json:"editable"` } // TransactionCountResponse represents transaction count response type TransactionCountResponse struct { TotalCount int64 `json:"totalCount"` } // TransactionInfoPageWrapperResponse represents a response of transaction which contains items and next id type TransactionInfoPageWrapperResponse struct { Items TransactionInfoResponseSlice `json:"items"` NextTimeSequenceId *int64 `json:"nextTimeSequenceId,string"` } // TransactionInfoPageWrapperResponse2 represents a response of transaction which contains items and count type TransactionInfoPageWrapperResponse2 struct { Items TransactionInfoResponseSlice `json:"items"` TotalCount int64 `json:"totalCount"` } // TransactionStatisticResponse represents an item of transaction amounts type TransactionStatisticResponse struct { StartTime int64 `json:"startTime"` EndTime int64 `json:"endTime"` Items []*TransactionStatisticResponseItem `json:"items"` } // TransactionStatisticResponseItem represents total amount item for an response type TransactionStatisticResponseItem struct { CategoryId int64 `json:"categoryId,string"` AccountId int64 `json:"accountId,string"` TotalAmount int64 `json:"amount"` } // TransactionAmountsResponseItem represents an item of transaction amounts type TransactionAmountsResponseItem struct { StartTime int64 `json:"startTime"` EndTime int64 `json:"endTime"` Amounts []*TransactionAmountsResponseItemAmountInfo `json:"amounts"` } // TransactionMonthAmountsResponseItem represents an item of transaction month amounts type TransactionMonthAmountsResponseItem struct { Year int32 `json:"year"` Month int32 `json:"month"` Amounts []*TransactionAmountsResponseItemAmountInfo `json:"amounts"` } // TransactionAmountsResponseItemAmountInfo represents amount info for an response item type TransactionAmountsResponseItemAmountInfo struct { Currency string `json:"currency"` IncomeAmount int64 `json:"incomeAmount"` ExpenseAmount int64 `json:"expenseAmount"` } // IsEditable returns whether this transaction can be edited func (t *Transaction) IsEditable(currentUser *User, utcOffset int16, account *Account, relatedAccount *Account) bool { if currentUser == nil || !currentUser.CanEditTransactionByTransactionTime(t.TransactionTime, utcOffset) { return false } if account == nil || account.Hidden { return false } if t.Type == TRANSACTION_DB_TYPE_TRANSFER_OUT { if relatedAccount == nil || relatedAccount.Hidden { return false } } return true } // ToTransactionInfoResponse returns a view-object according to database model func (t *Transaction) ToTransactionInfoResponse(tagIds []int64, editable bool) *TransactionInfoResponse { var transactionType TransactionType if t.Type == TRANSACTION_DB_TYPE_MODIFY_BALANCE { transactionType = TRANSACTION_TYPE_MODIFY_BALANCE } else if t.Type == TRANSACTION_DB_TYPE_EXPENSE { transactionType = TRANSACTION_TYPE_EXPENSE } else if t.Type == TRANSACTION_DB_TYPE_INCOME { transactionType = TRANSACTION_TYPE_INCOME } else if t.Type == TRANSACTION_DB_TYPE_TRANSFER_OUT { transactionType = TRANSACTION_TYPE_TRANSFER } else if t.Type == TRANSACTION_DB_TYPE_TRANSFER_IN { transactionType = TRANSACTION_TYPE_TRANSFER } else { return nil } sourceAccountId := t.AccountId sourceAmount := t.Amount destinationAccountId := int64(0) destinationAmount := int64(0) if t.Type == TRANSACTION_DB_TYPE_TRANSFER_OUT { destinationAccountId = t.RelatedAccountId destinationAmount = t.RelatedAccountAmount } else if t.Type == TRANSACTION_DB_TYPE_TRANSFER_IN { sourceAccountId = t.RelatedAccountId sourceAmount = t.RelatedAccountAmount destinationAccountId = t.AccountId destinationAmount = t.Amount } return &TransactionInfoResponse{ Id: t.TransactionId, TimeSequenceId: t.TransactionTime, Type: transactionType, CategoryId: t.CategoryId, Time: utils.GetUnixTimeFromTransactionTime(t.TransactionTime), UtcOffset: t.TimezoneUtcOffset, SourceAccountId: sourceAccountId, DestinationAccountId: destinationAccountId, SourceAmount: sourceAmount, DestinationAmount: destinationAmount, HideAmount: t.HideAmount, TagIds: utils.Int64ArrayToStringArray(tagIds), Comment: t.Comment, Editable: editable, } } // GetTransactionAmountsRequestItems returns request items by query parameters func (t *TransactionAmountsRequest) GetTransactionAmountsRequestItems() ([]*TransactionAmountsRequestItem, error) { items := strings.Split(t.Query, "|") requestItems := make([]*TransactionAmountsRequestItem, 0, len(items)) for i := 0; i < len(items); i++ { itemValues := strings.Split(items[i], "_") if len(itemValues) != 3 { return nil, errs.ErrQueryItemsInvalid } startTime, err := utils.StringToInt64(itemValues[1]) if err != nil { return nil, err } endTime, err := utils.StringToInt64(itemValues[2]) if err != nil { return nil, err } requestItem := &TransactionAmountsRequestItem{ Name: itemValues[0], StartTime: startTime, EndTime: endTime, } requestItems = append(requestItems, requestItem) } return requestItems, nil } // GetStartTimeAndEndTime returns start unix time and end unix time by request parameter func (t *TransactionMonthAmountsRequest) GetStartTimeAndEndTime(utcOffset int16) (int64, int64, error) { startUnixTime := int64(0) endUnixTime := time.Now().Unix() if t.StartYearMonth != "" { startTime, err := utils.ParseFromShortDateTime(fmt.Sprintf("%s-1 0:0:0", t.StartYearMonth), utcOffset) if err != nil { return 0, 0, err } startUnixTime = startTime.Unix() } if t.EndYearMonth != "" { endTime, err := utils.ParseFromShortDateTime(fmt.Sprintf("%s-1 0:0:0", t.EndYearMonth), utcOffset) if err != nil { return 0, 0, err } endTime = endTime.AddDate(0, 1, 0) endUnixTime = endTime.Unix() - 1 } return startUnixTime, endUnixTime, nil } // TransactionInfoResponseSlice represents the slice data structure of TransactionInfoResponse type TransactionInfoResponseSlice []*TransactionInfoResponse // Len returns the count of items func (s TransactionInfoResponseSlice) Len() int { return len(s) } // Swap swaps two items func (s TransactionInfoResponseSlice) Swap(i, j int) { s[i], s[j] = s[j], s[i] } // Less reports whether the first item is less than the second one func (s TransactionInfoResponseSlice) Less(i, j int) bool { if s[i].Time != s[j].Time { return s[i].Time > s[j].Time } return s[i].Id > s[j].Id } // TransactionMonthAmountsResponseItemSlice represents the slice data structure of TransactionMonthAmountsResponseItem type TransactionMonthAmountsResponseItemSlice []*TransactionMonthAmountsResponseItem // Len returns the count of items func (s TransactionMonthAmountsResponseItemSlice) Len() int { return len(s) } // Swap swaps two items func (s TransactionMonthAmountsResponseItemSlice) Swap(i, j int) { s[i], s[j] = s[j], s[i] } // Less reports whether the first item is less than the second one func (s TransactionMonthAmountsResponseItemSlice) Less(i, j int) bool { if s[i].Year != s[j].Year { return s[i].Year > s[j].Year } return s[i].Month > s[j].Month }