invoices
This commit is contained in:
parent
347bce3fd6
commit
ac61674b69
15
.bruno/BRF/Invoice.bru
Normal file
15
.bruno/BRF/Invoice.bru
Normal file
@ -0,0 +1,15 @@
|
||||
meta {
|
||||
name: Invoice
|
||||
type: http
|
||||
seq: 7
|
||||
}
|
||||
|
||||
get {
|
||||
url: {{base_url}}/
|
||||
body: none
|
||||
auth: inherit
|
||||
}
|
||||
|
||||
settings {
|
||||
encodeUrl: true
|
||||
}
|
||||
15
.bruno/BRF/Invoices.bru
Normal file
15
.bruno/BRF/Invoices.bru
Normal file
@ -0,0 +1,15 @@
|
||||
meta {
|
||||
name: Invoices
|
||||
type: http
|
||||
seq: 6
|
||||
}
|
||||
|
||||
get {
|
||||
url: {{base_url}}/api/invoices
|
||||
body: none
|
||||
auth: inherit
|
||||
}
|
||||
|
||||
settings {
|
||||
encodeUrl: true
|
||||
}
|
||||
15
.bruno/BRF/api-suppliers.bru
Normal file
15
.bruno/BRF/api-suppliers.bru
Normal file
@ -0,0 +1,15 @@
|
||||
meta {
|
||||
name: /api/suppliers
|
||||
type: http
|
||||
seq: 8
|
||||
}
|
||||
|
||||
get {
|
||||
url: {{base_url}}/
|
||||
body: none
|
||||
auth: inherit
|
||||
}
|
||||
|
||||
settings {
|
||||
encodeUrl: true
|
||||
}
|
||||
2
.gitignore
vendored
2
.gitignore
vendored
@ -1,5 +1,7 @@
|
||||
/dump
|
||||
/sie
|
||||
/invoices
|
||||
/uploads
|
||||
|
||||
# Logs
|
||||
logs
|
||||
|
||||
64
bin/add_fisken_invoices.ts
Normal file
64
bin/add_fisken_invoices.ts
Normal file
@ -0,0 +1,64 @@
|
||||
import fs from 'node:fs/promises'
|
||||
import { existsSync } from 'node:fs'
|
||||
import path from 'node:path'
|
||||
import knex from '../server/lib/knex.ts'
|
||||
|
||||
const dirs = process.argv.slice(2)
|
||||
|
||||
// 172-972, 2020-01-08, Great Security Sverige AB.pdf'
|
||||
const rFileName = /^172-(\d+),\s(\d{4,4}-\d{2,2}-\d{2,2}), (.*)\.pdf$/
|
||||
|
||||
for await (const dir of dirs) {
|
||||
await readdir(dir)
|
||||
}
|
||||
|
||||
knex.destroy()
|
||||
|
||||
async function readdir(dir: string) {
|
||||
const files = (await fs.readdir(dir)).toSorted((a: string, b: string) => {
|
||||
const [, aNum] = a.match(rFileName)
|
||||
const [, bNum] = b.match(rFileName)
|
||||
|
||||
if (parseInt(aNum) > parseInt(bNum)) {
|
||||
return 1
|
||||
} else {
|
||||
return -1
|
||||
}
|
||||
})
|
||||
|
||||
const trx = await knex.transaction()
|
||||
|
||||
for await (const originalFilename of files) {
|
||||
const result = originalFilename.match(rFileName)
|
||||
|
||||
if (!result) {
|
||||
throw new Error(originalFilename)
|
||||
}
|
||||
|
||||
const [, fiskenNumber, invoiceDate, supplierName] = result
|
||||
|
||||
let supplier = await trx('supplier').first('*').where('name', supplierName)
|
||||
|
||||
if (!supplier) {
|
||||
supplier = (await trx('supplier').insert({ name: supplierName, supplierTypeId: 1 }).returning('*'))[0]
|
||||
}
|
||||
|
||||
const invoice = (
|
||||
await trx('invoice').insert({ fiskenNumber, invoiceDate, supplierId: supplier.id }).returning('*')
|
||||
)[0]
|
||||
|
||||
const ext = path.extname(originalFilename)
|
||||
const filename = `${invoiceDate}_fisken_${fiskenNumber}_${supplierName.split(/[\s/]/).join('_').split(/[']/).join('')}${ext}`
|
||||
const pathname = path.join('uploads', 'invoices', filename)
|
||||
|
||||
if (!existsSync(pathname)) {
|
||||
console.info(filename)
|
||||
await fs.copyFile(path.join(dir, originalFilename), pathname)
|
||||
}
|
||||
|
||||
const file = (await trx('file').insert({ filename }).returning('id'))[0]
|
||||
await trx('filesToInvoice').insert({ fileId: file.id, invoiceId: invoice.id })
|
||||
}
|
||||
|
||||
await trx.commit()
|
||||
}
|
||||
73
bin/add_phm_invoices.ts
Normal file
73
bin/add_phm_invoices.ts
Normal file
@ -0,0 +1,73 @@
|
||||
import fs from 'node:fs/promises'
|
||||
import { existsSync } from 'node:fs'
|
||||
import path from 'node:path'
|
||||
import knex from '../server/lib/knex.ts'
|
||||
import split from '../server/lib/split.ts'
|
||||
import { csvParseRows } from 'd3-dsv'
|
||||
|
||||
const csvFilename = process.argv[2]
|
||||
const csvString = await fs.readFile(csvFilename, { encoding: 'utf8' })
|
||||
const rows = csvParseRows(csvString)
|
||||
|
||||
const trx = await knex.transaction()
|
||||
|
||||
for (const row of rows.toReversed()) {
|
||||
const [
|
||||
phmNumber,
|
||||
type,
|
||||
supplierId,
|
||||
supplierName,
|
||||
invoiceDate,
|
||||
dueDate,
|
||||
invoiceNumber,
|
||||
ocr,
|
||||
amount,
|
||||
vat,
|
||||
balance,
|
||||
currency,
|
||||
status,
|
||||
filesString,
|
||||
] = row
|
||||
|
||||
let supplier = await trx('supplier').first('*').where('name', supplierName)
|
||||
|
||||
if (!supplier) {
|
||||
supplier = (await trx('supplier').insert({ name: supplierName, supplierTypeId: 1 }).returning('*'))[0]
|
||||
}
|
||||
|
||||
const invoice = (
|
||||
await trx('invoice')
|
||||
.insert({
|
||||
invoiceDate,
|
||||
supplierId: supplier.id,
|
||||
dueDate,
|
||||
ocr,
|
||||
invoiceNumber,
|
||||
phmNumber,
|
||||
amount,
|
||||
})
|
||||
.returning('id')
|
||||
)[0]
|
||||
|
||||
const filenames = filesString.split(',').map((filename) => filename.trim())
|
||||
|
||||
// TODO handle names if multiple files with same extension (otherwise they will have the same name)
|
||||
for (const originalFilename of filenames) {
|
||||
const ext = path.extname(originalFilename)
|
||||
const filename = `${invoiceDate}_phm_${phmNumber}_${supplierName.split(/[\s/]/).join('_').split(/[']/).join('')}${ext}`
|
||||
const pathname = path.join('uploads', 'invoices', filename)
|
||||
|
||||
if (!existsSync(pathname)) {
|
||||
console.info(filename)
|
||||
await fs.copyFile(path.join('invoices', 'phm', originalFilename), pathname)
|
||||
}
|
||||
|
||||
const file = (await trx('file').insert({ filename }).returning('id'))[0]
|
||||
|
||||
trx('filesToInvoice').insert({ fileId: file.id, invoiceId: invoice.id })
|
||||
}
|
||||
}
|
||||
|
||||
trx.commit()
|
||||
|
||||
knex.destroy()
|
||||
@ -8,6 +8,8 @@ for await (const file of process.argv.slice(2)) {
|
||||
console.log(`- parsing file: ${file}`)
|
||||
|
||||
await parseStream(fh.readableWebStream())
|
||||
|
||||
await fh.close()
|
||||
}
|
||||
|
||||
knex.destroy()
|
||||
|
||||
@ -1,5 +1,5 @@
|
||||
import { h } from 'preact'
|
||||
import { Router } from 'preact-router'
|
||||
import { LocationProvider, Route, Router } from 'preact-iso'
|
||||
import Head from './head.ts'
|
||||
import Footer from './footer.tsx'
|
||||
import Header from './header.tsx'
|
||||
@ -10,27 +10,28 @@ import s from './app.module.scss'
|
||||
|
||||
export default function App({ error, url, title }) {
|
||||
return (
|
||||
<div id='app' className={s.base}>
|
||||
<Head>
|
||||
<title>{title || 'Untitled'}</title>
|
||||
</Head>
|
||||
<LocationProvider>
|
||||
<div id='app' className={s.base}>
|
||||
<Head>
|
||||
<title>{title || 'Untitled'}</title>
|
||||
</Head>
|
||||
|
||||
<Header routes={routes} />
|
||||
<Header routes={routes} />
|
||||
|
||||
<main className={s.main}>
|
||||
{error ? (
|
||||
<ErrorPage error={error} />
|
||||
) : (
|
||||
<Router url={url}>
|
||||
{routes.map((route) => (
|
||||
// @ts-ignore
|
||||
<route.component key={route.path} path={route.path} route={route} />
|
||||
))}
|
||||
</Router>
|
||||
)}
|
||||
</main>
|
||||
<main className={s.main}>
|
||||
{error ? (
|
||||
<ErrorPage error={error} />
|
||||
) : (
|
||||
<Router>
|
||||
{routes.map((route) => (
|
||||
<Route key={route.path} route={route} path={route.path} component={route.component} />
|
||||
))}
|
||||
</Router>
|
||||
)}
|
||||
</main>
|
||||
|
||||
<Footer />
|
||||
</div>
|
||||
<Footer />
|
||||
</div>
|
||||
</LocationProvider>
|
||||
)
|
||||
}
|
||||
|
||||
@ -5,11 +5,13 @@ const Header = ({ routes }) => (
|
||||
<h1>BRF Tegeltrasten</h1>
|
||||
<nav>
|
||||
<ul>
|
||||
{routes.map((route) => (
|
||||
<li key={route.path}>
|
||||
<a href={route.path}>{route.title}</a>
|
||||
</li>
|
||||
))}
|
||||
{routes.map((route) =>
|
||||
route.nav !== false ? (
|
||||
<li key={route.path}>
|
||||
<a href={route.path}>{route.title}</a>
|
||||
</li>
|
||||
) : null,
|
||||
)}
|
||||
</ul>
|
||||
</nav>
|
||||
</header>
|
||||
|
||||
31
client/public/components/invoice.tsx
Normal file
31
client/public/components/invoice.tsx
Normal file
@ -0,0 +1,31 @@
|
||||
import { h, type FunctionalComponent } from 'preact'
|
||||
import { useEffect, useState } from 'preact/hooks'
|
||||
import rek from 'rek'
|
||||
|
||||
interface Props {
|
||||
year: number
|
||||
}
|
||||
|
||||
const Invoices: FunctionalComponent<Props> = ({ year }) => {
|
||||
const [invoices, setInvoices] = useState([])
|
||||
|
||||
useEffect(() => {
|
||||
rek(`/api/invoices/by-year/${year}`).then(setInvoices)
|
||||
}, [year])
|
||||
|
||||
return (
|
||||
<table>
|
||||
<tbody>
|
||||
{invoices.map((invoice) => (
|
||||
<tr>
|
||||
<td>{invoice.accountNumber}</td>
|
||||
<td>{invoice.description}</td>
|
||||
<td>{invoice.amount}</td>
|
||||
</tr>
|
||||
))}
|
||||
</tbody>
|
||||
</table>
|
||||
)
|
||||
}
|
||||
|
||||
export default Invoices
|
||||
0
client/public/components/invoices.tsx
Normal file
0
client/public/components/invoices.tsx
Normal file
65
client/public/components/invoices_by_supplier_page.tsx
Normal file
65
client/public/components/invoices_by_supplier_page.tsx
Normal file
@ -0,0 +1,65 @@
|
||||
import { h } from 'preact'
|
||||
import Format from 'easy-tz/format'
|
||||
import { useEffect, useState } from 'preact/hooks'
|
||||
import { useRoute } from 'preact-iso'
|
||||
import rek from 'rek'
|
||||
import Head from './head.ts'
|
||||
|
||||
const format = Format.bind(null, null, 'YYYY.MM.DD')
|
||||
|
||||
const InvoicesPage = () => {
|
||||
const [supplier, setSupplier] = useState(null)
|
||||
const [invoices, setInvoices] = useState([])
|
||||
|
||||
const route = useRoute()
|
||||
|
||||
useEffect(() => {
|
||||
rek(`/api/suppliers/${route.params.supplier}`).then((supplier) => setSupplier(supplier))
|
||||
rek(`/api/invoices?supplier=${route.params.supplier}`).then((invoices) => setInvoices(invoices))
|
||||
}, [route.params.supplier])
|
||||
|
||||
return (
|
||||
<section>
|
||||
<Head>
|
||||
<title> : Invoices : {supplier?.name}</title>
|
||||
</Head>
|
||||
|
||||
<h1>Invoices for {supplier?.name}</h1>
|
||||
|
||||
<table>
|
||||
<thead>
|
||||
<tr>
|
||||
<th>ID</th>
|
||||
<th>Fisken</th>
|
||||
<th>PHM</th>
|
||||
<th>Invoice Date</th>
|
||||
<th>Due Date</th>
|
||||
<th>Number</th>
|
||||
<th>Amount</th>
|
||||
<th>Files</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{invoices.map((invoice) => (
|
||||
<tr>
|
||||
<td>{invoice.id}</td>
|
||||
<td>{invoice.fiskenNumber}</td>
|
||||
<td>{invoice.phmNumber}</td>
|
||||
<td>{format(invoice.invoiceDate)}</td>
|
||||
<td>{invoice.dueDate && format(invoice.dueDate)}</td>
|
||||
<td>{invoice.invoiceNumber}</td>
|
||||
<td>{invoice.amount}</td>
|
||||
<td>
|
||||
{invoice.files?.map((file) => (
|
||||
<a href={`/uploads/invoices/${file.filename}`}>{file.filename}</a>
|
||||
))}
|
||||
</td>
|
||||
</tr>
|
||||
))}
|
||||
</tbody>
|
||||
</table>
|
||||
</section>
|
||||
)
|
||||
}
|
||||
|
||||
export default InvoicesPage
|
||||
39
client/public/components/invoices_page.tsx
Normal file
39
client/public/components/invoices_page.tsx
Normal file
@ -0,0 +1,39 @@
|
||||
import { h } from 'preact'
|
||||
import { useEffect, useState } from 'preact/hooks'
|
||||
import rek from 'rek'
|
||||
import Head from './head.ts'
|
||||
import Invoice from './invoice.tsx'
|
||||
|
||||
const InvoicesPage = () => {
|
||||
const [suppliers, setSuppliers] = useState([])
|
||||
const [financialYears, setFinancialYears] = useState([])
|
||||
const [currentYear, setCurrentYear] = useState<number>(null)
|
||||
|
||||
useEffect(() => {
|
||||
rek('/api/suppliers').then((suppliers) => setSuppliers(suppliers))
|
||||
// rek('/api/financial-years').then((financialYears) => {
|
||||
// setFinancialYears(financialYears)
|
||||
// setCurrentYear(financialYears[financialYears.length - 1].year)
|
||||
// })
|
||||
}, [])
|
||||
console.log(suppliers)
|
||||
|
||||
return (
|
||||
<section>
|
||||
<Head>
|
||||
<title> : Invoices</title>
|
||||
</Head>
|
||||
|
||||
<h1>Invoices</h1>
|
||||
<ul>
|
||||
{suppliers?.map((supplier) => (
|
||||
<li>
|
||||
<a href={`/invoices/by-supplier/${supplier.id}`}>{supplier.name}</a>
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
</section>
|
||||
)
|
||||
}
|
||||
|
||||
export default InvoicesPage
|
||||
@ -1,4 +1,6 @@
|
||||
import Start from './components/start_page.tsx'
|
||||
import Invoices from './components/invoices_page.tsx'
|
||||
import InvoicesBySupplier from './components/invoices_by_supplier_page.tsx'
|
||||
import Objects from './components/objects_page.tsx'
|
||||
import Results from './components/results_page.tsx'
|
||||
|
||||
@ -15,6 +17,19 @@ export default [
|
||||
title: 'Objects',
|
||||
component: Objects,
|
||||
},
|
||||
{
|
||||
path: '/invoices',
|
||||
name: 'invoices',
|
||||
title: 'Invoices',
|
||||
component: Invoices,
|
||||
},
|
||||
{
|
||||
path: '/invoices/by-supplier/:supplier',
|
||||
name: 'invoices',
|
||||
title: 'Invoices',
|
||||
nav: false,
|
||||
component: InvoicesBySupplier,
|
||||
},
|
||||
{
|
||||
path: '/results',
|
||||
name: 'results',
|
||||
|
||||
@ -17,6 +17,7 @@ services:
|
||||
volumes:
|
||||
- ./client:/home/node/brf_books/client
|
||||
- ./server:/home/node/brf_books/server
|
||||
- ./uploads:/home/node/brf_books/uploads
|
||||
depends_on:
|
||||
- postgres
|
||||
- redis
|
||||
|
||||
@ -2,7 +2,7 @@
|
||||
-- PostgreSQL database dump
|
||||
--
|
||||
|
||||
\restrict H2xBPQq6I6IQHZkAgiuKoY9lao4r4KAPtiyNvDE9oc0DN75cJ1gNbkoGFqutWDp
|
||||
\restrict x1LNwo1MrXgJU7KVXELaKlpEPpIZLjRGuaweMIify4ofZcwqTGzXVX5DkRI11Hx
|
||||
|
||||
-- Dumped from database version 18.1
|
||||
-- Dumped by pg_dump version 18.1
|
||||
@ -156,6 +156,46 @@ CREATE SEQUENCE public.entry_id_seq
|
||||
ALTER SEQUENCE public.entry_id_seq OWNED BY public.entry.id;
|
||||
|
||||
|
||||
--
|
||||
-- Name: file; Type: TABLE; Schema: public; Owner: -
|
||||
--
|
||||
|
||||
CREATE TABLE public.file (
|
||||
id integer NOT NULL,
|
||||
filename text CONSTRAINT file_file_not_null NOT NULL
|
||||
);
|
||||
|
||||
|
||||
--
|
||||
-- Name: file_id_seq; Type: SEQUENCE; Schema: public; Owner: -
|
||||
--
|
||||
|
||||
CREATE SEQUENCE public.file_id_seq
|
||||
AS integer
|
||||
START WITH 1
|
||||
INCREMENT BY 1
|
||||
NO MINVALUE
|
||||
NO MAXVALUE
|
||||
CACHE 1;
|
||||
|
||||
|
||||
--
|
||||
-- Name: file_id_seq; Type: SEQUENCE OWNED BY; Schema: public; Owner: -
|
||||
--
|
||||
|
||||
ALTER SEQUENCE public.file_id_seq OWNED BY public.file.id;
|
||||
|
||||
|
||||
--
|
||||
-- Name: files_to_invoice; Type: TABLE; Schema: public; Owner: -
|
||||
--
|
||||
|
||||
CREATE TABLE public.files_to_invoice (
|
||||
invoice_id integer NOT NULL,
|
||||
file_id integer NOT NULL
|
||||
);
|
||||
|
||||
|
||||
--
|
||||
-- Name: financial_year; Type: TABLE; Schema: public; Owner: -
|
||||
--
|
||||
@ -188,6 +228,44 @@ CREATE SEQUENCE public.financial_year_id_seq
|
||||
ALTER SEQUENCE public.financial_year_id_seq OWNED BY public.financial_year.id;
|
||||
|
||||
|
||||
--
|
||||
-- Name: invoice; Type: TABLE; Schema: public; Owner: -
|
||||
--
|
||||
|
||||
CREATE TABLE public.invoice (
|
||||
id integer NOT NULL,
|
||||
financial_year_id integer,
|
||||
supplier_id integer NOT NULL,
|
||||
fisken_number integer,
|
||||
phm_number integer,
|
||||
invoice_number text,
|
||||
invoice_date date CONSTRAINT invoice_date_not_null NOT NULL,
|
||||
due_date date,
|
||||
ocr text,
|
||||
amount numeric(12,2)
|
||||
);
|
||||
|
||||
|
||||
--
|
||||
-- Name: invoice_id_seq; Type: SEQUENCE; Schema: public; Owner: -
|
||||
--
|
||||
|
||||
CREATE SEQUENCE public.invoice_id_seq
|
||||
AS integer
|
||||
START WITH 1
|
||||
INCREMENT BY 1
|
||||
NO MINVALUE
|
||||
NO MAXVALUE
|
||||
CACHE 1;
|
||||
|
||||
|
||||
--
|
||||
-- Name: invoice_id_seq; Type: SEQUENCE OWNED BY; Schema: public; Owner: -
|
||||
--
|
||||
|
||||
ALTER SEQUENCE public.invoice_id_seq OWNED BY public.invoice.id;
|
||||
|
||||
|
||||
--
|
||||
-- Name: journal; Type: TABLE; Schema: public; Owner: -
|
||||
--
|
||||
@ -251,6 +329,67 @@ CREATE SEQUENCE public.object_id_seq
|
||||
ALTER SEQUENCE public.object_id_seq OWNED BY public.object.id;
|
||||
|
||||
|
||||
--
|
||||
-- Name: supplier; Type: TABLE; Schema: public; Owner: -
|
||||
--
|
||||
|
||||
CREATE TABLE public.supplier (
|
||||
id integer NOT NULL,
|
||||
name text,
|
||||
supplier_type_id integer NOT NULL
|
||||
);
|
||||
|
||||
|
||||
--
|
||||
-- Name: supplier_id_seq; Type: SEQUENCE; Schema: public; Owner: -
|
||||
--
|
||||
|
||||
CREATE SEQUENCE public.supplier_id_seq
|
||||
AS integer
|
||||
START WITH 1
|
||||
INCREMENT BY 1
|
||||
NO MINVALUE
|
||||
NO MAXVALUE
|
||||
CACHE 1;
|
||||
|
||||
|
||||
--
|
||||
-- Name: supplier_id_seq; Type: SEQUENCE OWNED BY; Schema: public; Owner: -
|
||||
--
|
||||
|
||||
ALTER SEQUENCE public.supplier_id_seq OWNED BY public.supplier.id;
|
||||
|
||||
|
||||
--
|
||||
-- Name: supplier_type; Type: TABLE; Schema: public; Owner: -
|
||||
--
|
||||
|
||||
CREATE TABLE public.supplier_type (
|
||||
id integer NOT NULL,
|
||||
name text NOT NULL
|
||||
);
|
||||
|
||||
|
||||
--
|
||||
-- Name: supplier_type_id_seq; Type: SEQUENCE; Schema: public; Owner: -
|
||||
--
|
||||
|
||||
CREATE SEQUENCE public.supplier_type_id_seq
|
||||
AS integer
|
||||
START WITH 1
|
||||
INCREMENT BY 1
|
||||
NO MINVALUE
|
||||
NO MAXVALUE
|
||||
CACHE 1;
|
||||
|
||||
|
||||
--
|
||||
-- Name: supplier_type_id_seq; Type: SEQUENCE OWNED BY; Schema: public; Owner: -
|
||||
--
|
||||
|
||||
ALTER SEQUENCE public.supplier_type_id_seq OWNED BY public.supplier_type.id;
|
||||
|
||||
|
||||
--
|
||||
-- Name: transaction; Type: TABLE; Schema: public; Owner: -
|
||||
--
|
||||
@ -264,7 +403,8 @@ CREATE TABLE public.transaction (
|
||||
description text,
|
||||
transaction_date date,
|
||||
quantity numeric(12,2),
|
||||
signature text
|
||||
signature text,
|
||||
invoice_id integer
|
||||
);
|
||||
|
||||
|
||||
@ -319,6 +459,13 @@ ALTER TABLE ONLY public.dimension ALTER COLUMN id SET DEFAULT nextval('public.di
|
||||
ALTER TABLE ONLY public.entry ALTER COLUMN id SET DEFAULT nextval('public.entry_id_seq'::regclass);
|
||||
|
||||
|
||||
--
|
||||
-- Name: file id; Type: DEFAULT; Schema: public; Owner: -
|
||||
--
|
||||
|
||||
ALTER TABLE ONLY public.file ALTER COLUMN id SET DEFAULT nextval('public.file_id_seq'::regclass);
|
||||
|
||||
|
||||
--
|
||||
-- Name: financial_year id; Type: DEFAULT; Schema: public; Owner: -
|
||||
--
|
||||
@ -326,6 +473,13 @@ ALTER TABLE ONLY public.entry ALTER COLUMN id SET DEFAULT nextval('public.entry_
|
||||
ALTER TABLE ONLY public.financial_year ALTER COLUMN id SET DEFAULT nextval('public.financial_year_id_seq'::regclass);
|
||||
|
||||
|
||||
--
|
||||
-- Name: invoice id; Type: DEFAULT; Schema: public; Owner: -
|
||||
--
|
||||
|
||||
ALTER TABLE ONLY public.invoice ALTER COLUMN id SET DEFAULT nextval('public.invoice_id_seq'::regclass);
|
||||
|
||||
|
||||
--
|
||||
-- Name: journal id; Type: DEFAULT; Schema: public; Owner: -
|
||||
--
|
||||
@ -340,6 +494,20 @@ ALTER TABLE ONLY public.journal ALTER COLUMN id SET DEFAULT nextval('public.jour
|
||||
ALTER TABLE ONLY public.object ALTER COLUMN id SET DEFAULT nextval('public.object_id_seq'::regclass);
|
||||
|
||||
|
||||
--
|
||||
-- Name: supplier id; Type: DEFAULT; Schema: public; Owner: -
|
||||
--
|
||||
|
||||
ALTER TABLE ONLY public.supplier ALTER COLUMN id SET DEFAULT nextval('public.supplier_id_seq'::regclass);
|
||||
|
||||
|
||||
--
|
||||
-- Name: supplier_type id; Type: DEFAULT; Schema: public; Owner: -
|
||||
--
|
||||
|
||||
ALTER TABLE ONLY public.supplier_type ALTER COLUMN id SET DEFAULT nextval('public.supplier_type_id_seq'::regclass);
|
||||
|
||||
|
||||
--
|
||||
-- Name: transaction id; Type: DEFAULT; Schema: public; Owner: -
|
||||
--
|
||||
@ -387,6 +555,22 @@ ALTER TABLE ONLY public.entry
|
||||
ADD CONSTRAINT entry_pkey PRIMARY KEY (id);
|
||||
|
||||
|
||||
--
|
||||
-- Name: file file_pkey; Type: CONSTRAINT; Schema: public; Owner: -
|
||||
--
|
||||
|
||||
ALTER TABLE ONLY public.file
|
||||
ADD CONSTRAINT file_pkey PRIMARY KEY (id);
|
||||
|
||||
|
||||
--
|
||||
-- Name: files_to_invoice files_to_invoice_pkey; Type: CONSTRAINT; Schema: public; Owner: -
|
||||
--
|
||||
|
||||
ALTER TABLE ONLY public.files_to_invoice
|
||||
ADD CONSTRAINT files_to_invoice_pkey PRIMARY KEY (invoice_id, file_id);
|
||||
|
||||
|
||||
--
|
||||
-- Name: financial_year financial_year_pkey; Type: CONSTRAINT; Schema: public; Owner: -
|
||||
--
|
||||
@ -411,6 +595,14 @@ ALTER TABLE ONLY public.financial_year
|
||||
ADD CONSTRAINT financial_year_year_key UNIQUE (year);
|
||||
|
||||
|
||||
--
|
||||
-- Name: invoice invoice_pkey; Type: CONSTRAINT; Schema: public; Owner: -
|
||||
--
|
||||
|
||||
ALTER TABLE ONLY public.invoice
|
||||
ADD CONSTRAINT invoice_pkey PRIMARY KEY (id);
|
||||
|
||||
|
||||
--
|
||||
-- Name: journal journal_pkey; Type: CONSTRAINT; Schema: public; Owner: -
|
||||
--
|
||||
@ -435,6 +627,22 @@ ALTER TABLE ONLY public.object
|
||||
ADD CONSTRAINT object_pkey PRIMARY KEY (id);
|
||||
|
||||
|
||||
--
|
||||
-- Name: supplier supplier_pkey; Type: CONSTRAINT; Schema: public; Owner: -
|
||||
--
|
||||
|
||||
ALTER TABLE ONLY public.supplier
|
||||
ADD CONSTRAINT supplier_pkey PRIMARY KEY (id);
|
||||
|
||||
|
||||
--
|
||||
-- Name: supplier_type supplier_type_pkey; Type: CONSTRAINT; Schema: public; Owner: -
|
||||
--
|
||||
|
||||
ALTER TABLE ONLY public.supplier_type
|
||||
ADD CONSTRAINT supplier_type_pkey PRIMARY KEY (id);
|
||||
|
||||
|
||||
--
|
||||
-- Name: transaction transaction_pkey; Type: CONSTRAINT; Schema: public; Owner: -
|
||||
--
|
||||
@ -451,6 +659,38 @@ ALTER TABLE ONLY public.transactions_to_objects
|
||||
ADD CONSTRAINT transactions_to_objects_transaction_id_object_id_key UNIQUE (transaction_id, object_id);
|
||||
|
||||
|
||||
--
|
||||
-- Name: files_to_invoice files_to_invoice_file_id_fkey; Type: FK CONSTRAINT; Schema: public; Owner: -
|
||||
--
|
||||
|
||||
ALTER TABLE ONLY public.files_to_invoice
|
||||
ADD CONSTRAINT files_to_invoice_file_id_fkey FOREIGN KEY (file_id) REFERENCES public.file(id);
|
||||
|
||||
|
||||
--
|
||||
-- Name: files_to_invoice files_to_invoice_invoice_id_fkey; Type: FK CONSTRAINT; Schema: public; Owner: -
|
||||
--
|
||||
|
||||
ALTER TABLE ONLY public.files_to_invoice
|
||||
ADD CONSTRAINT files_to_invoice_invoice_id_fkey FOREIGN KEY (invoice_id) REFERENCES public.invoice(id);
|
||||
|
||||
|
||||
--
|
||||
-- Name: invoice invoice_financial_year_id_fkey; Type: FK CONSTRAINT; Schema: public; Owner: -
|
||||
--
|
||||
|
||||
ALTER TABLE ONLY public.invoice
|
||||
ADD CONSTRAINT invoice_financial_year_id_fkey FOREIGN KEY (financial_year_id) REFERENCES public.financial_year(id);
|
||||
|
||||
|
||||
--
|
||||
-- Name: invoice invoice_supplier_id_fkey; Type: FK CONSTRAINT; Schema: public; Owner: -
|
||||
--
|
||||
|
||||
ALTER TABLE ONLY public.invoice
|
||||
ADD CONSTRAINT invoice_supplier_id_fkey FOREIGN KEY (supplier_id) REFERENCES public.supplier(id);
|
||||
|
||||
|
||||
--
|
||||
-- Name: object object_dimension_id_fkey; Type: FK CONSTRAINT; Schema: public; Owner: -
|
||||
--
|
||||
@ -459,6 +699,14 @@ ALTER TABLE ONLY public.object
|
||||
ADD CONSTRAINT object_dimension_id_fkey FOREIGN KEY (dimension_id) REFERENCES public.dimension(id);
|
||||
|
||||
|
||||
--
|
||||
-- Name: supplier supplier_supplier_type_id_fkey; Type: FK CONSTRAINT; Schema: public; Owner: -
|
||||
--
|
||||
|
||||
ALTER TABLE ONLY public.supplier
|
||||
ADD CONSTRAINT supplier_supplier_type_id_fkey FOREIGN KEY (supplier_type_id) REFERENCES public.supplier_type(id);
|
||||
|
||||
|
||||
--
|
||||
-- Name: transaction transaction_entry_id_fkey; Type: FK CONSTRAINT; Schema: public; Owner: -
|
||||
--
|
||||
@ -467,6 +715,14 @@ ALTER TABLE ONLY public.transaction
|
||||
ADD CONSTRAINT transaction_entry_id_fkey FOREIGN KEY (entry_id) REFERENCES public.entry(id);
|
||||
|
||||
|
||||
--
|
||||
-- Name: transaction transaction_invoice_id_fkey; Type: FK CONSTRAINT; Schema: public; Owner: -
|
||||
--
|
||||
|
||||
ALTER TABLE ONLY public.transaction
|
||||
ADD CONSTRAINT transaction_invoice_id_fkey FOREIGN KEY (invoice_id) REFERENCES public.invoice(id);
|
||||
|
||||
|
||||
--
|
||||
-- Name: transaction transaction_object_id_fkey; Type: FK CONSTRAINT; Schema: public; Owner: -
|
||||
--
|
||||
@ -495,5 +751,5 @@ ALTER TABLE ONLY public.transactions_to_objects
|
||||
-- PostgreSQL database dump complete
|
||||
--
|
||||
|
||||
\unrestrict H2xBPQq6I6IQHZkAgiuKoY9lao4r4KAPtiyNvDE9oc0DN75cJ1gNbkoGFqutWDp
|
||||
\unrestrict x1LNwo1MrXgJU7KVXELaKlpEPpIZLjRGuaweMIify4ofZcwqTGzXVX5DkRI11Hx
|
||||
|
||||
|
||||
@ -0,0 +1,44 @@
|
||||
--
|
||||
-- PostgreSQL database dump
|
||||
--
|
||||
|
||||
\restrict bwzPcLzyW22uigCF5UMc3FAcSU9hliUs5ZBJ4ZpwZeegYAG8Md8d7l0M55Czl7h
|
||||
|
||||
-- Dumped from database version 18.1
|
||||
-- Dumped by pg_dump version 18.1
|
||||
|
||||
SET statement_timeout = 0;
|
||||
SET lock_timeout = 0;
|
||||
SET idle_in_transaction_session_timeout = 0;
|
||||
SET transaction_timeout = 0;
|
||||
SET client_encoding = 'UTF8';
|
||||
SET standard_conforming_strings = on;
|
||||
SELECT pg_catalog.set_config('search_path', '', false);
|
||||
SET check_function_bodies = false;
|
||||
SET xmloption = content;
|
||||
SET client_min_messages = warning;
|
||||
SET row_security = off;
|
||||
|
||||
--
|
||||
-- Data for Name: supplier_type; Type: TABLE DATA; Schema: public; Owner: -
|
||||
--
|
||||
|
||||
COPY public.supplier_type (id, name) FROM stdin;
|
||||
1 Company
|
||||
2 Person
|
||||
\.
|
||||
|
||||
|
||||
--
|
||||
-- Name: supplier_type_id_seq; Type: SEQUENCE SET; Schema: public; Owner: -
|
||||
--
|
||||
|
||||
SELECT pg_catalog.setval('public.supplier_type_id_seq', 2, true);
|
||||
|
||||
|
||||
--
|
||||
-- PostgreSQL database dump complete
|
||||
--
|
||||
|
||||
\unrestrict bwzPcLzyW22uigCF5UMc3FAcSU9hliUs5ZBJ4ZpwZeegYAG8Md8d7l0M55Czl7h
|
||||
|
||||
@ -20,7 +20,7 @@
|
||||
"test": "pnpm run test:client && pnpm run test:server",
|
||||
"test:client": "node --no-warnings --import=./client/test/jsdom_polyfills.ts --import=./client/test/register_tsx_hook.ts --test ./client/**/*.test.ts{,x}",
|
||||
"test:server": "node --no-warnings --test ./server/**/*.test.ts",
|
||||
"types": "tsgo"
|
||||
"types": "tsgo --skipLibCheck"
|
||||
},
|
||||
"dependencies": {
|
||||
"@bmp/console": "^0.1.0",
|
||||
@ -29,6 +29,7 @@
|
||||
"@fastify/static": "^8.3.0",
|
||||
"@fastify/type-provider-typebox": "^6.1.0",
|
||||
"chalk": "^5.6.2",
|
||||
"easy-tz": "^0.2.0",
|
||||
"fastify": "^5.6.2",
|
||||
"fastify-plugin": "^5.1.0",
|
||||
"knex": "^3.1.0",
|
||||
@ -38,7 +39,7 @@
|
||||
"pg-protocol": "^1.10.3",
|
||||
"pino-abstract-transport": "^3.0.0",
|
||||
"preact": "^10.27.2",
|
||||
"preact-router": "^4.1.2",
|
||||
"preact-iso": "^2.11.0",
|
||||
"rek": "^0.8.1"
|
||||
},
|
||||
"devDependencies": {
|
||||
@ -48,6 +49,7 @@
|
||||
"@types/lodash": "^4.17.16",
|
||||
"@types/node": "^24.10.1",
|
||||
"@typescript/native-preview": "7.0.0-dev.20251126.1",
|
||||
"d3-dsv": "^3.0.1",
|
||||
"esbuild": "^0.27.0",
|
||||
"globals": "^16.0.0",
|
||||
"husky": "^9.1.7",
|
||||
|
||||
58
pnpm-lock.yaml
generated
58
pnpm-lock.yaml
generated
@ -26,6 +26,9 @@ importers:
|
||||
chalk:
|
||||
specifier: ^5.6.2
|
||||
version: 5.6.2
|
||||
easy-tz:
|
||||
specifier: ^0.2.0
|
||||
version: 0.2.0
|
||||
fastify:
|
||||
specifier: ^5.6.2
|
||||
version: 5.6.2
|
||||
@ -53,9 +56,9 @@ importers:
|
||||
preact:
|
||||
specifier: ^10.27.2
|
||||
version: 10.27.2
|
||||
preact-router:
|
||||
specifier: ^4.1.2
|
||||
version: 4.1.2(preact@10.27.2)
|
||||
preact-iso:
|
||||
specifier: ^2.11.0
|
||||
version: 2.11.0(preact-render-to-string@6.6.3(preact@10.27.2))(preact@10.27.2)
|
||||
rek:
|
||||
specifier: ^0.8.1
|
||||
version: 0.8.1
|
||||
@ -78,6 +81,9 @@ importers:
|
||||
'@typescript/native-preview':
|
||||
specifier: 7.0.0-dev.20251126.1
|
||||
version: 7.0.0-dev.20251126.1
|
||||
d3-dsv:
|
||||
specifier: ^3.0.1
|
||||
version: 3.0.1
|
||||
esbuild:
|
||||
specifier: ^0.27.0
|
||||
version: 0.27.0
|
||||
@ -1114,6 +1120,10 @@ packages:
|
||||
resolution: {integrity: sha512-TywoWNNRbhoD0BXs1P3ZEScW8W5iKrnbithIl0YH+uCmBd0QpPOA8yc82DS3BIE5Ma6FnBVUsJ7wVUDz4dvOWQ==}
|
||||
engines: {node: '>=20'}
|
||||
|
||||
commander@7.2.0:
|
||||
resolution: {integrity: sha512-QrWXB+ZQSVPmIWIhtEO9H+gwHaMGYiF5ChvoJ+K9ZGHG/sVsa6yiesAD1GC/x46sET00Xlwo1u49RVVVzvcSkw==}
|
||||
engines: {node: '>= 10'}
|
||||
|
||||
content-disposition@0.5.4:
|
||||
resolution: {integrity: sha512-FveZTNuGw04cxlAiWbzi6zTAL/lhehaWbTtgluJh4/E95DqMwTmha3KZN1aAWA8cFIhHzMZUvLevkw5Rqk+tSQ==}
|
||||
engines: {node: '>= 0.6'}
|
||||
@ -1144,6 +1154,11 @@ packages:
|
||||
resolution: {integrity: sha512-OytmFH+13/QXONJcC75QNdMtKpceNk3u8ThBjyyYjkEcy/ekBwR1mMAuNvi3gdBPW3N5TlCzQ0WZw8H0lN/bDw==}
|
||||
engines: {node: '>=20'}
|
||||
|
||||
d3-dsv@3.0.1:
|
||||
resolution: {integrity: sha512-UG6OvdI5afDIFP9w4G0mNq50dSOsXHJaRE8arAS5o9ApWnIElp8GZw1Dun8vP8OyHOZ/QJUKUJwxiiCCnUwm+Q==}
|
||||
engines: {node: '>=12'}
|
||||
hasBin: true
|
||||
|
||||
data-urls@6.0.0:
|
||||
resolution: {integrity: sha512-BnBS08aLUM+DKamupXs3w2tJJoqU+AkaE/+6vQxi/G/DPmIZFJJp9Dkb1kM03AZx8ADehDUZgsNxju3mPXZYIA==}
|
||||
engines: {node: '>=20'}
|
||||
@ -1217,6 +1232,9 @@ packages:
|
||||
eastasianwidth@0.2.0:
|
||||
resolution: {integrity: sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==}
|
||||
|
||||
easy-tz@0.2.0:
|
||||
resolution: {integrity: sha512-Mf+tTNHaAUBqEMo9mvIdCIV5kXCazRCxGKH16itMxefrHy/FtHS5tnfOOS0DKMWU7rl/MnN9PzjdFfn8JCYqxg==}
|
||||
|
||||
electron-to-chromium@1.5.259:
|
||||
resolution: {integrity: sha512-I+oLXgpEJzD6Cwuwt1gYjxsDmu/S/Kd41mmLA3O+/uH2pFRO/DvOjUyGozL8j3KeLV6WyZ7ssPwELMsXCcsJAQ==}
|
||||
|
||||
@ -1848,10 +1866,16 @@ packages:
|
||||
resolution: {integrity: sha512-9ZhXKM/rw350N1ovuWHbGxnGh/SNJ4cnxHiM0rxE4VN41wsg8P8zWn9hv/buK00RP4WvlOyr/RBDiptyxVbkZQ==}
|
||||
engines: {node: '>=0.10.0'}
|
||||
|
||||
preact-router@4.1.2:
|
||||
resolution: {integrity: sha512-uICUaUFYh+XQ+6vZtQn1q+X6rSqwq+zorWOCLWPF5FAsQh3EJ+RsDQ9Ee+fjk545YWQHfUxhrBAaemfxEnMOUg==}
|
||||
preact-iso@2.11.0:
|
||||
resolution: {integrity: sha512-oThWJQcgcnaWh6UKy1qrBkxIWp5CkqvnHiFdLiDUxfNkGdpQ5veGQw9wOVS0NDp7X8xo98wxE4wng5jLv1e9Ug==}
|
||||
peerDependencies:
|
||||
preact: '>=10'
|
||||
preact: '>=10 || >= 11.0.0-0'
|
||||
preact-render-to-string: '>=6.4.0'
|
||||
|
||||
preact-render-to-string@6.6.3:
|
||||
resolution: {integrity: sha512-7oHG7jzjriqsFPkSPiPnzrQ0GcxFm6wOkYWNdStK5Ks9YlWSQQXKGBRAX4nKDdqX7HAQuRvI4pZNZMycK4WwDw==}
|
||||
peerDependencies:
|
||||
preact: '>=10 || >= 11.0.0-0'
|
||||
|
||||
preact@10.27.2:
|
||||
resolution: {integrity: sha512-5SYSgFKSyhCbk6SrXyMpqjb5+MQBgfvEKE/OC+PujcY34sOpqtr+0AZQtPYx5IA6VxynQ7rUPCtKzyovpj9Bpg==}
|
||||
@ -1933,6 +1957,9 @@ packages:
|
||||
engines: {node: '>=18.0.0', npm: '>=8.0.0'}
|
||||
hasBin: true
|
||||
|
||||
rw@1.3.3:
|
||||
resolution: {integrity: sha512-PdhdWy89SiZogBLaw42zdeqtRJ//zFd2PgQavcICDUgJT5oW10QCRKbJ6bg4r0/UY2M6BWd5tkxuGFRvCkgfHQ==}
|
||||
|
||||
safe-buffer@5.2.1:
|
||||
resolution: {integrity: sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==}
|
||||
|
||||
@ -3093,6 +3120,8 @@ snapshots:
|
||||
|
||||
commander@14.0.2: {}
|
||||
|
||||
commander@7.2.0: {}
|
||||
|
||||
content-disposition@0.5.4:
|
||||
dependencies:
|
||||
safe-buffer: 5.2.1
|
||||
@ -3128,6 +3157,12 @@ snapshots:
|
||||
'@csstools/css-syntax-patches-for-csstree': 1.0.17
|
||||
css-tree: 3.1.0
|
||||
|
||||
d3-dsv@3.0.1:
|
||||
dependencies:
|
||||
commander: 7.2.0
|
||||
iconv-lite: 0.6.3
|
||||
rw: 1.3.3
|
||||
|
||||
data-urls@6.0.0:
|
||||
dependencies:
|
||||
whatwg-mimetype: 4.0.0
|
||||
@ -3211,6 +3246,8 @@ snapshots:
|
||||
|
||||
eastasianwidth@0.2.0: {}
|
||||
|
||||
easy-tz@0.2.0: {}
|
||||
|
||||
electron-to-chromium@1.5.259: {}
|
||||
|
||||
emoji-regex@10.6.0: {}
|
||||
@ -3881,7 +3918,12 @@ snapshots:
|
||||
dependencies:
|
||||
xtend: 4.0.2
|
||||
|
||||
preact-router@4.1.2(preact@10.27.2):
|
||||
preact-iso@2.11.0(preact-render-to-string@6.6.3(preact@10.27.2))(preact@10.27.2):
|
||||
dependencies:
|
||||
preact: 10.27.2
|
||||
preact-render-to-string: 6.6.3(preact@10.27.2)
|
||||
|
||||
preact-render-to-string@6.6.3(preact@10.27.2):
|
||||
dependencies:
|
||||
preact: 10.27.2
|
||||
|
||||
@ -3973,6 +4015,8 @@ snapshots:
|
||||
'@rollup/rollup-win32-x64-msvc': 4.53.3
|
||||
fsevents: 2.3.3
|
||||
|
||||
rw@1.3.3: {}
|
||||
|
||||
safe-buffer@5.2.1: {}
|
||||
|
||||
safe-regex-test@1.1.0:
|
||||
|
||||
@ -1,3 +1,4 @@
|
||||
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'
|
||||
@ -19,6 +20,102 @@ 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({
|
||||
url: '/objects',
|
||||
method: 'GET',
|
||||
@ -122,6 +219,27 @@ const apiRoutes: FastifyPluginCallbackTypebox = (fastify, _, done) => {
|
||||
},
|
||||
})
|
||||
|
||||
fastify.route({
|
||||
url: '/suppliers',
|
||||
method: 'GET',
|
||||
async handler(req) {
|
||||
return knex('supplier').select('*').orderBy('name')
|
||||
},
|
||||
})
|
||||
|
||||
fastify.route({
|
||||
url: '/suppliers/:id',
|
||||
method: 'GET',
|
||||
schema: {
|
||||
params: Type.Object({
|
||||
id: Type.Number(),
|
||||
}),
|
||||
},
|
||||
async handler(req) {
|
||||
return knex('supplier').first('*').where('id', req.params.id)
|
||||
},
|
||||
})
|
||||
|
||||
done()
|
||||
}
|
||||
|
||||
|
||||
@ -1,4 +1,5 @@
|
||||
import fastify, { type FastifyServerOptions } from 'fastify'
|
||||
import fstatic from '@fastify/static'
|
||||
import StatusError from './lib/status_error.ts'
|
||||
import env from './env.ts'
|
||||
import ErrorHandler from './handlers/error.ts'
|
||||
@ -13,7 +14,10 @@ export default async (options: FastifyServerOptions) => {
|
||||
throw new StatusError(404)
|
||||
})
|
||||
|
||||
console.dir(env)
|
||||
server.register(fstatic, {
|
||||
root: new URL('../uploads', import.meta.url),
|
||||
prefix: '/uploads/',
|
||||
})
|
||||
|
||||
server.register(vitePlugin, {
|
||||
mode: env.NODE_ENV,
|
||||
|
||||
@ -12,5 +12,5 @@
|
||||
"erasableSyntaxOnly": true,
|
||||
"allowArbitraryExtensions": true
|
||||
},
|
||||
"include": ["global.d.ts", "client", "server"]
|
||||
"include": ["global.d.ts", "bin", "client", "server"]
|
||||
}
|
||||
|
||||
Loading…
Reference in New Issue
Block a user