|
| 1 | +package v4 |
| 2 | + |
| 3 | +import ( |
| 4 | + "fmt" |
| 5 | + "time" |
| 6 | + |
| 7 | + "github.com/envelope-zero/backend/v4/pkg/httperrors" |
| 8 | + "github.com/envelope-zero/backend/v4/pkg/httputil" |
| 9 | + "github.com/envelope-zero/backend/v4/pkg/models" |
| 10 | + "github.com/gin-gonic/gin" |
| 11 | + "github.com/google/uuid" |
| 12 | + "github.com/shopspring/decimal" |
| 13 | +) |
| 14 | + |
| 15 | +type AccountEditable struct { |
| 16 | + Name string `json:"name" example:"Cash" default:""` // Name of the account |
| 17 | + Note string `json:"note" example:"Money in my wallet" default:""` // A longer description for the account |
| 18 | + BudgetID uuid.UUID `json:"budgetId" example:"550dc009-cea6-4c12-b2a5-03446eb7b7cf"` // ID of the budget this account belongs to |
| 19 | + OnBudget bool `json:"onBudget" example:"true" default:"false"` // Does the account factor into the available budget? Always false when external: true |
| 20 | + External bool `json:"external" example:"false" default:"false"` // Does the account belong to the budget owner or not? |
| 21 | + InitialBalance decimal.Decimal `json:"initialBalance" example:"173.12" default:"0" minimum:"0.00000001" maximum:"999999999999.99999999" multipleOf:"0.00000001"` // Balance of the account before any transactions were recorded |
| 22 | + InitialBalanceDate *time.Time `json:"initialBalanceDate" example:"2017-05-12T00:00:00Z"` // Date of the initial balance |
| 23 | + Archived bool `json:"archived" example:"true" default:"false"` // Is the account archived? |
| 24 | + ImportHash string `json:"importHash" example:"867e3a26dc0baf73f4bff506f31a97f6c32088917e9e5cf1a5ed6f3f84a6fa70" default:""` // The SHA256 hash of a unique combination of values to use in duplicate detection for imports |
| 25 | +} |
| 26 | + |
| 27 | +// model returns the database resource for the editable fields |
| 28 | +func (editable AccountEditable) model() models.Account { |
| 29 | + return models.Account{ |
| 30 | + Name: editable.Name, |
| 31 | + Note: editable.Note, |
| 32 | + BudgetID: editable.BudgetID, |
| 33 | + OnBudget: editable.OnBudget, |
| 34 | + External: editable.External, |
| 35 | + InitialBalance: editable.InitialBalance, |
| 36 | + InitialBalanceDate: editable.InitialBalanceDate, |
| 37 | + Archived: editable.Archived, |
| 38 | + ImportHash: editable.ImportHash, |
| 39 | + } |
| 40 | +} |
| 41 | + |
| 42 | +type AccountLinks struct { |
| 43 | + Self string `json:"self" example:"https://example.com/api/v4/accounts/af892e10-7e0a-4fb8-b1bc-4b6d88401ed2"` // The account itself |
| 44 | + RecentEnvelopes string `json:"recentEnvelopes" example:"https://example.com/api/v4/accounts/af892e10-7e0a-4fb8-b1bc-4b6d88401ed2/recent-envelopes"` // Envelopes in recent transactions where this account was the target |
| 45 | + ComputedData string `json:"computedData" example:"https://example.com/api/v4/accounts/computed"` // Computed data endpoint for accounts |
| 46 | + Transactions string `json:"transactions" example:"https://example.com/api/v4/transactions?account=af892e10-7e0a-4fb8-b1bc-4b6d88401ed2"` // Transactions referencing the account |
| 47 | +} |
| 48 | + |
| 49 | +// Account is the API v4 representation of an Account in EZ. |
| 50 | +type Account struct { |
| 51 | + models.DefaultModel |
| 52 | + AccountEditable |
| 53 | + Links AccountLinks `json:"links"` |
| 54 | +} |
| 55 | + |
| 56 | +func newAccount(c *gin.Context, model models.Account) Account { |
| 57 | + url := c.GetString(string(models.DBContextURL)) |
| 58 | + |
| 59 | + return Account{ |
| 60 | + DefaultModel: model.DefaultModel, |
| 61 | + AccountEditable: AccountEditable{ |
| 62 | + Name: model.Name, |
| 63 | + Note: model.Note, |
| 64 | + BudgetID: model.BudgetID, |
| 65 | + OnBudget: model.OnBudget, |
| 66 | + External: model.External, |
| 67 | + InitialBalance: model.InitialBalance, |
| 68 | + InitialBalanceDate: model.InitialBalanceDate, |
| 69 | + Archived: model.Archived, |
| 70 | + ImportHash: model.ImportHash, |
| 71 | + }, |
| 72 | + Links: AccountLinks{ |
| 73 | + Self: fmt.Sprintf("%s/v4/accounts/%s", url, model.ID), |
| 74 | + RecentEnvelopes: fmt.Sprintf("%s/v4/accounts/%s/recent-envelopes", url, model.ID), |
| 75 | + ComputedData: fmt.Sprintf("%s/v4/accounts/computed", url), |
| 76 | + Transactions: fmt.Sprintf("%s/v4/transactions?account=%s", url, model.ID), |
| 77 | + }, |
| 78 | + } |
| 79 | +} |
| 80 | + |
| 81 | +type AccountListResponse struct { |
| 82 | + Data []Account `json:"data"` // List of accounts |
| 83 | + Error *string `json:"error" example:"the specified resource ID is not a valid UUID"` // The error, if any occurred |
| 84 | + Pagination *Pagination `json:"pagination"` // Pagination information |
| 85 | +} |
| 86 | + |
| 87 | +type AccountCreateResponse struct { |
| 88 | + Error *string `json:"error" example:"the specified resource ID is not a valid UUID"` // The error, if any occurred |
| 89 | + Data []AccountResponse `json:"data"` // List of created Accounts |
| 90 | +} |
| 91 | + |
| 92 | +func (a *AccountCreateResponse) appendError(err httperrors.Error, status int) int { |
| 93 | + s := err.Error() |
| 94 | + a.Data = append(a.Data, AccountResponse{Error: &s}) |
| 95 | + |
| 96 | + // The final status code is the highest HTTP status code number |
| 97 | + if err.Status > status { |
| 98 | + status = err.Status |
| 99 | + } |
| 100 | + |
| 101 | + return status |
| 102 | +} |
| 103 | + |
| 104 | +type AccountResponse struct { |
| 105 | + Data *Account `json:"data"` // Data for the account |
| 106 | + Error *string `json:"error" example:"the specified resource ID is not a valid UUID"` // The error, if any occurred for this transaction |
| 107 | +} |
| 108 | + |
| 109 | +type AccountQueryFilter struct { |
| 110 | + Name string `form:"name" filterField:"false"` // Fuzzy filter for the account name |
| 111 | + Note string `form:"note" filterField:"false"` // Fuzzy filter for the note |
| 112 | + BudgetID string `form:"budget"` // By budget ID |
| 113 | + OnBudget bool `form:"onBudget"` // Is the account on-budget? |
| 114 | + External bool `form:"external"` // Is the account external? |
| 115 | + Archived bool `form:"archived"` // Is the account archived? |
| 116 | + Search string `form:"search" filterField:"false"` // By string in name or note |
| 117 | + Offset uint `form:"offset" filterField:"false"` // The offset of the first Account returned. Defaults to 0. |
| 118 | + Limit int `form:"limit" filterField:"false"` // Maximum number of Accounts to return. Defaults to 50. |
| 119 | +} |
| 120 | + |
| 121 | +func (f AccountQueryFilter) model() (models.Account, httperrors.Error) { |
| 122 | + budgetID, err := httputil.UUIDFromString(f.BudgetID) |
| 123 | + if !err.Nil() { |
| 124 | + return models.Account{}, err |
| 125 | + } |
| 126 | + |
| 127 | + return models.Account{ |
| 128 | + BudgetID: budgetID, |
| 129 | + OnBudget: f.OnBudget, |
| 130 | + External: f.External, |
| 131 | + Archived: f.Archived, |
| 132 | + }, httperrors.Error{} |
| 133 | +} |
| 134 | + |
| 135 | +type RecentEnvelopesResponse struct { |
| 136 | + Data []RecentEnvelope `json:"data"` // Data for the account |
| 137 | + Error *string `json:"error" example:"the specified resource ID is not a valid UUID"` // The error, if any occurred for this transaction |
| 138 | +} |
| 139 | + |
| 140 | +type RecentEnvelope struct { |
| 141 | + Name string `json:"name"` |
| 142 | + ID *uuid.UUID `json:"id"` |
| 143 | +} |
| 144 | + |
| 145 | +type AccountComputedRequest struct { |
| 146 | + Time time.Time `form:"time"` // The time for which the computation is requested |
| 147 | + IDs []string `form:"ids"` // A list of UUIDs for the accounts |
| 148 | +} |
| 149 | + |
| 150 | +type AccountComputedData struct { |
| 151 | + ID uuid.UUID `json:"id" example:"95018a69-758b-46c6-8bab-db70d9614f9d"` // ID of the account |
| 152 | + Balance decimal.Decimal `json:"balance" example:"2735.17"` // Balance of the account, including all transactions referencing it |
| 153 | + ReconciledBalance decimal.Decimal `json:"reconciledBalance" example:"2539.57"` // Balance of the account, including all reconciled transactions referencing it |
| 154 | +} |
| 155 | + |
| 156 | +type AccountComputedDataResponse struct { |
| 157 | + Data []AccountComputedData `json:"data"` |
| 158 | + Error *string `json:"error"` |
| 159 | +} |
0 commit comments