Make invoice due date optional with 14-day default
Datum vystaveni defaults to today and Datum splatnosti defaults to 14 days after the issue date. Both fields are now optional date inputs and only override the default when a user enters a custom value. Applies to the invoice editor and generated PDF. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
This commit is contained in:
+27
-1
@@ -445,6 +445,20 @@ app.delete('/api/invoices', requireAuth, async (req, res, next) => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
const pdfText = (value) => String(value || 'Neuvedeno');
|
const pdfText = (value) => String(value || 'Neuvedeno');
|
||||||
|
const formatCzechDate = (value) => {
|
||||||
|
const date = new Date(`${value}T00:00:00`);
|
||||||
|
return Number.isNaN(date.getTime()) ? '' : date.toLocaleDateString('cs-CZ');
|
||||||
|
};
|
||||||
|
const toDateInputValue = (date) => date.toISOString().slice(0, 10);
|
||||||
|
const resolveIssueDate = (issueDate) => issueDate || toDateInputValue(new Date());
|
||||||
|
const resolveDueDate = (dueDate, issueDate) => {
|
||||||
|
if (dueDate) {
|
||||||
|
return dueDate;
|
||||||
|
}
|
||||||
|
const due = new Date(`${resolveIssueDate(issueDate)}T00:00:00`);
|
||||||
|
due.setDate(due.getDate() + 14);
|
||||||
|
return toDateInputValue(due);
|
||||||
|
};
|
||||||
const formatCzechNumber = (value) => Number(value || 0).toLocaleString('cs-CZ', {
|
const formatCzechNumber = (value) => Number(value || 0).toLocaleString('cs-CZ', {
|
||||||
minimumFractionDigits: 2,
|
minimumFractionDigits: 2,
|
||||||
maximumFractionDigits: 2
|
maximumFractionDigits: 2
|
||||||
@@ -518,13 +532,25 @@ const renderInvoicePdf = async (document, invoice) => {
|
|||||||
const partyWidth = (pageWidth - 24) / 2;
|
const partyWidth = (pageWidth - 24) / 2;
|
||||||
const customerX = document.page.margins.left + partyWidth + 24;
|
const customerX = document.page.margins.left + partyWidth + 24;
|
||||||
|
|
||||||
|
const issueDate = resolveIssueDate(data.issueDate);
|
||||||
|
const dueDate = resolveDueDate(data.dueDate, data.issueDate);
|
||||||
|
|
||||||
const headerY = document.y;
|
const headerY = document.y;
|
||||||
document.fillColor(navy).font(boldFont).fontSize(25).text('Faktura', document.page.margins.left, headerY);
|
document.fillColor(navy).font(boldFont).fontSize(25).text('Faktura', document.page.margins.left, headerY);
|
||||||
document.fillColor(navy).font(boldFont).fontSize(16).text(invoice.invoice_number, customerX, headerY, {
|
document.fillColor(navy).font(boldFont).fontSize(16).text(invoice.invoice_number, customerX, headerY, {
|
||||||
width: partyWidth,
|
width: partyWidth,
|
||||||
align: 'right'
|
align: 'right'
|
||||||
});
|
});
|
||||||
document.y = headerY + 40;
|
document.fillColor(bodyText).font(regularFont).fontSize(9)
|
||||||
|
.text(`Datum vystavení: ${formatCzechDate(issueDate)}`, customerX, headerY + 22, {
|
||||||
|
width: partyWidth,
|
||||||
|
align: 'right'
|
||||||
|
})
|
||||||
|
.text(`Datum splatnosti: ${formatCzechDate(dueDate)}`, customerX, headerY + 35, {
|
||||||
|
width: partyWidth,
|
||||||
|
align: 'right'
|
||||||
|
});
|
||||||
|
document.y = headerY + 52;
|
||||||
|
|
||||||
const partyTop = document.y;
|
const partyTop = document.y;
|
||||||
const drawParty = (x, title, profile, name) => {
|
const drawParty = (x, title, profile, name) => {
|
||||||
|
|||||||
@@ -308,13 +308,13 @@
|
|||||||
|
|
||||||
<div class="due-date-group">
|
<div class="due-date-group">
|
||||||
<div class="form-group">
|
<div class="form-group">
|
||||||
<label for="dueDateOption">Datum splatnosti</label>
|
<label for="issueDate">Datum vystavení</label>
|
||||||
<select id="dueDateOption">
|
<input type="date" id="issueDate" name="issueDate" placeholder="Dnešní datum" />
|
||||||
<option value="14">14 dní</option>
|
</div>
|
||||||
<option value="30">30 dní</option>
|
<div class="form-group">
|
||||||
</select>
|
<label for="dueDate">Datum splatnosti</label>
|
||||||
|
<input type="date" id="dueDate" name="dueDate" placeholder="14 dní od vystavení" />
|
||||||
</div>
|
</div>
|
||||||
<output id="dueDateDisplay" class="due-date-display"></output>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|||||||
+17
-11
@@ -40,8 +40,8 @@ const passwordForm = document.getElementById("passwordForm");
|
|||||||
const savePasswordButton = document.getElementById("savePasswordButton");
|
const savePasswordButton = document.getElementById("savePasswordButton");
|
||||||
const cancelPasswordButton = document.getElementById("cancelPasswordButton");
|
const cancelPasswordButton = document.getElementById("cancelPasswordButton");
|
||||||
const passwordStatus = document.getElementById("passwordStatus");
|
const passwordStatus = document.getElementById("passwordStatus");
|
||||||
const dueDateOption = document.getElementById("dueDateOption");
|
const issueDateInput = document.getElementById("issueDate");
|
||||||
const dueDateDisplay = document.getElementById("dueDateDisplay");
|
const dueDateInput = document.getElementById("dueDate");
|
||||||
const paymentPresetSelect = document.getElementById("paymentPresetSelect");
|
const paymentPresetSelect = document.getElementById("paymentPresetSelect");
|
||||||
const paymentPresetName = document.getElementById("paymentPresetName");
|
const paymentPresetName = document.getElementById("paymentPresetName");
|
||||||
const savePaymentPresetButton = document.getElementById("savePaymentPresetButton");
|
const savePaymentPresetButton = document.getElementById("savePaymentPresetButton");
|
||||||
@@ -108,17 +108,17 @@ const getClientLabel = (client) => [
|
|||||||
|
|
||||||
const formatCzechDate = (dateValue) => new Date(`${dateValue}T00:00:00`).toLocaleDateString("cs-CZ");
|
const formatCzechDate = (dateValue) => new Date(`${dateValue}T00:00:00`).toLocaleDateString("cs-CZ");
|
||||||
|
|
||||||
const getDueDays = () => Number.parseInt(dueDateOption.value, 10) || 14;
|
const toDateInputValue = (date) => date.toISOString().slice(0, 10);
|
||||||
|
|
||||||
|
const getIssueDate = () => issueDateInput.value || toDateInputValue(new Date());
|
||||||
|
|
||||||
const getDueDate = () => {
|
const getDueDate = () => {
|
||||||
const dueDate = new Date();
|
if (dueDateInput.value) {
|
||||||
dueDate.setHours(0, 0, 0, 0);
|
return dueDateInput.value;
|
||||||
dueDate.setDate(dueDate.getDate() + getDueDays());
|
}
|
||||||
return dueDate.toISOString().slice(0, 10);
|
const dueDate = new Date(`${getIssueDate()}T00:00:00`);
|
||||||
};
|
dueDate.setDate(dueDate.getDate() + 14);
|
||||||
|
return toDateInputValue(dueDate);
|
||||||
const updateDueDate = () => {
|
|
||||||
dueDateDisplay.textContent = `Splatnost: ${formatCzechDate(getDueDate())}`;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
const updateClientTypeFields = () => {
|
const updateClientTypeFields = () => {
|
||||||
@@ -234,6 +234,8 @@ deletePaymentPresetButton.addEventListener("click", async () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
const readInvoiceData = () => ({
|
const readInvoiceData = () => ({
|
||||||
|
issueDate: getIssueDate(),
|
||||||
|
dueDate: getDueDate(),
|
||||||
supplier: {
|
supplier: {
|
||||||
...readProfileFields("dodavatel"),
|
...readProfileFields("dodavatel"),
|
||||||
typSubjektu: supplierTypeSelect.value
|
typSubjektu: supplierTypeSelect.value
|
||||||
@@ -263,6 +265,8 @@ const clearInvoiceEditor = () => {
|
|||||||
currentInvoiceId = null;
|
currentInvoiceId = null;
|
||||||
invoiceNumberDisplay.textContent = "Nová faktura";
|
invoiceNumberDisplay.textContent = "Nová faktura";
|
||||||
invoiceSaveStatus.textContent = "";
|
invoiceSaveStatus.textContent = "";
|
||||||
|
issueDateInput.value = "";
|
||||||
|
dueDateInput.value = "";
|
||||||
writeProfileFields("odberatel", {});
|
writeProfileFields("odberatel", {});
|
||||||
clientSelect.value = "";
|
clientSelect.value = "";
|
||||||
clientTypeSelect.value = "osoba";
|
clientTypeSelect.value = "osoba";
|
||||||
@@ -282,6 +286,8 @@ const loadInvoiceIntoEditor = (invoice) => {
|
|||||||
currentInvoiceId = invoice.id;
|
currentInvoiceId = invoice.id;
|
||||||
invoiceNumberDisplay.textContent = invoice.invoiceNumber;
|
invoiceNumberDisplay.textContent = invoice.invoiceNumber;
|
||||||
invoiceSaveStatus.textContent = "";
|
invoiceSaveStatus.textContent = "";
|
||||||
|
issueDateInput.value = invoice.data.issueDate || "";
|
||||||
|
dueDateInput.value = invoice.data.dueDate || "";
|
||||||
writeProfileFields("dodavatel", invoice.data.supplier || {});
|
writeProfileFields("dodavatel", invoice.data.supplier || {});
|
||||||
supplierTypeSelect.value = invoice.data.supplier?.typSubjektu || "osoba";
|
supplierTypeSelect.value = invoice.data.supplier?.typSubjektu || "osoba";
|
||||||
updateSupplierTypeFields();
|
updateSupplierTypeFields();
|
||||||
|
|||||||
Reference in New Issue
Block a user