WIP knex > kysely

This commit is contained in:
Linus Miller 2025-12-18 07:30:51 +01:00
parent 9d9ee1b4ce
commit 72eeb03425
16 changed files with 266 additions and 294 deletions

View File

@ -11,7 +11,7 @@ get {
} }
params:path { params:path {
id: 1000 id: 10
} }
settings { settings {

View File

@ -12,7 +12,6 @@ get {
params:query { params:query {
supplier: 150 supplier: 150
:
} }
settings { settings {

View File

@ -5,11 +5,17 @@ meta {
} }
get { get {
url: {{base_url}}/api/invoices url: {{base_url}}/api/invoices?limit=2&supplierId=10
body: none body: none
auth: inherit auth: inherit
} }
params:query {
limit: 2
supplierId: 10
~year: 2015
}
settings { settings {
encodeUrl: true encodeUrl: true
timeout: 0 timeout: 0

View File

@ -1,16 +1,15 @@
meta { meta {
name: /api/objects/:id name: /api/journals
type: http type: http
seq: 12 seq: 20
} }
get { get {
url: {{base_url}}/api/objects/10 url: {{base_url}}/api/journals
body: none body: none
auth: inherit auth: inherit
} }
settings { settings {
encodeUrl: true encodeUrl: true
timeout: 0
} }

View File

@ -0,0 +1,20 @@
meta {
name: /api/objects/:id/transactions
type: http
seq: 12
}
get {
url: {{base_url}}/api/objects/:id/transactions
body: none
auth: inherit
}
params:path {
id: 10
}
settings {
encodeUrl: true
timeout: 0
}

View File

@ -5,11 +5,15 @@ meta {
} }
get { get {
url: {{base_url}}/api/results/2018 url: {{base_url}}/api/results/:year
body: none body: none
auth: inherit auth: inherit
} }
params:path {
year: 2017
}
settings { settings {
encodeUrl: true encodeUrl: true
timeout: 0 timeout: 0

View File

@ -5,14 +5,14 @@ meta {
} }
get { get {
url: {{base_url}}/api/transactions?year=2020&accountNumber=4800 url: {{base_url}}/api/transactions?year=2020
body: none body: none
auth: inherit auth: inherit
} }
params:query { params:query {
year: 2020 year: 2020
accountNumber: 4800 ~accountNumber: 4800
} }
settings { settings {

View File

@ -1,9 +1,25 @@
import { type SelectQueryBuilder, type ReferenceExpression, type OperandValueExpression, sql } from 'kysely' import { type SelectQueryBuilder, type ReferenceExpression, type OperandValueExpression, sql } from 'kysely'
type ColumnsOf<DB, T extends keyof DB> = T extends any ? keyof DB[T] & string : never
type AppearsIn<DB, TB extends keyof DB, Col extends string> = {
[T in TB]: Col extends ColumnsOf<DB, T> ? T : never
}[TB]
type IsUnion<T, U = T> = T extends any ? ([U] extends [T] ? false : true) : never
type UniqueColumn<DB, TB extends keyof DB> = {
[C in ColumnsOf<DB, TB>]: IsUnion<AppearsIn<DB, TB, C>> extends true ? never : C
}[ColumnsOf<DB, TB>]
type PrefixedColumn<DB, TB extends keyof DB> = TB extends any ? `${TB & string}.${keyof DB[TB] & string}` : never
type UnambiguousColumn<DB, TB extends keyof DB> = PrefixedColumn<DB, TB> | UniqueColumn<DB, TB>
type WhereInput<DB, TB extends keyof DB> = Partial<{ type WhereInput<DB, TB extends keyof DB> = Partial<{
[C in keyof DB[TB]]: [C in UnambiguousColumn<DB, TB>]:
| OperandValueExpression<DB, TB, DB[TB][C]> | OperandValueExpression<DB, TB, any>
| readonly OperandValueExpression<DB, TB, DB[TB][C]>[] | readonly OperandValueExpression<DB, TB, any>[]
| null | null
}> }>
@ -16,10 +32,16 @@ export function applyWhere<DB, TB extends keyof DB, O>(
if (value === null) { if (value === null) {
return builder.where(column, 'is', null) return builder.where(column, 'is', null)
} else if (Array.isArray(value)) { }
if (Array.isArray(value)) {
return builder.where(column, 'in', value) return builder.where(column, 'in', value)
} }
if (value === undefined) {
return builder
}
return builder.where(column, '=', value) return builder.where(column, '=', value)
}, builder) }, builder)
} }
@ -28,20 +50,19 @@ interface PaginationInput<DB, TB extends keyof DB> {
where: WhereInput<DB, TB> where: WhereInput<DB, TB>
limit?: number limit?: number
offset?: number offset?: number
orderBy?: { sort?: UnambiguousColumn<DB, TB> | `-${UnambiguousColumn<DB, TB>}`
column: ReferenceExpression<DB, TB>
direction?: 'asc' | 'desc'
}
} }
export function applyPagination<DB, TB extends keyof DB, O>( export function applyPagination<DB, TB extends keyof DB, O>(
builder: SelectQueryBuilder<DB, TB, O>, builder: SelectQueryBuilder<DB, TB, O>,
{ limit, offset, orderBy }: PaginationInput<DB, TB>, { limit, offset, sort }: PaginationInput<DB, TB>,
): SelectQueryBuilder<DB, TB, O> { ): SelectQueryBuilder<DB, TB, O> {
let qb = builder let qb = builder
if (orderBy) { if (sort) {
qb = qb.orderBy(orderBy.column, orderBy.direction ?? 'asc') const columnName = (sort.startsWith('-') ? sort.slice(1) : sort) as UnambiguousColumn<DB, TB>
qb = qb.orderBy(columnName, columnName === sort ? 'asc' : 'desc')
} }
if (limit !== undefined) { if (limit !== undefined) {
@ -68,13 +89,15 @@ export async function paginate<DB, TB extends keyof DB, O>(
countQuery = applyWhere(countQuery, input.where) countQuery = applyWhere(countQuery, input.where)
} }
const [countRow, items] = await Promise.all([countQuery.executeTakeFirstOrThrow(), dataQuery.execute()]) const [countRow, data] = await Promise.all([countQuery.executeTakeFirstOrThrow(), dataQuery.execute()])
return { return {
items, data,
totalCount: (countRow as { totalCount: number }).totalCount, meta: {
limit: input.limit, totalCount: (countRow as { totalCount: number }).totalCount,
offset: input.offset, limit: input.limit,
count: items.length, offset: input.offset,
count: data.length,
},
} }
} }

View File

@ -1,32 +1,30 @@
import _ from 'lodash' import _ from 'lodash'
import type { FastifyPluginCallbackZod } from 'fastify-type-provider-zod' import type { FastifyPluginCallbackZod } from 'fastify-type-provider-zod'
import knex from '../../lib/knex.ts' import { sql } from 'kysely'
const balanceRoutes: FastifyPluginCallbackZod = (fastify, _, done) => { const balanceRoutes: FastifyPluginCallbackZod = (fastify, _, done) => {
const { db } = fastify
fastify.route({ fastify.route({
url: '/', url: '/',
method: 'GET', method: 'GET',
async handler() { async handler() {
const financialYears = await knex('financialYear').select('*').orderBy('year', 'asc') const financialYears = await db.selectFrom('financialYear').select('year').orderBy('year').execute()
return knex('accountBalance AS ab') return db
.selectFrom('accountBalance as ab')
.innerJoin('financialYear as fy', 'fy.id', 'ab.financialYearId')
.innerJoin('account as a', 'a.number', 'ab.accountNumber')
.select(['ab.accountNumber', 'a.description'])
.select( .select(
'ab.accountNumber', financialYears.map((fy) =>
'a.description', sql`SUM(CASE WHEN fy.year = ${fy.year} THEN ab."out" ELSE 0 END)`.as(fy.year.toString()),
Object.fromEntries(
financialYears.map((fy) => [
fy.year,
knex.raw(`SUM(CASE WHEN fy.year = ${fy.year} THEN ab."out" ELSE 0 END)`),
]),
), ),
) )
.innerJoin('financialYear AS fy', 'fy.id', 'ab.financialYearId') .groupBy(['ab.accountNumber', 'a.description'])
.innerJoin('account AS a', function () {
this.on('ab.accountNumber', '=', 'a.number')
})
.groupBy('ab.accountNumber', 'a.description')
.where('ab.accountNumber', '<', 3000)
.orderBy('ab.accountNumber') .orderBy('ab.accountNumber')
.where('ab.accountNumber', '<', 3000)
.execute()
}, },
}) })

View File

@ -1,34 +1,38 @@
import _ from 'lodash' import _ from 'lodash'
import * as z from 'zod' import * as z from 'zod'
import type { FastifyPluginCallbackZod } from 'fastify-type-provider-zod' import type { FastifyPluginCallbackZod } from 'fastify-type-provider-zod'
import knex from '../../lib/knex.ts' import { jsonArrayFrom } from 'kysely/helpers/postgres'
import { applyWhere } from '../../lib/kysely_helpers.ts'
const entryRoutes: FastifyPluginCallbackZod = (fastify, _, done) => { const entryRoutes: FastifyPluginCallbackZod = (fastify, _, done) => {
const { db } = fastify
fastify.route({ fastify.route({
url: '/', url: '/',
method: 'GET', method: 'GET',
schema: { schema: {
querystring: z.object({ querystring: z.object({
journal: z.string(), journal: z.string().optional(),
year: z.coerce.number(), year: z.coerce.number().optional(),
}), }),
}, },
async handler(req) { async handler(request) {
const financialYearId = (await knex('financialYear').first('id').where('year', req.query.year))?.id return applyWhere(
const journalId = (await knex('journal').first('id').where('identifier', req.query.journal))?.id db
.selectFrom('entry as e')
if (!financialYearId || !journalId) { .innerJoin('transaction as t', 'e.id', 't.entryId')
return null .innerJoin('journal as j', 'j.id', 'e.journalId')
} .innerJoin('financialYear as fy', 'fy.id', 'e.financialYearId')
.selectAll('e')
return knex('entry AS e') .select((eb) => eb.fn.sum('t.amount').as('amount'))
.select('e.*') .orderBy('e.id')
.sum('t.amount AS amount') .where('t.amount', '>', 0 as ANY)
.innerJoin('transaction AS t', 'e.id', 't.entryId') .groupBy('e.id'),
.orderBy('e.id') {
.where({ financialYearId, journalId }) year: request.query.year,
.andWhere('t.amount', '>', 0) 'j.identifier': request.query.journal,
.groupBy('e.id') },
).execute()
}, },
}) })
@ -37,27 +41,35 @@ const entryRoutes: FastifyPluginCallbackZod = (fastify, _, done) => {
method: 'GET', method: 'GET',
schema: { schema: {
params: z.object({ params: z.object({
id: z.number(), id: z.coerce.number(),
}), }),
}, },
async handler(req) { async handler(req) {
return knex('entry AS e') return db
.first('e.id', 'j.identifier AS journal', 'e.number', 'e.entryDate', 'e.transactionDate', 'e.description', { .selectFrom('entry as e')
transactions: knex .innerJoin('journal as j', 'e.journalId', 'j.id')
.select(knex.raw('json_agg(transactions)')) .innerJoin('transaction as t', 'e.id', 't.entryId')
.from( .select([
knex('transaction') 'e.id',
.select('accountNumber', 'objectId') 'j.identifier as journal',
.where('transaction.entryId', knex.ref('e.id')) 'e.number',
.as('transactions'), 'e.entryDate',
), 'e.transactionDate',
}) 'e.description',
.sum('t.amount AS amount') (eb) => eb.fn.sum('t.amount').as('amount'),
.innerJoin('journal AS j', 'e.journalId', 'j.id') (eb) =>
.innerJoin('transaction AS t', 'e.id', 't.entryId') jsonArrayFrom(
.where('e.id', req.params.id) eb
.andWhere('t.amount', '>', 0) .selectFrom('transaction as t')
.groupBy('e.id', 'j.identifier') .select(['id', 'accountNumber', 'amount', 'description'])
.selectAll()
.whereRef('t.entryId', '=', 'e.id'),
).as('transactions'),
])
.groupBy(['e.id', 'j.identifier'])
.where('e.id', '=', req.params.id)
.where('t.amount', '>', 0 as ANY)
.execute()
}, },
}) })

View File

@ -1,13 +1,13 @@
import _ from 'lodash' import _ from 'lodash'
import type { FastifyPluginCallbackZod } from 'fastify-type-provider-zod' import type { FastifyPluginCallbackZod } from 'fastify-type-provider-zod'
import knex from '../../lib/knex.ts'
const financialYearRoutes: FastifyPluginCallbackZod = (fastify, _, done) => { const financialYearRoutes: FastifyPluginCallbackZod = (fastify, _, done) => {
const { db } = fastify
fastify.route({ fastify.route({
url: '/', url: '/',
method: 'GET', method: 'GET',
handler() { handler() {
return knex('financialYear').select('*').orderBy('startDate', 'desc') return db.selectFrom('financialYear').selectAll().orderBy('startDate', 'desc').execute()
}, },
}) })

View File

@ -1,52 +1,52 @@
import _ from 'lodash' import _ from 'lodash'
import { jsonArrayFrom } from 'kysely/helpers/postgres'
import * as z from 'zod' import * as z from 'zod'
import type { FastifyPluginCallbackZod } from 'fastify-type-provider-zod' import type { FastifyPluginCallbackZod } from 'fastify-type-provider-zod'
import knex from '../../lib/knex.ts' import { applyWhere, paginate } from '../../lib/kysely_helpers.ts'
import StatusError from '../../lib/status_error.ts'
const invoiceRoutes: FastifyPluginCallbackZod = (fastify, _, done) => { const invoiceRoutes: FastifyPluginCallbackZod = (fastify, _, done) => {
const { db } = fastify
fastify.route({ fastify.route({
url: '/', url: '/',
method: 'GET', method: 'GET',
schema: { schema: {
querystring: z.object({ querystring: z.object({
year: z.number().optional(), year: z.coerce.number().optional(),
supplier: z.number().optional(), supplierId: z.coerce.number().optional(),
limit: z.coerce.number().default(100),
offset: z.coerce.number().optional(),
sort: z.literal(['supplierId', 'i.id', 'invoiceDate', 'dueDate']).default('i.id'),
}), }),
}, },
async handler(req) { async handler(request) {
let query: { financialYearId?: number; supplierId?: number } = {} const { offset, limit, sort, ...where } = request.query
const baseQuery = db.selectFrom('invoice as i').leftJoin('financialYear as fy', 'fy.id', 'i.financialYearId')
if (req.query.year) { return paginate(
const year = await knex('financialYear').first('*').where('year', req.query.year) baseQuery
.selectAll('i')
if (!year) throw new StatusError(404, `Year ${req.query.year} not found.`) .select(['fy.year'])
query.financialYearId = year.id .select((eb) => [
} jsonArrayFrom(
eb
if (req.query.supplier) { .selectFrom('file as f')
query.supplierId = req.query.supplier .innerJoin('filesToInvoice as fi', 'f.id', 'fi.fileId')
} .select(['id', 'filename'])
.whereRef('fi.invoiceId', '=', 'i.id'),
return knex('invoice AS i') ).as('files'),
.select('i.*', 'fy.year', { jsonArrayFrom(eb.selectFrom('transaction as t').selectAll().whereRef('t.invoiceId', '=', 'i.id')).as(
files: knex 'transactions',
.select(knex.raw('json_agg(files)'))
.from(
knex
.select('id', 'filename')
.from('file AS f')
.innerJoin('filesToInvoice AS fi', 'f.id', 'fi.fileId')
.where('fi.invoiceId', knex.ref('i.id'))
.as('files'),
), ),
transactions: knex ]),
.select(knex.raw('json_agg(transactions)')) baseQuery,
.from(knex.select('*').from('transaction AS t').where('t.invoiceId', knex.ref('i.id')).as('transactions')), {
}) where,
.leftOuterJoin('financialYear AS fy', 'i.financialYearId', 'fy.id') limit,
.orderBy('i.invoiceDate') offset,
.where(query) sort,
},
)
}, },
}) })
@ -55,25 +55,19 @@ const invoiceRoutes: FastifyPluginCallbackZod = (fastify, _, done) => {
method: 'GET', method: 'GET',
schema: { schema: {
querystring: z.object({ querystring: z.object({
year: z.number().optional(), supplierId: z.number().optional(),
supplier: z.number().optional(),
}), }),
response: {
200: z.object({
totalAmount: z.coerce.number(),
}),
},
}, },
async handler(req) { handler(request) {
let query: { financialYearId?: number; supplierId?: number } = {} return applyWhere(
db.selectFrom('invoice').select((eb) => eb.fn.sum('amount').as('totalAmount')),
if (req.query.year) { request.query,
const year = await knex('financialYear').first('*').where('year', req.query.year) ).executeTakeFirst()
if (!year) throw new StatusError(404, `Year ${req.query.year} not found.`)
query.financialYearId = year.id
}
if (req.query.supplier) {
query.supplierId = req.query.supplier
}
return knex('invoice AS i').first().sum('i.amount AS amount').where(query)
}, },
}) })
@ -82,71 +76,29 @@ const invoiceRoutes: FastifyPluginCallbackZod = (fastify, _, done) => {
method: 'GET', method: 'GET',
schema: { schema: {
params: z.object({ params: z.object({
id: z.number(), id: z.coerce.number(),
}), }),
}, },
handler(req) { handler(request) {
return knex('invoice AS i') return db
.first('i.*', 'fy.year', { .selectFrom('invoice as i')
files: knex .leftJoin('financialYear as fy', 'fy.id', 'i.financialYearId')
.select(knex.raw('json_agg(files)')) .selectAll('i')
.from( .select(['fy.year'])
knex .select((eb) => [
.select('id', 'filename') jsonArrayFrom(
.from('file AS f') eb
.innerJoin('filesToInvoice AS fi', 'f.id', 'fi.fileId') .selectFrom('file as f')
.where('fi.invoiceId', knex.ref('i.id')) .innerJoin('filesToInvoice as fi', 'f.id', 'fi.fileId')
.as('files'), .select(['id', 'filename'])
), .whereRef('fi.invoiceId', '=', 'i.id'),
transactions: knex ).as('files'),
.select(knex.raw('json_agg(transactions)')) jsonArrayFrom(eb.selectFrom('transaction as t').selectAll().whereRef('t.invoiceId', '=', 'i.id')).as(
.from(knex.select('*').from('transaction AS t').where('t.invoiceId', knex.ref('i.id')).as('transactions')), 'transactions',
}) ),
.leftOuterJoin('financialYear AS fy', 'i.financialYearId', 'fy.id') ])
.where('i.id', req.params.id) .where('i.id', '=', request.params.id)
}, .execute()
})
fastify.route({
url: '/by-supplier/:supplier',
method: 'GET',
schema: {
params: z.object({
supplier: z.number(),
}),
},
handler(req) {
return knex('invoice AS i')
.select('*', {
files: knex
.select(knex.raw('json_agg(files)'))
.from(
knex
.select('id', 'filename')
.from('file AS f')
.innerJoin('filesToInvoice AS fi', 'f.id', 'fi.fileId')
.where('fi.invoiceId', knex.ref('i.id'))
.as('files'),
),
})
.where('supplierId', req.params.supplier)
},
})
fastify.route({
url: '/by-year/:year',
method: 'GET',
schema: {
params: z.object({
year: z.number(),
}),
},
async handler(req) {
const year = await knex('financialYear').first('*').where('year', req.params.year)
if (!year) throw new StatusError(404, `Year ${req.params.year} not found.`)
return knex('invoice').select('*').where('financialYearId', year.id)
}, },
}) })

View File

@ -1,13 +1,14 @@
import _ from 'lodash' import _ from 'lodash'
import type { FastifyPluginCallbackZod } from 'fastify-type-provider-zod' import type { FastifyPluginCallbackZod } from 'fastify-type-provider-zod'
import knex from '../../lib/knex.ts'
const journalRoutes: FastifyPluginCallbackZod = (fastify, _, done) => { const journalRoutes: FastifyPluginCallbackZod = (fastify, _, done) => {
const { db } = fastify
fastify.route({ fastify.route({
url: '/', url: '/',
method: 'GET', method: 'GET',
handler() { handler() {
return knex('journal').select('*').orderBy('identifier') return db.selectFrom('journal').selectAll().orderBy('identifier').execute()
}, },
}) })

View File

@ -1,39 +1,38 @@
import _ from 'lodash' import _ from 'lodash'
import * as z from 'zod' import * as z from 'zod'
import type { FastifyPluginCallbackZod } from 'fastify-type-provider-zod' import type { FastifyPluginCallbackZod } from 'fastify-type-provider-zod'
import knex from '../../lib/knex.ts'
const objectRoutes: FastifyPluginCallbackZod = (fastify, _, done) => { const objectRoutes: FastifyPluginCallbackZod = (fastify, _, done) => {
const { db } = fastify
fastify.route({ fastify.route({
url: '/', url: '/',
method: 'GET', method: 'GET',
handler() { handler() {
return knex('object AS o') return db
.select('o.id', 'o.number', 'o.name', 'd.number AS dimensionNumber', 'd.name AS dimensionName') .selectFrom('object as o')
.innerJoin('dimension AS d', function () { .innerJoin('dimension as d', 'd.id', 'o.dimensionId')
this.on('o.dimensionId', '=', 'd.id') .select(['o.id', 'o.number', 'o.name', 'd.number as dimensionNumber', 'd.name as dimensionName'])
}) .execute()
}, },
}) })
fastify.route({ fastify.route({
url: '/:id', url: '/:id/transactions',
method: 'GET', method: 'GET',
schema: { schema: {
params: z.object({ params: z.object({
id: z.number(), id: z.coerce.number(),
}), }),
}, },
async handler(req) { async handler(request) {
return knex('transaction AS t') return db
.select('t.entryId', 'e.transactionDate', 't.accountNumber', 't.amount') .selectFrom('transaction as t')
.innerJoin('transactions_to_objects AS to', function () { .innerJoin('transactionsToObjects as to', 't.id', 'to.transactionId')
this.on('t.id', 'to.transactionId') .innerJoin('entry as e', 'e.id', 't.entryId')
}) .select(['t.entryId', 'e.transactionDate', 't.accountNumber', 't.amount'])
.innerJoin('entry AS e', function () { .where('to.objectId', '=', request.params.id)
this.on('e.id', '=', 't.entryId') .execute()
})
.where('to.objectId', req.params.id)
}, },
}) })

View File

@ -1,69 +1,33 @@
import _ from 'lodash' import _ from 'lodash'
import * as z from 'zod' import * as z from 'zod'
import type { FastifyPluginCallbackZod } from 'fastify-type-provider-zod' import type { FastifyPluginCallbackZod } from 'fastify-type-provider-zod'
import knex from '../../lib/knex.ts' import { sql } from 'kysely'
const resultRoutes: FastifyPluginCallbackZod = (fastify, _, done) => { const resultRoutes: FastifyPluginCallbackZod = (fastify, _, done) => {
const { db } = fastify
fastify.route({ fastify.route({
url: '/', url: '/',
method: 'GET', method: 'GET',
async handler() { async handler() {
const financialYears = await knex('financialYear').select('*').orderBy('year', 'asc') const financialYears = await db.selectFrom('financialYear').selectAll().orderBy('year', 'asc').execute()
return knex('transaction AS t') return db
.selectFrom('transaction as t')
.innerJoin('entry as e', 't.entryId', 'e.id')
.innerJoin('financialYear as fy', 'fy.id', 'e.financialYearId')
.innerJoin('account as a', 't.accountNumber', 'a.number')
.select(['t.accountNumber', 'a.description'])
.select( .select(
't.accountNumber', financialYears.map((fy) =>
'a.description', sql`SUM(CASE WHEN fy.year = ${fy.year} THEN t.amount ELSE 0 END)`.as(fy.year.toString()),
Object.fromEntries(
financialYears.map((fy) => [
fy.year,
knex.raw(`SUM(CASE WHEN fy.year = ${fy.year} THEN t.amount ELSE 0 END)`),
]),
), ),
) )
.sum('t.amount AS amount') .groupBy(['t.accountNumber', 'a.description'])
.innerJoin('entry AS e', function () {
this.on('t.entryId', '=', 'e.id')
})
.innerJoin('financialYear AS fy', 'fy.id', 'e.financialYearId')
.innerJoin('account AS a', function () {
this.on('t.accountNumber', '=', 'a.number')
})
.groupBy('t.accountNumber', 'a.description')
.where('t.accountNumber', '>=', 3000) .where('t.accountNumber', '>=', 3000)
.orderBy('t.accountNumber') .orderBy('t.accountNumber')
.execute()
}, },
// async handler() {
// const years = await knex('financialYear').select('*')
// const accounts = await knex('account').select('*')
// return Promise.all(
// years.map((year) =>
// knex('account AS a')
// .select('a.number', 'a.description')
// .sum('t.amount as amount')
// .innerJoin('transaction AS t', function () {
// this.on('t.accountNumber', '=', 'a.number')
// })
// .innerJoin('entry AS e', function () {
// this.on('t.entryId', '=', 'e.id')
// })
// .groupBy('a.number', 'a.description')
// .where('a.number', '>=', 3000)
// .where('e.financialYearId', year.id)
// .orderBy('a.number')
// .then((result) => ({
// startDate: year.startDate,
// endDate: year.endDate,
// result,
// })),
// ),
// ).then((years) => ({
// accounts,
// years,
// }))
// },
}) })
fastify.route({ fastify.route({
@ -71,27 +35,24 @@ const resultRoutes: FastifyPluginCallbackZod = (fastify, _, done) => {
method: 'GET', method: 'GET',
schema: { schema: {
params: z.object({ params: z.object({
year: z.number(), year: z.coerce.number(),
}), }),
}, },
async handler(req) { async handler(request) {
const year = await knex('financialYear').first('*').where('year', req.params.year) return (
db
if (!year) return null .selectFrom('transaction as t')
.innerJoin('entry as e', 't.entryId', 'e.id')
return knex('transaction AS t') .innerJoin('financialYear as fy', 'fy.id', 'e.financialYearId')
.select('t.accountNumber', 'a.description') .innerJoin('account as a', 't.accountNumber', 'a.number')
.sum('t.amount as amount') .select(['t.accountNumber', 'a.description', (eb) => eb.fn.sum('t.amount').as('amount')])
.innerJoin('account AS a', function () { // .sum('t.amount AS amount')
this.on('t.accountNumber', '=', 'a.number') .groupBy(['t.accountNumber', 'a.description'])
}) .where('t.accountNumber', '>=', 3000)
.innerJoin('entry AS e', function () { .where('year', '=', request.params.year)
this.on('t.entryId', '=', 'e.id') .orderBy('t.accountNumber')
}) .execute()
.groupBy('t.accountNumber', 'a.description') )
.where('t.accountNumber', '>=', 3000)
.where('e.financialYearId', year.id)
.orderBy('t.accountNumber')
}, },
}) })

View File

@ -13,20 +13,20 @@ const transactionRoutes: FastifyPluginCallbackZod = (fastify, _, done) => {
querystring: z.object({ querystring: z.object({
year: z.optional(z.coerce.number()), year: z.optional(z.coerce.number()),
accountNumber: z.optional(z.coerce.number()), accountNumber: z.optional(z.coerce.number()),
limit: z.number().default(2), limit: z.coerce.number().default(100),
sort: z.string().default('t.id'), offset: z.coerce.number().optional(),
offset: z.number().optional(), sort: z.literal(['accountNumber', 'e.transactionDate', 't.id']).default('t.id'),
}), }),
}, },
async handler(request) { async handler(request) {
const { offset, limit, sort: _sort, ...where } = request.query const { offset, limit, sort, ...where } = request.query
const baseQuery = db const baseQuery = db
.selectFrom('transaction as t') .selectFrom('transaction as t')
.innerJoin('entry as e', 't.entryId', 'e.id') .innerJoin('entry as e', 't.entryId', 'e.id')
.innerJoin('financialYear as fy', 'e.financialYearId', 'fy.id') .innerJoin('financialYear as fy', 'e.financialYearId', 'fy.id')
const result = await paginate( return paginate(
baseQuery.select([ baseQuery.select([
't.accountNumber', 't.accountNumber',
'e.transactionDate', 'e.transactionDate',
@ -38,14 +38,12 @@ const transactionRoutes: FastifyPluginCallbackZod = (fastify, _, done) => {
]), ]),
baseQuery, baseQuery,
{ {
// @ts-ignore
where, where,
limit, limit,
offset, offset,
sort,
}, },
) )
return result
}, },
}) })