add transactions and random fixes
This commit is contained in:
parent
fa4563f771
commit
e5bbd266e9
15
.bruno/BRF/api-transactions.bru
Normal file
15
.bruno/BRF/api-transactions.bru
Normal file
@ -0,0 +1,15 @@
|
|||||||
|
meta {
|
||||||
|
name: /api/transactions
|
||||||
|
type: http
|
||||||
|
seq: 14
|
||||||
|
}
|
||||||
|
|
||||||
|
get {
|
||||||
|
url: {{base_url}}/api/transactions
|
||||||
|
body: none
|
||||||
|
auth: inherit
|
||||||
|
}
|
||||||
|
|
||||||
|
settings {
|
||||||
|
encodeUrl: true
|
||||||
|
}
|
||||||
@ -20,7 +20,7 @@ const AccountsPage = () => {
|
|||||||
</Head>
|
</Head>
|
||||||
|
|
||||||
<h1>Accounts</h1>
|
<h1>Accounts</h1>
|
||||||
<table>
|
<table className='grid'>
|
||||||
<thead>
|
<thead>
|
||||||
<th>Number</th>
|
<th>Number</th>
|
||||||
<th>Description</th>
|
<th>Description</th>
|
||||||
|
|||||||
@ -1,14 +1,43 @@
|
|||||||
import { h } from 'preact'
|
import { h } from 'preact'
|
||||||
|
import { useCallback, useEffect } from 'preact/hooks'
|
||||||
import { LocationProvider, Route, Router } from 'preact-iso'
|
import { LocationProvider, Route, Router } from 'preact-iso'
|
||||||
|
import { get } from 'lowline'
|
||||||
import Head from './head.ts'
|
import Head from './head.ts'
|
||||||
import Footer from './footer.tsx'
|
import Footer from './footer.tsx'
|
||||||
import Header from './header.tsx'
|
import Header from './header.tsx'
|
||||||
import ErrorPage from './error_page.tsx'
|
import ErrorPage from './error_page.tsx'
|
||||||
import routes from '../routes.ts'
|
import routes from '../routes.ts'
|
||||||
|
import throttle from '../../shared/utils/throttle.ts'
|
||||||
|
|
||||||
import s from './app.module.scss'
|
import s from './app.module.scss'
|
||||||
|
|
||||||
|
const remember = throttle(function remember() {
|
||||||
|
window.history.replaceState(
|
||||||
|
{
|
||||||
|
...window.history.state,
|
||||||
|
scrollTop: window.scrollY,
|
||||||
|
},
|
||||||
|
'',
|
||||||
|
null,
|
||||||
|
)
|
||||||
|
}, 100)
|
||||||
|
|
||||||
export default function App({ error, url, title }) {
|
export default function App({ error, url, title }) {
|
||||||
|
useEffect(() => {
|
||||||
|
addEventListener('scroll', remember)
|
||||||
|
|
||||||
|
return () => removeEventListener('scroll', remember)
|
||||||
|
})
|
||||||
|
|
||||||
|
const onRouteChange = useCallback(() => {
|
||||||
|
const offset = get(window.history, 'state.scrollTop')
|
||||||
|
console.log('offset', offset)
|
||||||
|
|
||||||
|
setTimeout(() => {
|
||||||
|
window.scrollTo(0, offset || 0)
|
||||||
|
}, 100)
|
||||||
|
}, [])
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<LocationProvider>
|
<LocationProvider>
|
||||||
<div id='app' className={s.base}>
|
<div id='app' className={s.base}>
|
||||||
@ -22,7 +51,7 @@ export default function App({ error, url, title }) {
|
|||||||
{error ? (
|
{error ? (
|
||||||
<ErrorPage error={error} />
|
<ErrorPage error={error} />
|
||||||
) : (
|
) : (
|
||||||
<Router>
|
<Router onRouteChange={onRouteChange}>
|
||||||
{routes.map((route) => (
|
{routes.map((route) => (
|
||||||
<Route key={route.path} route={route} path={route.path} component={route.component} />
|
<Route key={route.path} route={route} path={route.path} component={route.component} />
|
||||||
))}
|
))}
|
||||||
|
|||||||
@ -34,7 +34,7 @@ const EntriesPage = () => {
|
|||||||
setJournals(journals)
|
setJournals(journals)
|
||||||
})
|
})
|
||||||
rek('/api/financial-years').then((financialYears) => {
|
rek('/api/financial-years').then((financialYears) => {
|
||||||
setFinancialYears(financialYears.toReversed())
|
setFinancialYears(financialYears)
|
||||||
})
|
})
|
||||||
}, [])
|
}, [])
|
||||||
|
|
||||||
@ -68,7 +68,7 @@ const EntriesPage = () => {
|
|||||||
<h2>
|
<h2>
|
||||||
{selectedJournal} : {selectedYear}
|
{selectedJournal} : {selectedYear}
|
||||||
</h2>
|
</h2>
|
||||||
<table>
|
<table className='grid'>
|
||||||
<thead>
|
<thead>
|
||||||
<tr>
|
<tr>
|
||||||
<th>ID</th>
|
<th>ID</th>
|
||||||
@ -85,10 +85,13 @@ const EntriesPage = () => {
|
|||||||
<td>
|
<td>
|
||||||
<a href={`/entries/${entry.id}`}>{entry.id}</a>
|
<a href={`/entries/${entry.id}`}>{entry.id}</a>
|
||||||
</td>
|
</td>
|
||||||
<td>{entry.number}</td>
|
<td>
|
||||||
|
{entry.journal}
|
||||||
|
{entry.number}
|
||||||
|
</td>
|
||||||
<td>{entry.entryDate?.slice(0, 10)}</td>
|
<td>{entry.entryDate?.slice(0, 10)}</td>
|
||||||
<td>{entry.transactionDate?.slice(0, 10)}</td>
|
<td>{entry.transactionDate?.slice(0, 10)}</td>
|
||||||
<td>{entry.amount}</td>
|
<td className='tar'>{entry.amount}</td>
|
||||||
<td>{entry.description}</td>
|
<td>{entry.description}</td>
|
||||||
</tr>
|
</tr>
|
||||||
))}
|
))}
|
||||||
|
|||||||
@ -11,10 +11,6 @@ const EntriesPage = () => {
|
|||||||
const location = useLocation()
|
const location = useLocation()
|
||||||
const route = useRoute()
|
const route = useRoute()
|
||||||
|
|
||||||
// console.dir(route)
|
|
||||||
// console.dir(location)
|
|
||||||
console.log(entry)
|
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
rek(`/api/entries/${route.params.id}`).then((entry) => {
|
rek(`/api/entries/${route.params.id}`).then((entry) => {
|
||||||
setEntry(entry)
|
setEntry(entry)
|
||||||
@ -23,8 +19,6 @@ const EntriesPage = () => {
|
|||||||
|
|
||||||
if (!entry) return
|
if (!entry) return
|
||||||
|
|
||||||
console.log(entry)
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<section>
|
<section>
|
||||||
<Head>
|
<Head>
|
||||||
@ -37,7 +31,7 @@ const EntriesPage = () => {
|
|||||||
Entry {entry.journal} {entry.number}
|
Entry {entry.journal} {entry.number}
|
||||||
</h1>
|
</h1>
|
||||||
|
|
||||||
<table>
|
<table className='grid'>
|
||||||
<thead>
|
<thead>
|
||||||
<tr>
|
<tr>
|
||||||
<th>ID</th>
|
<th>ID</th>
|
||||||
@ -58,14 +52,14 @@ const EntriesPage = () => {
|
|||||||
<td>{entry.number}</td>
|
<td>{entry.number}</td>
|
||||||
<td>{entry.entryDate?.slice(0, 10)}</td>
|
<td>{entry.entryDate?.slice(0, 10)}</td>
|
||||||
<td>{entry.transactionDate?.slice(0, 10)}</td>
|
<td>{entry.transactionDate?.slice(0, 10)}</td>
|
||||||
<td>{entry.amount}</td>
|
<td className='tar'>{entry.amount}</td>
|
||||||
<td>{entry.description}</td>
|
<td>{entry.description}</td>
|
||||||
</tr>
|
</tr>
|
||||||
</tbody>
|
</tbody>
|
||||||
</table>
|
</table>
|
||||||
|
|
||||||
<h2>Transactions</h2>
|
<h2>Transactions</h2>
|
||||||
<table>
|
<table className='grid'>
|
||||||
<thead>
|
<thead>
|
||||||
<tr>
|
<tr>
|
||||||
<th>Account</th>
|
<th>Account</th>
|
||||||
@ -78,8 +72,8 @@ const EntriesPage = () => {
|
|||||||
{entry?.transactions?.map((transaction) => (
|
{entry?.transactions?.map((transaction) => (
|
||||||
<tr>
|
<tr>
|
||||||
<td>{transaction.account_number}</td>
|
<td>{transaction.account_number}</td>
|
||||||
<td>{transaction.amount >= 0 ? formatNumber(transaction.amount) : null}</td>
|
<td className='tar'>{transaction.amount >= 0 ? formatNumber(transaction.amount) : null}</td>
|
||||||
<td>{transaction.amount < 0 ? formatNumber(Math.abs(transaction.amount)) : null}</td>
|
<td className='tar'>{transaction.amount < 0 ? formatNumber(Math.abs(transaction.amount)) : null}</td>
|
||||||
<td>{transaction.description}</td>
|
<td>{transaction.description}</td>
|
||||||
</tr>
|
</tr>
|
||||||
))}
|
))}
|
||||||
|
|||||||
16
client/public/components/header.module.scss
Normal file
16
client/public/components/header.module.scss
Normal file
@ -0,0 +1,16 @@
|
|||||||
|
@use '../../shared/styles/utils';
|
||||||
|
|
||||||
|
.nav {
|
||||||
|
> ul {
|
||||||
|
@include utils.wipe-list();
|
||||||
|
|
||||||
|
display: flex;
|
||||||
|
|
||||||
|
> li {
|
||||||
|
> a {
|
||||||
|
display: block;
|
||||||
|
padding: 5px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -1,9 +1,10 @@
|
|||||||
import { h } from 'preact'
|
import { h } from 'preact'
|
||||||
|
import s from './header.module.scss'
|
||||||
|
|
||||||
const Header = ({ routes }) => (
|
const Header = ({ routes }) => (
|
||||||
<header>
|
<header>
|
||||||
<h1>BRF Tegeltrasten</h1>
|
<h1>BRF Tegeltrasten</h1>
|
||||||
<nav>
|
<nav className={s.nav}>
|
||||||
<ul>
|
<ul>
|
||||||
{routes.map((route) =>
|
{routes.map((route) =>
|
||||||
route.nav !== false ? (
|
route.nav !== false ? (
|
||||||
|
|||||||
@ -14,13 +14,24 @@ const Result: FunctionalComponent<Props> = ({ objectId }) => {
|
|||||||
}, [objectId])
|
}, [objectId])
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<table>
|
<table className='grid'>
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th>Entry</th>
|
||||||
|
<th>Date</th>
|
||||||
|
<th>Account</th>
|
||||||
|
<th>Amount</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
<tbody>
|
<tbody>
|
||||||
{transactions.map((transaction) => (
|
{transactions.map((transaction) => (
|
||||||
<tr>
|
<tr>
|
||||||
|
<td>
|
||||||
|
<a href={`/entries/${transaction.entryId}`}>{transaction.entryId}</a>
|
||||||
|
</td>
|
||||||
<td>{transaction.transactionDate.slice(0, 10)}</td>
|
<td>{transaction.transactionDate.slice(0, 10)}</td>
|
||||||
<td>{transaction.accountNumber}</td>
|
<td>{transaction.accountNumber}</td>
|
||||||
<td>{transaction.amount}</td>
|
<td className='tar'>{transaction.amount}</td>
|
||||||
</tr>
|
</tr>
|
||||||
))}
|
))}
|
||||||
</tbody>
|
</tbody>
|
||||||
|
|||||||
@ -1,16 +1,9 @@
|
|||||||
.table {
|
.table {
|
||||||
td:nth-child(3),
|
a {
|
||||||
td:nth-child(3) ~ td {
|
text-decoration: none;
|
||||||
text-align: right;
|
color: black;
|
||||||
}
|
&:hover {
|
||||||
|
font-weight: bold;
|
||||||
tr:hover {
|
}
|
||||||
background: #eee;
|
|
||||||
}
|
|
||||||
|
|
||||||
td,
|
|
||||||
th {
|
|
||||||
border: 1px solid #ccc;
|
|
||||||
padding: 3px 5px;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,5 +1,6 @@
|
|||||||
import { h } from 'preact'
|
import { h } from 'preact'
|
||||||
import { useEffect, useState } from 'preact/hooks'
|
import { useEffect, useState } from 'preact/hooks'
|
||||||
|
import cn from 'classnames'
|
||||||
import rek from 'rek'
|
import rek from 'rek'
|
||||||
import Head from './head.ts'
|
import Head from './head.ts'
|
||||||
import Result from './result.tsx'
|
import Result from './result.tsx'
|
||||||
@ -13,7 +14,7 @@ const ResultsPage = () => {
|
|||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
rek(`/api/results`).then(setResults)
|
rek(`/api/results`).then(setResults)
|
||||||
rek(`/api/financial-years`).then((years) => setYears(years.map((fy) => fy.year).toReversed()))
|
rek(`/api/financial-years`).then((years) => setYears(years.map((fy) => fy.year)))
|
||||||
}, [])
|
}, [])
|
||||||
|
|
||||||
return (
|
return (
|
||||||
@ -24,7 +25,7 @@ const ResultsPage = () => {
|
|||||||
|
|
||||||
<h1>Results</h1>
|
<h1>Results</h1>
|
||||||
{years.length && results.length && (
|
{years.length && results.length && (
|
||||||
<table className={s.table}>
|
<table className={cn('grid', s.table)}>
|
||||||
<thead>
|
<thead>
|
||||||
<tr>
|
<tr>
|
||||||
<th>Account</th>
|
<th>Account</th>
|
||||||
@ -40,7 +41,11 @@ const ResultsPage = () => {
|
|||||||
<td>{result.accountNumber}</td>
|
<td>{result.accountNumber}</td>
|
||||||
<td>{result.description}</td>
|
<td>{result.description}</td>
|
||||||
{years.map((year) => (
|
{years.map((year) => (
|
||||||
<td>{formatNumber(result[year])}</td>
|
<td className='tar'>
|
||||||
|
<a href={`/transactions?year=${year}&accountNumber=${result.accountNumber}`}>
|
||||||
|
{formatNumber(result[year])}
|
||||||
|
</a>
|
||||||
|
</td>
|
||||||
))}
|
))}
|
||||||
</tr>
|
</tr>
|
||||||
))}
|
))}
|
||||||
|
|||||||
96
client/public/components/transactions_page.tsx
Normal file
96
client/public/components/transactions_page.tsx
Normal file
@ -0,0 +1,96 @@
|
|||||||
|
import { h } from 'preact'
|
||||||
|
import { useCallback, useEffect, useState } from 'preact/hooks'
|
||||||
|
import { useLocation } from 'preact-iso'
|
||||||
|
import { isEmpty } from 'lowline'
|
||||||
|
import qs from 'mini-qs'
|
||||||
|
import rek from 'rek'
|
||||||
|
import Head from './head.ts'
|
||||||
|
import serializeForm from '../../shared/utils/serialize_form.ts'
|
||||||
|
import { formatNumber } from '../utils/format_number.ts'
|
||||||
|
|
||||||
|
const TransactionsPage = () => {
|
||||||
|
const [financialYears, setFinancialYears] = useState([])
|
||||||
|
const [transactions, setTransactions] = useState([])
|
||||||
|
|
||||||
|
const location = useLocation()
|
||||||
|
|
||||||
|
console.log(location)
|
||||||
|
|
||||||
|
const onSubmit = useCallback((e: SubmitEvent) => {
|
||||||
|
e.preventDefault()
|
||||||
|
|
||||||
|
const values = serializeForm(e.target as HTMLFormElement)
|
||||||
|
|
||||||
|
const search = !isEmpty(values) ? '?' + qs.stringify(values) : ''
|
||||||
|
|
||||||
|
location.route(`/transactions${search}`)
|
||||||
|
}, [])
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
rek('/api/financial-years').then((financialYears) => {
|
||||||
|
setFinancialYears(financialYears)
|
||||||
|
})
|
||||||
|
}, [])
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
const search = location.url.split('?')[1] || ''
|
||||||
|
|
||||||
|
rek(`/api/transactions${search ? '?' + search : ''}`).then((transactions) => {
|
||||||
|
setTransactions(transactions)
|
||||||
|
})
|
||||||
|
}, [location.url])
|
||||||
|
|
||||||
|
return (
|
||||||
|
<section>
|
||||||
|
<Head>
|
||||||
|
<title> : Transactions</title>
|
||||||
|
</Head>
|
||||||
|
|
||||||
|
<form onSubmit={onSubmit}>
|
||||||
|
<select name='year' defaultValue={location.query.year || '2025'}>
|
||||||
|
{financialYears.map((financialYear) => (
|
||||||
|
<option value={financialYear.year}>{financialYear.year}</option>
|
||||||
|
))}
|
||||||
|
</select>
|
||||||
|
<input type='text' name='accountNumber' defaultValue={location.query.accountNumber || ''} />
|
||||||
|
<button>Search</button>
|
||||||
|
</form>
|
||||||
|
|
||||||
|
<h1>Transactions</h1>
|
||||||
|
<div>
|
||||||
|
<table className='grid'>
|
||||||
|
<thead>
|
||||||
|
<th>Date</th>
|
||||||
|
<th>Account</th>
|
||||||
|
<th>Debit</th>
|
||||||
|
<th>Credit</th>
|
||||||
|
<th>Description</th>
|
||||||
|
<th>Entry</th>
|
||||||
|
<th>Entry Description</th>
|
||||||
|
<th>Invoice</th>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
{transactions.map((transaction) => (
|
||||||
|
<tr>
|
||||||
|
<td>{transaction.transactionDate.slice(0, 10)}</td>
|
||||||
|
<td>{transaction.accountNumber}</td>
|
||||||
|
<td className='tar'>{transaction.amount >= 0 ? formatNumber(transaction.amount) : null}</td>
|
||||||
|
<td className='tar'>{transaction.amount < 0 ? formatNumber(Math.abs(transaction.amount)) : null}</td>
|
||||||
|
<td>{transaction.description}</td>
|
||||||
|
<td>
|
||||||
|
<a href={`/entries/${transaction.entryId}`}>{transaction.entryId}</a>
|
||||||
|
</td>
|
||||||
|
<td>{transaction.entryDescription}</td>
|
||||||
|
<td>
|
||||||
|
<a href={`/invoices/${transaction.invoiceId}`}>{transaction.invoiceId}</a>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
))}
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export default TransactionsPage
|
||||||
@ -7,6 +7,7 @@ import InvoicesBySupplier from './components/invoices_by_supplier_page.tsx'
|
|||||||
import Objects from './components/objects_page.tsx'
|
import Objects from './components/objects_page.tsx'
|
||||||
import Results from './components/results_page.tsx'
|
import Results from './components/results_page.tsx'
|
||||||
import Start from './components/start_page.tsx'
|
import Start from './components/start_page.tsx'
|
||||||
|
import Transactions from './components/transactions_page.tsx'
|
||||||
|
|
||||||
export default [
|
export default [
|
||||||
{
|
{
|
||||||
@ -66,4 +67,10 @@ export default [
|
|||||||
title: 'Results',
|
title: 'Results',
|
||||||
component: Results,
|
component: Results,
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
path: '/transactions',
|
||||||
|
name: 'transactions',
|
||||||
|
title: 'Transactions',
|
||||||
|
component: Transactions,
|
||||||
|
},
|
||||||
]
|
]
|
||||||
|
|||||||
@ -7,4 +7,20 @@
|
|||||||
table {
|
table {
|
||||||
border-collapse: collapse;
|
border-collapse: collapse;
|
||||||
border-spacing: 0;
|
border-spacing: 0;
|
||||||
|
|
||||||
|
&.grid {
|
||||||
|
tr:hover {
|
||||||
|
background: #eee;
|
||||||
|
}
|
||||||
|
|
||||||
|
td,
|
||||||
|
th {
|
||||||
|
border: 1px solid #ccc;
|
||||||
|
padding: 3px 5px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.tar {
|
||||||
|
text-align: right;
|
||||||
}
|
}
|
||||||
|
|||||||
24
client/shared/utils/throttle.ts
Normal file
24
client/shared/utils/throttle.ts
Normal file
@ -0,0 +1,24 @@
|
|||||||
|
// copied and modified from https://github.com/sindresorhus/throttleit/blob/ed1d22c70a964ef0299d0400dbfd1fbedef56a59/index.js
|
||||||
|
|
||||||
|
export default function throttle(fnc, wait) {
|
||||||
|
let timeout
|
||||||
|
let lastCallTime = 0
|
||||||
|
|
||||||
|
return function throttled(...args) {
|
||||||
|
clearTimeout(timeout)
|
||||||
|
|
||||||
|
const now = Date.now()
|
||||||
|
const timeSinceLastCall = now - lastCallTime
|
||||||
|
const delayForNextCall = wait - timeSinceLastCall
|
||||||
|
|
||||||
|
if (delayForNextCall <= 0) {
|
||||||
|
lastCallTime = now
|
||||||
|
fnc(...args)
|
||||||
|
} else {
|
||||||
|
timeout = setTimeout(() => {
|
||||||
|
lastCallTime = Date.now()
|
||||||
|
fnc(...args)
|
||||||
|
}, delayForNextCall)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -10,6 +10,7 @@ import journals from './api/journals.ts'
|
|||||||
import objects from './api/objects.ts'
|
import objects from './api/objects.ts'
|
||||||
import results from './api/results.ts'
|
import results from './api/results.ts'
|
||||||
import suppliers from './api/suppliers.ts'
|
import suppliers from './api/suppliers.ts'
|
||||||
|
import transactions from './api/transactions.ts'
|
||||||
|
|
||||||
export const FinancialYear = Type.Object({
|
export const FinancialYear = Type.Object({
|
||||||
year: Type.Number(),
|
year: Type.Number(),
|
||||||
@ -28,6 +29,7 @@ const apiRoutes: FastifyPluginCallbackTypebox = (fastify, _, done) => {
|
|||||||
fastify.register(objects, { prefix: '/objects' })
|
fastify.register(objects, { prefix: '/objects' })
|
||||||
fastify.register(results, { prefix: '/results' })
|
fastify.register(results, { prefix: '/results' })
|
||||||
fastify.register(suppliers, { prefix: '/suppliers' })
|
fastify.register(suppliers, { prefix: '/suppliers' })
|
||||||
|
fastify.register(transactions, { prefix: '/transactions' })
|
||||||
|
|
||||||
done()
|
done()
|
||||||
}
|
}
|
||||||
|
|||||||
@ -7,7 +7,7 @@ const financialYearRoutes: FastifyPluginCallbackTypebox = (fastify, _, done) =>
|
|||||||
url: '/',
|
url: '/',
|
||||||
method: 'GET',
|
method: 'GET',
|
||||||
handler() {
|
handler() {
|
||||||
return knex('financialYear').select('*')
|
return knex('financialYear').select('*').orderBy('startDate', 'desc')
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|||||||
@ -25,7 +25,7 @@ const objectRoutes: FastifyPluginCallbackTypebox = (fastify, _, done) => {
|
|||||||
},
|
},
|
||||||
async handler(req) {
|
async handler(req) {
|
||||||
return knex('transaction AS t')
|
return knex('transaction AS t')
|
||||||
.select('e.transactionDate', 't.accountNumber', 't.amount')
|
.select('t.entryId', 'e.transactionDate', 't.accountNumber', 't.amount')
|
||||||
.innerJoin('transactions_to_objects AS to', function () {
|
.innerJoin('transactions_to_objects AS to', function () {
|
||||||
this.on('t.id', 'to.transactionId')
|
this.on('t.id', 'to.transactionId')
|
||||||
})
|
})
|
||||||
|
|||||||
61
server/routes/api/transactions.ts
Normal file
61
server/routes/api/transactions.ts
Normal file
@ -0,0 +1,61 @@
|
|||||||
|
import _ from 'lodash'
|
||||||
|
import { Type, type Static, type FastifyPluginCallbackTypebox } from '@fastify/type-provider-typebox'
|
||||||
|
import knex from '../../lib/knex.ts'
|
||||||
|
import StatusError from '../../lib/status_error.ts'
|
||||||
|
|
||||||
|
const transactionRoutes: FastifyPluginCallbackTypebox = (fastify, _, done) => {
|
||||||
|
fastify.route({
|
||||||
|
url: '/',
|
||||||
|
method: 'GET',
|
||||||
|
schema: {
|
||||||
|
querystring: Type.Object({
|
||||||
|
year: Type.Optional(Type.Number()),
|
||||||
|
accountNumber: Type.Optional(Type.Number()),
|
||||||
|
}),
|
||||||
|
},
|
||||||
|
async handler(req) {
|
||||||
|
const query: { financialYearId?: number; accountNumber?: number } = {}
|
||||||
|
|
||||||
|
if (req.query.year) {
|
||||||
|
const year = await knex('financialYear').first('*').where('year', req.query.year)
|
||||||
|
|
||||||
|
if (!year) throw new StatusError(404, `Year ${req.query.year} not found.`)
|
||||||
|
query.financialYearId = year.id
|
||||||
|
}
|
||||||
|
|
||||||
|
if (req.query.accountNumber) {
|
||||||
|
query.accountNumber = req.query.accountNumber
|
||||||
|
}
|
||||||
|
|
||||||
|
return knex('transaction AS t')
|
||||||
|
.select(
|
||||||
|
't.accountNumber',
|
||||||
|
'e.transactionDate',
|
||||||
|
't.entryId',
|
||||||
|
't.amount',
|
||||||
|
't.description',
|
||||||
|
't.invoiceId',
|
||||||
|
'e.description AS entryDescription',
|
||||||
|
)
|
||||||
|
.innerJoin('entry AS e', 't.entry_id', 'e.id')
|
||||||
|
.where(query)
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
|
fastify.route({
|
||||||
|
url: '/:id',
|
||||||
|
method: 'GET',
|
||||||
|
schema: {
|
||||||
|
params: Type.Object({
|
||||||
|
id: Type.Number(),
|
||||||
|
}),
|
||||||
|
},
|
||||||
|
async handler(req) {
|
||||||
|
return knex('transaction').first('*').where('id', req.params.id)
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
|
done()
|
||||||
|
}
|
||||||
|
|
||||||
|
export default transactionRoutes
|
||||||
Loading…
Reference in New Issue
Block a user