Compare commits

..

No commits in common. "7d084e570e1ae7d46941833d9175a6ec3a68fb17" and "52807c532237081b182b2a17b9ef67723fd87894" have entirely different histories.

8 changed files with 199 additions and 251 deletions

View File

@ -40,6 +40,12 @@ class AccountSelector {
*/ */
#debitCredit; #debitCredit;
/**
* The prefix of the HTML ID and class
* @type {string}
*/
#prefix;
/** /**
* The button to clear the account * The button to clear the account
* @type {HTMLButtonElement} * @type {HTMLButtonElement}
@ -85,14 +91,14 @@ class AccountSelector {
constructor(lineItemEditor, debitCredit) { constructor(lineItemEditor, debitCredit) {
this.#lineItemEditor = lineItemEditor this.#lineItemEditor = lineItemEditor
this.#debitCredit = debitCredit; this.#debitCredit = debitCredit;
const prefix = "accounting-account-selector-" + debitCredit; this.#prefix = "accounting-account-selector-" + debitCredit;
this.#query = document.getElementById(prefix + "-query"); this.#query = document.getElementById(this.#prefix + "-query");
this.#queryNoResult = document.getElementById(prefix + "-option-no-result"); this.#queryNoResult = document.getElementById(this.#prefix + "-option-no-result");
this.#optionList = document.getElementById(prefix + "-option-list"); this.#optionList = document.getElementById(this.#prefix + "-option-list");
// noinspection JSValidateTypes // noinspection JSValidateTypes
this.#options = Array.from(document.getElementsByClassName(prefix + "-option")); this.#options = Array.from(document.getElementsByClassName(this.#prefix + "-option"));
this.#more = document.getElementById(prefix + "-more"); this.#more = document.getElementById(this.#prefix + "-more");
this.#clearButton = document.getElementById(prefix + "-btn-clear"); this.#clearButton = document.getElementById(this.#prefix + "-btn-clear");
this.#more.onclick = () => { this.#more.onclick = () => {
this.#more.classList.add("d-none"); this.#more.classList.add("d-none");
this.#filterOptions(); this.#filterOptions();

View File

@ -41,7 +41,7 @@ class DescriptionEditor {
#form; #form;
/** /**
* The prefix of the HTML ID and class names * The prefix of the HTML ID and class
* @type {string} * @type {string}
*/ */
prefix; prefix;
@ -278,7 +278,7 @@ class TabPlane {
editor; editor;
/** /**
* The prefix of the HTML ID and class names * The prefix of the HTML ID and classes
* @type {string} * @type {string}
*/ */
prefix; prefix;
@ -1019,7 +1019,7 @@ class RecurringTransactionTab extends TabPlane {
* @return {string} the description of the recurring item * @return {string} the description of the recurring item
*/ */
#getDescription(itemButton) { #getDescription(itemButton) {
const today = new Date(this.editor.lineItemEditor.form.date); const today = new Date(this.editor.lineItemEditor.form.getDate());
const thisMonth = today.getMonth() + 1; const thisMonth = today.getMonth() + 1;
const lastMonth = (thisMonth + 10) % 12 + 1; const lastMonth = (thisMonth + 10) % 12 + 1;
const lastBimonthlyFrom = ((thisMonth + thisMonth % 2 + 8) % 12 + 1); const lastBimonthlyFrom = ((thisMonth + thisMonth % 2 + 8) % 12 + 1);

View File

@ -111,7 +111,6 @@ class JournalEntryForm {
constructor() { constructor() {
this.#element = document.getElementById("accounting-form"); this.#element = document.getElementById("accounting-form");
this.lineItemTemplate = this.#element.dataset.lineItemTemplate; this.lineItemTemplate = this.#element.dataset.lineItemTemplate;
this.lineItemEditor = new JournalEntryLineItemEditor(this);
this.#date = document.getElementById("accounting-date"); this.#date = document.getElementById("accounting-date");
this.#dateError = document.getElementById("accounting-date-error"); this.#dateError = document.getElementById("accounting-date-error");
this.#currencyControl = document.getElementById("accounting-currencies"); this.#currencyControl = document.getElementById("accounting-currencies");
@ -122,6 +121,7 @@ class JournalEntryForm {
this.#addCurrencyButton = document.getElementById("accounting-add-currency"); this.#addCurrencyButton = document.getElementById("accounting-add-currency");
this.#note = document.getElementById("accounting-note"); this.#note = document.getElementById("accounting-note");
this.#noteError = document.getElementById("accounting-note-error"); this.#noteError = document.getElementById("accounting-note-error");
this.lineItemEditor = new JournalEntryLineItemEditor(this);
this.#addCurrencyButton.onclick = () => { this.#addCurrencyButton.onclick = () => {
const newIndex = 1 + (this.#currencies.length === 0? 0: Math.max(...this.#currencies.map((currency) => currency.index))); const newIndex = 1 + (this.#currencies.length === 0? 0: Math.max(...this.#currencies.map((currency) => currency.index)));
@ -159,7 +159,7 @@ class JournalEntryForm {
*/ */
#resetDeleteCurrencyButtons() { #resetDeleteCurrencyButtons() {
if (this.#currencies.length === 1) { if (this.#currencies.length === 1) {
this.#currencies[0].setDeleteButtonShown(false); this.#currencies[0].deleteButton.classList.add("d-none");
} else { } else {
for (const currency of this.#currencies) { for (const currency of this.#currencies) {
let isAnyLineItemMatched = false; let isAnyLineItemMatched = false;
@ -169,7 +169,11 @@ class JournalEntryForm {
break; break;
} }
} }
currency.setDeleteButtonShown(!isAnyLineItemMatched); if (isAnyLineItemMatched) {
currency.deleteButton.classList.add("d-none");
} else {
currency.deleteButton.classList.remove("d-none");
}
} }
} }
} }
@ -180,8 +184,10 @@ class JournalEntryForm {
*/ */
#initializeDragAndDropReordering() { #initializeDragAndDropReordering() {
initializeDragAndDropReordering(this.#currencyList, () => { initializeDragAndDropReordering(this.#currencyList, () => {
for (const currency of this.#currencies) { const currencyId = Array.from(this.#currencyList.children).map((currency) => currency.id);
currency.resetNo(); this.#currencies.sort((a, b) => currencyId.indexOf(a.element.id) - currencyId.indexOf(b.element.id));
for (let i = 0; i < this.#currencies.length; i++) {
this.#currencies[i].no.value = String(i + 1);
} }
}); });
} }
@ -207,7 +213,7 @@ class JournalEntryForm {
* @return {string[]} the account codes used in the form * @return {string[]} the account codes used in the form
*/ */
getAccountCodesUsed(debitCredit) { getAccountCodesUsed(debitCredit) {
return this.getLineItems(debitCredit).map((lineItem) => lineItem.accountCode) return this.getLineItems(debitCredit).map((lineItem) => lineItem.getAccountCode())
.filter((code) => code !== null); .filter((code) => code !== null);
} }
@ -216,7 +222,7 @@ class JournalEntryForm {
* *
* @return {string} the date * @return {string} the date
*/ */
get date() { getDate() {
return this.#date.value; return this.#date.value;
} }
@ -227,7 +233,7 @@ class JournalEntryForm {
updateMinDate() { updateMinDate() {
let lastOriginalLineItemDate = null; let lastOriginalLineItemDate = null;
for (const lineItem of this.getLineItems()) { for (const lineItem of this.getLineItems()) {
const date = lineItem.originalLineItemDate; const date = lineItem.getOriginalLineItemDate();
if (date !== null) { if (date !== null) {
if (lastOriginalLineItemDate === null || lastOriginalLineItemDate < date) { if (lastOriginalLineItemDate === null || lastOriginalLineItemDate < date) {
lastOriginalLineItemDate = date; lastOriginalLineItemDate = date;
@ -343,7 +349,7 @@ class CurrencySubForm {
* The element * The element
* @type {HTMLDivElement} * @type {HTMLDivElement}
*/ */
#element; element;
/** /**
* The journal entry form * The journal entry form
@ -357,6 +363,12 @@ class CurrencySubForm {
*/ */
index; index;
/**
* The prefix of the HTML ID and class
* @type {string}
*/
#prefix;
/** /**
* The control * The control
* @type {HTMLDivElement} * @type {HTMLDivElement}
@ -373,7 +385,7 @@ class CurrencySubForm {
* The number * The number
* @type {HTMLInputElement} * @type {HTMLInputElement}
*/ */
#no; no;
/** /**
* The currency code * The currency code
@ -391,7 +403,7 @@ class CurrencySubForm {
* The button to delete the currency * The button to delete the currency
* @type {HTMLButtonElement} * @type {HTMLButtonElement}
*/ */
#deleteButton; deleteButton;
/** /**
* The debit sub-form * The debit sub-form
@ -412,58 +424,36 @@ class CurrencySubForm {
* @param element {HTMLDivElement} the currency sub-form element * @param element {HTMLDivElement} the currency sub-form element
*/ */
constructor(form, element) { constructor(form, element) {
this.#element = element; this.element = element;
this.form = form; this.form = form;
this.index = parseInt(this.#element.dataset.index); this.index = parseInt(this.element.dataset.index);
const prefix = "accounting-currency-" + String(this.index); this.#prefix = "accounting-currency-" + String(this.index);
this.#control = document.getElementById(prefix + "-control"); this.#control = document.getElementById(this.#prefix + "-control");
this.#error = document.getElementById(prefix + "-error"); this.#error = document.getElementById(this.#prefix + "-error");
this.#no = document.getElementById(prefix + "-no"); this.no = document.getElementById(this.#prefix + "-no");
this.#code = document.getElementById(prefix + "-code"); this.#code = document.getElementById(this.#prefix + "-code");
this.#codeSelect = document.getElementById(prefix + "-code-select"); this.#codeSelect = document.getElementById(this.#prefix + "-code-select");
this.#deleteButton = document.getElementById(prefix + "-delete"); this.deleteButton = document.getElementById(this.#prefix + "-delete");
const debitElement = document.getElementById(prefix + "-debit"); const debitElement = document.getElementById(this.#prefix + "-debit");
this.#debit = debitElement === null? null: new DebitCreditSubForm(this, debitElement, "debit"); this.#debit = debitElement === null? null: new DebitCreditSubForm(this, debitElement, "debit");
const creditElement = document.getElementById(prefix + "-credit"); const creditElement = document.getElementById(this.#prefix + "-credit");
this.#credit = creditElement == null? null: new DebitCreditSubForm(this, creditElement, "credit"); this.#credit = creditElement == null? null: new DebitCreditSubForm(this, creditElement, "credit");
this.#codeSelect.onchange = () => this.#code.value = this.#codeSelect.value; this.#codeSelect.onchange = () => this.#code.value = this.#codeSelect.value;
this.#deleteButton.onclick = () => { this.deleteButton.onclick = () => {
this.#element.parentElement.removeChild(this.#element); this.element.parentElement.removeChild(this.element);
this.form.deleteCurrency(this); this.form.deleteCurrency(this);
}; };
} }
/**
* Reset the order number.
*
*/
resetNo() {
const siblings = Array.from(this.#element.parentElement.children);
this.#no.value = String(siblings.indexOf(this.#element) + 1);
}
/** /**
* Returns the currency code. * Returns the currency code.
* *
* @return {string} the currency code * @return {string} the currency code
*/ */
get currencyCode() { getCurrencyCode() {
return this.#code.value; return this.#code.value;
} }
/**
* Sets whether the delete button is shown.
*
* @param isShown {boolean} true to show, or false otherwise
*/
setDeleteButtonShown(isShown) {
if (isShown) {
this.#deleteButton.classList.remove("d-none");
} else {
this.#deleteButton.classList.add("d-none");
}
}
/** /**
* Returns all the line items in the form. * Returns all the line items in the form.
* *
@ -489,7 +479,7 @@ class CurrencySubForm {
updateCodeSelectorStatus() { updateCodeSelectorStatus() {
let isEnabled = true; let isEnabled = true;
for (const lineItem of this.getLineItems()) { for (const lineItem of this.getLineItems()) {
if (lineItem.originalLineItemId !== null) { if (lineItem.getOriginalLineItemId() !== null) {
isEnabled = false; isEnabled = false;
break; break;
} }
@ -521,7 +511,7 @@ class CurrencySubForm {
*/ */
validateBalance() { validateBalance() {
if (this.#debit !== null && this.#credit !== null) { if (this.#debit !== null && this.#credit !== null) {
if (!this.#debit.total.equals(this.#credit.total)) { if (!this.#debit.getTotal().equals(this.#credit.getTotal())) {
this.#control.classList.add("is-invalid"); this.#control.classList.add("is-invalid");
this.#error.innerText = A_("The totals of the debit and credit amounts do not match."); this.#error.innerText = A_("The totals of the debit and credit amounts do not match.");
return false; return false;
@ -551,12 +541,6 @@ class DebitCreditSubForm {
*/ */
#element; #element;
/**
* The content
* @type {HTMLDivElement}
*/
#content;
/** /**
* The currencyIndex * The currencyIndex
* @type {number} * @type {number}
@ -570,7 +554,7 @@ class DebitCreditSubForm {
debitCredit; debitCredit;
/** /**
* The prefix of the HTML ID and class names * The prefix of the HTML ID and class
* @type {string} * @type {string}
*/ */
#prefix; #prefix;
@ -618,15 +602,12 @@ class DebitCreditSubForm {
this.#currencyIndex = currency.index; this.#currencyIndex = currency.index;
this.debitCredit = debitCredit; this.debitCredit = debitCredit;
this.#prefix = "accounting-currency-" + String(this.#currencyIndex) + "-" + debitCredit; this.#prefix = "accounting-currency-" + String(this.#currencyIndex) + "-" + debitCredit;
this.#content = document.getElementById(this.#prefix + "-content");
this.#error = document.getElementById(this.#prefix + "-error"); this.#error = document.getElementById(this.#prefix + "-error");
this.#lineItemList = document.getElementById(this.#prefix + "-list"); this.#lineItemList = document.getElementById(this.#prefix + "-list");
// noinspection JSValidateTypes // noinspection JSValidateTypes
this.lineItems = Array.from(document.getElementsByClassName(this.#prefix)).map((element) => new LineItemSubForm(this, element)); this.lineItems = Array.from(document.getElementsByClassName(this.#prefix)).map((element) => new LineItemSubForm(this, element));
this.#total = document.getElementById(this.#prefix + "-total"); this.#total = document.getElementById(this.#prefix + "-total");
this.#addLineItemButton = document.getElementById(this.#prefix + "-add-line-item"); this.#addLineItemButton = document.getElementById(this.#prefix + "-add-line-item");
this.#resetContent();
this.#addLineItemButton.onclick = () => this.currency.form.lineItemEditor.onAddNew(this); this.#addLineItemButton.onclick = () => this.currency.form.lineItemEditor.onAddNew(this);
this.#resetDeleteLineItemButtons(); this.#resetDeleteLineItemButtons();
this.#initializeDragAndDropReordering(); this.#initializeDragAndDropReordering();
@ -638,7 +619,7 @@ class DebitCreditSubForm {
* @returns {LineItemSubForm} the newly-added line item sub-form * @returns {LineItemSubForm} the newly-added line item sub-form
*/ */
addLineItem() { addLineItem() {
const newIndex = 1 + (this.lineItems.length === 0? 0: Math.max(...this.lineItems.map((lineItem) => lineItem.index))); const newIndex = 1 + (this.lineItems.length === 0? 0: Math.max(...this.lineItems.map((lineItem) => lineItem.lineItemIndex)));
const html = this.currency.form.lineItemTemplate const html = this.currency.form.lineItemTemplate
.replaceAll("CURRENCY_INDEX", escapeHtml(String(this.#currencyIndex))) .replaceAll("CURRENCY_INDEX", escapeHtml(String(this.#currencyIndex)))
.replaceAll("DEBIT_CREDIT", escapeHtml(this.debitCredit)) .replaceAll("DEBIT_CREDIT", escapeHtml(this.debitCredit))
@ -646,7 +627,6 @@ class DebitCreditSubForm {
this.#lineItemList.insertAdjacentHTML("beforeend", html); this.#lineItemList.insertAdjacentHTML("beforeend", html);
const lineItem = new LineItemSubForm(this, document.getElementById(this.#prefix + "-" + String(newIndex))); const lineItem = new LineItemSubForm(this, document.getElementById(this.#prefix + "-" + String(newIndex)));
this.lineItems.push(lineItem); this.lineItems.push(lineItem);
this.#resetContent();
this.#resetDeleteLineItemButtons(); this.#resetDeleteLineItemButtons();
this.#initializeDragAndDropReordering(); this.#initializeDragAndDropReordering();
this.validate(); this.validate();
@ -664,7 +644,6 @@ class DebitCreditSubForm {
this.updateTotal(); this.updateTotal();
this.currency.updateCodeSelectorStatus(); this.currency.updateCodeSelectorStatus();
this.currency.form.updateMinDate(); this.currency.form.updateMinDate();
this.#resetContent();
this.#resetDeleteLineItemButtons(); this.#resetDeleteLineItemButtons();
} }
@ -674,45 +653,27 @@ class DebitCreditSubForm {
*/ */
#resetDeleteLineItemButtons() { #resetDeleteLineItemButtons() {
if (this.lineItems.length === 1) { if (this.lineItems.length === 1) {
this.lineItems[0].setDeleteButtonShown(false); this.lineItems[0].deleteButton.classList.add("d-none");
} else { } else {
for (const lineItem of this.lineItems) { for (const lineItem of this.lineItems) {
lineItem.setDeleteButtonShown(!lineItem.isMatched); if (lineItem.isMatched) {
lineItem.deleteButton.classList.add("d-none");
} else {
lineItem.deleteButton.classList.remove("d-none");
}
} }
} }
} }
/**
* Resets the layout of the content.
*
*/
#resetContent() {
if (this.lineItems.length === 0) {
this.#element.classList.remove("accounting-not-empty");
this.#element.classList.add("accounting-clickable");
this.#element.dataset.bsToggle = "modal"
this.#element.dataset.bsTarget = "#" + this.currency.form.lineItemEditor.modal.id;
this.#element.onclick = () => this.currency.form.lineItemEditor.onAddNew(this);
this.#content.classList.add("d-none");
} else {
this.#element.classList.add("accounting-not-empty");
this.#element.classList.remove("accounting-clickable");
delete this.#element.dataset.bsToggle;
delete this.#element.dataset.bsTarget;
this.#element.onclick = null;
this.#content.classList.remove("d-none");
}
}
/** /**
* Returns the total amount. * Returns the total amount.
* *
* @return {Decimal} the total amount * @return {Decimal} the total amount
*/ */
get total() { getTotal() {
let total = new Decimal("0"); let total = new Decimal("0");
for (const lineItem of this.lineItems) { for (const lineItem of this.lineItems) {
const amount = lineItem.amount; const amount = lineItem.getAmount();
if (amount !== null) { if (amount !== null) {
total = total.plus(amount); total = total.plus(amount);
} }
@ -725,7 +686,7 @@ class DebitCreditSubForm {
* *
*/ */
updateTotal() { updateTotal() {
this.#total.innerText = formatDecimal(this.total); this.#total.innerText = formatDecimal(this.getTotal());
this.currency.validateBalance(); this.currency.validateBalance();
} }
@ -735,8 +696,10 @@ class DebitCreditSubForm {
*/ */
#initializeDragAndDropReordering() { #initializeDragAndDropReordering() {
initializeDragAndDropReordering(this.#lineItemList, () => { initializeDragAndDropReordering(this.#lineItemList, () => {
for (const lineItem of this.lineItems) { const lineItemId = Array.from(this.#lineItemList.children).map((lineItem) => lineItem.id);
lineItem.resetNo(); this.lineItems.sort((a, b) => lineItemId.indexOf(a.element.id) - lineItemId.indexOf(b.element.id));
for (let i = 0; i < this.lineItems.length; i++) {
this.lineItems[i].no.value = String(i + 1);
} }
}); });
} }
@ -788,7 +751,7 @@ class LineItemSubForm {
* The element * The element
* @type {HTMLLIElement} * @type {HTMLLIElement}
*/ */
#element; element;
/** /**
* Either "debit" or "credit" * Either "debit" or "credit"
@ -800,7 +763,7 @@ class LineItemSubForm {
* The line item index * The line item index
* @type {number} * @type {number}
*/ */
index; lineItemIndex;
/** /**
* Whether this is an original line item with offsets * Whether this is an original line item with offsets
@ -808,6 +771,12 @@ class LineItemSubForm {
*/ */
isMatched; isMatched;
/**
* The prefix of the HTML ID and class
* @type {string}
*/
#prefix;
/** /**
* The control * The control
* @type {HTMLDivElement} * @type {HTMLDivElement}
@ -824,7 +793,7 @@ class LineItemSubForm {
* The number * The number
* @type {HTMLInputElement} * @type {HTMLInputElement}
*/ */
#no; no;
/** /**
* The account code * The account code
@ -884,7 +853,7 @@ class LineItemSubForm {
* The button to delete line item * The button to delete line item
* @type {HTMLButtonElement} * @type {HTMLButtonElement}
*/ */
#deleteButton; deleteButton;
/** /**
* Constructs the line item sub-form. * Constructs the line item sub-form.
@ -894,47 +863,38 @@ class LineItemSubForm {
*/ */
constructor(debitCredit, element) { constructor(debitCredit, element) {
this.debitCreditSubForm = debitCredit; this.debitCreditSubForm = debitCredit;
this.#element = element; this.element = element;
this.debitCredit = element.dataset.debitCredit; this.debitCredit = element.dataset.debitCredit;
this.index = parseInt(element.dataset.lineItemIndex); this.lineItemIndex = parseInt(element.dataset.lineItemIndex);
this.isMatched = element.classList.contains("accounting-matched-line-item"); this.isMatched = element.classList.contains("accounting-matched-line-item");
const prefix = "accounting-currency-" + element.dataset.currencyIndex + "-" + this.debitCredit + "-" + String(this.index); this.#prefix = "accounting-currency-" + element.dataset.currencyIndex + "-" + this.debitCredit + "-" + this.lineItemIndex;
this.#control = document.getElementById(prefix + "-control"); this.#control = document.getElementById(this.#prefix + "-control");
this.#error = document.getElementById(prefix + "-error"); this.#error = document.getElementById(this.#prefix + "-error");
this.#no = document.getElementById(prefix + "-no"); this.no = document.getElementById(this.#prefix + "-no");
this.#accountCode = document.getElementById(prefix + "-account-code"); this.#accountCode = document.getElementById(this.#prefix + "-account-code");
this.#accountText = document.getElementById(prefix + "-account-text"); this.#accountText = document.getElementById(this.#prefix + "-account-text");
this.#description = document.getElementById(prefix + "-description"); this.#description = document.getElementById(this.#prefix + "-description");
this.#descriptionText = document.getElementById(prefix + "-description-text"); this.#descriptionText = document.getElementById(this.#prefix + "-description-text");
this.#originalLineItemId = document.getElementById(prefix + "-original-line-item-id"); this.#originalLineItemId = document.getElementById(this.#prefix + "-original-line-item-id");
this.#originalLineItemText = document.getElementById(prefix + "-original-line-item-text"); this.#originalLineItemText = document.getElementById(this.#prefix + "-original-line-item-text");
this.#offsets = document.getElementById(prefix + "-offsets"); this.#offsets = document.getElementById(this.#prefix + "-offsets");
this.#amount = document.getElementById(prefix + "-amount"); this.#amount = document.getElementById(this.#prefix + "-amount");
this.#amountText = document.getElementById(prefix + "-amount-text"); this.#amountText = document.getElementById(this.#prefix + "-amount-text");
this.#deleteButton = document.getElementById(prefix + "-delete"); this.deleteButton = document.getElementById(this.#prefix + "-delete");
this.#control.onclick = () => this.debitCreditSubForm.currency.form.lineItemEditor.onEdit(this); this.#control.onclick = () => this.debitCreditSubForm.currency.form.lineItemEditor.onEdit(this);
this.#deleteButton.onclick = () => { this.deleteButton.onclick = () => {
this.#element.parentElement.removeChild(this.#element); this.element.parentElement.removeChild(this.element);
this.debitCreditSubForm.deleteLineItem(this); this.debitCreditSubForm.deleteLineItem(this);
}; };
} }
/**
* Reset the order number.
*
*/
resetNo() {
const siblings = Array.from(this.#element.parentElement.children);
this.#no.value = String(siblings.indexOf(this.#element) + 1);
}
/** /**
* Returns whether the line item needs offset. * Returns whether the line item needs offset.
* *
* @return {boolean} true if the line item needs offset, or false otherwise * @return {boolean} true if the line item needs offset, or false otherwise
*/ */
get isNeedOffset() { isNeedOffset() {
return "isNeedOffset" in this.#element.dataset; return "isNeedOffset" in this.element.dataset;
} }
/** /**
@ -942,7 +902,7 @@ class LineItemSubForm {
* *
* @return {string|null} the ID of the original line item * @return {string|null} the ID of the original line item
*/ */
get originalLineItemId() { getOriginalLineItemId() {
return this.#originalLineItemId.value === ""? null: this.#originalLineItemId.value; return this.#originalLineItemId.value === ""? null: this.#originalLineItemId.value;
} }
@ -951,7 +911,7 @@ class LineItemSubForm {
* *
* @return {string|null} the date of the original line item * @return {string|null} the date of the original line item
*/ */
get originalLineItemDate() { getOriginalLineItemDate() {
return this.#originalLineItemId.dataset.date === ""? null: this.#originalLineItemId.dataset.date; return this.#originalLineItemId.dataset.date === ""? null: this.#originalLineItemId.dataset.date;
} }
@ -960,7 +920,7 @@ class LineItemSubForm {
* *
* @return {string|null} the text of the original line item * @return {string|null} the text of the original line item
*/ */
get originalLineItemText() { getOriginalLineItemText() {
return this.#originalLineItemId.dataset.text === ""? null: this.#originalLineItemId.dataset.text; return this.#originalLineItemId.dataset.text === ""? null: this.#originalLineItemId.dataset.text;
} }
@ -969,7 +929,7 @@ class LineItemSubForm {
* *
* @return {string|null} the description * @return {string|null} the description
*/ */
get description() { getDescription() {
return this.#description.value === ""? null: this.#description.value; return this.#description.value === ""? null: this.#description.value;
} }
@ -978,7 +938,7 @@ class LineItemSubForm {
* *
* @return {string|null} the account code * @return {string|null} the account code
*/ */
get accountCode() { getAccountCode() {
return this.#accountCode.value === ""? null: this.#accountCode.value; return this.#accountCode.value === ""? null: this.#accountCode.value;
} }
@ -987,7 +947,7 @@ class LineItemSubForm {
* *
* @return {string|null} the account text * @return {string|null} the account text
*/ */
get accountText() { getAccountText() {
return this.#accountCode.dataset.text === ""? null: this.#accountCode.dataset.text; return this.#accountCode.dataset.text === ""? null: this.#accountCode.dataset.text;
} }
@ -996,7 +956,7 @@ class LineItemSubForm {
* *
* @return {Decimal|null} the amount * @return {Decimal|null} the amount
*/ */
get amount() { getAmount() {
return this.#amount.value === ""? null: new Decimal(this.#amount.value); return this.#amount.value === ""? null: new Decimal(this.#amount.value);
} }
@ -1005,23 +965,10 @@ class LineItemSubForm {
* *
* @return {Decimal|null} the minimal amount * @return {Decimal|null} the minimal amount
*/ */
get amountMin() { getAmountMin() {
return this.#amount.dataset.min === ""? null: new Decimal(this.#amount.dataset.min); return this.#amount.dataset.min === ""? null: new Decimal(this.#amount.dataset.min);
} }
/**
* Sets whether the delete button is shown.
*
* @param isShown {boolean} true to show, or false otherwise
*/
setDeleteButtonShown(isShown) {
if (isShown) {
this.#deleteButton.classList.remove("d-none");
} else {
this.#deleteButton.classList.add("d-none");
}
}
/** /**
* Validates the form. * Validates the form.
* *

View File

@ -44,7 +44,7 @@ class JournalEntryLineItemEditor {
* The bootstrap modal * The bootstrap modal
* @type {HTMLDivElement} * @type {HTMLDivElement}
*/ */
modal; #modal;
/** /**
* Either "debit" or "credit" * Either "debit" or "credit"
@ -53,7 +53,7 @@ class JournalEntryLineItemEditor {
debitCredit; debitCredit;
/** /**
* The prefix of the HTML ID and class names * The prefix of the HTML ID and class
* @type {string} * @type {string}
*/ */
#prefix = "accounting-line-item-editor" #prefix = "accounting-line-item-editor"
@ -190,6 +190,12 @@ class JournalEntryLineItemEditor {
*/ */
description = null; description = null;
/**
* The amount
* @type {string}
*/
amount = "";
/** /**
* The description editors * The description editors
* @type {{debit: DescriptionEditor, credit: DescriptionEditor}} * @type {{debit: DescriptionEditor, credit: DescriptionEditor}}
@ -216,7 +222,7 @@ class JournalEntryLineItemEditor {
constructor(form) { constructor(form) {
this.form = form; this.form = form;
this.#element = document.getElementById(this.#prefix); this.#element = document.getElementById(this.#prefix);
this.modal = document.getElementById(this.#prefix + "-modal"); this.#modal = document.getElementById(this.#prefix + "-modal");
this.#originalLineItemContainer = document.getElementById(this.#prefix + "-original-line-item-container"); this.#originalLineItemContainer = document.getElementById(this.#prefix + "-original-line-item-container");
this.#originalLineItemControl = document.getElementById(this.#prefix + "-original-line-item-control"); this.#originalLineItemControl = document.getElementById(this.#prefix + "-original-line-item-control");
this.#originalLineItemText = document.getElementById(this.#prefix + "-original-line-item"); this.#originalLineItemText = document.getElementById(this.#prefix + "-original-line-item");
@ -243,31 +249,14 @@ class JournalEntryLineItemEditor {
if (this.lineItem === null) { if (this.lineItem === null) {
this.lineItem = this.#debitCreditSubForm.addLineItem(); this.lineItem = this.#debitCreditSubForm.addLineItem();
} }
this.amount = this.#amountInput.value;
this.lineItem.save(this); this.lineItem.save(this);
bootstrap.Modal.getInstance(this.modal).hide(); bootstrap.Modal.getInstance(this.#modal).hide();
} }
return false; return false;
}; };
} }
/**
* Returns the amount.
*
* @return {string} the amount
*/
get amount() {
return this.#amountInput.value;
}
/**
* Returns the currency code.
*
* @return {string} the currency code
*/
get currencyCode() {
return this.#debitCreditSubForm.currency.currencyCode;
}
/** /**
* Saves the original line item from the original line item selector. * Saves the original line item from the original line item selector.
* *
@ -319,6 +308,15 @@ class JournalEntryLineItemEditor {
this.#amountInput.max = ""; this.#amountInput.max = "";
} }
/**
* Returns the currency code.
*
* @return {string} the currency code
*/
getCurrencyCode() {
return this.#debitCreditSubForm.currency.getCurrencyCode();
}
/** /**
* Saves the description from the description editor. * Saves the description from the description editor.
* *
@ -333,7 +331,7 @@ class JournalEntryLineItemEditor {
this.description = description === ""? null: description; this.description = description === ""? null: description;
this.#descriptionText.innerText = description; this.#descriptionText.innerText = description;
this.#validateDescription(); this.#validateDescription();
bootstrap.Modal.getOrCreateInstance(this.modal).show(); bootstrap.Modal.getOrCreateInstance(this.#modal).show();
} }
/** /**
@ -522,10 +520,10 @@ class JournalEntryLineItemEditor {
this.lineItem = lineItem; this.lineItem = lineItem;
this.#debitCreditSubForm = lineItem.debitCreditSubForm; this.#debitCreditSubForm = lineItem.debitCreditSubForm;
this.debitCredit = this.#debitCreditSubForm.debitCredit; this.debitCredit = this.#debitCreditSubForm.debitCredit;
this.isNeedOffset = lineItem.isNeedOffset; this.isNeedOffset = lineItem.isNeedOffset();
this.originalLineItemId = lineItem.originalLineItemId; this.originalLineItemId = lineItem.getOriginalLineItemId();
this.originalLineItemDate = lineItem.originalLineItemDate; this.originalLineItemDate = lineItem.getOriginalLineItemDate();
this.originalLineItemText = lineItem.originalLineItemText; this.originalLineItemText = lineItem.getOriginalLineItemText();
this.#originalLineItemText.innerText = this.originalLineItemText; this.#originalLineItemText.innerText = this.originalLineItemText;
if (this.originalLineItemId === null) { if (this.originalLineItemId === null) {
this.#originalLineItemContainer.classList.add("d-none"); this.#originalLineItemContainer.classList.add("d-none");
@ -535,25 +533,25 @@ class JournalEntryLineItemEditor {
this.#originalLineItemControl.classList.add("accounting-not-empty"); this.#originalLineItemControl.classList.add("accounting-not-empty");
} }
this.#setEnableDescriptionAccount(!lineItem.isMatched && this.originalLineItemId === null); this.#setEnableDescriptionAccount(!lineItem.isMatched && this.originalLineItemId === null);
this.description = lineItem.description; this.description = lineItem.getDescription();
if (this.description === null) { if (this.description === null) {
this.#descriptionControl.classList.remove("accounting-not-empty"); this.#descriptionControl.classList.remove("accounting-not-empty");
} else { } else {
this.#descriptionControl.classList.add("accounting-not-empty"); this.#descriptionControl.classList.add("accounting-not-empty");
} }
this.#descriptionText.innerText = this.description === null? "": this.description; this.#descriptionText.innerText = this.description === null? "": this.description;
if (lineItem.accountCode === null) { if (lineItem.getAccountCode() === null) {
this.#accountControl.classList.remove("accounting-not-empty"); this.#accountControl.classList.remove("accounting-not-empty");
} else { } else {
this.#accountControl.classList.add("accounting-not-empty"); this.#accountControl.classList.add("accounting-not-empty");
} }
this.accountCode = lineItem.accountCode; this.accountCode = lineItem.getAccountCode();
this.accountText = lineItem.accountText; this.accountText = lineItem.getAccountText();
this.#accountText.innerText = this.accountText; this.#accountText.innerText = this.accountText;
this.#amountInput.value = lineItem.amount === null? "": String(lineItem.amount); this.#amountInput.value = lineItem.getAmount() === null? "": String(lineItem.getAmount());
const maxAmount = this.#getMaxAmount(); const maxAmount = this.#getMaxAmount();
this.#amountInput.max = maxAmount === null? "": maxAmount; this.#amountInput.max = maxAmount === null? "": maxAmount;
this.#amountInput.min = lineItem.amountMin === null? "": String(lineItem.amountMin); this.#amountInput.min = lineItem.getAmountMin() === null? "": String(lineItem.getAmountMin());
this.#validate(); this.#validate();
} }

View File

@ -197,7 +197,7 @@ class RecurringExpenseIncomeSubForm {
editor; editor;
/** /**
* The prefix of the HTML ID and class names * The prefix of HTML ID and class
* @type {string} * @type {string}
*/ */
#prefix; #prefix;
@ -313,8 +313,10 @@ class RecurringExpenseIncomeSubForm {
*/ */
#initializeDragAndDropReordering() { #initializeDragAndDropReordering() {
initializeDragAndDropReordering(this.#itemList, () => { initializeDragAndDropReordering(this.#itemList, () => {
for (const item of this.#items) { const itemId = Array.from(this.#itemList.children).map((item) => item.id);
item.resetNo(); this.#items.sort((a, b) => itemId.indexOf(a.element.id) - itemId.indexOf(b.element.id));
for (let i = 0; i < this.#items.length; i++) {
this.#items[i].no.value = String(i + 1);
} }
}); });
} }
@ -363,7 +365,7 @@ class RecurringItemSubForm {
* The element * The element
* @type {HTMLLIElement} * @type {HTMLLIElement}
*/ */
#element; element;
/** /**
* The item index * The item index
@ -384,10 +386,10 @@ class RecurringItemSubForm {
#error; #error;
/** /**
* The order number * The number
* @type {HTMLInputElement} * @type {HTMLInputElement}
*/ */
#no; no;
/** /**
* The name input * The name input
@ -439,12 +441,12 @@ class RecurringItemSubForm {
*/ */
constructor(expenseIncomeSubForm, element) { constructor(expenseIncomeSubForm, element) {
this.#expenseIncomeSubForm = expenseIncomeSubForm this.#expenseIncomeSubForm = expenseIncomeSubForm
this.#element = element; this.element = element;
this.itemIndex = parseInt(element.dataset.itemIndex); this.itemIndex = parseInt(element.dataset.itemIndex);
const prefix = "accounting-recurring-" + expenseIncomeSubForm.expenseIncome + "-" + element.dataset.itemIndex; const prefix = "accounting-recurring-" + expenseIncomeSubForm.expenseIncome + "-" + element.dataset.itemIndex;
this.#control = document.getElementById(prefix + "-control"); this.#control = document.getElementById(prefix + "-control");
this.#error = document.getElementById(prefix + "-error"); this.#error = document.getElementById(prefix + "-error");
this.#no = document.getElementById(prefix + "-no"); this.no = document.getElementById(prefix + "-no");
this.#name = document.getElementById(prefix + "-name"); this.#name = document.getElementById(prefix + "-name");
this.#nameText = document.getElementById(prefix + "-name-text"); this.#nameText = document.getElementById(prefix + "-name-text");
this.#accountCode = document.getElementById(prefix + "-account-code"); this.#accountCode = document.getElementById(prefix + "-account-code");
@ -455,26 +457,17 @@ class RecurringItemSubForm {
this.#control.onclick = () => this.#expenseIncomeSubForm.editor.onEdit(this); this.#control.onclick = () => this.#expenseIncomeSubForm.editor.onEdit(this);
this.deleteButton.onclick = () => { this.deleteButton.onclick = () => {
this.#element.parentElement.removeChild(this.#element); this.element.parentElement.removeChild(this.element);
this.#expenseIncomeSubForm.deleteItem(this); this.#expenseIncomeSubForm.deleteItem(this);
}; };
} }
/**
* Reset the order number.
*
*/
resetNo() {
const siblings = Array.from(this.#element.parentElement.children);
this.#no.value = String(siblings.indexOf(this.#element) + 1);
}
/** /**
* Returns the name. * Returns the name.
* *
* @return {string|null} the name * @return {string|null} the name
*/ */
get name() { getName() {
return this.#name.value === ""? null: this.#name.value; return this.#name.value === ""? null: this.#name.value;
} }
@ -483,7 +476,7 @@ class RecurringItemSubForm {
* *
* @return {string|null} the account code * @return {string|null} the account code
*/ */
get accountCode() { getAccountCode() {
return this.#accountCode.value === ""? null: this.#accountCode.value; return this.#accountCode.value === ""? null: this.#accountCode.value;
} }
@ -492,7 +485,7 @@ class RecurringItemSubForm {
* *
* @return {string|null} the account text * @return {string|null} the account text
*/ */
get accountText() { getAccountText() {
return this.#accountCode.dataset.text === ""? null: this.#accountCode.dataset.text; return this.#accountCode.dataset.text === ""? null: this.#accountCode.dataset.text;
} }
@ -501,7 +494,7 @@ class RecurringItemSubForm {
* *
* @return {string|null} the description template * @return {string|null} the description template
*/ */
get descriptionTemplate() { getDescriptionTemplate() {
return this.#descriptionTemplate.value === ""? null: this.#descriptionTemplate.value; return this.#descriptionTemplate.value === ""? null: this.#descriptionTemplate.value;
} }
@ -511,12 +504,12 @@ class RecurringItemSubForm {
* @param editor {RecurringItemEditor} the recurring item editor * @param editor {RecurringItemEditor} the recurring item editor
*/ */
save(editor) { save(editor) {
this.#name.value = editor.name === null? "": editor.name; this.#name.value = editor.getName() === null? "": editor.getName();
this.#nameText.innerText = this.#name.value; this.#nameText.innerText = this.#name.value;
this.#accountCode.value = editor.accountCode; this.#accountCode.value = editor.accountCode;
this.#accountCode.dataset.text = editor.accountText; this.#accountCode.dataset.text = editor.accountText;
this.#accountText.innerText = editor.accountText; this.#accountText.innerText = editor.accountText;
this.#descriptionTemplate.value = editor.descriptionTemplate === null? "": editor.descriptionTemplate; this.#descriptionTemplate.value = editor.getDescriptionTemplate() === null? "": editor.getDescriptionTemplate();
this.#descriptionTemplateText.innerText = this.#descriptionTemplate.value; this.#descriptionTemplateText.innerText = this.#descriptionTemplate.value;
this.validate(); this.validate();
} }
@ -684,7 +677,7 @@ class RecurringItemEditor {
* *
* @return {string|null} the name * @return {string|null} the name
*/ */
get name() { getName() {
return this.#name.value === ""? null: this.#name.value; return this.#name.value === ""? null: this.#name.value;
} }
@ -693,7 +686,7 @@ class RecurringItemEditor {
* *
* @return {string|null} the description template * @return {string|null} the description template
*/ */
get descriptionTemplate() { getDescriptionTemplate() {
return this.#descriptionTemplate.value === ""? null: this.#descriptionTemplate.value; return this.#descriptionTemplate.value === ""? null: this.#descriptionTemplate.value;
} }
@ -749,16 +742,16 @@ class RecurringItemEditor {
*/ */
onEdit(item) { onEdit(item) {
this.#item = item; this.#item = item;
this.#name.value = item.name === null? "": item.name; this.#name.value = item.getName() === null? "": item.getName();
this.accountCode = item.accountCode; this.accountCode = item.getAccountCode();
this.accountText = item.accountText; this.accountText = item.getAccountText();
if (this.accountText === null) { if (this.accountText === null) {
this.#accountControl.classList.remove("accounting-not-empty"); this.#accountControl.classList.remove("accounting-not-empty");
} else { } else {
this.#accountControl.classList.add("accounting-not-empty"); this.#accountControl.classList.add("accounting-not-empty");
} }
this.#accountContainer.innerText = this.accountText === null? "": this.accountText; this.#accountContainer.innerText = item.getAccountText() == null? "": item.getAccountText();
this.#descriptionTemplate.value = item.descriptionTemplate === null? "": item.descriptionTemplate; this.#descriptionTemplate.value = item.getDescriptionTemplate() === null? "": item.getDescriptionTemplate();
this.#validate(); this.#validate();
} }

View File

@ -35,7 +35,7 @@ class OriginalLineItemSelector {
lineItemEditor; lineItemEditor;
/** /**
* The prefix of the HTML ID and class names * The prefix of the HTML ID and class
* @type {string} * @type {string}
*/ */
#prefix = "accounting-original-line-item-selector"; #prefix = "accounting-original-line-item-selector";
@ -113,8 +113,8 @@ class OriginalLineItemSelector {
const otherLineItems = form.getLineItems().filter((lineItem) => lineItem !== currentLineItem); const otherLineItems = form.getLineItems().filter((lineItem) => lineItem !== currentLineItem);
let otherOffset = new Decimal(0); let otherOffset = new Decimal(0);
for (const otherLineItem of otherLineItems) { for (const otherLineItem of otherLineItems) {
if (otherLineItem.originalLineItemId === originalLineItemId) { if (otherLineItem.getOriginalLineItemId() === originalLineItemId) {
const amount = otherLineItem.amount; const amount = otherLineItem.getAmount();
if (amount !== null) { if (amount !== null) {
otherOffset = otherOffset.plus(amount); otherOffset = otherOffset.plus(amount);
} }
@ -131,8 +131,8 @@ class OriginalLineItemSelector {
const otherLineItems = this.lineItemEditor.form.getLineItems().filter((lineItem) => lineItem !== this.lineItemEditor.lineItem); const otherLineItems = this.lineItemEditor.form.getLineItems().filter((lineItem) => lineItem !== this.lineItemEditor.lineItem);
const otherOffsets = {} const otherOffsets = {}
for (const otherLineItem of otherLineItems) { for (const otherLineItem of otherLineItems) {
const otherOriginalLineItemId = otherLineItem.originalLineItemId; const otherOriginalLineItemId = otherLineItem.getOriginalLineItemId();
const amount = otherLineItem.amount; const amount = otherLineItem.getAmount();
if (otherOriginalLineItemId === null || amount === null) { if (otherOriginalLineItemId === null || amount === null) {
continue; continue;
} }
@ -178,7 +178,7 @@ class OriginalLineItemSelector {
* *
*/ */
onOpen() { onOpen() {
this.#currencyCode = this.lineItemEditor.currencyCode; this.#currencyCode = this.lineItemEditor.getCurrencyCode();
this.#debitCredit = this.lineItemEditor.debitCredit; this.#debitCredit = this.lineItemEditor.debitCredit;
for (const option of this.#options) { for (const option of this.#options) {
option.setActive(option.id === this.lineItemEditor.originalLineItemId); option.setActive(option.id === this.lineItemEditor.originalLineItemId);
@ -341,7 +341,7 @@ class OriginalLineItem {
*/ */
isMatched(debitCredit, currencyCode, query = null) { isMatched(debitCredit, currencyCode, query = null) {
return this.netBalance.greaterThan(0) return this.netBalance.greaterThan(0)
&& this.date <= this.#selector.lineItemEditor.form.date && this.date <= this.#selector.lineItemEditor.form.getDate()
&& this.#isDebitCreditMatches(debitCredit) && this.#isDebitCreditMatches(debitCredit)
&& this.#currencyCode === currencyCode && this.#currencyCode === currencyCode
&& this.#isQueryMatches(query); && this.#isQueryMatches(query);

View File

@ -33,6 +33,12 @@ document.addEventListener("DOMContentLoaded", () => {
*/ */
class PeriodChooser { class PeriodChooser {
/**
* The prefix of the HTML ID and class
* @type {string}
*/
prefix;
/** /**
* The modal of the period chooser * The modal of the period chooser
* @type {HTMLDivElement} * @type {HTMLDivElement}
@ -50,8 +56,8 @@ class PeriodChooser {
* *
*/ */
constructor() { constructor() {
const prefix = "accounting-period-chooser"; this.prefix = "accounting-period-chooser";
this.modal = document.getElementById(prefix + "-modal"); this.modal = document.getElementById(this.prefix + "-modal");
for (const cls of [MonthTab, YearTab, DayTab, CustomTab]) { for (const cls of [MonthTab, YearTab, DayTab, CustomTab]) {
const tab = new cls(this); const tab = new cls(this);
this.tabPlanes[tab.tabId()] = tab; this.tabPlanes[tab.tabId()] = tab;
@ -88,7 +94,7 @@ class TabPlane {
chooser; chooser;
/** /**
* The prefix of the HTML ID and class names * The prefix of the HTML ID and class
* @type {string} * @type {string}
*/ */
prefix; prefix;

View File

@ -20,31 +20,29 @@ Author: imacat@mail.imacat.idv.tw (imacat)
First written: 2023/3/21 First written: 2023/3/21
#} #}
<div class="mb-2"> <div class="mb-2">
<div id="accounting-currency-{{ currency_index }}-{{ debit_credit }}" class="form-control accounting-material-text-field {% if line_item_forms %} accounting-not-empty {% else %} accounting-clickable {% endif %} {% if debit_errors %} is-invalid {% endif %}"> <div id="accounting-currency-{{ currency_index }}-{{ debit_credit }}" class="form-control accounting-material-text-field accounting-not-empty {% if debit_errors %} is-invalid {% endif %}">
<label class="form-label" for="accounting-currency-{{ currency_index }}-{{ debit_credit }}">{{ header }}</label> <label class="form-label" for="accounting-currency-{{ currency_index }}-{{ debit_credit }}">{{ header }}</label>
<div id="accounting-currency-{{ currency_index }}-{{ debit_credit }}-content" class="mt-2 {% if not line_item_forms %} d-none {% endif %}"> <ul id="accounting-currency-{{ currency_index }}-{{ debit_credit }}-list" class="list-group accounting-line-item-list">
<ul id="accounting-currency-{{ currency_index }}-{{ debit_credit }}-list" class="list-group accounting-line-item-list"> {% for line_item_form in line_item_forms %}
{% for line_item_form in line_item_forms %} {% with currency_index = currency_index,
{% with currency_index = currency_index, line_item_index = loop.index,
line_item_index = loop.index, only_one_line_item_form = line_item_forms|length == 1,
only_one_line_item_form = line_item_forms|length == 1, form = line_item_form.form %}
form = line_item_form.form %} {% include "accounting/journal-entry/include/form-line-item.html" %}
{% include "accounting/journal-entry/include/form-line-item.html" %} {% endwith %}
{% endwith %} {% endfor %}
{% endfor %} </ul>
</ul>
<div class="d-flex justify-content-between mt-2 mb-2"> <div class="d-flex justify-content-between mb-2">
<div>{{ A_("Total") }}</div> <div>{{ A_("Total") }}</div>
<div><span id="accounting-currency-{{ currency_index }}-{{ debit_credit }}-total" class="badge rounded-pill bg-primary">{{ debit_credit_total }}</span></div> <div><span id="accounting-currency-{{ currency_index }}-{{ debit_credit }}-total" class="badge rounded-pill bg-primary">{{ debit_credit_total }}</span></div>
</div> </div>
<div> <div>
<button id="accounting-currency-{{ currency_index }}-{{ debit_credit }}-add-line-item" class="btn btn-primary" type="button" data-currency-index="{{ currency_index }}" data-debit-credit="{{ debit_credit }}" data-bs-toggle="modal" data-bs-target="#accounting-line-item-editor-modal"> <button id="accounting-currency-{{ currency_index }}-{{ debit_credit }}-add-line-item" class="btn btn-primary" type="button" data-currency-index="{{ currency_index }}" data-debit-credit="{{ debit_credit }}" data-bs-toggle="modal" data-bs-target="#accounting-line-item-editor-modal">
<i class="fas fa-plus"></i> <i class="fas fa-plus"></i>
{{ A_("New") }} {{ A_("New") }}
</button> </button>
</div>
</div> </div>
</div> </div>
<div id="accounting-currency-{{ currency_index }}-{{ debit_credit }}-error" class="invalid-feedback">{% if debit_credit_errors %}{{ debit_credit_errors[0] }}{% endif %}</div> <div id="accounting-currency-{{ currency_index }}-{{ debit_credit }}-error" class="invalid-feedback">{% if debit_credit_errors %}{{ debit_credit_errors[0] }}{% endif %}</div>