v1.6.8: remove offer exchange rate, add invoice item description, offer filters, table animations

- Remove exchange_rate and exchange_rate_date from quotations (schema, service, PDF, form)
- Add item_description field to invoice_items (schema, migration, service, form, PDF)
- Add offer status/customer/order filters with tab-based UI
- Clean up offer statuses to active/ordered/invalidated
- Allow invalidate action for non-ordered offers (not just expired)
- Add fade animations on offers and invoices table data changes

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
BOHA
2026-05-28 12:58:51 +02:00
parent 66b98e2765
commit 0330453ad6
20 changed files with 3126 additions and 3029 deletions

View File

@@ -23,6 +23,7 @@ const ALLOWED_SORT_FIELDS = [
interface InvoiceItemInput {
description?: string;
item_description?: string;
quantity?: number;
unit?: string;
unit_price?: number;
@@ -374,6 +375,7 @@ export async function createInvoice(body: Record<string, any>) {
data: (body.items as InvoiceItemInput[]).map((item, i) => ({
invoice_id: invoice.id,
description: item.description ?? null,
item_description: item.item_description ?? null,
quantity: item.quantity ?? 1,
unit: item.unit ?? null,
unit_price: item.unit_price ?? 0,
@@ -471,6 +473,7 @@ export async function updateInvoice(id: number, body: Record<string, any>) {
data: (body.items as InvoiceItemInput[]).map((item, i) => ({
invoice_id: id,
description: item.description ?? null,
item_description: item.item_description ?? null,
quantity: item.quantity ?? 1,
unit: item.unit ?? null,
unit_price: item.unit_price ?? 0,

View File

@@ -44,6 +44,7 @@ interface ListOffersParams {
search: string;
status?: string;
customer_id?: number;
has_order?: string;
}
function enrichQuotation(q: any) {
@@ -72,13 +73,24 @@ function enrichQuotation(q: any) {
}
export async function listOffers(params: ListOffersParams) {
const { page, limit, skip, sort, order, search, status, customer_id } =
params;
const {
page,
limit,
skip,
sort,
order,
search,
status,
customer_id,
has_order,
} = params;
const sortField = ALLOWED_SORT_FIELDS.includes(sort) ? sort : "id";
const where: Record<string, unknown> = {};
if (status) where.status = status;
if (customer_id) where.customer_id = customer_id;
if (has_order === "yes") where.order_id = { not: null };
if (has_order === "no") where.order_id = null;
if (search) {
where.OR = [
{ quotation_number: { contains: search } },
@@ -184,7 +196,6 @@ export async function createOffer(body: Record<string, any>) {
language: body.language ? String(body.language) : "cs",
vat_rate: body.vat_rate != null ? Number(body.vat_rate) : 21.0,
apply_vat: body.apply_vat !== false,
exchange_rate: body.exchange_rate ? Number(body.exchange_rate) : 1.0,
status: body.status ? String(body.status) : "active",
scope_title: body.scope_title ? String(body.scope_title) : null,
scope_description: body.scope_description
@@ -255,8 +266,6 @@ export async function updateOffer(id: number, body: Record<string, any>) {
body.apply_vat === 1 ||
body.apply_vat === "1"
: undefined,
exchange_rate:
body.exchange_rate !== undefined ? Number(body.exchange_rate) : undefined,
status: body.status !== undefined ? String(body.status) : undefined,
project_code:
body.project_code !== undefined
@@ -354,7 +363,6 @@ export async function duplicateOffer(id: number) {
language: original.language,
vat_rate: original.vat_rate,
apply_vat: original.apply_vat,
exchange_rate: original.exchange_rate,
status: "active",
scope_title: original.scope_title,
scope_description: original.scope_description,

View File

@@ -206,7 +206,6 @@ export async function createOrderFromQuotation(
language: quotation.language || "cs",
vat_rate: quotation.vat_rate ?? 21.0,
apply_vat: quotation.apply_vat ?? true,
exchange_rate: quotation.exchange_rate ?? 1.0,
scope_title: quotation.scope_title,
scope_description: quotation.scope_description,
attachment_data: attachmentBuffer