WIP: Add Drafts, Templates, and Recurring Transactions #45

Draft
andrew wants to merge 18 commits from drafts into main
5 changed files with 143 additions and 28 deletions
Showing only changes of commit 13aadc2358 - Show all commits

View File

@ -59,6 +59,13 @@ export interface Account {
currentBalance: number | null currentBalance: number | null
} }
export interface SimpleAccountResponse {
id: number
name: string
type: string
numberSuffix: string
}
export interface AccountCreationPayload { export interface AccountCreationPayload {
type: string type: string
numberSuffix: string numberSuffix: string

View File

@ -1,8 +1,28 @@
import type { SimpleAccountResponse } from './account'
import type { Attachment } from './attachment' import type { Attachment } from './attachment'
import { ApiClient } from './base' import { ApiClient } from './base'
import type { Currency } from './data' import type { Currency } from './data'
import { type Page, type PageRequest } from './pagination' import { type Page, type PageRequest } from './pagination'
export interface SimpleVendorResponse {
id: number
name: string
}
export interface SimpleCategoryResponse {
id: number
name: string
color: string
}
export interface TransactionLineItemResponse {
idx: number
valuePerItem: number
quantity: number
description: string
category: TransactionCategory | null
}
export interface TransactionVendor { export interface TransactionVendor {
id: number id: number
name: string name: string
@ -47,10 +67,10 @@ export interface TransactionsListItem {
currency: Currency currency: Currency
description: string description: string
internalTransfer: boolean internalTransfer: boolean
vendor: TransactionsListItemVendor | null vendor: SimpleVendorResponse | null
category: TransactionsListItemCategory | null category: SimpleCategoryResponse | null
creditedAccount: TransactionsListItemAccount | null creditedAccount: SimpleAccountResponse | null
debitedAccount: TransactionsListItemAccount | null debitedAccount: SimpleAccountResponse | null
tags: string[] tags: string[]
} }
@ -82,28 +102,13 @@ export interface TransactionDetail {
internalTransfer: boolean internalTransfer: boolean
vendor: TransactionVendor | null vendor: TransactionVendor | null
category: TransactionCategory | null category: TransactionCategory | null
creditedAccount: TransactionDetailAccount | null creditedAccount: SimpleAccountResponse | null
debitedAccount: TransactionDetailAccount | null debitedAccount: SimpleAccountResponse | null
tags: string[] tags: string[]
lineItems: TransactionDetailLineItem[] lineItems: TransactionLineItemResponse[]
attachments: Attachment[] attachments: Attachment[]
} }
export interface TransactionDetailAccount {
id: number
name: string
type: string
numberSuffix: string
}
export interface TransactionDetailLineItem {
idx: number
valuePerItem: number
quantity: number
description: string
category: TransactionCategory | null
}
export interface AddTransactionPayload { export interface AddTransactionPayload {
timestamp: string timestamp: string
amount: number amount: number
@ -144,6 +149,56 @@ export interface AggregateTransactionData {
currencies: AggregateTransactionCurrencyData[] currencies: AggregateTransactionCurrencyData[]
} }
export interface TransactionDraftListItem {
id: number
addedAt: string
templateName: string | null
timestamp: string | null
amount: number | null
currency: Currency | null
description: string | null
internalTransfer: boolean | null
vendor: SimpleVendorResponse | null
category: SimpleCategoryResponse | null
creditedAccount: SimpleAccountResponse | null
debitedAccount: SimpleAccountResponse | null
tags: string[]
}
export interface TransactionDraftResponse {
id: number
addedAt: string
templateName: string | null
timestamp: string | null
amount: number | null
currency: Currency | null
description: string | null
internalTransfer: boolean | null
vendor: SimpleVendorResponse | null
category: SimpleCategoryResponse | null
creditedAccount: SimpleAccountResponse | null
debitedAccount: SimpleAccountResponse | null
tags: string[]
lineItems: TransactionLineItemResponse[]
attachments: Attachment[]
}
export interface TransactionDraftPayload {
templateName: string | null
timestamp: string | null
amount: number | null
currencyCode: string | null
description: string | null
internalTransfer: boolean | null
vendorId: number | null
categoryId: number | null
creditedAccountId: number | null
debitedAccountId: number | null
tags: string[]
lineItems: AddTransactionPayloadLineItem[]
attachmentIdsToRemove: number[]
}
export class TransactionApiClient extends ApiClient { export class TransactionApiClient extends ApiClient {
readonly path: string readonly path: string
@ -277,4 +332,57 @@ export class TransactionApiClient extends ApiClient {
getAllTags(): Promise<string[]> { getAllTags(): Promise<string[]> {
return super.getJson(this.path + '/transaction-tags') return super.getJson(this.path + '/transaction-tags')
} }
// Drafts:
getDrafts(
paginationOptions: PageRequest | undefined = undefined,
): Promise<Page<TransactionDraftListItem>> {
return super.getJsonPage(this.path + '/transaction-drafts', paginationOptions)
}
getTemplateDrafts(
paginationOptions: PageRequest | undefined = undefined,
): Promise<Page<TransactionDraftListItem>> {
const params = new URLSearchParams()
params.append('template', 'true')
if (paginationOptions !== undefined) {
params.append('page', paginationOptions.page + '')
params.append('size', paginationOptions.size + '')
for (const sort of paginationOptions.sorts) {
params.append('sort', sort.attribute + ',' + sort.dir)
}
}
return super.getJson(this.path + '/transaction-drafts?' + params.toString())
}
getDraft(id: number): Promise<TransactionDraftResponse> {
return super.getJson(this.path + '/transaction-drafts/' + id)
}
addDraft(data: TransactionDraftPayload, files: File[] = []): Promise<TransactionDraftResponse> {
const formData = new FormData()
formData.append('payload', JSON.stringify(data))
for (const file of files) {
formData.append('file', file)
}
return super.postFormData(this.path + '/transaction-drafts', formData)
}
updateDraft(
id: number,
data: TransactionDraftPayload,
files: File[] = [],
): Promise<TransactionDraftResponse> {
const formData = new FormData()
formData.append('payload', JSON.stringify(data))
for (const file of files) {
formData.append('file', file)
}
return super.putFormData(this.path + '/transaction-drafts/' + id, formData)
}
deleteDraft(id: number): Promise<void> {
return super.delete(this.path + '/transaction-drafts/' + id)
}
} }

View File

@ -1,10 +1,10 @@
<script setup lang="ts"> <script setup lang="ts">
import type { TransactionDetailLineItem } from '@/api/transaction' import type { TransactionLineItemResponse } from '@/api/transaction'
import AppButton from './common/AppButton.vue' import AppButton from './common/AppButton.vue'
import { formatMoney, type Currency } from '@/api/data' import { formatMoney, type Currency } from '@/api/data'
defineProps<{ defineProps<{
lineItem: TransactionDetailLineItem lineItem: TransactionLineItemResponse
currency: Currency currency: Currency
totalCount?: number totalCount?: number
editable?: boolean editable?: boolean

View File

@ -4,7 +4,7 @@ transaction. This editor shows a table of current line items, and includes a
modal for adding a new one. modal for adding a new one.
--> -->
<script setup lang="ts"> <script setup lang="ts">
import { type TransactionCategoryTree, type TransactionDetailLineItem } from '@/api/transaction' import { type TransactionCategoryTree, type TransactionLineItemResponse } from '@/api/transaction'
import AppButton from '@/components/common/AppButton.vue' import AppButton from '@/components/common/AppButton.vue'
import FormGroup from '@/components/common/form/FormGroup.vue' import FormGroup from '@/components/common/form/FormGroup.vue'
import { floatMoneyToInteger, formatMoney, type Currency } from '@/api/data' import { floatMoneyToInteger, formatMoney, type Currency } from '@/api/data'
@ -15,7 +15,7 @@ import CategorySelect from './CategorySelect.vue'
import LineItemCard from './LineItemCard.vue' import LineItemCard from './LineItemCard.vue'
import AppBadge from './common/AppBadge.vue' import AppBadge from './common/AppBadge.vue'
const model = defineModel<TransactionDetailLineItem[]>({ required: true }) const model = defineModel<TransactionLineItemResponse[]>({ required: true })
const props = defineProps<{ const props = defineProps<{
transactionAmount: number transactionAmount: number
currency: Currency currency: Currency

View File

@ -17,7 +17,7 @@ import {
TransactionApiClient, TransactionApiClient,
type AddTransactionPayload, type AddTransactionPayload,
type TransactionDetail, type TransactionDetail,
type TransactionDetailLineItem, type TransactionLineItemResponse,
type TransactionVendor, type TransactionVendor,
} from '@/api/transaction' } from '@/api/transaction'
import AppPage from '@/components/common/AppPage.vue' import AppPage from '@/components/common/AppPage.vue'
@ -144,7 +144,7 @@ const vendor: Ref<TransactionVendor | null> = ref(null)
const categoryId: Ref<number | null> = ref(null) const categoryId: Ref<number | null> = ref(null)
const creditedAccountId: Ref<number | null> = ref(null) const creditedAccountId: Ref<number | null> = ref(null)
const debitedAccountId: Ref<number | null> = ref(null) const debitedAccountId: Ref<number | null> = ref(null)
const lineItems: Ref<TransactionDetailLineItem[]> = ref([]) const lineItems: Ref<TransactionLineItemResponse[]> = ref([])
const tags: Ref<string[]> = ref([]) const tags: Ref<string[]> = ref([])
const customTagInput = ref('') const customTagInput = ref('')
const customTagInputValid = ref(false) const customTagInputValid = ref(false)