link transactions to invoices
This commit is contained in:
parent
abc561258a
commit
ee25824424
@ -1,15 +0,0 @@
|
|||||||
meta {
|
|
||||||
name: Invoice
|
|
||||||
type: http
|
|
||||||
seq: 7
|
|
||||||
}
|
|
||||||
|
|
||||||
get {
|
|
||||||
url: {{base_url}}/
|
|
||||||
body: none
|
|
||||||
auth: inherit
|
|
||||||
}
|
|
||||||
|
|
||||||
settings {
|
|
||||||
encodeUrl: true
|
|
||||||
}
|
|
||||||
20
.bruno/BRF/api-invoice--id.bru
Normal file
20
.bruno/BRF/api-invoice--id.bru
Normal file
@ -0,0 +1,20 @@
|
|||||||
|
meta {
|
||||||
|
name: /api/invoice/:id
|
||||||
|
type: http
|
||||||
|
seq: 7
|
||||||
|
}
|
||||||
|
|
||||||
|
get {
|
||||||
|
url: {{base_url}}/api/invoices/:id
|
||||||
|
body: none
|
||||||
|
auth: inherit
|
||||||
|
}
|
||||||
|
|
||||||
|
params:path {
|
||||||
|
id: 234
|
||||||
|
}
|
||||||
|
|
||||||
|
settings {
|
||||||
|
encodeUrl: true
|
||||||
|
timeout: 0
|
||||||
|
}
|
||||||
@ -52,8 +52,10 @@ async function readdir(dir: string) {
|
|||||||
const pathname = path.join('uploads', 'invoices', filename)
|
const pathname = path.join('uploads', 'invoices', filename)
|
||||||
|
|
||||||
if (!existsSync(pathname)) {
|
if (!existsSync(pathname)) {
|
||||||
console.info(filename)
|
console.info('COPYING: ' + filename)
|
||||||
await fs.copyFile(path.join(dir, originalFilename), pathname)
|
await fs.copyFile(path.join(dir, originalFilename), pathname)
|
||||||
|
} else {
|
||||||
|
console.info('ALREADY EXISTS: ' + filename)
|
||||||
}
|
}
|
||||||
|
|
||||||
const file = (await trx('file').insert({ filename }).returning('id'))[0]
|
const file = (await trx('file').insert({ filename }).returning('id'))[0]
|
||||||
|
|||||||
@ -58,13 +58,14 @@ for (const row of rows.toReversed()) {
|
|||||||
const pathname = path.join('uploads', 'invoices', filename)
|
const pathname = path.join('uploads', 'invoices', filename)
|
||||||
|
|
||||||
if (!existsSync(pathname)) {
|
if (!existsSync(pathname)) {
|
||||||
console.info(filename)
|
console.info('COPYING: ' + filename)
|
||||||
await fs.copyFile(path.join('invoices', 'phm', originalFilename), pathname)
|
await fs.copyFile(path.join('invoices', 'phm', originalFilename), pathname)
|
||||||
|
} else {
|
||||||
|
console.info('ALREADY EXISTS: ' + filename)
|
||||||
}
|
}
|
||||||
|
|
||||||
const file = (await trx('file').insert({ filename }).returning('id'))[0]
|
const file = (await trx('file').insert({ filename }).returning('id'))[0]
|
||||||
|
await trx('filesToInvoice').insert({ fileId: file.id, invoiceId: invoice.id })
|
||||||
trx('filesToInvoice').insert({ fileId: file.id, invoiceId: invoice.id })
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
102
client/public/components/invoice_page.tsx
Normal file
102
client/public/components/invoice_page.tsx
Normal file
@ -0,0 +1,102 @@
|
|||||||
|
import { h } from 'preact'
|
||||||
|
import { useEffect, useState } from 'preact/hooks'
|
||||||
|
import { useRoute } from 'preact-iso'
|
||||||
|
import rek from 'rek'
|
||||||
|
import Head from './head.ts'
|
||||||
|
import { formatNumber } from '../utils/format_number.ts'
|
||||||
|
|
||||||
|
const InvoicePage = () => {
|
||||||
|
const [invoice, setInvoice] = useState(null)
|
||||||
|
const route = useRoute()
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
rek(`/api/invoices/${route.params.id}`).then((invoice) => setInvoice(invoice))
|
||||||
|
}, [])
|
||||||
|
|
||||||
|
return (
|
||||||
|
<section>
|
||||||
|
<Head>
|
||||||
|
<title> : Invoice : {invoice?.id}</title>
|
||||||
|
</Head>
|
||||||
|
|
||||||
|
<h1>Invoice</h1>
|
||||||
|
<table>
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th>what</th>
|
||||||
|
<th>who</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
{invoice && (
|
||||||
|
<tbody>
|
||||||
|
<tr>
|
||||||
|
<td>ID</td>
|
||||||
|
<td>{invoice.id}</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td>Date</td>
|
||||||
|
<td>{invoice.invoiceDate}</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td>Due Date</td>
|
||||||
|
<td>{invoice.dueDate}</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td>Fisken</td>
|
||||||
|
<td>{invoice.fiskenNumber}</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td>PHM</td>
|
||||||
|
<td>{invoice.phmNumber}</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td>Amount</td>
|
||||||
|
<td>{invoice.amount}</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td>Files</td>
|
||||||
|
<td>
|
||||||
|
{invoice.files?.map((file) => (
|
||||||
|
<a href={`/uploads/invoices/${file.filename}`} target='blank'>
|
||||||
|
{file.filename}
|
||||||
|
</a>
|
||||||
|
))}
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td>ID</td>
|
||||||
|
<td>{invoice.id}</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td>ID</td>
|
||||||
|
<td>{invoice.id}</td>
|
||||||
|
</tr>
|
||||||
|
</tbody>
|
||||||
|
)}
|
||||||
|
</table>
|
||||||
|
|
||||||
|
<table>
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th>Account</th>
|
||||||
|
<th>Debit</th>
|
||||||
|
<th>Credit</th>
|
||||||
|
<th>Description</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
{invoice?.transactions?.map((transaction) => (
|
||||||
|
<tr>
|
||||||
|
<td>{transaction.account_number}</td>
|
||||||
|
<td>{transaction.amount >= 0 ? formatNumber(transaction.amount) : null}</td>
|
||||||
|
<td>{transaction.amount < 0 ? formatNumber(Math.abs(transaction.amount)) : null}</td>
|
||||||
|
<td>{transaction.description}</td>
|
||||||
|
</tr>
|
||||||
|
))}
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</section>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export default InvoicePage
|
||||||
@ -42,7 +42,9 @@ const InvoicesPage = () => {
|
|||||||
<tbody>
|
<tbody>
|
||||||
{invoices.map((invoice) => (
|
{invoices.map((invoice) => (
|
||||||
<tr>
|
<tr>
|
||||||
<td>{invoice.id}</td>
|
<td>
|
||||||
|
<a href={`/invoices/${invoice.id}`}>{invoice.id}</a>
|
||||||
|
</td>
|
||||||
<td>{invoice.fiskenNumber}</td>
|
<td>{invoice.fiskenNumber}</td>
|
||||||
<td>{invoice.phmNumber}</td>
|
<td>{invoice.phmNumber}</td>
|
||||||
<td>{format(invoice.invoiceDate)}</td>
|
<td>{format(invoice.invoiceDate)}</td>
|
||||||
@ -51,7 +53,9 @@ const InvoicesPage = () => {
|
|||||||
<td>{invoice.amount}</td>
|
<td>{invoice.amount}</td>
|
||||||
<td>
|
<td>
|
||||||
{invoice.files?.map((file) => (
|
{invoice.files?.map((file) => (
|
||||||
<a href={`/uploads/invoices/${file.filename}`}>{file.filename}</a>
|
<a href={`/uploads/invoices/${file.filename}`} target='_blank'>
|
||||||
|
{file.filename}
|
||||||
|
</a>
|
||||||
))}
|
))}
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
|
|||||||
@ -1,4 +1,5 @@
|
|||||||
import Accounts from './components/accounts_page.tsx'
|
import Accounts from './components/accounts_page.tsx'
|
||||||
|
import Invoice from './components/invoice_page.tsx'
|
||||||
import Invoices from './components/invoices_page.tsx'
|
import Invoices from './components/invoices_page.tsx'
|
||||||
import InvoicesBySupplier from './components/invoices_by_supplier_page.tsx'
|
import InvoicesBySupplier from './components/invoices_by_supplier_page.tsx'
|
||||||
import Objects from './components/objects_page.tsx'
|
import Objects from './components/objects_page.tsx'
|
||||||
@ -30,12 +31,19 @@ export default [
|
|||||||
title: 'Invoices',
|
title: 'Invoices',
|
||||||
component: Invoices,
|
component: Invoices,
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
path: '/invoices/:id',
|
||||||
|
name: 'invoice',
|
||||||
|
title: 'Invoice',
|
||||||
|
component: Invoice,
|
||||||
|
nav: false,
|
||||||
|
},
|
||||||
{
|
{
|
||||||
path: '/invoices/by-supplier/:supplier',
|
path: '/invoices/by-supplier/:supplier',
|
||||||
name: 'invoices',
|
name: 'invoices',
|
||||||
title: 'Invoices',
|
title: 'Invoices',
|
||||||
nav: false,
|
|
||||||
component: InvoicesBySupplier,
|
component: InvoicesBySupplier,
|
||||||
|
nav: false,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
path: '/results',
|
path: '/results',
|
||||||
|
|||||||
16
client/public/utils/format_number.ts
Normal file
16
client/public/utils/format_number.ts
Normal file
@ -0,0 +1,16 @@
|
|||||||
|
const priceFormatter = new Intl.NumberFormat('sv-SE', {
|
||||||
|
style: 'currency',
|
||||||
|
currency: 'SEK',
|
||||||
|
minimumFractionDigits: 0,
|
||||||
|
maximumFractionDigits: 0,
|
||||||
|
})
|
||||||
|
|
||||||
|
export const formatPrice = (price) => priceFormatter.format(price)
|
||||||
|
|
||||||
|
const numberFormatter = new Intl.NumberFormat('sv-SE', {
|
||||||
|
style: 'decimal',
|
||||||
|
minimumFractionDigits: 0,
|
||||||
|
maximumFractionDigits: 0,
|
||||||
|
})
|
||||||
|
|
||||||
|
export const formatNumber = (nbr) => numberFormatter.format(nbr)
|
||||||
@ -2,7 +2,7 @@
|
|||||||
-- PostgreSQL database dump
|
-- PostgreSQL database dump
|
||||||
--
|
--
|
||||||
|
|
||||||
\restrict x1LNwo1MrXgJU7KVXELaKlpEPpIZLjRGuaweMIify4ofZcwqTGzXVX5DkRI11Hx
|
\restrict LKaLyA1IGSZwb31KR2e5GrFZPB7KuPkTsiIxHVwV0aqqukXTaBdKHj8rPqgoMsg
|
||||||
|
|
||||||
-- Dumped from database version 18.1
|
-- Dumped from database version 18.1
|
||||||
-- Dumped by pg_dump version 18.1
|
-- Dumped by pg_dump version 18.1
|
||||||
@ -239,7 +239,7 @@ CREATE TABLE public.invoice (
|
|||||||
fisken_number integer,
|
fisken_number integer,
|
||||||
phm_number integer,
|
phm_number integer,
|
||||||
invoice_number text,
|
invoice_number text,
|
||||||
invoice_date date CONSTRAINT invoice_date_not_null NOT NULL,
|
invoice_date date,
|
||||||
due_date date,
|
due_date date,
|
||||||
ocr text,
|
ocr text,
|
||||||
amount numeric(12,2)
|
amount numeric(12,2)
|
||||||
@ -751,5 +751,5 @@ ALTER TABLE ONLY public.transactions_to_objects
|
|||||||
-- PostgreSQL database dump complete
|
-- PostgreSQL database dump complete
|
||||||
--
|
--
|
||||||
|
|
||||||
\unrestrict x1LNwo1MrXgJU7KVXELaKlpEPpIZLjRGuaweMIify4ofZcwqTGzXVX5DkRI11Hx
|
\unrestrict LKaLyA1IGSZwb31KR2e5GrFZPB7KuPkTsiIxHVwV0aqqukXTaBdKHj8rPqgoMsg
|
||||||
|
|
||||||
|
|||||||
@ -41,8 +41,9 @@ export default async function parseStream(stream: ReadableStream, decoder: Decod
|
|||||||
const journals = new Map()
|
const journals = new Map()
|
||||||
|
|
||||||
let currentEntryId: number
|
let currentEntryId: number
|
||||||
const details: Record<string, string> = {}
|
let currentInvoiceId: number
|
||||||
let currentYear = null
|
let currentYear = null
|
||||||
|
const details: Record<string, string> = {}
|
||||||
|
|
||||||
const trx = await knex.transaction()
|
const trx = await knex.transaction()
|
||||||
|
|
||||||
@ -191,12 +192,43 @@ export default async function parseStream(stream: ReadableStream, decoder: Decod
|
|||||||
}
|
}
|
||||||
case '#TRANS': {
|
case '#TRANS': {
|
||||||
const { objectList, ...transaction } = parseTrans(line)
|
const { objectList, ...transaction } = parseTrans(line)
|
||||||
|
// Faktura 6364710056 172-2 - SBAB Bank AB
|
||||||
|
// Faktura 2312 172-6 - Bredablick Frvaltning AB
|
||||||
|
const rFisken = /Faktura.*172-(\d*)\s+-\s+(.+)/
|
||||||
|
|
||||||
|
const result = transaction.description?.match(rFisken)
|
||||||
|
|
||||||
|
let invoiceId: number
|
||||||
|
|
||||||
|
if (result) {
|
||||||
|
const [, fiskenNumber, supplierName] = result
|
||||||
|
// invoiceId = (await trx('invoice').first('id').where({ fiskenNumber})).id
|
||||||
|
invoiceId = (await trx('invoice').first('id').where({ fiskenNumber }))?.id
|
||||||
|
|
||||||
|
if (!invoiceId) {
|
||||||
|
let supplierId = (await trx('supplier').first('id').where('name', supplierName))?.id
|
||||||
|
|
||||||
|
if (!supplierId) {
|
||||||
|
supplierId = (await trx('supplier').insert({ name: supplierName, supplierTypeId: 1 }).returning('id'))[0]
|
||||||
|
?.id
|
||||||
|
}
|
||||||
|
|
||||||
|
invoiceId = (
|
||||||
|
await trx('invoice').insert({ financialYearId: currentYear.id, fiskenNumber, supplierId }).returning('id')
|
||||||
|
)[0]?.id
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (invoiceId && currentInvoiceId) {
|
||||||
|
throw new Error('invoiceId and currentInvoiceId')
|
||||||
|
}
|
||||||
|
|
||||||
const transactionId = (
|
const transactionId = (
|
||||||
await trx('transaction')
|
await trx('transaction')
|
||||||
.insert({
|
.insert({
|
||||||
entryId: currentEntryId,
|
entryId: currentEntryId,
|
||||||
...transaction,
|
...transaction,
|
||||||
|
invoiceId: invoiceId || currentInvoiceId,
|
||||||
})
|
})
|
||||||
.returning('id')
|
.returning('id')
|
||||||
)[0].id
|
)[0].id
|
||||||
@ -261,6 +293,26 @@ export default async function parseStream(stream: ReadableStream, decoder: Decod
|
|||||||
|
|
||||||
let journalId = await getJournalId(journal)
|
let journalId = await getJournalId(journal)
|
||||||
|
|
||||||
|
// Fisken - Fakturajournal 1248
|
||||||
|
// Fisken - Utbetalningsjournal 1056
|
||||||
|
|
||||||
|
// Levfakt EURO FINANS AB (5) / Levfakt Per Holmberg (6)
|
||||||
|
// Levbet EURO FINANS AB (5) / Levbet Per Holmberg (6)
|
||||||
|
const rPhm = /^Lev(?:fakt|bet)\s+[^(]*\((\d+)\)$/
|
||||||
|
|
||||||
|
const result = rest.description.match(rPhm)
|
||||||
|
|
||||||
|
if (result) {
|
||||||
|
currentInvoiceId = (
|
||||||
|
await trx('invoice')
|
||||||
|
.select('invoice.*', 'supplier.name')
|
||||||
|
.innerJoin('supplier', 'invoice.supplierId', 'supplier.id')
|
||||||
|
.where('phmNumber', result[1])
|
||||||
|
)[0]?.id
|
||||||
|
} else {
|
||||||
|
currentInvoiceId = null
|
||||||
|
}
|
||||||
|
|
||||||
currentEntryId = (
|
currentEntryId = (
|
||||||
await trx('entry')
|
await trx('entry')
|
||||||
.insert({
|
.insert({
|
||||||
|
|||||||
@ -4,6 +4,7 @@ import knex from '../lib/knex.ts'
|
|||||||
import StatusError from '../lib/status_error.ts'
|
import StatusError from '../lib/status_error.ts'
|
||||||
|
|
||||||
import accounts from './api/accounts.ts'
|
import accounts from './api/accounts.ts'
|
||||||
|
import invoices from './api/invoices.ts'
|
||||||
|
|
||||||
export const FinancialYear = Type.Object({
|
export const FinancialYear = Type.Object({
|
||||||
year: Type.Number(),
|
year: Type.Number(),
|
||||||
@ -15,6 +16,7 @@ export type FinancialYearType = Static<typeof FinancialYear>
|
|||||||
|
|
||||||
const apiRoutes: FastifyPluginCallbackTypebox = (fastify, _, done) => {
|
const apiRoutes: FastifyPluginCallbackTypebox = (fastify, _, done) => {
|
||||||
fastify.register(accounts, { prefix: '/accounts' })
|
fastify.register(accounts, { prefix: '/accounts' })
|
||||||
|
fastify.register(invoices, { prefix: '/invoices' })
|
||||||
|
|
||||||
fastify.route({
|
fastify.route({
|
||||||
url: '/financial-years',
|
url: '/financial-years',
|
||||||
@ -24,102 +26,6 @@ const apiRoutes: FastifyPluginCallbackTypebox = (fastify, _, done) => {
|
|||||||
},
|
},
|
||||||
})
|
})
|
||||||
|
|
||||||
fastify.route({
|
|
||||||
url: '/invoices',
|
|
||||||
method: 'GET',
|
|
||||||
schema: {
|
|
||||||
querystring: Type.Object({
|
|
||||||
year: Type.Optional(Type.Number()),
|
|
||||||
supplier: Type.Optional(Type.Number()),
|
|
||||||
}),
|
|
||||||
},
|
|
||||||
async handler(req) {
|
|
||||||
let query: { financialYearId?: number; supplierId?: 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.supplier) {
|
|
||||||
query.supplierId = req.query.supplier
|
|
||||||
}
|
|
||||||
|
|
||||||
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(query)
|
|
||||||
},
|
|
||||||
})
|
|
||||||
|
|
||||||
fastify.route({
|
|
||||||
url: '/invoices/:id',
|
|
||||||
method: 'GET',
|
|
||||||
schema: {
|
|
||||||
params: Type.Object({
|
|
||||||
id: Type.Number(),
|
|
||||||
}),
|
|
||||||
},
|
|
||||||
handler(req) {
|
|
||||||
return knex('invoice').first('*').where('id', req.params.id)
|
|
||||||
},
|
|
||||||
})
|
|
||||||
|
|
||||||
fastify.route({
|
|
||||||
url: '/invoices/by-supplier/:supplier',
|
|
||||||
method: 'GET',
|
|
||||||
schema: {
|
|
||||||
params: Type.Object({
|
|
||||||
supplier: Type.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: '/invoices/by-year/:year',
|
|
||||||
method: 'GET',
|
|
||||||
schema: {
|
|
||||||
params: Type.Object({
|
|
||||||
year: Type.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)
|
|
||||||
},
|
|
||||||
})
|
|
||||||
|
|
||||||
fastify.route({
|
fastify.route({
|
||||||
url: '/objects',
|
url: '/objects',
|
||||||
method: 'GET',
|
method: 'GET',
|
||||||
|
|||||||
127
server/routes/api/invoices.ts
Normal file
127
server/routes/api/invoices.ts
Normal file
@ -0,0 +1,127 @@
|
|||||||
|
import _ from 'lodash'
|
||||||
|
import { Type, type FastifyPluginCallbackTypebox } from '@fastify/type-provider-typebox'
|
||||||
|
import knex from '../../lib/knex.ts'
|
||||||
|
import StatusError from '../../lib/status_error.ts'
|
||||||
|
|
||||||
|
const apiRoutes: FastifyPluginCallbackTypebox = (fastify, _, done) => {
|
||||||
|
fastify.route({
|
||||||
|
url: '/',
|
||||||
|
method: 'GET',
|
||||||
|
schema: {
|
||||||
|
querystring: Type.Object({
|
||||||
|
year: Type.Optional(Type.Number()),
|
||||||
|
supplier: Type.Optional(Type.Number()),
|
||||||
|
}),
|
||||||
|
},
|
||||||
|
async handler(req) {
|
||||||
|
let query: { financialYearId?: number; supplierId?: 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.supplier) {
|
||||||
|
query.supplierId = req.query.supplier
|
||||||
|
}
|
||||||
|
|
||||||
|
return knex('invoice AS i')
|
||||||
|
.select('i.*', 'fy.year', {
|
||||||
|
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'),
|
||||||
|
),
|
||||||
|
transactions: knex
|
||||||
|
.select(knex.raw('json_agg(transactions)'))
|
||||||
|
.from(knex.select('*').from('transaction AS t').where('t.invoice_id', knex.ref('i.id')).as('transactions')),
|
||||||
|
})
|
||||||
|
.leftOuterJoin('financialYear AS fy', 'i.financialYearId', 'fy.id')
|
||||||
|
.where(query)
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
|
fastify.route({
|
||||||
|
url: '/:id',
|
||||||
|
method: 'GET',
|
||||||
|
schema: {
|
||||||
|
params: Type.Object({
|
||||||
|
id: Type.Number(),
|
||||||
|
}),
|
||||||
|
},
|
||||||
|
handler(req) {
|
||||||
|
return knex('invoice AS i')
|
||||||
|
.first('i.*', 'fy.year', {
|
||||||
|
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'),
|
||||||
|
),
|
||||||
|
transactions: knex
|
||||||
|
.select(knex.raw('json_agg(transactions)'))
|
||||||
|
.from(knex.select('*').from('transaction AS t').where('t.invoice_id', knex.ref('i.id')).as('transactions')),
|
||||||
|
})
|
||||||
|
.leftOuterJoin('financialYear AS fy', 'i.financialYearId', 'fy.id')
|
||||||
|
.where('i.id', req.params.id)
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
|
fastify.route({
|
||||||
|
url: '/by-supplier/:supplier',
|
||||||
|
method: 'GET',
|
||||||
|
schema: {
|
||||||
|
params: Type.Object({
|
||||||
|
supplier: Type.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: Type.Object({
|
||||||
|
year: Type.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)
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
|
done()
|
||||||
|
}
|
||||||
|
|
||||||
|
export default apiRoutes
|
||||||
Loading…
Reference in New Issue
Block a user