Compare commits

..

9 Commits

56 changed files with 1265 additions and 984 deletions
+28 -69
View File
@@ -48,7 +48,7 @@ class AccountForm {
/**
* The control of the base account
* @type {HTMLDivElement}
* @type {HTMLButtonElement}
*/
#baseControl;
@@ -60,7 +60,7 @@ class AccountForm {
/**
* The base account
* @type {HTMLDivElement}
* @type {HTMLSpanElement}
*/
#base;
@@ -225,15 +225,16 @@ class AccountForm {
/**
* The base account selector.
*
* @extends {BaseCombobox<BaseAccountOption>}
* @private
*/
class BaseAccountSelector {
class BaseAccountSelector extends BaseCombobox {
/**
* The account form
* @type {AccountForm}
*/
form;
#form;
/**
* The selector modal
@@ -241,12 +242,6 @@ class BaseAccountSelector {
*/
#modal;
/**
* The query input
* @type {HTMLInputElement}
*/
#query;
/**
* The error message when the query has no result
* @type {HTMLParagraphElement}
@@ -259,12 +254,6 @@ class BaseAccountSelector {
*/
#optionList;
/**
* The options
* @type {BaseAccountOption[]}
*/
#options;
/**
* The button to clear the base account value
* @type {HTMLButtonElement}
@@ -277,29 +266,32 @@ class BaseAccountSelector {
* @param form {AccountForm} the form
*/
constructor(form) {
this.form = form;
const prefix = "accounting-base-selector";
const query = document.getElementById(`${prefix}-query`);
const options = Array.from(document.getElementsByClassName(`${prefix}-option`)).map((element) => new BaseAccountOption(element, form.saveBaseAccount.bind(form)));
super(query, options);
this.#form = form;
this.#modal = document.getElementById(`${prefix}-modal`);
this.#query = document.getElementById(`${prefix}-query`);
this.#modal.addEventListener("hidden.bs.modal", () => this.#form.onBaseAccountSelectorClosed());
this.#queryNoResult = document.getElementById(`${prefix}-option-no-result`);
this.#optionList = document.getElementById(`${prefix}-option-list`);
this.#options = Array.from(document.getElementsByClassName(`${prefix}-option`)).map((element) => new BaseAccountOption(this, element));
this.#clearButton = document.getElementById(`${prefix}-clear`);
this.#modal.addEventListener("hidden.bs.modal", () => this.form.onBaseAccountSelectorClosed());
this.#query.oninput = () => this.#filterOptions();
this.#clearButton.onclick = () => this.form.clearBaseAccount();
this.#clearButton = document.getElementById(`${prefix}-clear`);
this.#clearButton.onclick = () => this.#form.clearBaseAccount();
}
/**
* Filters the options.
*
* @override
*/
#filterOptions() {
filterOptions() {
this.shownOptions = [];
let isAnyMatched = false;
for (const option of this.#options) {
if (option.isMatched(this.#query.value)) {
for (const option of this.options) {
if (option.isMatched(this.query.value)) {
option.setShown(true);
this.shownOptions.push(option);
isAnyMatched = true;
} else {
option.setShown(false);
@@ -319,12 +311,11 @@ class BaseAccountSelector {
*
*/
onOpen() {
this.#query.value = "";
this.#filterOptions();
for (const option of this.#options) {
option.setActive(option.code === this.form.baseCode);
}
if (this.form.baseCode === null) {
this.query.value = "";
this.filterOptions();
this.query.removeAttribute("aria-activedescendant");
this.selectOption(this.shownOptions.find((option) => option.code === this.#form.baseCode));
if (this.#form.baseCode === null) {
this.#clearButton.classList.add("btn-secondary")
this.#clearButton.classList.remove("btn-danger");
this.#clearButton.disabled = true;
@@ -341,13 +332,7 @@ class BaseAccountSelector {
*
* @private
*/
class BaseAccountOption {
/**
* The element
* @type {HTMLLIElement}
*/
#element;
class BaseAccountOption extends BaseOption {
/**
* The account code
@@ -370,16 +355,16 @@ class BaseAccountOption {
/**
* Constructs the account in the base account selector.
*
* @param selector {BaseAccountSelector} the base account selector
* @param element {HTMLLIElement} the element
* @param save {function(BaseAccountOption): void} the callback to save the option
*/
constructor(selector, element) {
this.#element = element;
constructor(element, save) {
super(element);
this.code = element.dataset.code;
this.text = element.dataset.text;
this.#queryValues = JSON.parse(element.dataset.queryValues);
this.#element.onclick = () => selector.form.saveBaseAccount(this);
element.onclick = () => save(this);
}
/**
@@ -399,30 +384,4 @@ class BaseAccountOption {
}
return false;
}
/**
* Sets whether the option is shown.
*
* @param isShown {boolean} true to show, or false otherwise
*/
setShown(isShown) {
if (isShown) {
this.#element.classList.remove("d-none");
} else {
this.#element.classList.add("d-none");
}
}
/**
* Sets whether the option is active.
*
* @param isActive {boolean} true if active, or false otherwise
*/
setActive(isActive) {
if (isActive) {
this.#element.classList.add("active");
} else {
this.#element.classList.remove("active");
}
}
}
+237
View File
@@ -0,0 +1,237 @@
/* The Mia! Accounting Project
* base-combobox.js: The JavaScript for the base abstract combobox
*/
/* Copyright (c) 2026 imacat.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
/* Author: imacat@mail.imacat.idv.tw (imacat)
* First written: 2026/4/16
*/
"use strict";
/**
* The base abstract combobox.
*
* @abstract
* @template {BaseOption} T
*/
class BaseCombobox {
/**
* The query input
* @type {HTMLInputElement}
*/
query;
/**
* The options
* @type {T[]}
*/
options;
/**
* The options that are shown
* @type {T[]}
*/
shownOptions;
/**
* Constructs a base abstract combobox.
*
* @param query {HTMLInputElement} the query input
* @param options {T[]} the options
*/
constructor(query, options) {
this.query = query;
this.query.oninput = () => this.filterOptions();
this.query.onkeydown = this.onQueryKeyDown.bind(this);
this.options = options;
this.shownOptions = [];
}
/**
* Actions when keys are pressed on the query input.
*
* @param event {KeyboardEvent} the key event
*/
onQueryKeyDown(event) {
if (this.shownOptions.length === 0) {
return;
}
const currentID = this.query.getAttribute("aria-activedescendant");
const currentIndex = this.shownOptions.findIndex((option) => option.elementID === currentID);
let newIndex;
switch (event.key) {
case "ArrowUp":
if (currentIndex === -1) {
newIndex = this.shownOptions.length - 1;
} else {
newIndex = (currentIndex - 1 + this.shownOptions.length) % this.shownOptions.length;
}
break;
case "ArrowDown":
if (currentIndex === -1) {
newIndex = 0;
} else {
newIndex = (currentIndex + 1) % this.shownOptions.length;
}
break;
case "Home":
if (this.query.value !== "") {
return;
}
newIndex = 0;
break;
case "End":
if (this.query.value !== "") {
return;
}
newIndex = this.shownOptions.length - 1;
break;
case "PageUp":
if (currentIndex === -1) {
newIndex = this.shownOptions.length - 1;
} else {
newIndex = Math.max(currentIndex - 10, 0);
}
break;
case "PageDown":
if (currentIndex === -1) {
newIndex = 0;
} else {
newIndex = Math.min(currentIndex + 10, this.shownOptions.length - 1);
}
break;
case "Enter":
event.preventDefault();
if (currentIndex !== -1) {
this.shownOptions[currentIndex].click();
}
return;
case "Escape":
if (this.query.value !== "") {
event.preventDefault();
event.stopPropagation();
this.query.value = "";
this.filterOptions();
}
return;
default:
return;
}
event.preventDefault();
this.selectOption(this.shownOptions[newIndex]);
}
/**
* Filters the options.
*
* @abstract
*/
filterOptions() {
throw new Error("Method not implemented");
}
/**
* Selects an option.
*
* @param option {T|undefined} the option.
*/
selectOption(option) {
this.options.forEach((opt) => opt.setActive(false));
if (option === undefined) {
return;
}
option.setActive(true);
this.query.setAttribute("aria-activedescendant", option.elementID);
option.scrollIntoView();
}
}
/**
* The base abstract option
*
* @abstract
*/
class BaseOption {
/**
* The element
* @type {HTMLLIElement}
*/
#element;
/**
* The element ID
* @type {string}
*/
elementID;
/**
* Constructs the base abstract option.
*
* @param element {HTMLLIElement} the element
*/
constructor(element) {
this.#element = element;
this.elementID = element.id;
}
/**
* Sets whether the option is shown.
*
* @param isShown {boolean} true to show, or false otherwise
*/
setShown(isShown) {
if (isShown) {
this.#element.classList.remove("d-none");
} else {
this.#element.classList.add("d-none");
}
}
/**
* Sets whether the option is active.
*
* @param isActive {boolean} true if active, or false otherwise
*/
setActive(isActive) {
if (isActive) {
this.#element.classList.add("active");
this.#element.ariaSelected = "true";
} else {
this.#element.classList.remove("active");
this.#element.ariaSelected = "false";
}
}
/**
* Clicks the option.
*
*/
click() {
this.#element.click();
}
/**
* Scrolls the option into view.
*
*/
scrollIntoView() {
this.#element.scrollIntoView({block: "nearest"});
}
}
+177
View File
@@ -0,0 +1,177 @@
/* The Mia! Accounting Project
* base-tablist.js: The JavaScript for base abstract tablist
*/
/* Copyright (c) 2026 imacat.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
/* Author: imacat@mail.imacat.idv.tw (imacat)
* First written: 2026/4/16
*/
"use strict";
/**
* The base abstract tablist.
*
* @abstract
* @template {BaseTab} T
*/
class BaseTablist {
/**
* The tabs.
* @type {T[]}
*/
tabs;
/**
* The current tab.
* @type {T}
*/
currentTab;
/**
* Constructs a new base abstract tablist.
*
* @param tablist {HTMLUListElement} the tab list
*/
constructor(tablist) {
tablist.onkeydown = this.onTabKeyDown.bind(this);
}
/**
* Actions when keys are pressed on the tabs.
*
* @param event {KeyboardEvent} the key event
*/
onTabKeyDown(event) {
const currentIndex = this.tabs.indexOf(this.currentTab);
if (currentIndex === -1) {
return;
}
let newIndex = currentIndex;
switch (event.key) {
case "ArrowRight":
newIndex = (newIndex + 1) % this.tabs.length;
break;
case "ArrowLeft":
newIndex = (newIndex - 1 + this.tabs.length) % this.tabs.length;
break;
case "Home":
newIndex = 0;
break;
case "End":
newIndex = this.tabs.length - 1;
break;
default:
return;
}
event.preventDefault();
this.tabs[newIndex].focus();
this.onTabFocus(this.tabs[newIndex]);
}
/**
* Actions when a tab is focused.
*
* @param tab {T} the tab
*/
onTabFocus(tab) { /* Do nothing */ }
/**
* Switches to a tab.
*
* @param tab {T} the tab
*/
switchTo(tab) {
this.tabs.forEach(t => t.setActive(t === tab));
this.currentTab = tab;
this.currentTab.onActivated();
}
}
/**
* The base abstract tab.
*
* @abstract
*/
class BaseTab {
/**
* The tab element.
* @type {HTMLButtonElement}
*/
#tab;
/**
* The panel element.
* @type {HTMLDivElement}
*/
#panel;
/**
* Constructs a new base abstract tab.
*
* @param tab {HTMLButtonElement} The tab element.
* @param panel {HTMLDivElement} The panel element.
* @param switchTo {function(BaseTab): void} The function to switch to the tab.
*/
constructor(tab, panel, switchTo) {
this.#tab = tab;
this.#panel = panel;
this.#tab.onclick = () => switchTo(this);
}
/**
* Sets the active state of the tab.
*
* @param isActive {boolean} true if the tab is active, false otherwise
*/
setActive(isActive) {
if (isActive) {
this.#tab.classList.add("active");
this.#tab.tabIndex = 0;
this.#tab.ariaSelected = "true";
this.#panel.classList.remove("d-none");
} else {
this.#tab.classList.remove("active");
this.#tab.tabIndex = -1;
this.#tab.ariaSelected = "false";
this.#panel.classList.add("d-none");
}
}
/**
* Returns whether the tab is active.
*
* @returns {boolean} true if the tab is active, false otherwise
*/
isActive() {
return this.#tab.classList.contains("active");
}
/**
* Actions when the tab is activated.
*/
onActivated() { /* Do nothing */ }
/**
* Focuses the tab.
*/
focus() {
this.#tab.focus();
}
}
+167 -191
View File
@@ -25,8 +25,9 @@
/**
* A description editor.
*
* @extends {BaseTablist<BaseDescriptionEditorTab>}
*/
class DescriptionEditor {
class DescriptionEditor extends BaseTablist {
/**
* The line item editor
@@ -58,12 +59,6 @@ class DescriptionEditor {
*/
debitCredit;
/**
* The current tab
* @type {DescriptionEditorTabPlane}
*/
currentTab;
/**
* The description input
* @type {HTMLInputElement}
@@ -125,10 +120,10 @@ class DescriptionEditor {
selectedAccount = null;
/**
* The tab planes
* @type {{general: DescriptionEditorGeneralTagTab, travel: DescriptionEditorGeneralTripTab, bus: DescriptionEditorBusTripTab, recurring: DescriptionEditorRecurringTab, annotation: DescriptionEditorAnnotationTab}}
* The tabs by their ID.
* @type {DescriptionEditorTabFactory}
*/
tabPlanes = {};
#tabsByID;
/**
* Constructs a description editor.
@@ -137,31 +132,38 @@ class DescriptionEditor {
* @param debitCredit {string} either "debit" or "credit"
*/
constructor(lineItemEditor, debitCredit) {
const prefix = `accounting-description-editor-${debitCredit}`;
super(document.getElementById(`${prefix}-tab-list`));
this.prefix = prefix;
this.lineItemEditor = lineItemEditor;
this.debitCredit = debitCredit;
this.prefix = `accounting-description-editor-${debitCredit}`;
this.#form = document.getElementById(this.prefix);
this.#modal = document.getElementById(`${this.prefix}-modal`);
this.#descriptionInput = document.getElementById(`${this.prefix}-description`);
this.#offsetButton = document.getElementById(`${this.prefix}-offset`);
this.number = document.getElementById(`${this.prefix}-annotation-number`);
this.note = document.getElementById(`${this.prefix}-annotation-note`);
this.#confirmedAccountPlaceholder = new DescriptionEditorConfirmedAccount(this, document.getElementById(`${this.prefix}-account-confirmed`));
this.#allSuggestedAccounts = Array.from(document.getElementsByClassName(`${this.prefix}-account`)).map((button) => new DescriptionEditorSuggestedAccount(this, button));
this.#form = document.getElementById(prefix);
this.#modal = document.getElementById(`${prefix}-modal`);
this.#descriptionInput = document.getElementById(`${prefix}-description`);
this.#offsetButton = document.getElementById(`${prefix}-offset`);
this.number = document.getElementById(`${prefix}-annotation-number`);
this.note = document.getElementById(`${prefix}-annotation-note`);
this.#confirmedAccountPlaceholder = new DescriptionEditorConfirmedAccount(this, document.getElementById(`${prefix}-account-confirmed`));
this.#allSuggestedAccounts = Array.from(document.getElementsByClassName(`${prefix}-account`)).map((button) => new DescriptionEditorSuggestedAccount(this, button));
for (const cls of [DescriptionEditorGeneralTagTab, DescriptionEditorGeneralTripTab, DescriptionEditorBusTripTab, DescriptionEditorRecurringTab, DescriptionEditorAnnotationTab]) {
const tab = new cls(this);
this.tabPlanes[tab.tabId()] = tab;
}
this.currentTab = this.tabPlanes.general;
this.#tabsByID = new DescriptionEditorTabFactory(this);
this.tabs = [this.#tabsByID.general, this.#tabsByID.travel, this.#tabsByID.bus, this.#tabsByID.recurring, this.#tabsByID.annotation];
this.currentTab = this.tabs[0];
this.#descriptionInput.onchange = () => this.#onDescriptionChange();
this.#offsetButton.onclick = () => this.lineItemEditor.originalLineItemSelector.onOpen();
this.#offsetButton.onclick = () => this.lineItemEditor.originalLineItemSelector.onOpen(this.#modal.id);
this.#form.onsubmit = () => {
if (this.currentTab.validate()) {
this.#submit();
}
return false;
};
const closeButton = document.getElementById(`${prefix}-close`);
this.#modal.onkeydown = (event) => {
if (event.key === "Escape") {
closeButton.click();
}
};
}
/**
@@ -199,26 +201,26 @@ class DescriptionEditor {
*
*/
#onDescriptionChange() {
this.#resetTabPlanes();
this.#resetTabs();
this.selectedAccount = null;
this.description = this.description.trim();
for (const tabPlane of [this.tabPlanes.recurring, this.tabPlanes.bus, this.tabPlanes.travel, this.tabPlanes.general]) {
if (tabPlane.populate()) {
for (const tab of [this.#tabsByID.recurring, this.#tabsByID.bus, this.#tabsByID.travel, this.#tabsByID.general]) {
if (tab.populate()) {
break;
}
}
this.tabPlanes.annotation.populate();
this.#tabsByID.annotation.populate();
}
/**
* Resets the tab planes.
* Resets the tabs.
*
*/
#resetTabPlanes() {
for (const tabPlane of Object.values(this.tabPlanes)) {
tabPlane.reset();
#resetTabs() {
for (const tab of this.tabs) {
tab.reset();
}
this.tabPlanes.general.switchToMe();
this.switchTo(this.tabs[0]);
}
/**
@@ -463,12 +465,63 @@ class DescriptionEditorConfirmedAccount extends DescriptionEditorAccount {
}
/**
* A tab plane.
* The tab factory.
*
* @private
*/
class DescriptionEditorTabFactory {
/**
* The general tag tab
* @type {GeneralTagTab}
*/
general;
/**
* The general trip tab
* @type {GeneralTripTab}
*/
travel;
/**
* The bus trip tab
* @type {BusTripTab}
*/
bus;
/**
* The recurring transactions tab
* @type {RecurringTab}
*/
recurring;
/**
* The annotation tab
* @type {AnnotationTab}
*/
annotation;
/**
* Constructs the tab factory
*
* @param editor {DescriptionEditor} the parent description editor
*/
constructor(editor) {
this.general = new GeneralTagTab(editor);
this.travel = new GeneralTripTab(editor);
this.bus = new BusTripTab(editor);
this.recurring = new RecurringTab(editor);
this.annotation = new AnnotationTab(editor);
}
}
/**
* The base abstract tab in the description editor.
*
* @abstract
* @private
*/
class DescriptionEditorTabPlane {
class BaseDescriptionEditorTab extends BaseTab {
/**
* The parent description editor
@@ -483,47 +536,29 @@ class DescriptionEditorTabPlane {
prefix;
/**
* The tab
* @type {HTMLSpanElement}
*/
#tab;
/**
* The page
* @type {HTMLDivElement}
*/
#page;
/**
* Constructs a tab plane.
* Constructs a base abstract tab in the description editor.
*
* @param tabID {string} the tab ID
* @param editor {DescriptionEditor} the parent description editor
*/
constructor(editor) {
constructor(tabID, editor) {
const prefix = `${editor.prefix}-${tabID}`;
const tab = document.getElementById(`${prefix}-tab`);
const panel = document.getElementById(`${prefix}-panel`);
super(tab, panel, editor.switchTo.bind(editor));
this.editor = editor;
this.prefix = `${this.editor.prefix}-${this.tabId()}`;
this.#tab = document.getElementById(`${this.prefix}-tab`);
this.#page = document.getElementById(`${this.prefix}-page`);
this.#tab.onclick = () => this.switchToMe();
this.prefix = prefix;
}
/**
* The tab ID
*
* @return {string}
* @abstract
*/
tabId() { throw new Error("Method not implemented.") };
/**
* Resets the tab plane input.
* Resets the tab panel input.
*
* @abstract
*/
reset() { throw new Error("Method not implemented."); }
/**
* Populates the tab plane with the description input.
* Populates the tab panel with the description input.
*
* @return {boolean} true if the description input matches this tab, or false otherwise
* @abstract
@@ -531,39 +566,21 @@ class DescriptionEditorTabPlane {
populate() { throw new Error("Method not implemented."); }
/**
* Validates the input in the tab plane.
* Validates the input in the tab panel.
*
* @return {boolean} true if valid, or false otherwise
* @abstract
*/
validate() { throw new Error("Method not implemented."); }
/**
* Switches to the tab plane.
*
*/
switchToMe() {
for (const tabPlane of Object.values(this.editor.tabPlanes)) {
tabPlane.#tab.classList.remove("active")
tabPlane.#tab.ariaCurrent = "false";
tabPlane.#page.classList.add("d-none");
tabPlane.#page.ariaCurrent = "false";
}
this.#tab.classList.add("active");
this.#tab.ariaCurrent = "page";
this.#page.classList.remove("d-none");
this.#page.ariaCurrent = "page";
this.editor.currentTab = this;
}
}
/**
* A tag plane with selectable tags.
* The base abstract tab with selectable tags.
*
* @abstract
* @private
*/
class DescriptionEditorTagTabPlane extends DescriptionEditorTabPlane {
class BaseTagTab extends BaseDescriptionEditorTab {
/**
* The tag input
@@ -581,20 +598,21 @@ class DescriptionEditorTagTabPlane extends DescriptionEditorTabPlane {
* The tag buttons
* @type {HTMLButtonElement[]}
*/
tagButtons;
#tagButtons;
/**
* Constructs a tab plane.
* Constructs a base abstract tab with selectable tags.
*
* @param tabID {string} the tab ID
* @param editor {DescriptionEditor} the parent description editor
* @override
*/
constructor(editor) {
super(editor);
constructor(tabID, editor) {
super(tabID, editor);
this.tag = document.getElementById(`${this.prefix}-tag`);
this.tagError = document.getElementById(`${this.prefix}-tag-error`);
// noinspection JSValidateTypes
this.tagButtons = Array.from(document.getElementsByClassName(`${this.prefix}-btn-tag`));
this.#tagButtons = Array.from(document.getElementsByClassName(`${this.prefix}-btn-tag`));
this.initializeTagButtons();
this.tag.onchange = () => {
this.onTagChange();
@@ -609,7 +627,7 @@ class DescriptionEditorTagTabPlane extends DescriptionEditorTabPlane {
onTagChange() {
this.tag.value = this.tag.value.trim();
let isMatched = false;
for (const tagButton of this.tagButtons) {
for (const tagButton of this.#tagButtons) {
if (tagButton.dataset.value === this.tag.value) {
tagButton.classList.remove("btn-outline-primary");
tagButton.classList.add("btn-primary");
@@ -627,19 +645,18 @@ class DescriptionEditorTagTabPlane extends DescriptionEditorTabPlane {
}
/**
* Updates the description according to the input in the tab plane.
* Updates the description according to the input in the tab panel.
*
* @abstract
*/
updateDescription() { throw new Error("Method not implemented."); }
/**
* Switches to the tab plane.
*
* @inheritDoc
* @override
*/
switchToMe() {
super.switchToMe();
for (const tagButton of this.tagButtons) {
onActivated() {
for (const tagButton of this.#tagButtons) {
if (tagButton.classList.contains("btn-primary")) {
this.editor.updateCurrentSuggestedAccounts(tagButton);
return;
@@ -653,9 +670,9 @@ class DescriptionEditorTagTabPlane extends DescriptionEditorTabPlane {
*
*/
initializeTagButtons() {
for (const tagButton of this.tagButtons) {
for (const tagButton of this.#tagButtons) {
tagButton.onclick = () => {
for (const otherButton of this.tagButtons) {
for (const otherButton of this.#tagButtons) {
otherButton.classList.remove("btn-primary");
otherButton.classList.add("btn-outline-primary");
}
@@ -701,7 +718,7 @@ class DescriptionEditorTagTabPlane extends DescriptionEditorTabPlane {
}
/**
* Resets the tab plane input.
* Resets the tab panel input.
*
* @override
*/
@@ -709,7 +726,7 @@ class DescriptionEditorTagTabPlane extends DescriptionEditorTabPlane {
this.tag.value = "";
this.tag.classList.remove("is-invalid");
this.tagError.innerText = "";
for (const tagButton of this.tagButtons) {
for (const tagButton of this.#tagButtons) {
tagButton.classList.remove("btn-primary");
tagButton.classList.add("btn-outline-primary");
}
@@ -717,24 +734,24 @@ class DescriptionEditorTagTabPlane extends DescriptionEditorTabPlane {
}
/**
* The general tag tab plane.
* The general tag tab.
*
* @private
*/
class DescriptionEditorGeneralTagTab extends DescriptionEditorTagTabPlane {
class GeneralTagTab extends BaseTagTab {
/**
* The tab ID
* Constructs a general tag tab.
*
* @return {string}
* @abstract
* @param editor {DescriptionEditor} the parent description editor
* @override
*/
tabId() {
return "general";
};
constructor(editor) {
super("general", editor);
}
/**
* Updates the description according to the input in the tab plane.
* Updates the description according to the input in the tab panel.
*
* @override
*/
@@ -749,7 +766,7 @@ class DescriptionEditorGeneralTagTab extends DescriptionEditorTagTabPlane {
}
/**
* Populates the tab plane with the description input.
* Populates the tab panel with the description input.
*
* @return {boolean} true if the description input matches this tab, or false otherwise
* @override
@@ -763,12 +780,12 @@ class DescriptionEditorGeneralTagTab extends DescriptionEditorTagTabPlane {
this.tag.value = found[1];
this.onTagChange();
}
this.switchToMe();
this.editor.switchTo(this);
return true;
}
/**
* Validates the input in the tab plane.
* Validates the input in the tab panel.
*
* @return {boolean} true if valid, or false otherwise
*/
@@ -778,11 +795,11 @@ class DescriptionEditorGeneralTagTab extends DescriptionEditorTagTabPlane {
}
/**
* The general trip tab plane.
* The general trip tab.
*
* @private
*/
class DescriptionEditorGeneralTripTab extends DescriptionEditorTagTabPlane {
class GeneralTripTab extends BaseTagTab {
/**
* The origin
@@ -815,13 +832,13 @@ class DescriptionEditorGeneralTripTab extends DescriptionEditorTagTabPlane {
#directionButtons;
/**
* Constructs a tab plane.
* Constructs a general trip tab.
*
* @param editor {DescriptionEditor} the parent description editor
* @override
*/
constructor(editor) {
super(editor);
super("travel", editor);
this.#from = document.getElementById(`${this.prefix}-from`);
this.#fromError = document.getElementById(`${this.prefix}-from-error`);
this.#to = document.getElementById(`${this.prefix}-to`);
@@ -852,17 +869,7 @@ class DescriptionEditorGeneralTripTab extends DescriptionEditorTagTabPlane {
}
/**
* The tab ID
*
* @return {string}
* @abstract
*/
tabId() {
return "travel";
};
/**
* Updates the description according to the input in the tab plane.
* Updates the description according to the input in the tab panel.
*
* @override
*/
@@ -878,7 +885,7 @@ class DescriptionEditorGeneralTripTab extends DescriptionEditorTagTabPlane {
}
/**
* Resets the tab plane input.
* Resets the tab panel input.
*
* @override
*/
@@ -902,7 +909,7 @@ class DescriptionEditorGeneralTripTab extends DescriptionEditorTagTabPlane {
}
/**
* Populates the tab plane with the description input.
* Populates the tab panel with the description input.
*
* @return {boolean} true if the description input matches this tab, or false otherwise
* @override
@@ -927,12 +934,12 @@ class DescriptionEditorGeneralTripTab extends DescriptionEditorTagTabPlane {
}
}
this.#to.value = found[4];
this.switchToMe();
this.editor.switchTo(this);
return true;
}
/**
* Validates the input in the tab plane.
* Validates the input in the tab panel.
*
* @return {boolean} true if valid, or false otherwise
* @override
@@ -977,11 +984,11 @@ class DescriptionEditorGeneralTripTab extends DescriptionEditorTagTabPlane {
}
/**
* The bus trip tab plane.
* The bus trip tab.
*
* @private
*/
class DescriptionEditorBusTripTab extends DescriptionEditorTagTabPlane {
class BusTripTab extends BaseTagTab {
/**
* The route
@@ -1020,13 +1027,13 @@ class DescriptionEditorBusTripTab extends DescriptionEditorTagTabPlane {
#toError;
/**
* Constructs a tab plane.
* Constructs a bus trip tab.
*
* @param editor {DescriptionEditor} the parent description editor
* @override
*/
constructor(editor) {
super(editor);
super("bus", editor);
this.#route = document.getElementById(`${this.prefix}-route`);
this.#routeError = document.getElementById(`${this.prefix}-route-error`);
this.#from = document.getElementById(`${this.prefix}-from`);
@@ -1051,17 +1058,7 @@ class DescriptionEditorBusTripTab extends DescriptionEditorTagTabPlane {
}
/**
* The tab ID
*
* @return {string}
* @abstract
*/
tabId() {
return "bus";
};
/**
* Updates the description according to the input in the tab plane.
* Updates the description according to the input in the tab panel.
*
* @override
*/
@@ -1070,7 +1067,7 @@ class DescriptionEditorBusTripTab extends DescriptionEditorTagTabPlane {
}
/**
* Resets the tab plane input.
* Resets the tab panel input.
*
* @override
*/
@@ -1088,7 +1085,7 @@ class DescriptionEditorBusTripTab extends DescriptionEditorTagTabPlane {
}
/**
* Populates the tab plane with the description input.
* Populates the tab panel with the description input.
*
* @return {boolean} true if the description input matches this tab, or false otherwise
* @override
@@ -1105,12 +1102,12 @@ class DescriptionEditorBusTripTab extends DescriptionEditorTagTabPlane {
this.#route.value = found[2];
this.#from.value = found[3];
this.#to.value = found[4];
this.switchToMe();
this.editor.switchTo(this);
return true;
}
/**
* Validates the input in the tab plane.
* Validates the input in the tab panel.
*
* @return {boolean} true if valid, or false otherwise
*/
@@ -1165,11 +1162,11 @@ class DescriptionEditorBusTripTab extends DescriptionEditorTagTabPlane {
}
/**
* The recurring transaction tab plane.
* The recurring transaction tab.
*
* @private
*/
class DescriptionEditorRecurringTab extends DescriptionEditorTabPlane {
class RecurringTab extends BaseDescriptionEditorTab {
/**
* The month names
@@ -1184,13 +1181,13 @@ class DescriptionEditorRecurringTab extends DescriptionEditorTabPlane {
#itemButtons;
/**
* Constructs a tab plane.
* Constructs a recurring transaction tab.
*
* @param editor {DescriptionEditor} the parent description editor
* @override
*/
constructor(editor) {
super(editor);
super("recurring", editor);
this.#monthNames = [
"",
A_("January"), A_("February"), A_("March"), A_("April"),
@@ -1232,17 +1229,7 @@ class DescriptionEditorRecurringTab extends DescriptionEditorTabPlane {
}
/**
* The tab ID
*
* @return {string}
* @abstract
*/
tabId() {
return "recurring";
};
/**
* Resets the tab plane input.
* Resets the tab panel input.
*
* @override
*/
@@ -1254,7 +1241,7 @@ class DescriptionEditorRecurringTab extends DescriptionEditorTabPlane {
}
/**
* Populates the tab plane with the description input.
* Populates the tab panel with the description input.
*
* @return {boolean} true if the description input matches this tab, or false otherwise
* @override
@@ -1264,7 +1251,7 @@ class DescriptionEditorRecurringTab extends DescriptionEditorTabPlane {
if (this.#getDescription(itemButton) === this.editor.description) {
itemButton.classList.add("btn-primary");
itemButton.classList.remove("btn-outline-primary");
this.switchToMe();
this.editor.switchTo(this);
return true;
}
}
@@ -1272,11 +1259,10 @@ class DescriptionEditorRecurringTab extends DescriptionEditorTabPlane {
}
/**
* Switches to the tab plane.
*
* @inheritDoc
* @override
*/
switchToMe() {
super.switchToMe();
onActivated() {
for (const itemButton of this.#itemButtons) {
if (itemButton.classList.contains("btn-primary")) {
this.editor.updateCurrentSuggestedAccounts(itemButton);
@@ -1287,7 +1273,7 @@ class DescriptionEditorRecurringTab extends DescriptionEditorTabPlane {
}
/**
* Validates the input in the tab plane.
* Validates the input in the tab panel.
*
* @return {boolean} true if valid, or false otherwise
* @override
@@ -1298,20 +1284,20 @@ class DescriptionEditorRecurringTab extends DescriptionEditorTabPlane {
}
/**
* The annotation tab plane.
* The annotation tab.
*
* @private
*/
class DescriptionEditorAnnotationTab extends DescriptionEditorTabPlane {
class AnnotationTab extends BaseDescriptionEditorTab {
/**
* Constructs a tab plane.
* Constructs an annotation tab.
*
* @param editor {DescriptionEditor} the parent description editor
* @override
*/
constructor(editor) {
super(editor);
super("annotation", editor);
this.editor.number.onchange = () => this.updateDescription();
this.editor.note.onchange = () => {
this.editor.note.value = this.editor.note.value.trim();
@@ -1320,17 +1306,7 @@ class DescriptionEditorAnnotationTab extends DescriptionEditorTabPlane {
}
/**
* The tab ID
*
* @return {string}
* @abstract
*/
tabId() {
return "annotation";
};
/**
* Updates the description according to the input in the tab plane.
* Updates the description according to the input in the tab panel.
*
* @override
*/
@@ -1348,7 +1324,7 @@ class DescriptionEditorAnnotationTab extends DescriptionEditorTabPlane {
}
/**
* Resets the tab plane input.
* Resets the tab panel input.
*
* @override
*/
@@ -1358,7 +1334,7 @@ class DescriptionEditorAnnotationTab extends DescriptionEditorTabPlane {
}
/**
* Populates the tab plane with the description input.
* Populates the tab panel with the description input.
*
* @return {boolean} true if the description input matches this tab, or false otherwise
* @override
@@ -1382,7 +1358,7 @@ class DescriptionEditorAnnotationTab extends DescriptionEditorTabPlane {
}
/**
* Validates the input in the tab plane.
* Validates the input in the tab panel.
*
* @return {boolean} true if valid, or false otherwise
* @override
@@ -25,15 +25,16 @@
/**
* The account selector.
*
* @extends {BaseCombobox<BaseJournalEntryAccountOption>}
* @private
*/
class JournalEntryAccountSelector {
class JournalEntryAccountSelector extends BaseCombobox {
/**
* The line item editor
* @type {JournalEntryLineItemEditor}
*/
lineItemEditor;
#lineItemEditor;
/**
* Either "debit" or "credit"
@@ -47,12 +48,6 @@ class JournalEntryAccountSelector {
*/
#clearButton
/**
* The query input
* @type {HTMLInputElement}
*/
#query;
/**
* The error message when the query has no result
* @type {HTMLParagraphElement}
@@ -65,15 +60,9 @@ class JournalEntryAccountSelector {
*/
#optionList;
/**
* The options
* @type {JournalEntryAccountOption[]}
*/
#options;
/**
* The more item to show all accounts
* @type {HTMLLIElement}
* @type {MoreItems}
*/
#more;
@@ -90,40 +79,55 @@ class JournalEntryAccountSelector {
* @param debitCredit {string} either "debit" or "credit"
*/
constructor(lineItemEditor, debitCredit) {
this.lineItemEditor = lineItemEditor;
this.#debitCredit = debitCredit;
const prefix = `accounting-account-selector-${debitCredit}`;
this.#query = document.getElementById(`${prefix}-query`);
const query = document.getElementById(`${prefix}-query`);
const options = Array.from(document.getElementsByClassName(`${prefix}-option`)).map((element) => new JournalEntryAccountOption(element, lineItemEditor.saveAccount.bind(lineItemEditor)));
super(query, options);
this.#lineItemEditor = lineItemEditor;
this.#debitCredit = debitCredit;
this.#queryNoResult = document.getElementById(`${prefix}-option-no-result`);
this.#optionList = document.getElementById(`${prefix}-option-list`);
this.#options = Array.from(document.getElementsByClassName(`${prefix}-option`)).map((element) => new JournalEntryAccountOption(this, element));
this.#more = document.getElementById(`${prefix}-more`);
this.#clearButton = document.getElementById(`${prefix}-btn-clear`);
this.#more.onclick = () => {
const moreElement = document.getElementById(`${prefix}-more`);
this.#more = new MoreItems(moreElement);
moreElement.onclick = () => {
this.#isShowMore = true;
this.#more.classList.add("d-none");
this.#filterOptions();
this.#more.setShown(false);
this.filterOptions();
};
this.#clearButton = document.getElementById(`${prefix}-btn-clear`);
this.#clearButton.onclick = () => this.#lineItemEditor.clearAccount();
const modal = document.getElementById(`${prefix}-modal`);
const closeButton = document.getElementById(`${prefix}-close`);
modal.onkeydown = (event) => {
if (event.key === "Escape") {
closeButton.click();
}
};
this.#query.oninput = () => this.#filterOptions();
this.#clearButton.onclick = () => this.lineItemEditor.clearAccount();
}
/**
* Filters the options.
*
* @override
*/
#filterOptions() {
filterOptions() {
this.shownOptions = [];
const codesInUse = this.#getCodesUsedInForm();
let isAnyMatched = false;
for (const option of this.#options) {
if (option.isMatched(this.#isShowMore, codesInUse, this.#query.value)) {
for (const option of this.options) {
if (option.isMatched(this.#isShowMore, codesInUse, this.query.value)) {
option.setShown(true);
this.shownOptions.push(option);
isAnyMatched = true;
} else {
option.setShown(false);
}
}
if (!this.#isShowMore) {
this.shownOptions.push(this.#more);
}
if (!isAnyMatched && this.#isShowMore) {
this.#optionList.classList.add("d-none");
this.#queryNoResult.classList.remove("d-none");
@@ -139,9 +143,9 @@ class JournalEntryAccountSelector {
* @return {string[]} the account codes that are used in the form
*/
#getCodesUsedInForm() {
const inUse = this.lineItemEditor.form.getAccountCodesUsed(this.#debitCredit);
if (this.lineItemEditor.account !== null) {
inUse.push(this.lineItemEditor.account.code);
const inUse = this.#lineItemEditor.form.getAccountCodesUsed(this.#debitCredit);
if (this.#lineItemEditor.account !== null) {
inUse.push(this.#lineItemEditor.account.code);
}
return inUse
}
@@ -151,14 +155,13 @@ class JournalEntryAccountSelector {
*
*/
onOpen() {
this.#query.value = "";
this.query.value = "";
this.#isShowMore = false;
this.#more.classList.remove("d-none");
this.#filterOptions();
for (const option of this.#options) {
option.setActive(this.lineItemEditor.account !== null && option.code === this.lineItemEditor.account.code);
}
if (this.lineItemEditor.account === null) {
this.#more.setShown(true);
this.filterOptions();
this.query.removeAttribute("aria-activedescendant");
this.selectOption(this.shownOptions.find((option) => this.#lineItemEditor.account !== null && option.code === this.#lineItemEditor.account.code));
if (this.#lineItemEditor.account === null) {
this.#clearButton.classList.add("btn-secondary");
this.#clearButton.classList.remove("btn-danger");
this.#clearButton.disabled = true;
@@ -169,6 +172,17 @@ class JournalEntryAccountSelector {
}
}
/**
* Selects an option.
*
* @param option {BaseJournalEntryAccountOption|undefined} the option.
* @override
*/
selectOption(option) {
this.#more.setActive(false);
super.selectOption(option);
}
/**
* Returns the account selector instances.
*
@@ -186,23 +200,37 @@ class JournalEntryAccountSelector {
}
/**
* An account option
* The base abstract account option
*
* @private
*/
class JournalEntryAccountOption {
/**
* The element
* @type {HTMLLIElement}
*/
#element;
class BaseJournalEntryAccountOption extends BaseOption {
/**
* The account code
* @type {string}
*/
code;
code = "";
/**
* Returns whether the account matches the query.
*
* @param isShowMore {boolean} true to show all accounts, or false to show only those in use
* @param codesInUse {string[]} the account codes that are used in the form
* @param query {string} the query term
* @return {boolean} true if the option matches, or false otherwise
*/
isMatched(isShowMore, codesInUse, query) {
return false;
}
}
/**
* An account option
*
* @private
*/
class JournalEntryAccountOption extends BaseJournalEntryAccountOption {
/**
* The account title
@@ -237,11 +265,11 @@ class JournalEntryAccountOption {
/**
* Constructs the account in the account selector.
*
* @param selector {JournalEntryAccountSelector} the account selector
* @param element {HTMLLIElement} the element
* @param save {function(JournalEntryAccountOption): void} the callback to save the option
*/
constructor(selector, element) {
this.#element = element;
constructor(element, save) {
super(element);
this.code = element.dataset.code;
this.title = element.dataset.title;
this.text = element.dataset.text;
@@ -249,7 +277,7 @@ class JournalEntryAccountOption {
this.isNeedOffset = element.classList.contains("accounting-account-is-need-offset");
this.#queryValues = JSON.parse(element.dataset.queryValues);
this.#element.onclick = () => selector.lineItemEditor.saveAccount(this);
element.onclick = () => save(this);
}
/**
@@ -259,6 +287,7 @@ class JournalEntryAccountOption {
* @param codesInUse {string[]} the account codes that are used in the form
* @param query {string} the query term
* @return {boolean} true if the option matches, or false otherwise
* @override
*/
isMatched(isShowMore, codesInUse, query) {
return this.#isInUseMatched(isShowMore, codesInUse) && this.#isQueryMatched(query);
@@ -292,30 +321,12 @@ class JournalEntryAccountOption {
}
return false;
}
/**
* Sets whether the option is shown.
*
* @param isShown {boolean} true to show, or false otherwise
*/
setShown(isShown) {
if (isShown) {
this.#element.classList.remove("d-none");
} else {
this.#element.classList.add("d-none");
}
}
/**
* Sets whether the option is active.
*
* @param isActive {boolean} true if active, or false otherwise
*/
setActive(isActive) {
if (isActive) {
this.#element.classList.add("active");
} else {
this.#element.classList.remove("active");
}
}
}
/**
* The more item to show all accounts.
*
* @private
*/
class MoreItems extends BaseJournalEntryAccountOption {
}
@@ -701,12 +701,23 @@ class DebitCreditSubForm {
this.#element.classList.add("accounting-not-empty");
this.currency.form.lineItemEditor.onAddNew(this);
};
this.#element.role = "button";
this.#element.tabIndex = 0;
this.#element.onkeydown = (event) => {
if (event.key === "Enter" || event.key === " ") {
event.preventDefault();
this.#element.click();
}
};
} 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.#element.removeAttribute("role");
this.#element.tabIndex = -1;
this.#element.onkeydown = null;
}
setElementShown(this.#content, this.lineItems.length !== 0);
}
@@ -986,6 +997,12 @@ class LineItemSubForm {
this.#element.parentElement.removeChild(this.#element);
this.debitCreditSubForm.deleteLineItem(this);
};
this.#control.onkeydown = (event) => {
if (event.key === "Enter" || event.key === " ") {
event.preventDefault();
this.#control.click();
}
};
}
/**
@@ -66,13 +66,13 @@ class JournalEntryLineItemEditor {
/**
* The control of the original line item
* @type {HTMLDivElement}
* @type {HTMLButtonElement}
*/
#originalLineItemControl;
/**
* The original line item
* @type {HTMLDivElement}
* @type {HTMLSpanElement}
*/
#originalLineItemText;
@@ -90,13 +90,13 @@ class JournalEntryLineItemEditor {
/**
* The control of the description
* @type {HTMLDivElement}
* @type {HTMLButtonElement}
*/
#descriptionControl;
/**
* The description
* @type {HTMLDivElement}
* @type {HTMLSpanElement}
*/
#descriptionText;
@@ -108,13 +108,13 @@ class JournalEntryLineItemEditor {
/**
* The control of the account
* @type {HTMLDivElement}
* @type {HTMLButtonElement}
*/
#accountControl;
/**
* The account
* @type {HTMLDivElement}
* @type {HTMLSpanElement}
*/
#accountText;
@@ -228,7 +228,7 @@ class JournalEntryLineItemEditor {
this.#accountSelectors = JournalEntryAccountSelector.getInstances(this);
this.originalLineItemSelector = new OriginalLineItemSelector(this);
this.#originalLineItemControl.onclick = () => this.originalLineItemSelector.onOpen()
this.#originalLineItemControl.onclick = () => this.originalLineItemSelector.onOpen(this.modal.id)
this.#originalLineItemDelete.onclick = () => this.clearOriginalLineItem();
this.#descriptionControl.onclick = () => this.#descriptionEditors[this.debitCredit].onOpen();
this.#accountControl.onclick = () => this.#accountSelectors[this.debitCredit].onOpen();
+51 -73
View File
@@ -298,6 +298,14 @@ class RecurringExpenseIncomeSubForm {
this.#element.dataset.bsTarget = `#${this.editor.modal.id}`;
this.#element.onclick = () => this.editor.onAddNew();
this.#content.classList.add("d-none");
this.#element.role = "button";
this.#element.tabIndex = 0;
this.#element.onkeydown = (event) => {
if (event.key === "Enter" || event.key === " ") {
event.preventDefault();
this.#element.click();
}
};
} else {
this.#element.classList.add("accounting-not-empty");
this.#element.classList.remove("accounting-clickable");
@@ -305,6 +313,9 @@ class RecurringExpenseIncomeSubForm {
delete this.#element.dataset.bsTarget;
this.#element.onclick = null;
this.#content.classList.remove("d-none");
this.#element.removeAttribute("role");
this.#element.tabIndex = -1;
this.#element.onkeydown = null;
}
}
@@ -375,7 +386,7 @@ class RecurringItemSubForm {
/**
* The control
* @type {HTMLDivElement}
* @type {HTMLButtonElement}
*/
#control;
@@ -399,7 +410,7 @@ class RecurringItemSubForm {
/**
* The text display of the name
* @type {HTMLDivElement}
* @type {HTMLSpanElement}
*/
#nameText;
@@ -411,7 +422,7 @@ class RecurringItemSubForm {
/**
* The text display of the account
* @type {HTMLDivElement}
* @type {HTMLSpanElement}
*/
#accountText;
@@ -423,7 +434,7 @@ class RecurringItemSubForm {
/**
* The text display of the description template
* @type {HTMLDivElement}
* @type {HTMLSpanElement}
*/
#descriptionTemplateText;
@@ -595,13 +606,13 @@ class RecurringItemEditor {
/**
* The control of the account
* @type {HTMLDivElement}
* @type {HTMLButtonElement}
*/
#accountControl;
/**
* The text display of the account
* @type {HTMLDivElement}
* @type {HTMLSpanElement}
*/
#accountContainer;
@@ -832,15 +843,16 @@ class RecurringItemEditor {
/**
* The account selector for the recurring item editor.
*
* @extends {BaseCombobox<RecurringAccount>}
* @private
*/
class RecurringAccountSelector {
class RecurringAccountSelector extends BaseCombobox {
/**
* The recurring item editor
* @type {RecurringItemEditor}
*/
editor;
#editor;
/**
* Either "expense" or "income"
@@ -848,12 +860,6 @@ class RecurringAccountSelector {
*/
#expenseIncome;
/**
* The query input
* @type {HTMLInputElement}
*/
#query;
/**
* The error message when the query has no result
* @type {HTMLParagraphElement}
@@ -866,12 +872,6 @@ class RecurringAccountSelector {
*/
#optionList;
/**
* The account options
* @type {RecurringAccount[]}
*/
#options;
/**
* The button to clear the account
* @type {HTMLButtonElement}
@@ -884,28 +884,39 @@ class RecurringAccountSelector {
* @param editor {RecurringItemEditor} the recurring item editor
*/
constructor(editor) {
this.editor = editor;
this.#expenseIncome = editor.expenseIncome;
const prefix = `accounting-recurring-accounting-selector-${editor.expenseIncome}`;
this.#query = document.getElementById(`${prefix}-query`);
const query = document.getElementById(`${prefix}-query`);
const options = Array.from(document.getElementsByClassName(`${prefix}-option`)).map((element) => new RecurringAccount(element, editor.saveAccount.bind(editor)));
super(query, options);
this.#editor = editor;
this.#expenseIncome = editor.expenseIncome;
this.#queryNoResult = document.getElementById(`${prefix}-option-no-result`);
this.#optionList = document.getElementById(`${prefix}-option-list`);
this.#options = Array.from(document.getElementsByClassName(`${prefix}-option`)).map((element) => new RecurringAccount(this, element));
this.#clearButton = document.getElementById(`${prefix}-clear`);
this.#query.oninput = () => this.#filterOptions();
this.#clearButton.onclick = () => this.editor.clearAccount();
this.#clearButton = document.getElementById(`${prefix}-clear`);
this.#clearButton.onclick = () => this.#editor.clearAccount();
const modal = document.getElementById(`${prefix}-modal`);
const closeButton = document.getElementById(`${prefix}-close`);
modal.onkeydown = (event) => {
if (event.key === "Escape") {
closeButton.click();
}
};
}
/**
* Filters the options.
*
* @override
*/
#filterOptions() {
filterOptions() {
this.shownOptions = [];
let isAnyMatched = false;
for (const option of this.#options) {
if (option.isMatched(this.#query.value)) {
for (const option of this.options) {
if (option.isMatched(this.query.value)) {
option.setShown(true);
this.shownOptions.push(option);
isAnyMatched = true;
} else {
option.setShown(false);
@@ -925,12 +936,11 @@ class RecurringAccountSelector {
*
*/
onOpen() {
this.#query.value = "";
this.#filterOptions();
for (const option of this.#options) {
option.setActive(option.code === this.editor.accountCode);
}
if (this.editor.accountCode === null) {
this.query.value = "";
this.filterOptions();
this.query.removeAttribute("aria-activedescendant");
this.selectOption(this.shownOptions.find((option) => option.code === this.#editor.accountCode));
if (this.#editor.accountCode === null) {
this.#clearButton.classList.add("btn-secondary");
this.#clearButton.classList.remove("btn-danger");
this.#clearButton.disabled = true;
@@ -947,13 +957,7 @@ class RecurringAccountSelector {
*
* @private
*/
class RecurringAccount {
/**
* The element
* @type {HTMLLIElement}
*/
#element;
class RecurringAccount extends BaseOption {
/**
* The account code
@@ -976,16 +980,16 @@ class RecurringAccount {
/**
* Constructs the account in the account selector for the recurring item editor.
*
* @param selector {RecurringAccountSelector} the account selector
* @param element {HTMLLIElement} the element
* @param save {function(RecurringAccount): void} the callback to save the option
*/
constructor(selector, element) {
this.#element = element;
constructor(element, save) {
super(element);
this.code = element.dataset.code;
this.text = element.dataset.text;
this.#queryValues = JSON.parse(element.dataset.queryValues);
this.#element.onclick = () => selector.editor.saveAccount(this);
element.onclick = () => save(this);
}
/**
@@ -1005,30 +1009,4 @@ class RecurringAccount {
}
return false;
}
/**
* Sets whether the option is shown.
*
* @param isShown {boolean} true to show, or false otherwise
*/
setShown(isShown) {
if (isShown) {
this.#element.classList.remove("d-none");
} else {
this.#element.classList.add("d-none");
}
}
/**
* Sets whether the option is active.
*
* @param isActive {boolean} true if active, or false otherwise
*/
setActive(isActive) {
if (isActive) {
this.#element.classList.add("active");
} else {
this.#element.classList.remove("active");
}
}
}
@@ -25,27 +25,16 @@
/**
* The original line item selector.
*
* @extends {BaseCombobox<OriginalLineItem>}
* @private
*/
class OriginalLineItemSelector {
class OriginalLineItemSelector extends BaseCombobox {
/**
* The line item editor
* @type {JournalEntryLineItemEditor}
*/
lineItemEditor;
/**
* The prefix of the HTML ID and class names
* @type {string}
*/
#prefix = "accounting-original-line-item-selector";
/**
* The query input
* @type {HTMLInputElement}
*/
#query;
#lineItemEditor;
/**
* The error message when the query has no result
@@ -59,12 +48,6 @@ class OriginalLineItemSelector {
*/
#optionList;
/**
* The options
* @type {OriginalLineItem[]}
*/
#options;
/**
* The options by their ID
* @type {Object.<string, OriginalLineItem>}
@@ -82,22 +65,37 @@ class OriginalLineItemSelector {
*/
#debitCredit;
/**
* The close button.
* @type {HTMLButtonElement}
*/
#closeButton;
/**
* Constructs an original line item selector.
*
* @param lineItemEditor {JournalEntryLineItemEditor} the line item editor
*/
constructor(lineItemEditor) {
this.lineItemEditor = lineItemEditor;
this.#query = document.getElementById(`${this.#prefix}-query`);
this.#queryNoResult = document.getElementById(`${this.#prefix}-option-no-result`);
this.#optionList = document.getElementById(`${this.#prefix}-option-list`);
this.#options = Array.from(document.getElementsByClassName(`${this.#prefix}-option`)).map((element) => new OriginalLineItem(this, element));
const prefix = "accounting-original-line-item-selector";
const query = document.getElementById(`${prefix}-query`);
const options = Array.from(document.getElementsByClassName(`${prefix}-option`)).map((element) => new OriginalLineItem(element, lineItemEditor.saveOriginalLineItem.bind(lineItemEditor), lineItemEditor.form));
super(query, options);
this.#lineItemEditor = lineItemEditor;
this.#queryNoResult = document.getElementById(`${prefix}-option-no-result`);
this.#optionList = document.getElementById(`${prefix}-option-list`);
this.#optionById = {};
for (const option of this.#options) {
for (const option of this.options) {
this.#optionById[option.id] = option;
}
this.#query.oninput = () => this.#filterOptions();
this.#closeButton = document.getElementById(`${prefix}-close`);
const modal = document.getElementById(`${prefix}-modal`);
modal.onkeydown = (event) => {
if (event.key === "Escape") {
this.#closeButton.click();
}
};
}
/**
@@ -127,7 +125,7 @@ class OriginalLineItemSelector {
*
*/
#updateNetBalances() {
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 = {}
for (const otherLineItem of otherLineItems) {
const otherOriginalLineItemId = otherLineItem.originalLineItemId;
@@ -140,7 +138,7 @@ class OriginalLineItemSelector {
}
otherOffsets[otherOriginalLineItemId] = otherOffsets[otherOriginalLineItemId].plus(amount);
}
for (const option of this.#options) {
for (const option of this.options) {
if (option.id in otherOffsets) {
option.updateNetBalance(otherOffsets[option.id]);
} else {
@@ -152,12 +150,15 @@ class OriginalLineItemSelector {
/**
* Filters the options.
*
* @override
*/
#filterOptions() {
filterOptions() {
this.shownOptions = [];
let isAnyMatched = false;
for (const option of this.#options) {
if (option.isMatched(this.#debitCredit, this.#currencyCode, this.#query.value)) {
for (const option of this.options) {
if (option.isMatched(this.#debitCredit, this.#currencyCode, this.query.value)) {
option.setShown(true);
this.shownOptions.push(option);
isAnyMatched = true;
} else {
option.setShown(false);
@@ -175,16 +176,17 @@ class OriginalLineItemSelector {
/**
* The callback when the original line item selector is shown.
*
* @param parentID {string} the ID of the parent element
*/
onOpen() {
this.#currencyCode = this.lineItemEditor.currencyCode;
this.#debitCredit = this.lineItemEditor.debitCredit;
for (const option of this.#options) {
option.setActive(option.id === this.lineItemEditor.originalLineItemId);
}
this.#query.value = "";
onOpen(parentID) {
this.#closeButton.dataset.bsTarget = `#${parentID}`;
this.#currencyCode = this.#lineItemEditor.currencyCode;
this.#debitCredit = this.#lineItemEditor.debitCredit;
this.query.value = "";
this.#updateNetBalances();
this.#filterOptions();
this.filterOptions();
this.query.removeAttribute("aria-activedescendant");
this.selectOption(this.shownOptions.find((option) => option.id === this.#lineItemEditor.originalLineItemId));
}
}
@@ -193,7 +195,7 @@ class OriginalLineItemSelector {
*
* @private
*/
class OriginalLineItem {
class OriginalLineItem extends BaseOption {
/**
* The journal entry form
@@ -201,12 +203,6 @@ class OriginalLineItem {
*/
#form;
/**
* The element
* @type {HTMLLIElement}
*/
#element;
/**
* The ID
* @type {string}
@@ -276,12 +272,13 @@ class OriginalLineItem {
/**
* Constructs an original line item.
*
* @param selector {OriginalLineItemSelector} the original line item selector
* @param element {HTMLLIElement} the element
* @param save {function(OriginalLineItem): void} the callback to save the option
* @param form {JournalEntryForm} the journal entry form
*/
constructor(selector, element) {
this.#form = selector.lineItemEditor.form;
this.#element = element;
constructor(element, save, form) {
super(element);
this.#form = form;
this.id = element.dataset.id;
this.date = element.dataset.date;
this.#debitCredit = element.dataset.debitCredit;
@@ -293,7 +290,8 @@ class OriginalLineItem {
this.netBalanceText = document.getElementById(`accounting-original-line-item-selector-option-${this.id}-net-balance`);
this.text = element.dataset.text;
this.#queryValues = JSON.parse(element.dataset.queryValues);
this.#element.onclick = () => selector.lineItemEditor.saveOriginalLineItem(this);
element.onclick = () => save(this);
}
/**
@@ -382,30 +380,4 @@ class OriginalLineItem {
const whole = Number(this.netBalance.minus(frac));
return String(whole) + String(frac).substring(1);
}
/**
* Sets whether the option is shown.
*
* @param isShown {boolean} true to show, or false otherwise
*/
setShown(isShown) {
if (isShown) {
this.#element.classList.remove("d-none");
} else {
this.#element.classList.add("d-none");
}
}
/**
* Sets whether the option is active.
*
* @param isActive {boolean} true if active, or false otherwise
*/
setActive(isActive) {
if (isActive) {
this.#element.classList.add("active");
} else {
this.#element.classList.remove("active");
}
}
}
+57 -113
View File
@@ -30,21 +30,16 @@ document.addEventListener("DOMContentLoaded", () => {
/**
* The period chooser.
*
* @extends {BaseTablist<BasePeriodTab>}
* @private
*/
class PeriodChooser {
class PeriodChooser extends BaseTablist {
/**
* The modal of the period chooser
* @type {HTMLDivElement}
* The URL template for different periods.
* @type {string}
*/
modal;
/**
* The tab planes
* @type {{month: MonthTab, year: YearTab, day: DayTab, custom: CustomTab}}
*/
tabPlanes = {};
urlTemplate;
/**
* Constructs the period chooser.
@@ -52,13 +47,24 @@ class PeriodChooser {
*/
constructor() {
const prefix = "accounting-period-chooser";
this.modal = document.getElementById(`${prefix}-modal`);
for (const cls of [MonthTab, YearTab, DayTab, CustomTab]) {
const tab = new cls(this);
this.tabPlanes[tab.tabId()] = tab;
super(document.getElementById(`${prefix}-tab-list`));
this.tabs = [new MonthTab(this), new YearTab(this), new DayTab(this), new CustomTab(this)];
const modal = document.getElementById(`${prefix}-modal`);
this.urlTemplate = modal.dataset.urlTemplate;
for (const tab of this.tabs) {
if (tab.isActive()) {
this.currentTab = tab;
break;
}
}
}
/**
* @inheritDoc
* @override
*/
onTabFocus(tab) { this.switchTo(tab); }
/**
* The period chooser.
* @type {PeriodChooser}
@@ -75,12 +81,12 @@ class PeriodChooser {
}
/**
* A tab plane.
* A base abstract period tab.
*
* @abstract
* @private
*/
class TabPlane {
class BasePeriodTab extends BaseTab {
/**
* The period chooser
@@ -95,62 +101,27 @@ class TabPlane {
prefix;
/**
* The tab
* @type {HTMLSpanElement}
*/
#tab;
/**
* The page
* @type {HTMLDivElement}
*/
#page;
/**
* Constructs a tab plane.
* Constructs a base abstract period tab.
*
* @param tabID {string} the tab ID
* @param chooser {PeriodChooser} the period chooser
*/
constructor(chooser) {
constructor(tabID, chooser) {
const prefix = `accounting-period-chooser-${tabID}`;
const tab = document.getElementById(`${prefix}-tab`);
const panel = document.getElementById(`${prefix}-panel`);
super(tab, panel, chooser.switchTo.bind(chooser));
this.chooser = chooser;
this.prefix = `accounting-period-chooser-${this.tabId()}`;
this.#tab = document.getElementById(`${this.prefix}-tab`);
this.#page = document.getElementById(`${this.prefix}-page`);
this.#tab.onclick = () => this.#switchToMe();
}
/**
* The tab ID
*
* @return {string}
* @abstract
*/
tabId() { throw new Error("Method not implemented.") };
/**
* Switches to the tab plane.
*
*/
#switchToMe() {
for (const tabPlane of Object.values(this.chooser.tabPlanes)) {
tabPlane.#tab.classList.remove("active")
tabPlane.#tab.ariaCurrent = "false";
tabPlane.#page.classList.add("d-none");
tabPlane.#page.ariaCurrent = "false";
}
this.#tab.classList.add("active");
this.#tab.ariaCurrent = "page";
this.#page.classList.remove("d-none");
this.#page.ariaCurrent = "page";
this.prefix = prefix;
}
}
/**
* The month tab plane.
* The month tab.
*
* @private
*/
class MonthTab extends TabPlane {
class MonthTab extends BasePeriodTab {
/**
* The month chooser.
@@ -159,12 +130,12 @@ class MonthTab extends TabPlane {
#monthChooser
/**
* Constructs a tab plane.
* Constructs a month tab.
*
* @param chooser {PeriodChooser} the period chooser
*/
constructor(chooser) {
super(chooser);
super("month", chooser);
const monthChooser = document.getElementById(`${this.prefix}-chooser`);
if (monthChooser !== null) {
this.#monthChooser = new tempusDominus.TempusDominus(monthChooser, {
@@ -184,45 +155,36 @@ class MonthTab extends TabPlane {
const date = e.detail.date;
const zeroPaddedMonth = `0${date.month + 1}`.slice(-2)
const period = `${date.year}-${zeroPaddedMonth}`;
window.location = chooser.modal.dataset.urlTemplate
window.location = chooser.urlTemplate
.replaceAll("PERIOD", period);
});
}
}
}
/**
* The year tab.
*
* @private
*/
class YearTab extends BasePeriodTab {
/**
* The tab ID
* Constructs a year tab.
*
* @return {string}
* @param chooser {PeriodChooser} the period chooser
*/
tabId() {
return "month";
constructor(chooser) {
super("year", chooser);
}
}
/**
* The year tab plane.
* The day tab.
*
* @private
*/
class YearTab extends TabPlane {
/**
* The tab ID
*
* @return {string}
*/
tabId() {
return "year";
}
}
/**
* The day tab plane.
*
* @private
*/
class DayTab extends TabPlane {
class DayTab extends BasePeriodTab {
/**
* The day input
@@ -237,18 +199,18 @@ class DayTab extends TabPlane {
#dateError;
/**
* Constructs a tab plane.
* Constructs a day tab.
*
* @param chooser {PeriodChooser} the period chooser
*/
constructor(chooser) {
super(chooser);
super("day", chooser);
this.#date = document.getElementById(`${this.prefix}-date`);
this.#dateError = document.getElementById(`${this.prefix}-date-error`);
if (this.#date !== null) {
this.#date.onchange = () => {
if (this.#validateDate()) {
window.location = chooser.modal.dataset.urlTemplate
window.location = chooser.urlTemplate
.replaceAll("PERIOD", this.#date.value);
}
};
@@ -275,23 +237,14 @@ class DayTab extends TabPlane {
this.#dateError.innerText = "";
return true;
}
/**
* The tab ID
*
* @return {string}
*/
tabId() {
return "day";
}
}
/**
* The custom tab plane.
* The custom tab.
*
* @private
*/
class CustomTab extends TabPlane {
class CustomTab extends BasePeriodTab {
/**
* The start of the period
@@ -324,12 +277,12 @@ class CustomTab extends TabPlane {
#confirm;
/**
* Constructs a tab plane.
* Constructs a custom tab.
*
* @param chooser {PeriodChooser} the period chooser
*/
constructor(chooser) {
super(chooser);
super("custom", chooser);
this.#start = document.getElementById(`${this.prefix}-start`);
this.#startError = document.getElementById(`${this.prefix}-start-error`);
this.#end = document.getElementById(`${this.prefix}-end`);
@@ -351,7 +304,7 @@ class CustomTab extends TabPlane {
isValid = this.#validateStart() && isValid;
isValid = this.#validateEnd() && isValid;
if (isValid) {
window.location = chooser.modal.dataset.urlTemplate
window.location = chooser.urlTemplate
.replaceAll("PERIOD", `${this.#start.value}-${this.#end.value}`);
}
};
@@ -407,13 +360,4 @@ class CustomTab extends TabPlane {
this.#endError.innerText = "";
return true;
}
/**
* The tab ID
*
* @return {string}
*/
tabId() {
return "custom";
}
}
@@ -27,28 +27,28 @@ First written: 2023/1/31
<div class="mb-3 accounting-toolbar">
<a class="btn btn-primary" role="button" href="{{ url_for("accounting.account.list")|accounting_or_next }}">
<i class="fa-solid fa-circle-chevron-left"></i>
<i class="fa-solid fa-circle-chevron-left" aria-hidden="true"></i>
<span class="d-none d-md-inline">{{ A_("Back") }}</span>
</a>
{% if accounting_can_edit() %}
<a class="btn btn-primary d-none d-md-inline" role="button" href="{{ url_for("accounting.account.edit", account=obj)|accounting_inherit_next }}">
<i class="fa-solid fa-pen-to-square"></i>
<i class="fa-solid fa-pen-to-square" aria-hidden="true"></i>
{{ A_("Edit") }}
</a>
{% endif %}
<a class="btn btn-primary" role="button" href="{{ url_for("accounting.account.order", base=obj.base)|accounting_append_next }}">
<i class="fa-solid fa-bars-staggered"></i>
<i class="fa-solid fa-bars-staggered" aria-hidden="true"></i>
<span class="d-none d-md-inline">{{ A_("Order") }}</span>
</a>
{% if accounting_can_edit() %}
{% if obj.can_delete %}
<button class="btn btn-danger" type="button" data-bs-toggle="modal" data-bs-target="#accounting-delete-modal">
<i class="fa-solid fa-trash"></i>
<i class="fa-solid fa-trash" aria-hidden="true"></i>
<span class="d-none d-md-inline">{{ A_("Delete") }}</span>
</button>
{% else %}
<button class="btn btn-secondary" type="button" disabled="disabled">
<i class="fa-solid fa-trash"></i>
<i class="fa-solid fa-trash" aria-hidden="true"></i>
<span class="d-none d-md-inline">{{ A_("Delete") }}</span>
</button>
{% endif %}
@@ -57,8 +57,8 @@ First written: 2023/1/31
{% if accounting_can_edit() %}
<div class="d-md-none accounting-material-fab">
<a class="btn btn-primary" role="button" href="{{ url_for("accounting.account.edit", account=obj)|accounting_inherit_next }}">
<i class="fa-solid fa-pen-to-square"></i>
<a class="btn btn-primary" role="button" href="{{ url_for("accounting.account.edit", account=obj)|accounting_inherit_next }}" aria-label="{{ A_("Edit") }}">
<i class="fa-solid fa-pen-to-square" aria-hidden="true"></i>
</a>
</div>
{% endif %}
@@ -22,6 +22,7 @@ First written: 2023/2/1
{% extends "accounting/base.html" %}
{% block accounting_scripts %}
<script src="{{ url_for("accounting.static", filename="js/base-combobox.js") }}"></script>
<script src="{{ url_for("accounting.static", filename="js/account-form.js") }}"></script>
{% endblock %}
@@ -29,7 +30,7 @@ First written: 2023/2/1
<div class="mb-3 accounting-toolbar">
<a class="btn btn-primary" role="button" href="{% block back_url %}{% endblock %}">
<i class="fa-solid fa-circle-chevron-left"></i>
<i class="fa-solid fa-circle-chevron-left" aria-hidden="true"></i>
<span class="d-none d-md-inline">{{ A_("Back") }}</span>
</a>
</div>
@@ -41,9 +42,9 @@ First written: 2023/2/1
{% endif %}
<div class="form-floating mb-3">
<input id="accounting-base-code" type="hidden" name="base_code" value="{{ form.base_code.data|accounting_default }}">
<div id="accounting-base-control" class="form-control accounting-clickable accounting-material-text-field {% if form.base_code.data %} accounting-not-empty {% endif %} {% if form.base_code.errors %} is-invalid {% endif %}" data-bs-toggle="modal" data-bs-target="#accounting-base-selector-modal">
<label class="form-label" for="accounting-base">{{ A_("Base account") }}</label>
<div id="accounting-base">
<button id="accounting-base-control" class="form-control text-start accounting-material-text-field {% if form.base_code.data %} accounting-not-empty {% endif %} {% if form.base_code.errors %} is-invalid {% endif %}" type="button" data-bs-toggle="modal" data-bs-target="#accounting-base-selector-modal">
<span class="form-label">{{ A_("Base account") }}</span>
<span id="accounting-base">
{% if form.base_code.data %}
{% if form.base_code.errors %}
{{ A_("(Unknown)") }}
@@ -51,15 +52,15 @@ First written: 2023/2/1
{{ form.selected_base }}
{% endif %}
{% endif %}
</div>
</div>
<div id="accounting-base-error" class="invalid-feedback">{% if form.base_code.errors %}{{ form.base_code.errors[0] }}{% endif %}</div>
</span>
</button>
<div id="accounting-base-error" class="invalid-feedback" role="alert">{% if form.base_code.errors %}{{ form.base_code.errors[0] }}{% endif %}</div>
</div>
<div class="form-floating mb-3">
<input id="accounting-title" class="form-control {% if form.title.errors %} is-invalid {% endif %}" type="text" name="title" value="{{ form.title.data|accounting_default }}" placeholder=" " required="required">
<label class="form-label" for="accounting-title">{{ A_("Title") }}</label>
<div id="accounting-title-error" class="invalid-feedback">{% if form.title.errors %}{{ form.title.errors[0] }}{% endif %}</div>
<div id="accounting-title-error" class="invalid-feedback" role="alert">{% if form.title.errors %}{{ form.title.errors[0] }}{% endif %}</div>
</div>
<div id="accounting-is-need-offset-control" class="form-check form-switch mb-3 {% if form.base_code.data[0] not in ["1", "2", "3"] %} d-none {% endif %}">
@@ -71,14 +72,14 @@ First written: 2023/2/1
<div class="d-none d-md-block">
<button class="btn btn-primary" type="submit">
<i class="fa-solid fa-floppy-disk"></i>
<i class="fa-solid fa-floppy-disk" aria-hidden="true"></i>
{{ A_("Save") }}
</button>
</div>
<div class="d-md-none accounting-material-fab">
<button class="btn btn-primary" type="submit">
<i class="fa-solid fa-floppy-disk"></i>
<button class="btn btn-primary" type="submit" aria-label="{{ A_("Save") }}">
<i class="fa-solid fa-floppy-disk" aria-hidden="true"></i>
</button>
</div>
</form>
@@ -92,21 +93,21 @@ First written: 2023/2/1
</div>
<div class="modal-body">
<div class="input-group mb-2">
<input id="accounting-base-selector-query" class="form-control form-control-sm" type="search" placeholder=" " required="required">
<input id="accounting-base-selector-query" class="form-control form-control-sm" type="search" placeholder=" " required="required" role="combobox" aria-expanded="true" aria-controls="accounting-base-selector-option-list" aria-autocomplete="list" aria-activedescendant="">
<label class="input-group-text" for="accounting-base-selector-query">
<i class="fa-solid fa-magnifying-glass"></i>
<i class="fa-solid fa-magnifying-glass" aria-hidden="true"></i>
{{ A_("Search") }}
</label>
</div>
<ul id="accounting-base-selector-option-list" class="list-group accounting-selector-list">
<ul id="accounting-base-selector-option-list" class="list-group accounting-selector-list" role="listbox" tabindex="-1" aria-labelledby="accounting-base-selector-modal-label">
{% for base in form.base_options %}
<li class="list-group-item accounting-clickable accounting-base-selector-option" data-code="{{ base.code }}" data-text="{{ base }}" data-query-values="{{ base.query_values|tojson|forceescape }}" data-bs-dismiss="modal">
<li id="accounting-base-selector-option-{{ base.code }}" class="list-group-item accounting-clickable accounting-base-selector-option" role="option" aria-selected="false" data-code="{{ base.code }}" data-text="{{ base }}" data-query-values="{{ base.query_values|tojson|forceescape }}" data-bs-dismiss="modal">
{{ base }}
</li>
{% endfor %}
</ul>
<p id="accounting-base-selector-option-no-result" class="d-none">{{ A_("There is no data.") }}</p>
<p id="accounting-base-selector-option-no-result" class="d-none" role="status">{{ A_("There is no data.") }}</p>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">{{ A_("Cancel") }}</button>
@@ -28,7 +28,7 @@ First written: 2023/1/30
<div class="mb-2 accounting-toolbar">
{% if accounting_can_edit() %}
<a class="btn btn-primary text-nowrap d-none d-md-block" role="button" href="{{ url_for("accounting.account.create")|accounting_append_next }}">
<i class="fa-solid fa-plus"></i>
<i class="fa-solid fa-plus" aria-hidden="true"></i>
{{ A_("New") }}
</a>
{% endif %}
@@ -36,7 +36,7 @@ First written: 2023/1/30
<input id="accounting-toolbar-search" class="form-control form-control-sm" type="search" name="q" value="{{ request.args.q }}" placeholder=" " required="required">
<label id="accounting-toolbar-search-label" for="accounting-toolbar-search" class="input-group-text">
<button type="submit">
<i class="fa-solid fa-magnifying-glass"></i>
<i class="fa-solid fa-magnifying-glass" aria-hidden="true"></i>
<span class="d-none d-md-inline">{{ A_("Search") }}</span>
</button>
</label>
@@ -45,8 +45,8 @@ First written: 2023/1/30
{% if accounting_can_edit() %}
<div class="d-md-none accounting-material-fab">
<a class="btn btn-primary" role="button" href="{{ url_for("accounting.account.create")|accounting_append_next }}">
<i class="fa-solid fa-plus"></i>
<a class="btn btn-primary" role="button" href="{{ url_for("accounting.account.create")|accounting_append_next }}" aria-label="{{ A_("New") }}">
<i class="fa-solid fa-plus" aria-hidden="true"></i>
</a>
</div>
{% endif %}
@@ -32,7 +32,7 @@ First written: 2023/2/2
<div class="mb-3 accounting-toolbar">
<a class="btn btn-primary" role="button" href="{{ url_for("accounting.account.list")|accounting_or_next }}">
<i class="fa-solid fa-circle-chevron-left"></i>
<i class="fa-solid fa-circle-chevron-left" aria-hidden="true"></i>
<span class="d-none d-md-inline">{{ A_("Back") }}</span>
</a>
</div>
@@ -51,21 +51,21 @@ First written: 2023/2/2
<span id="accounting-order-{{ account.id }}-code">{{ account.code }}</span>
{{ account.title }}
</div>
<i class="fa-solid fa-bars"></i>
<i class="fa-solid fa-bars" aria-hidden="true"></i>
</li>
{% endfor %}
</ul>
<div class="d-none d-md-block">
<button class="btn btn-primary" type="submit">
<i class="fa-solid fa-floppy-disk"></i>
<i class="fa-solid fa-floppy-disk" aria-hidden="true"></i>
{{ A_("Save") }}
</button>
</div>
<div class="d-md-none accounting-material-fab">
<button class="btn btn-primary" type="submit">
<i class="fa-solid fa-floppy-disk"></i>
<button class="btn btn-primary" type="submit" aria-label="{{ A_("Save") }}">
<i class="fa-solid fa-floppy-disk" aria-hidden="true"></i>
</button>
</div>
</form>
@@ -27,7 +27,7 @@ First written: 2023/2/1
<div class="mb-3 accounting-toolbar">
<a class="btn btn-primary" role="button" href="{{ url_for("accounting.account.list")|accounting_or_next }}">
<i class="fa-solid fa-circle-chevron-left"></i>
<i class="fa-solid fa-circle-chevron-left" aria-hidden="true"></i>
<span class="d-none d-md-inline">{{ A_("Back") }}</span>
</a>
</div>
@@ -30,7 +30,7 @@ First written: 2023/1/26
<input id="accounting-toolbar-search" class="form-control form-control-sm" type="search" name="q" value="{{ request.args.q }}" placeholder=" " required="required">
<label id="accounting-toolbar-search-label" for="accounting-toolbar-search" class="input-group-text">
<button type="submit">
<i class="fa-solid fa-magnifying-glass"></i>
<i class="fa-solid fa-magnifying-glass" aria-hidden="true"></i>
<span class="d-none d-md-inline">{{ A_("Search") }}</span>
</button>
</label>
@@ -27,24 +27,24 @@ First written: 2023/2/6
<div class="mb-3 accounting-toolbar">
<a class="btn btn-primary" role="button" href="{{ url_for("accounting.currency.list")|accounting_or_next }}">
<i class="fa-solid fa-circle-chevron-left"></i>
<i class="fa-solid fa-circle-chevron-left" aria-hidden="true"></i>
<span class="d-none d-md-inline">{{ A_("Back") }}</span>
</a>
{% if accounting_can_edit() %}
<a class="btn btn-primary d-none d-md-inline" role="button" href="{{ url_for("accounting.currency.edit", currency=obj)|accounting_inherit_next }}">
<i class="fa-solid fa-pen-to-square"></i>
<i class="fa-solid fa-pen-to-square" aria-hidden="true"></i>
{{ A_("Edit") }}
</a>
{% endif %}
{% if accounting_can_edit() %}
{% if obj.can_delete %}
<button class="btn btn-danger" type="button" data-bs-toggle="modal" data-bs-target="#accounting-delete-modal">
<i class="fa-solid fa-trash"></i>
<i class="fa-solid fa-trash" aria-hidden="true"></i>
<span class="d-none d-md-inline">{{ A_("Delete") }}</span>
</button>
{% else %}
<button class="btn btn-secondary" type="button" disabled="disabled">
<i class="fa-solid fa-trash"></i>
<i class="fa-solid fa-trash" aria-hidden="true"></i>
<span class="d-none d-md-inline">{{ A_("Delete") }}</span>
</button>
{% endif %}
@@ -53,8 +53,8 @@ First written: 2023/2/6
{% if accounting_can_edit() %}
<div class="d-md-none accounting-material-fab">
<a class="btn btn-primary" role="button" href="{{ url_for("accounting.currency.edit", currency=obj)|accounting_inherit_next }}">
<i class="fa-solid fa-pen-to-square"></i>
<a class="btn btn-primary" role="button" href="{{ url_for("accounting.currency.edit", currency=obj)|accounting_inherit_next }}" aria-label="{{ A_("Edit") }}">
<i class="fa-solid fa-pen-to-square" aria-hidden="true"></i>
</a>
</div>
{% endif %}
@@ -29,7 +29,7 @@ First written: 2023/2/6
<div class="mb-3 accounting-toolbar">
<a class="btn btn-primary" role="button" href="{% block back_url %}{% endblock %}">
<i class="fa-solid fa-circle-chevron-left"></i>
<i class="fa-solid fa-circle-chevron-left" aria-hidden="true"></i>
<span class="d-none d-md-inline">{{ A_("Back") }}</span>
</a>
</div>
@@ -42,25 +42,25 @@ First written: 2023/2/6
<div class="form-floating mb-3">
<input id="accounting-code" class="form-control {% if form.code.errors %} is-invalid {% endif %}" type="text" name="code" value="{{ form.code.data|accounting_default }}" placeholder=" " required="required" data-exists-url="{{ url_for("accounting.currency-api.exists") }}" data-original="{% block original_code %}{% endblock %}" data-blocklist="{{ form.CODE_BLOCKLIST|tojson|forceescape }}">
<label class="form-label" for="accounting-code">{{ A_("Code") }}</label>
<div id="accounting-code-error" class="invalid-feedback">{% if form.code.errors %}{{ form.code.errors[0] }}{% endif %}</div>
<div id="accounting-code-error" class="invalid-feedback" role="alert">{% if form.code.errors %}{{ form.code.errors[0] }}{% endif %}</div>
</div>
<div class="form-floating mb-3">
<input id="accounting-name" class="form-control {% if form.name.errors %} is-invalid {% endif %}" type="text" name="name" value="{{ form.name.data|accounting_default }}" placeholder=" " required="required">
<label class="form-label" for="accounting-name">{{ A_("Name") }}</label>
<div id="accounting-name-error" class="invalid-feedback">{% if form.name.errors %}{{ form.name.errors[0] }}{% endif %}</div>
<div id="accounting-name-error" class="invalid-feedback" role="alert">{% if form.name.errors %}{{ form.name.errors[0] }}{% endif %}</div>
</div>
<div class="d-none d-md-block">
<button class="btn btn-primary" type="submit">
<i class="fa-solid fa-floppy-disk"></i>
<i class="fa-solid fa-floppy-disk" aria-hidden="true"></i>
{{ A_("Save") }}
</button>
</div>
<div class="d-md-none accounting-material-fab">
<button class="btn btn-primary" type="submit">
<i class="fa-solid fa-floppy-disk"></i>
<button class="btn btn-primary" type="submit" aria-label="{{ A_("Save") }}">
<i class="fa-solid fa-floppy-disk" aria-hidden="true"></i>
</button>
</div>
</form>
@@ -28,7 +28,7 @@ First written: 2023/2/6
<div class="mb-2 accounting-toolbar">
{% if accounting_can_edit() %}
<a class="btn btn-primary text-nowrap d-none d-md-block" role="button" href="{{ url_for("accounting.currency.create")|accounting_append_next }}">
<i class="fa-solid fa-plus"></i>
<i class="fa-solid fa-plus" aria-hidden="true"></i>
{{ A_("New") }}
</a>
{% endif %}
@@ -36,7 +36,7 @@ First written: 2023/2/6
<input id="accounting-toolbar-search" class="form-control form-control-sm" type="search" name="q" value="{{ request.args.q }}" placeholder=" " required="required">
<label id="accounting-toolbar-search-label" for="accounting-toolbar-search" class="input-group-text">
<button type="submit">
<i class="fa-solid fa-magnifying-glass"></i>
<i class="fa-solid fa-magnifying-glass" aria-hidden="true"></i>
<span class="d-none d-md-inline">{{ A_("Search") }}</span>
</button>
</label>
@@ -45,8 +45,8 @@ First written: 2023/2/6
{% if accounting_can_edit() %}
<div class="d-md-none accounting-material-fab">
<a class="btn btn-primary" role="button" href="{{ url_for("accounting.currency.create")|accounting_append_next }}">
<i class="fa-solid fa-plus"></i>
<a class="btn btn-primary" role="button" href="{{ url_for("accounting.currency.create")|accounting_append_next }}" aria-label="{{ A_("New") }}">
<i class="fa-solid fa-plus" aria-hidden="true"></i>
</a>
</div>
{% endif %}
@@ -22,39 +22,39 @@ First written: 2023/1/26
{# <ul> For SonarQube not to complain about incorrect HTML #}
{% if accounting_can_view() %}
<li class="nav-item dropdown">
<span class="nav-link dropdown-toggle" data-bs-toggle="dropdown">
<i class="fa-solid fa-file-invoice-dollar"></i>
<button class="nav-link dropdown-toggle" type="button" data-bs-toggle="dropdown" aria-expanded="false">
<i class="fa-solid fa-file-invoice-dollar" aria-hidden="true"></i>
{{ A_("Accounting") }}
</span>
</button>
<ul class="dropdown-menu">
<li>
<a class="dropdown-item {% if request.endpoint and request.endpoint.startswith("accounting-report.") %} active {% endif %}" href="{{ url_for("accounting-report.default") }}">
<i class="fa-solid fa-book"></i>
<i class="fa-solid fa-book" aria-hidden="true"></i>
{{ A_("Reports") }}
</a>
</li>
<li>
<a class="dropdown-item {% if request.endpoint and request.endpoint.startswith("accounting.account.") %} active {% endif %}" href="{{ url_for("accounting.account.list") }}">
<i class="fa-solid fa-clipboard"></i>
<i class="fa-solid fa-clipboard" aria-hidden="true"></i>
{{ A_("Accounts") }}
</a>
</li>
<li>
<a class="dropdown-item {% if request.endpoint and request.endpoint.startswith("accounting.base-account.") %} active {% endif %}" href="{{ url_for("accounting.base-account.list") }}">
<i class="fa-solid fa-list"></i>
<i class="fa-solid fa-list" aria-hidden="true"></i>
{{ A_("Base Accounts") }}
</a>
</li>
<li>
<a class="dropdown-item {% if request.endpoint and request.endpoint.startswith("accounting.currency.") %} active {% endif %}" href="{{ url_for("accounting.currency.list") }}">
<i class="fa-solid fa-money-bill-wave"></i>
<i class="fa-solid fa-money-bill-wave" aria-hidden="true"></i>
{{ A_("Currencies") }}
</a>
</li>
{% if accounting_can_admin() %}
<li>
<a class="dropdown-item {% if request.endpoint and request.endpoint.startswith("accounting.option.") %} active {% endif %}" href="{{ url_for("accounting.option.detail") }}">
<i class="fa-solid fa-gear"></i>
<i class="fa-solid fa-gear" aria-hidden="true"></i>
{{ A_("Settings") }}
</a>
</li>
@@ -38,7 +38,7 @@ First written: 2023/1/26
{% endif %}
{% endfor %}
<li class="page-item d-none d-md-inline active dropdown">
<div class="page-link dropdown-toggle" data-bs-toggle="dropdown" aria-expanded="false">
<div class="page-link dropdown-toggle" role="button" tabindex="0" data-bs-toggle="dropdown" aria-expanded="false">
{{ pagination.page_size }}
</div>
<ul class="dropdown-menu">
@@ -23,7 +23,7 @@ First written: 2023/2/26
{% block as_transfer %}
<a class="btn btn-primary" role="button" href="{{ url_for("accounting.journal-entry.edit", journal_entry=obj)|accounting_journal_entry_to_transfer|accounting_inherit_next }}">
<i class="fa-solid fa-table-columns"></i>
<i class="fa-solid fa-table-columns" aria-hidden="true"></i>
<span class="d-none d-md-inline">{{ A_("As Transfer") }}</span>
</a>
{% endblock %}
@@ -19,31 +19,31 @@ account-selector-modal.html: The modal for the account selector
Author: imacat@mail.imacat.idv.tw (imacat)
First written: 2023/2/25
#}
<div id="accounting-account-selector-{{ debit_credit }}-modal" class="modal fade accounting-account-selector" data-debit-credit="{{ debit_credit }}" tabindex="-1" aria-labelledby="accounting-account-selector-{{ debit_credit }}-modal-label" aria-hidden="true">
<div id="accounting-account-selector-{{ debit_credit }}-modal" class="modal fade accounting-account-selector" data-debit-credit="{{ debit_credit }}" tabindex="-1" aria-labelledby="accounting-account-selector-{{ debit_credit }}-modal-label" aria-hidden="true" data-bs-keyboard="false">
<div class="modal-dialog">
<div class="modal-content">
<div class="modal-header">
<h1 class="modal-title fs-5" id="accounting-account-selector-{{ debit_credit }}-modal-label">{{ A_("Select Account") }}</h1>
<button type="button" class="btn-close" data-bs-toggle="modal" data-bs-target="#accounting-line-item-editor-modal" aria-label="{{ A_("Close") }}"></button>
<button id="accounting-account-selector-{{ debit_credit }}-close" type="button" class="btn-close" data-bs-toggle="modal" data-bs-target="#accounting-line-item-editor-modal" aria-label="{{ A_("Close") }}"></button>
</div>
<div class="modal-body">
<div class="input-group mb-2">
<input id="accounting-account-selector-{{ debit_credit }}-query" class="form-control form-control-sm" type="search" placeholder=" " required="required">
<input id="accounting-account-selector-{{ debit_credit }}-query" class="form-control form-control-sm" type="search" placeholder=" " required="required" role="combobox" aria-expanded="true" aria-controls="accounting-account-selector-{{ debit_credit }}-option-list" aria-autocomplete="list" aria-activedescendant="">
<label class="input-group-text" for="accounting-account-selector-{{ debit_credit }}-query">
<i class="fa-solid fa-magnifying-glass"></i>
<i class="fa-solid fa-magnifying-glass" aria-hidden="true"></i>
{{ A_("Search") }}
</label>
</div>
<ul id="accounting-account-selector-{{ debit_credit }}-option-list" class="list-group accounting-selector-list">
<ul id="accounting-account-selector-{{ debit_credit }}-option-list" class="list-group accounting-selector-list" role="listbox" tabindex="-1" aria-labelledby="accounting-account-selector-{{ debit_credit }}-modal-label">
{% for account in account_options %}
<li id="accounting-account-selector-{{ debit_credit }}-option-{{ account.code }}" class="list-group-item accounting-clickable accounting-account-selector-{{ debit_credit }}-option {% if account.is_in_use %} accounting-account-is-in-use {% endif %} {% if account.is_need_offset %} accounting-account-is-need-offset {% endif %}" data-code="{{ account.code }}" data-title="{{ account.title }}" data-text="{{ account }}" data-query-values="{{ account.query_values|tojson|forceescape }}" data-bs-toggle="modal" data-bs-target="#accounting-line-item-editor-modal">
<li id="accounting-account-selector-{{ debit_credit }}-option-{{ account.code }}" class="list-group-item accounting-clickable accounting-account-selector-{{ debit_credit }}-option {% if account.is_in_use %} accounting-account-is-in-use {% endif %} {% if account.is_need_offset %} accounting-account-is-need-offset {% endif %}" role="option" aria-selected="false" data-code="{{ account.code }}" data-title="{{ account.title }}" data-text="{{ account }}" data-query-values="{{ account.query_values|tojson|forceescape }}" data-bs-toggle="modal" data-bs-target="#accounting-line-item-editor-modal">
{{ account }}
</li>
{% endfor %}
<li id="accounting-account-selector-{{ debit_credit }}-more" class="list-group-item accounting-clickable">{{ A_("More…") }}</li>
<li id="accounting-account-selector-{{ debit_credit }}-more" class="list-group-item accounting-clickable" role="option" aria-selected="false">{{ A_("More…") }}</li>
</ul>
<p id="accounting-account-selector-{{ debit_credit }}-option-no-result" class="d-none">{{ A_("There is no data.") }}</p>
<p id="accounting-account-selector-{{ debit_credit }}-option-no-result" class="d-none" role="status">{{ A_("There is no data.") }}</p>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-secondary" data-bs-toggle="modal" data-bs-target="#accounting-line-item-editor-modal">{{ A_("Cancel") }}</button>
@@ -21,14 +21,14 @@ First written: 2023/2/28
#}
<form id="accounting-description-editor-{{ description_editor.debit_credit }}" class="accounting-description-editor" data-debit-credit="{{ description_editor.debit_credit }}">
<input type="hidden" name="csrf_token" value="{{ csrf_token() }}">
<div id="accounting-description-editor-{{ description_editor.debit_credit }}-modal" class="modal fade" tabindex="-1" aria-labelledby="accounting-description-editor-{{ description_editor.debit_credit }}-modal-label" aria-hidden="true">
<div id="accounting-description-editor-{{ description_editor.debit_credit }}-modal" class="modal fade" tabindex="-1" aria-labelledby="accounting-description-editor-{{ description_editor.debit_credit }}-modal-label" aria-hidden="true" data-bs-keyboard="false">
<div class="modal-dialog">
<div class="modal-content">
<div class="modal-header">
<h1 class="modal-title fs-5" id="accounting-description-editor-{{ description_editor.debit_credit }}-modal-label">
<label for="accounting-description-editor-{{ description_editor.debit_credit }}-description">{{ A_("Description") }}</label>
</h1>
<button class="btn-close" type="button" data-bs-toggle="modal" data-bs-target="#accounting-line-item-editor-modal" aria-label="{{ A_("Close") }}"></button>
<button id="accounting-description-editor-{{ description_editor.debit_credit }}-close" class="btn-close" type="button" data-bs-toggle="modal" data-bs-target="#accounting-line-item-editor-modal" aria-label="{{ A_("Close") }}"></button>
</div>
<div class="modal-body">
<div class="d-flex justify-content-between mb-3">
@@ -39,40 +39,40 @@ First written: 2023/2/28
</div>
{# Tab navigation #}
<ul class="nav nav-tabs mb-2">
<ul id="accounting-description-editor-{{ description_editor.debit_credit }}-tab-list" class="nav nav-tabs mb-2" role="tablist" aria-label="{{ A_("Description Type") }}">
<li class="nav-item">
<span id="accounting-description-editor-{{ description_editor.debit_credit }}-general-tab" class="nav-link active accounting-clickable" aria-current="page">
<button id="accounting-description-editor-{{ description_editor.debit_credit }}-general-tab" class="nav-link active accounting-clickable" type="button" tabindex="0" role="tab" aria-controls="accounting-description-editor-{{ description_editor.debit_credit }}-general-panel" aria-selected="true">
{{ A_("General") }}
</span>
</button>
</li>
<li class="nav-item">
<span id="accounting-description-editor-{{ description_editor.debit_credit }}-travel-tab" class="nav-link accounting-clickable" aria-current="false">
<button id="accounting-description-editor-{{ description_editor.debit_credit }}-travel-tab" class="nav-link accounting-clickable" type="button" tabindex="-1" role="tab" aria-controls="accounting-description-editor-{{ description_editor.debit_credit }}-travel-panel" aria-selected="false">
{{ A_("Travel") }}
</span>
</button>
</li>
<li class="nav-item">
<span id="accounting-description-editor-{{ description_editor.debit_credit }}-bus-tab" class="nav-link accounting-clickable" aria-current="false">
<button id="accounting-description-editor-{{ description_editor.debit_credit }}-bus-tab" class="nav-link accounting-clickable" type="button" tabindex="-1" role="tab" aria-controls="accounting-description-editor-{{ description_editor.debit_credit }}-bus-panel" aria-selected="false">
{{ A_("Bus") }}
</span>
</button>
</li>
<li class="nav-item">
<span id="accounting-description-editor-{{ description_editor.debit_credit }}-recurring-tab" class="nav-link accounting-clickable" aria-current="false">
<button id="accounting-description-editor-{{ description_editor.debit_credit }}-recurring-tab" class="nav-link accounting-clickable" type="button" tabindex="-1" role="tab" aria-controls="accounting-description-editor-{{ description_editor.debit_credit }}-recurring-panel" aria-selected="false">
{{ A_("Recurring") }}
</span>
</button>
</li>
<li class="nav-item">
<span id="accounting-description-editor-{{ description_editor.debit_credit }}-annotation-tab" class="nav-link accounting-clickable" aria-current="false">
<button id="accounting-description-editor-{{ description_editor.debit_credit }}-annotation-tab" class="nav-link accounting-clickable" type="button" tabindex="-1" role="tab" aria-controls="accounting-description-editor-{{ description_editor.debit_credit }}-annotation-panel" aria-selected="false">
{{ A_("Annotation") }}
</span>
</button>
</li>
</ul>
{# A general description with a tag #}
<div id="accounting-description-editor-{{ description_editor.debit_credit }}-general-page" aria-current="page" aria-labelledby="accounting-description-editor-{{ description_editor.debit_credit }}-general-tab">
<div id="accounting-description-editor-{{ description_editor.debit_credit }}-general-panel" role="tabpanel" aria-labelledby="accounting-description-editor-{{ description_editor.debit_credit }}-general-tab">
<div class="form-floating mb-2">
<input id="accounting-description-editor-{{ description_editor.debit_credit }}-general-tag" class="form-control" type="text" value="" placeholder=" ">
<label class="form-label" for="accounting-description-editor-{{ description_editor.debit_credit }}-general-tag">{{ A_("Tag") }}</label>
<div id="accounting-description-editor-{{ description_editor.debit_credit }}-general-tag-error" class="invalid-feedback"></div>
<div id="accounting-description-editor-{{ description_editor.debit_credit }}-general-tag-error" class="invalid-feedback" role="alert"></div>
</div>
<div class="accounting-description-editor-buttons">
@@ -85,11 +85,11 @@ First written: 2023/2/28
</div>
{# A general trip with the origin and distination #}
<div id="accounting-description-editor-{{ description_editor.debit_credit }}-travel-page" class="d-none" aria-current="false" aria-labelledby="accounting-description-editor-{{ description_editor.debit_credit }}-travel-tab">
<div id="accounting-description-editor-{{ description_editor.debit_credit }}-travel-panel" class="d-none" role="tabpanel" aria-labelledby="accounting-description-editor-{{ description_editor.debit_credit }}-travel-tab">
<div class="form-floating mb-2">
<input id="accounting-description-editor-{{ description_editor.debit_credit }}-travel-tag" class="form-control" type="text" value="" placeholder=" ">
<label class="form-label" for="accounting-description-editor-{{ description_editor.debit_credit }}-travel-tag">{{ A_("Tag") }}</label>
<div id="accounting-description-editor-{{ description_editor.debit_credit }}-travel-tag-error" class="invalid-feedback"></div>
<div id="accounting-description-editor-{{ description_editor.debit_credit }}-travel-tag-error" class="invalid-feedback" role="alert"></div>
</div>
<div class="accounting-description-editor-buttons">
@@ -104,7 +104,7 @@ First written: 2023/2/28
<div class="form-floating">
<input id="accounting-description-editor-{{ description_editor.debit_credit }}-travel-from" class="form-control" type="text" value="" placeholder=" ">
<label class="form-label" for="accounting-description-editor-{{ description_editor.debit_credit }}-travel-from">{{ A_("From") }}</label>
<div id="accounting-description-editor-{{ description_editor.debit_credit }}-travel-from-error" class="invalid-feedback"></div>
<div id="accounting-description-editor-{{ description_editor.debit_credit }}-travel-from-error" class="invalid-feedback" role="alert"></div>
</div>
<div class="btn-group-vertical ms-1 me-1">
<button class="btn btn-primary accounting-description-editor-{{ description_editor.debit_credit }}-travel-direction accounting-default" type="button" tabindex="-1" data-arrow="&rarr;">&rarr;</button>
@@ -113,23 +113,23 @@ First written: 2023/2/28
<div class="form-floating">
<input id="accounting-description-editor-{{ description_editor.debit_credit }}-travel-to" class="form-control" type="text" value="" placeholder=" ">
<label class="form-label" for="accounting-description-editor-{{ description_editor.debit_credit }}-travel-to">{{ A_("To") }}</label>
<div id="accounting-description-editor-{{ description_editor.debit_credit }}-travel-to-error" class="invalid-feedback"></div>
<div id="accounting-description-editor-{{ description_editor.debit_credit }}-travel-to-error" class="invalid-feedback" role="alert"></div>
</div>
</div>
</div>
{# A bus trip with the route name or route number, the origin and distination #}
<div id="accounting-description-editor-{{ description_editor.debit_credit }}-bus-page" class="d-none" aria-current="false" aria-labelledby="accounting-description-editor-{{ description_editor.debit_credit }}-bus-tab">
<div id="accounting-description-editor-{{ description_editor.debit_credit }}-bus-panel" class="d-none" role="tabpanel" aria-labelledby="accounting-description-editor-{{ description_editor.debit_credit }}-bus-tab">
<div class="d-flex justify-content-between mb-2">
<div class="form-floating me-2">
<input id="accounting-description-editor-{{ description_editor.debit_credit }}-bus-tag" class="form-control" type="text" value="" placeholder=" ">
<label class="form-label" for="accounting-description-editor-{{ description_editor.debit_credit }}-bus-tag">{{ A_("Tag") }}</label>
<div id="accounting-description-editor-{{ description_editor.debit_credit }}-bus-tag-error" class="invalid-feedback"></div>
<div id="accounting-description-editor-{{ description_editor.debit_credit }}-bus-tag-error" class="invalid-feedback" role="alert"></div>
</div>
<div class="form-floating">
<input id="accounting-description-editor-{{ description_editor.debit_credit }}-bus-route" class="form-control" type="text" value="" placeholder=" ">
<label class="form-label" for="accounting-description-editor-{{ description_editor.debit_credit }}-bus-route">{{ A_("Route") }}</label>
<div id="accounting-description-editor-{{ description_editor.debit_credit }}-bus-route-error" class="invalid-feedback"></div>
<div id="accounting-description-editor-{{ description_editor.debit_credit }}-bus-route-error" class="invalid-feedback" role="alert"></div>
</div>
</div>
@@ -145,18 +145,18 @@ First written: 2023/2/28
<div class="form-floating me-2">
<input id="accounting-description-editor-{{ description_editor.debit_credit }}-bus-from" class="form-control" type="text" value="" placeholder=" ">
<label class="form-label" for="accounting-description-editor-{{ description_editor.debit_credit }}-bus-from">{{ A_("From") }}</label>
<div id="accounting-description-editor-{{ description_editor.debit_credit }}-bus-from-error" class="invalid-feedback"></div>
<div id="accounting-description-editor-{{ description_editor.debit_credit }}-bus-from-error" class="invalid-feedback" role="alert"></div>
</div>
<div class="form-floating">
<input id="accounting-description-editor-{{ description_editor.debit_credit }}-bus-to" class="form-control" type="text" value="" placeholder=" ">
<label class="form-label" for="accounting-description-editor-{{ description_editor.debit_credit }}-bus-to">{{ A_("To") }}</label>
<div id="accounting-description-editor-{{ description_editor.debit_credit }}-bus-to-error" class="invalid-feedback"></div>
<div id="accounting-description-editor-{{ description_editor.debit_credit }}-bus-to-error" class="invalid-feedback" role="alert"></div>
</div>
</div>
</div>
{# A recurring transaction #}
<div id="accounting-description-editor-{{ description_editor.debit_credit }}-recurring-page" class="d-none" aria-current="false" aria-labelledby="accounting-description-editor-{{ description_editor.debit_credit }}-recurring-tab">
<div id="accounting-description-editor-{{ description_editor.debit_credit }}-recurring-panel" class="d-none" role="tabpanel" aria-labelledby="accounting-description-editor-{{ description_editor.debit_credit }}-recurring-tab">
<div class="accounting-description-editor-buttons">
{% for recurring in description_editor.recurring %}
<button class="btn btn-outline-primary accounting-description-editor-{{ description_editor.debit_credit }}-recurring-item" type="button" tabindex="-1" data-template="{{ recurring.description_template }}" data-accounts="{{ recurring.account_codes|tojson|forceescape }}">
@@ -167,17 +167,17 @@ First written: 2023/2/28
</div>
{# The annotation #}
<div id="accounting-description-editor-{{ description_editor.debit_credit }}-annotation-page" class="d-none" aria-current="false" aria-labelledby="accounting-description-editor-{{ description_editor.debit_credit }}-annotation-tab">
<div id="accounting-description-editor-{{ description_editor.debit_credit }}-annotation-panel" class="d-none" role="tabpanel" aria-labelledby="accounting-description-editor-{{ description_editor.debit_credit }}-annotation-tab">
<div class="form-floating">
<input id="accounting-description-editor-{{ description_editor.debit_credit }}-annotation-number" class="form-control" type="number" min="1" value="" placeholder=" ">
<label class="form-label" for="accounting-description-editor-{{ description_editor.debit_credit }}-annotation-number">{{ A_("The Number of Items") }}</label>
<div id="accounting-description-editor-{{ description_editor.debit_credit }}-annotation-number-error" class="invalid-feedback"></div>
<div id="accounting-description-editor-{{ description_editor.debit_credit }}-annotation-number-error" class="invalid-feedback" role="alert"></div>
</div>
<div class="form-floating mt-2">
<input id="accounting-description-editor-{{ description_editor.debit_credit }}-annotation-note" class="form-control" type="text" value="" placeholder=" ">
<label class="form-label" for="accounting-description-editor-{{ description_editor.debit_credit }}-annotation-note">{{ A_("Note") }}</label>
<div id="accounting-description-editor-{{ description_editor.debit_credit }}-annotation-note-error" class="invalid-feedback"></div>
<div id="accounting-description-editor-{{ description_editor.debit_credit }}-annotation-note-error" class="invalid-feedback" role="alert"></div>
</div>
</div>
@@ -27,29 +27,29 @@ First written: 2023/2/26
<div class="mb-3 accounting-toolbar">
<a class="btn btn-primary" role="button" href="{{ url_for("accounting-report.default")|accounting_or_next }}">
<i class="fa-solid fa-circle-chevron-left"></i>
<i class="fa-solid fa-circle-chevron-left" aria-hidden="true"></i>
<span class="d-none d-md-inline">{{ A_("Back") }}</span>
</a>
{% if accounting_can_edit() %}
<a class="btn btn-primary d-none d-md-inline" role="button" href="{{ url_for("accounting.journal-entry.edit", journal_entry=obj)|accounting_inherit_next }}">
<i class="fa-solid fa-pen-to-square"></i>
<i class="fa-solid fa-pen-to-square" aria-hidden="true"></i>
{{ A_("Edit") }}
</a>
{% endif %}
<a class="btn btn-primary" role="button" href="{{ url_for("accounting.journal-entry.order", date=obj.date)|accounting_append_next }}">
<i class="fa-solid fa-bars-staggered"></i>
<i class="fa-solid fa-bars-staggered" aria-hidden="true"></i>
<span class="d-none d-md-inline">{{ A_("Order") }}</span>
</a>
{% if accounting_can_edit() %}
{% block as_transfer %}{% endblock %}
{% if obj.can_delete %}
<button class="btn btn-danger" type="button" data-bs-toggle="modal" data-bs-target="#accounting-delete-modal">
<i class="fa-solid fa-trash"></i>
<i class="fa-solid fa-trash" aria-hidden="true"></i>
<span class="d-none d-md-inline">{{ A_("Delete") }}</span>
</button>
{% else %}
<button class="btn btn-secondary" type="button" disabled="disabled">
<i class="fa-solid fa-trash"></i>
<i class="fa-solid fa-trash" aria-hidden="true"></i>
<span class="d-none d-md-inline">{{ A_("Delete") }}</span>
</button>
{% endif %}
@@ -58,8 +58,8 @@ First written: 2023/2/26
{% if accounting_can_edit() %}
<div class="d-md-none accounting-material-fab">
<a class="btn btn-primary" role="button" href="{{ url_for("accounting.journal-entry.edit", journal_entry=obj)|accounting_inherit_next }}">
<i class="fa-solid fa-pen-to-square"></i>
<a class="btn btn-primary" role="button" href="{{ url_for("accounting.journal-entry.edit", journal_entry=obj)|accounting_inherit_next }}" aria-label="{{ A_("Edit") }}">
<i class="fa-solid fa-pen-to-square" aria-hidden="true"></i>
</a>
</div>
{% endif %}
@@ -104,7 +104,7 @@ First written: 2023/2/26
{% if obj.note %}
<div class="card mb-3">
<div class="card-body">
<i class="far fa-comment-dots"></i>
<i class="far fa-comment-dots" aria-hidden="true"></i>
{{ obj.note|accounting_journal_entry_text2html|safe }}
</div>
</div>
@@ -31,17 +31,17 @@ First written: 2023/3/21
{% endfor %}
</select>
<label class="form-label" for="accounting-currency-{{ currency_index }}-code-select">{{ A_("Currency") }}</label>
<div id="accounting-currency-{{ currency_index }}-code-error" class="invalid-feedback">{% if currency_code_errors %}{{ currency_code_errors[0] }}{% endif %}</div>
<div id="accounting-currency-{{ currency_index }}-code-error" class="invalid-feedback" role="alert">{% if currency_code_errors %}{{ currency_code_errors[0] }}{% endif %}</div>
</div>
<div>
<button id="accounting-currency-{{ currency_index }}-delete" class="btn btn-danger rounded-circle {% if only_one_currency_form %} d-none {% endif %}" type="button" data-target="accounting-currency-{{ currency_index }}">
<i class="fas fa-minus"></i>
<button id="accounting-currency-{{ currency_index }}-delete" class="btn btn-danger rounded-circle {% if only_one_currency_form %} d-none {% endif %}" type="button" data-target="accounting-currency-{{ currency_index }}" aria-label="{{ A_("Remove") }}">
<i class="fas fa-minus" aria-hidden="true"></i>
</button>
</div>
</div>
{% block line_items %}{% endblock %}
</div>
<div id="accounting-currency-{{ currency_index }}-error" class="invalid-feedback">{% if currency_errors %}{{ currency_errors[0] }}{% endif %}</div>
<div id="accounting-currency-{{ currency_index }}-error" class="invalid-feedback" role="alert">{% if currency_errors %}{{ currency_errors[0] }}{% endif %}</div>
</div>
@@ -20,8 +20,8 @@ Author: imacat@mail.imacat.idv.tw (imacat)
First written: 2023/3/21
#}
<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 %}">
<label class="form-label" for="accounting-currency-{{ currency_index }}-{{ debit_credit }}">{{ header }}</label>
<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 %}" {% if line_item_forms %} tabindex="-1" {% else %} role="button" tabindex="0" {% endif %}>
<span class="form-label">{{ header }}</span>
<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">
{% for line_item_form in line_item_forms %}
@@ -36,16 +36,16 @@ First written: 2023/3/21
<div class="d-flex justify-content-between mt-2 mb-2">
<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" aria-live="polite">{{ debit_credit_total }}</span></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">
<i class="fas fa-plus"></i>
<i class="fas fa-plus" aria-hidden="true"></i>
{{ A_("New") }}
</button>
</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" role="alert">{% if debit_credit_errors %}{{ debit_credit_errors[0] }}{% endif %}</div>
</div>
@@ -30,7 +30,7 @@ First written: 2023/2/25
<input id="accounting-currency-{{ currency_index }}-{{ debit_credit }}-{{ line_item_index }}-description" type="hidden" name="currency-{{ currency_index }}-{{ debit_credit }}-{{ line_item_index }}-description" value="{{ form.description.data|accounting_default }}">
<input id="accounting-currency-{{ currency_index }}-{{ debit_credit }}-{{ line_item_index }}-amount" type="hidden" name="currency-{{ currency_index }}-{{ debit_credit }}-{{ line_item_index }}-amount" value="{{ form.amount.data|accounting_journal_entry_format_amount_input }}" data-min="{{ form.offset_total|accounting_default("0") }}">
<div class="accounting-line-item-content">
<div id="accounting-currency-{{ currency_index }}-{{ debit_credit }}-{{ line_item_index }}-control" class="form-control clickable d-flex justify-content-between {% if form.all_errors %} is-invalid {% endif %}" data-bs-toggle="modal" data-bs-target="#accounting-line-item-editor-modal">
<div id="accounting-currency-{{ currency_index }}-{{ debit_credit }}-{{ line_item_index }}-control" class="form-control clickable d-flex justify-content-between {% if form.all_errors %} is-invalid {% endif %}" role="button" tabindex="0" data-bs-toggle="modal" data-bs-target="#accounting-line-item-editor-modal">
<div>
<div class="small">
<span id="accounting-currency-{{ currency_index }}-{{ debit_credit }}-{{ line_item_index }}-account-text-code" class="d-none d-md-inline">{{ form.account_code.data|accounting_default }}</span>
@@ -65,12 +65,12 @@ First written: 2023/2/25
</div>
<div><span id="accounting-currency-{{ currency_index }}-{{ debit_credit }}-{{ line_item_index }}-amount-text" class="badge rounded-pill bg-primary">{{ form.amount.data|accounting_format_amount }}</span></div>
</div>
<div id="accounting-currency-{{ currency_index }}-{{ debit_credit }}-{{ line_item_index }}-error" class="invalid-feedback">{% if form.all_errors %}{{ form.all_errors[0] }}{% endif %}</div>
<div id="accounting-currency-{{ currency_index }}-{{ debit_credit }}-{{ line_item_index }}-error" class="invalid-feedback" role="alert">{% if form.all_errors %}{{ form.all_errors[0] }}{% endif %}</div>
</div>
<div>
<button id="accounting-currency-{{ currency_index }}-{{ debit_credit }}-{{ line_item_index }}-delete" class="btn btn-danger rounded-circle {% if only_one_form or form.offsets %} d-none {% endif %}" type="button" data-target="accounting-currency-{{ currency_index }}-{{ debit_credit }}-{{ line_item_index }}">
<i class="fas fa-minus"></i>
<button id="accounting-currency-{{ currency_index }}-{{ debit_credit }}-{{ line_item_index }}-delete" class="btn btn-danger rounded-circle {% if only_one_form or form.offsets %} d-none {% endif %}" type="button" data-target="accounting-currency-{{ currency_index }}-{{ debit_credit }}-{{ line_item_index }}" aria-label="{{ A_("Remove") }}">
<i class="fas fa-minus" aria-hidden="true"></i>
</button>
</div>
</li>
@@ -23,10 +23,12 @@ First written: 2023/2/26
{% block accounting_scripts %}
<script src="{{ url_for("accounting.static", filename="js/drag-and-drop-reorder.js") }}"></script>
<script src="{{ url_for("accounting.static", filename="js/base-combobox.js") }}"></script>
<script src="{{ url_for("accounting.static", filename="js/journal-entry-form.js") }}"></script>
<script src="{{ url_for("accounting.static", filename="js/journal-entry-line-item-editor.js") }}"></script>
<script src="{{ url_for("accounting.static", filename="js/journal-entry-account-selector.js") }}"></script>
<script src="{{ url_for("accounting.static", filename="js/original-line-item-selector.js") }}"></script>
<script src="{{ url_for("accounting.static", filename="js/base-tablist.js") }}"></script>
<script src="{{ url_for("accounting.static", filename="js/description-editor.js") }}"></script>
{% endblock %}
@@ -34,7 +36,7 @@ First written: 2023/2/26
<div class="mb-3 accounting-toolbar">
<a class="btn btn-primary" role="button" href="{% block back_url %}{% endblock %}">
<i class="fa-solid fa-circle-chevron-left"></i>
<i class="fa-solid fa-circle-chevron-left" aria-hidden="true"></i>
<span class="d-none d-md-inline">{{ A_("Back") }}</span>
</a>
</div>
@@ -48,7 +50,7 @@ First written: 2023/2/26
<div class="form-floating mb-3">
<input id="accounting-date" class="form-control {% if form.date.errors %} is-invalid {% endif %}" type="date" name="date" value="{{ form.date.data|accounting_default }}" max="{{ form.max_date|accounting_default }}" min="{{ form.min_date|accounting_default }}" placeholder=" " required="required">
<label class="form-label" for="accounting-date">{{ A_("Date") }}</label>
<div id="accounting-date-error" class="invalid-feedback">{% if form.date.errors %}{{ form.date.errors[0] }}{% endif %}</div>
<div id="accounting-date-error" class="invalid-feedback" role="alert">{% if form.date.errors %}{{ form.date.errors[0] }}{% endif %}</div>
</div>
<div class="mb-3">
@@ -60,30 +62,30 @@ First written: 2023/2/26
<div>
<button id="accounting-add-currency" class="btn btn-primary" type="button">
<i class="fas fa-plus"></i>
<i class="fas fa-plus" aria-hidden="true"></i>
{{ A_("New") }}
</button>
</div>
</div>
<div id="accounting-currencies-error" class="invalid-feedback">{% if form.currencies_errors %}{{ form.currencies_errors[0] }}{% endif %}</div>
<div id="accounting-currencies-error" class="invalid-feedback" role="alert">{% if form.currencies_errors %}{{ form.currencies_errors[0] }}{% endif %}</div>
</div>
<div class="form-floating mb-3">
<textarea id="accounting-note" class="form-control form-control-lg {% if form.note.errors %} is-invalid {% endif %}" name="note" rows="5" placeholder=" ">{{ form.note.data|accounting_default }}</textarea>
<label class="form-label" for="accounting-note">{{ A_("Note") }}</label>
<div id="accounting-note-error" class="invalid-feedback">{% if form.note.errors %}{{ form.note.errors[0] }}{% endif %}</div>
<div id="accounting-note-error" class="invalid-feedback" role="alert">{% if form.note.errors %}{{ form.note.errors[0] }}{% endif %}</div>
</div>
<div class="d-none d-md-block">
<button class="btn btn-primary" type="submit">
<i class="fa-solid fa-floppy-disk"></i>
<i class="fa-solid fa-floppy-disk" aria-hidden="true"></i>
{{ A_("Save") }}
</button>
</div>
<div class="d-md-none accounting-material-fab">
<button class="btn btn-primary" type="submit">
<i class="fa-solid fa-floppy-disk"></i>
<button class="btn btn-primary" type="submit" aria-label="{{ A_("Save") }}">
<i class="fa-solid fa-floppy-disk" aria-hidden="true"></i>
</button>
</div>
</form>
@@ -31,40 +31,40 @@ First written: 2023/2/25
<div class="modal-body">
<div id="accounting-line-item-editor-original-line-item-container" class="d-flex justify-content-between mb-3">
<div class="accounting-line-item-editor-original-line-item-content">
<div id="accounting-line-item-editor-original-line-item-control" class="form-control accounting-clickable accounting-material-text-field" data-bs-toggle="modal" data-bs-target="#accounting-original-line-item-selector-modal">
<label class="form-label" for="accounting-line-item-editor-original-line-item">{{ A_("Original Line Item") }}</label>
<div id="accounting-line-item-editor-original-line-item"></div>
</div>
<div id="accounting-line-item-editor-original-line-item-error" class="invalid-feedback"></div>
<button id="accounting-line-item-editor-original-line-item-control" class="form-control text-start accounting-material-text-field" type="button" data-bs-toggle="modal" data-bs-target="#accounting-original-line-item-selector-modal">
<span class="form-label">{{ A_("Original Line Item") }}</span>
<span id="accounting-line-item-editor-original-line-item"></span>
</button>
<div id="accounting-line-item-editor-original-line-item-error" class="invalid-feedback" role="alert"></div>
</div>
<div>
<button id="accounting-line-item-editor-original-line-item-delete" class="btn btn-danger rounded-circle" type="button">
<i class="fas fa-minus"></i>
<button id="accounting-line-item-editor-original-line-item-delete" class="btn btn-danger rounded-circle" type="button" aria-label="{{ A_("Remove") }}">
<i class="fas fa-minus" aria-hidden="true"></i>
</button>
</div>
</div>
<div class="mb-3">
<div id="accounting-line-item-editor-description-control" class="form-control accounting-clickable accounting-material-text-field" data-bs-toggle="modal" data-bs-target="">
<label class="form-label" for="accounting-line-item-editor-description">{{ A_("Description") }}</label>
<div id="accounting-line-item-editor-description"></div>
</div>
<div id="accounting-line-item-editor-description-error" class="invalid-feedback"></div>
<button id="accounting-line-item-editor-description-control" class="form-control text-start accounting-material-text-field" type="button" data-bs-toggle="modal" data-bs-target="">
<span class="form-label">{{ A_("Description") }}</span>
<span id="accounting-line-item-editor-description"></span>
</button>
<div id="accounting-line-item-editor-description-error" class="invalid-feedback" role="alert"></div>
</div>
<div class="mb-3">
<div id="accounting-line-item-editor-account-control" class="form-control accounting-clickable accounting-material-text-field" data-bs-toggle="modal" data-bs-target="">
<label class="form-label" for="accounting-line-item-editor-account">{{ A_("Account") }}</label>
<div id="accounting-line-item-editor-account"></div>
</div>
<div id="accounting-line-item-editor-account-error" class="invalid-feedback"></div>
<button id="accounting-line-item-editor-account-control" class="form-control text-start accounting-material-text-field" type="button" data-bs-toggle="modal" data-bs-target="">
<span class="form-label">{{ A_("Account") }}</span>
<span id="accounting-line-item-editor-account"></span>
</button>
<div id="accounting-line-item-editor-account-error" class="invalid-feedback" role="alert"></div>
</div>
<div class="form-floating mb-3">
<input id="accounting-line-item-editor-amount" class="form-control" type="number" value="" min="0" max="" step="0.01" placeholder=" " required="required">
<label for="accounting-line-item-editor-amount">{{ A_("Amount") }}</label>
<div id="accounting-line-item-editor-amount-error" class="invalid-feedback"></div>
<div id="accounting-line-item-editor-amount-error" class="invalid-feedback" role="alert"></div>
</div>
</div>
<div class="modal-footer">
@@ -19,25 +19,25 @@ original-line-item-selector-modal.html: The modal of the original line item sele
Author: imacat@mail.imacat.idv.tw (imacat)
First written: 2023/2/25
#}
<div id="accounting-original-line-item-selector-modal" class="modal fade" tabindex="-1" aria-labelledby="accounting-original-line-item-selector-modal-label" aria-hidden="true">
<div id="accounting-original-line-item-selector-modal" class="modal fade" tabindex="-1" aria-labelledby="accounting-original-line-item-selector-modal-label" aria-hidden="true" data-bs-keyboard="false">
<div class="modal-dialog">
<div class="modal-content">
<div class="modal-header">
<h1 class="modal-title fs-5" id="accounting-original-line-item-selector-modal-label">{{ A_("Select Original Line Item") }}</h1>
<button type="button" class="btn-close" data-bs-toggle="modal" data-bs-target="#accounting-line-item-editor-modal" aria-label="{{ A_("Close") }}"></button>
<button id="accounting-original-line-item-selector-close" type="button" class="btn-close" data-bs-toggle="modal" data-bs-target="#accounting-line-item-editor-modal" aria-label="{{ A_("Close") }}"></button>
</div>
<div class="modal-body">
<div class="input-group mb-2">
<input id="accounting-original-line-item-selector-query" class="form-control form-control-sm" type="search" placeholder=" " required="required">
<input id="accounting-original-line-item-selector-query" class="form-control form-control-sm" type="search" placeholder=" " required="required" role="combobox" aria-expanded="true" aria-controls="accounting-original-line-item-selector-option-list" aria-autocomplete="list" aria-activedescendant="">
<label class="input-group-text" for="accounting-original-line-item-selector-query">
<i class="fa-solid fa-magnifying-glass"></i>
<i class="fa-solid fa-magnifying-glass" aria-hidden="true"></i>
{{ A_("Search") }}
</label>
</div>
<ul id="accounting-original-line-item-selector-option-list" class="list-group accounting-selector-list">
<ul id="accounting-original-line-item-selector-option-list" class="list-group accounting-selector-list" role="listbox" tabindex="-1" aria-labelledby="accounting-original-line-item-selector-modal-label">
{% for line_item in form.original_line_item_options %}
<li id="accounting-original-line-item-selector-option-{{ line_item.id }}" class="list-group-item d-flex justify-content-between accounting-clickable accounting-original-line-item-selector-option" data-id="{{ line_item.id }}" data-date="{{ line_item.journal_entry.date }}" data-debit-credit="{{ "debit" if line_item.is_debit else "credit" }}" data-currency-code="{{ line_item.currency.code }}" data-account-code="{{ line_item.account_code }}" data-account-title="{{ line_item.account.title }}" data-account-text="{{ line_item.account }}" data-description="{{ line_item.description|accounting_default }}" data-net-balance="{{ line_item.net_balance|accounting_journal_entry_format_amount_input }}" data-text="{{ line_item }}" data-query-values="{{ line_item.query_values|tojson|forceescape }}" data-bs-toggle="modal" data-bs-target="#accounting-line-item-editor-modal">
<li id="accounting-original-line-item-selector-option-{{ line_item.id }}" class="list-group-item d-flex justify-content-between accounting-clickable accounting-original-line-item-selector-option" data-id="{{ line_item.id }}" data-date="{{ line_item.journal_entry.date }}" data-debit-credit="{{ "debit" if line_item.is_debit else "credit" }}" data-currency-code="{{ line_item.currency.code }}" data-account-code="{{ line_item.account_code }}" data-account-title="{{ line_item.account.title }}" data-account-text="{{ line_item.account }}" data-description="{{ line_item.description|accounting_default }}" data-net-balance="{{ line_item.net_balance|accounting_journal_entry_format_amount_input }}" data-text="{{ line_item }}" data-query-values="{{ line_item.query_values|tojson|forceescape }}" role="option" aria-selected="false" data-bs-toggle="modal" data-bs-target="#accounting-line-item-editor-modal">
<div>
<div class="small">
{{ line_item.journal_entry.date|accounting_format_date }}
@@ -55,7 +55,7 @@ First written: 2023/2/25
</li>
{% endfor %}
</ul>
<p id="accounting-original-line-item-selector-option-no-result" class="d-none">{{ A_("There is no data.") }}</p>
<p id="accounting-original-line-item-selector-option-no-result" class="d-none" role="status">{{ A_("There is no data.") }}</p>
</div>
</div>
</div>
@@ -32,7 +32,7 @@ First written: 2023/2/26
<div class="mb-3 accounting-toolbar">
<a class="btn btn-primary" role="button" href="{{ url_for("accounting-report.default")|accounting_or_next }}">
<i class="fa-solid fa-circle-chevron-left"></i>
<i class="fa-solid fa-circle-chevron-left" aria-hidden="true"></i>
<span class="d-none d-md-inline">{{ A_("Back") }}</span>
</a>
</div>
@@ -50,21 +50,21 @@ First written: 2023/2/26
{% with journal_entry = item %}
{% include "accounting/journal-entry/include/order-journal-entry.html" %}
{% endwith %}
<i class="fa-solid fa-bars"></i>
<i class="fa-solid fa-bars" aria-hidden="true"></i>
</li>
{% endfor %}
</ul>
<div class="d-none d-md-block">
<button class="btn btn-primary" type="submit">
<i class="fa-solid fa-floppy-disk"></i>
<i class="fa-solid fa-floppy-disk" aria-hidden="true"></i>
{{ A_("Save") }}
</button>
</div>
<div class="d-md-none accounting-material-fab">
<button class="btn btn-primary" type="submit">
<i class="fa-solid fa-floppy-disk"></i>
<button class="btn btn-primary" type="submit" aria-label="{{ A_("Save") }}">
<i class="fa-solid fa-floppy-disk" aria-hidden="true"></i>
</button>
</div>
</form>
@@ -23,7 +23,7 @@ First written: 2023/2/26
{% block as_transfer %}
<a class="btn btn-primary" role="button" href="{{ url_for("accounting.journal-entry.edit", journal_entry=obj)|accounting_journal_entry_to_transfer|accounting_inherit_next }}">
<i class="fa-solid fa-table-columns"></i>
<i class="fa-solid fa-table-columns" aria-hidden="true"></i>
<span class="d-none d-md-inline">{{ A_("As Transfer") }}</span>
</a>
{% endblock %}
@@ -27,14 +27,14 @@ First written: 2023/3/22
<div class="mb-3 accounting-toolbar">
<a class="btn btn-primary d-none d-md-inline" role="button" href="{{ url_for("accounting.option.edit")|accounting_inherit_next }}">
<i class="fa-solid fa-pen-to-square"></i>
<i class="fa-solid fa-pen-to-square" aria-hidden="true"></i>
{{ A_("Edit") }}
</a>
</div>
<div class="d-md-none accounting-material-fab">
<a class="btn btn-primary" role="button" href="{{ url_for("accounting.option.edit")|accounting_inherit_next }}">
<i class="fa-solid fa-pen-to-square"></i>
<a class="btn btn-primary" role="button" href="{{ url_for("accounting.option.edit")|accounting_inherit_next }}" aria-label="{{ A_("Edit") }}">
<i class="fa-solid fa-pen-to-square" aria-hidden="true"></i>
</a>
</div>
@@ -23,6 +23,7 @@ First written: 2023/3/22
{% block accounting_scripts %}
<script src="{{ url_for("accounting.static", filename="js/drag-and-drop-reorder.js") }}"></script>
<script src="{{ url_for("accounting.static", filename="js/base-combobox.js") }}"></script>
<script src="{{ url_for("accounting.static", filename="js/option-form.js") }}"></script>
{% endblock %}
@@ -32,7 +33,7 @@ First written: 2023/3/22
<div class="mb-3 accounting-toolbar">
<a class="btn btn-primary" role="button" href="{{ url_for("accounting.option.detail")|accounting_inherit_next }}">
<i class="fa-solid fa-circle-chevron-left"></i>
<i class="fa-solid fa-circle-chevron-left" aria-hidden="true"></i>
<span class="d-none d-md-inline">{{ A_("Back") }}</span>
</a>
</div>
@@ -49,7 +50,7 @@ First written: 2023/3/22
{% endfor %}
</select>
<label class="form-label" for="accounting-default-currency">{{ A_("Default Currency") }}</label>
<div id="accounting-default-currency-error" class="invalid-feedback">{% if form.default_currency_code.errors %}{{ form.default_currency_code.errors[0] }}{% endif %}</div>
<div id="accounting-default-currency-error" class="invalid-feedback" role="alert">{% if form.default_currency_code.errors %}{{ form.default_currency_code.errors[0] }}{% endif %}</div>
</div>
<div class="form-floating mb-3">
@@ -59,7 +60,7 @@ First written: 2023/3/22
{% endfor %}
</select>
<label class="form-label" for="accounting-default-ie-account">{{ A_("Default Account for the Income and Expenses Log") }}</label>
<div id="accounting-default-ie-account-error" class="invalid-feedback">{% if form.default_ie_account_code.errors %}{{ form.default_ie_account_code.errors[0] }}{% endif %}</div>
<div id="accounting-default-ie-account-error" class="invalid-feedback" role="alert">{% if form.default_ie_account_code.errors %}{{ form.default_ie_account_code.errors[0] }}{% endif %}</div>
</div>
{% with expense_income = "expense",
@@ -76,14 +77,14 @@ First written: 2023/3/22
<div class="d-none d-md-block">
<button class="btn btn-primary" type="submit">
<i class="fa-solid fa-floppy-disk"></i>
<i class="fa-solid fa-floppy-disk" aria-hidden="true"></i>
{{ A_("Save") }}
</button>
</div>
<div class="d-md-none accounting-material-fab">
<button class="btn btn-primary" type="submit">
<i class="fa-solid fa-floppy-disk"></i>
<button class="btn btn-primary" type="submit" aria-label="{{ A_("Save") }}">
<i class="fa-solid fa-floppy-disk" aria-hidden="true"></i>
</button>
</div>
</form>
@@ -19,8 +19,8 @@ form-recurring-expense-income.html: The recurring expense or income sub-form in
Author: imacat@mail.imacat.idv.tw (imacat)
First written: 2023/3/22
#}
<div id="accounting-recurring-{{ expense_income }}" class="form-control mb-3 accounting-material-text-field {% if recurring_items %} accounting-not-empty {% else %} accounting-clickable {% endif %}">
<label class="form-label" for="accounting-recurring-{{ expense_income }}">{{ label }}</label>
<div id="accounting-recurring-{{ expense_income }}" class="form-control mb-3 accounting-material-text-field {% if recurring_items %} accounting-not-empty {% else %} accounting-clickable {% endif %}" {% if recurring_items %} tabindex="-1" {% else %} role="button" tabindex="0" {% endif %}>
<span class="form-label">{{ label }}</span>
<div id="accounting-recurring-{{ expense_income }}-content" class="{% if not recurring_items %} d-none {% endif %}">
<ul id="accounting-recurring-{{ expense_income }}-list" class="list-group mb-2 mt-2">
{% for recurring_item in recurring_items %}
@@ -33,7 +33,7 @@ First written: 2023/3/22
<div>
<button id="accounting-recurring-{{ expense_income }}-add" class="btn btn-primary" type="button" data-bs-toggle="modal" data-bs-target="#accounting-recurring-item-editor-{{ expense_income }}-modal">
<i class="fas fa-plus"></i>
<i class="fas fa-plus" aria-hidden="true"></i>
{{ A_("New") }}
</button>
</div>
@@ -27,17 +27,17 @@ First written: 2023/3/22
<input id="accounting-recurring-{{ expense_income }}-{{ item_index }}-description-template" type="hidden" name="recurring-{{ expense_income }}-{{ item_index }}-description_template" value="{{ form.description_template.data|accounting_default }}">
<div class="d-flex justify-content-between">
<div class="w-100">
<div id="accounting-recurring-{{ expense_income }}-{{ item_index }}-control" class="form-control accounting-clickable {% if form.all_errors %} is-invalid {% endif %}" data-bs-toggle="modal" data-bs-target="#accounting-recurring-item-editor-{{ expense_income }}-modal">
<div id="accounting-recurring-{{ expense_income }}-{{ item_index }}-account-text" class="small">{{ form.account_text|accounting_default }}</div>
<div id="accounting-recurring-{{ expense_income }}-{{ item_index }}-name-text">{{ form.name.data|accounting_default }}</div>
<div id="accounting-recurring-{{ expense_income }}-{{ item_index }}-description-template-text" class="small">{{ form.description_template.data|accounting_default }}</div>
</div>
<div id="accounting-recurring-{{ expense_income }}-{{ item_index }}-error" class="invalid-feedback">{% if form.all_errors %}{{ form.all_errors[0] }}{% endif %}</div>
<button id="accounting-recurring-{{ expense_income }}-{{ item_index }}-control" class="form-control text-start {% if form.all_errors %} is-invalid {% endif %}" type="button" data-bs-toggle="modal" data-bs-target="#accounting-recurring-item-editor-{{ expense_income }}-modal">
<span id="accounting-recurring-{{ expense_income }}-{{ item_index }}-account-text" class="d-block small">{{ form.account_text|accounting_default }}</span>
<span id="accounting-recurring-{{ expense_income }}-{{ item_index }}-name-text" class="d-block">{{ form.name.data|accounting_default }}</span>
<span id="accounting-recurring-{{ expense_income }}-{{ item_index }}-description-template-text" class="d-block small">{{ form.description_template.data|accounting_default }}</span>
</button>
<div id="accounting-recurring-{{ expense_income }}-{{ item_index }}-error" class="invalid-feedback" role="alert">{% if form.all_errors %}{{ form.all_errors[0] }}{% endif %}</div>
</div>
<div class="ms-2">
<button id="accounting-recurring-{{ expense_income }}-{{ item_index }}-delete" class="btn btn-danger rounded-circle" type="button">
<i class="fas fa-minus"></i>
<button id="accounting-recurring-{{ expense_income }}-{{ item_index }}-delete" class="btn btn-danger rounded-circle" type="button" aria-label="{{ A_("Remove") }}">
<i class="fas fa-minus" aria-hidden="true"></i>
</button>
</div>
</div>
@@ -19,30 +19,30 @@ recurring-account-selector-modal.html: The modal of the account selector for the
Author: imacat@mail.imacat.idv.tw (imacat)
First written: 2023/3/22
#}
<div id="accounting-recurring-accounting-selector-{{ expense_income }}-modal" class="modal fade" tabindex="-1" aria-labelledby="accounting-recurring-accounting-selector-{{ expense_income }}-modal-label" aria-hidden="true">
<div id="accounting-recurring-accounting-selector-{{ expense_income }}-modal" class="modal fade" tabindex="-1" aria-labelledby="accounting-recurring-accounting-selector-{{ expense_income }}-modal-label" aria-hidden="true" data-bs-keyboard="false">
<div class="modal-dialog">
<div class="modal-content">
<div class="modal-header">
<h1 class="modal-title fs-5" id="accounting-recurring-accounting-selector-{{ expense_income }}-modal-label">{{ A_("Select Account") }}</h1>
<button type="button" class="btn-close" data-bs-toggle="modal" data-bs-target="#accounting-recurring-item-editor-{{ expense_income }}-modal" aria-label="{{ A_("Close") }}"></button>
<button id="accounting-recurring-accounting-selector-{{ expense_income }}-close" type="button" class="btn-close" data-bs-toggle="modal" data-bs-target="#accounting-recurring-item-editor-{{ expense_income }}-modal" aria-label="{{ A_("Close") }}"></button>
</div>
<div class="modal-body">
<div class="input-group mb-2">
<input id="accounting-recurring-accounting-selector-{{ expense_income }}-query" class="form-control form-control-sm" type="search" placeholder=" " required="required">
<input id="accounting-recurring-accounting-selector-{{ expense_income }}-query" class="form-control form-control-sm" type="search" placeholder=" " required="required" role="combobox" aria-expanded="true" aria-controls="accounting-recurring-accounting-selector-{{ expense_income }}-option-list" aria-autocomplete="list" aria-activedescendant="">
<label class="input-group-text" for="accounting-recurring-accounting-selector-{{ expense_income }}-query">
<i class="fa-solid fa-magnifying-glass"></i>
<i class="fa-solid fa-magnifying-glass" aria-hidden="true"></i>
{{ A_("Search") }}
</label>
</div>
<ul id="accounting-recurring-accounting-selector-{{ expense_income }}-option-list" class="list-group accounting-selector-list">
<ul id="accounting-recurring-accounting-selector-{{ expense_income }}-option-list" class="list-group accounting-selector-list" role="listbox" tabindex="-1" aria-labelledby="accounting-recurring-accounting-selector-{{ expense_income }}-modal-label">
{% for account in accounts %}
<li id="accounting-recurring-accounting-selector-{{ expense_income }}-option-{{ account.code }}" class="list-group-item accounting-clickable accounting-recurring-accounting-selector-{{ expense_income }}-option" data-code="{{ account.code }}" data-text="{{ account }}" data-query-values="{{ account.query_values|tojson|forceescape }}" data-bs-toggle="modal" data-bs-target="#accounting-recurring-item-editor-{{ expense_income }}-modal">
<li id="accounting-recurring-accounting-selector-{{ expense_income }}-option-{{ account.code }}" class="list-group-item accounting-clickable accounting-recurring-accounting-selector-{{ expense_income }}-option" data-code="{{ account.code }}" role="option" aria-selected="false" data-text="{{ account }}" data-query-values="{{ account.query_values|tojson|forceescape }}" data-bs-toggle="modal" data-bs-target="#accounting-recurring-item-editor-{{ expense_income }}-modal">
{{ account }}
</li>
{% endfor %}
</ul>
<p id="accounting-recurring-accounting-selector-{{ expense_income }}-option-no-result" class="d-none">{{ A_("There is no data.") }}</p>
<p id="accounting-recurring-accounting-selector-{{ expense_income }}-option-no-result" class="d-none" role="status">{{ A_("There is no data.") }}</p>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-secondary" data-bs-toggle="modal" data-bs-target="#accounting-recurring-item-editor-{{ expense_income }}-modal">{{ A_("Cancel") }}</button>
@@ -32,21 +32,21 @@ First written: 2023/3/22
<div class="form-floating mb-3">
<input id="accounting-recurring-item-editor-{{ expense_income }}-name" class="form-control" type="text" value="" placeholder=" " required="required">
<label for="accounting-recurring-item-editor-{{ expense_income }}-name">{{ A_("Name") }}</label>
<div id="accounting-recurring-item-editor-{{ expense_income }}-name-error" class="invalid-feedback"></div>
<div id="accounting-recurring-item-editor-{{ expense_income }}-name-error" class="invalid-feedback" role="alert"></div>
</div>
<div class="mb-3">
<div id="accounting-recurring-item-editor-{{ expense_income }}-account-control" class="form-control accounting-clickable accounting-material-text-field" data-bs-toggle="modal" data-bs-target="#accounting-recurring-accounting-selector-{{ expense_income }}-modal">
<label class="form-label" for="accounting-recurring-item-editor-{{ expense_income }}-account">{{ A_("Account") }}</label>
<div id="accounting-recurring-item-editor-{{ expense_income }}-account"></div>
</div>
<div id="accounting-recurring-item-editor-{{ expense_income }}-account-error" class="invalid-feedback"></div>
<button id="accounting-recurring-item-editor-{{ expense_income }}-account-control" class="form-control text-start accounting-material-text-field" type="button" data-bs-toggle="modal" data-bs-target="#accounting-recurring-accounting-selector-{{ expense_income }}-modal">
<span class="form-label">{{ A_("Account") }}</span>
<span id="accounting-recurring-item-editor-{{ expense_income }}-account"></span>
</button>
<div id="accounting-recurring-item-editor-{{ expense_income }}-account-error" class="invalid-feedback" role="alert"></div>
</div>
<div class="form-floating mb-3">
<input id="accounting-recurring-item-editor-{{ expense_income }}-description-template" class="form-control" type="text" value="" placeholder=" " required="required">
<label for="accounting-recurring-item-editor-{{ expense_income }}-description-template">{{ A_("Description Template") }}</label>
<div id="accounting-recurring-item-editor-{{ expense_income }}-description-template-error" class="invalid-feedback"></div>
<div id="accounting-recurring-item-editor-{{ expense_income }}-description-template-error" class="invalid-feedback" role="alert"></div>
</div>
<div class="mb-3 border-top accounting-recurring-description-template-illustration">
@@ -23,6 +23,7 @@ First written: 2023/3/7
{% block accounting_scripts %}
<script src="{{ url_for("accounting.static", filename="js/material-fab-speed-dial.js") }}"></script>
<script src="{{ url_for("accounting.static", filename="js/base-tablist.js") }}"></script>
<script src="{{ url_for("accounting.static", filename="js/period-chooser.js") }}"></script>
{% endblock %}
@@ -55,15 +56,15 @@ First written: 2023/3/7
</h2>
</div>
<div class="row accounting-report-table accounting-balance-sheet-table">
<div class="row accounting-report-table accounting-balance-sheet-table" role="table">
<div class="col-sm-6">
{% if report.assets.subsections %}
{% with section = report.assets %}
{% include "accounting/report/include/balance-sheet-section.html" %}
{% endwith %}
<div class="d-md-none d-flex justify-content-between accounting-report-table-row accounting-balance-sheet-total">
<div>{{ A_("Total") }}</div>
<div class="accounting-amount {% if report.assets.total < 0 %} text-danger {% endif %}">{{ report.assets.total|accounting_report_format_amount }}</div>
<div class="d-md-none d-flex justify-content-between accounting-report-table-row accounting-balance-sheet-total" role="row">
<div role="cell">{{ A_("Total") }}</div>
<div class="accounting-amount {% if report.assets.total < 0 %} text-danger {% endif %}" role="cell">{{ report.assets.total|accounting_report_format_amount }}</div>
</div>
{% endif %}
</div>
@@ -73,9 +74,9 @@ First written: 2023/3/7
{% with section = report.liabilities %}
{% include "accounting/report/include/balance-sheet-section.html" %}
{% endwith %}
<div class="d-flex justify-content-between accounting-report-table-row accounting-balance-sheet-subtotal">
<div>{{ A_("Total") }}</div>
<div class="accounting-amount {% if report.liabilities.total < 0 %} text-danger {% endif %}">{{ report.liabilities.total|accounting_report_format_amount }}</div>
<div class="d-flex justify-content-between accounting-report-table-row accounting-balance-sheet-subtotal" role="row">
<div role="cell">{{ A_("Total") }}</div>
<div class="accounting-amount {% if report.liabilities.total < 0 %} text-danger {% endif %}" role="cell">{{ report.liabilities.total|accounting_report_format_amount }}</div>
</div>
{% endif %}
@@ -83,15 +84,15 @@ First written: 2023/3/7
{% with section = report.owner_s_equity %}
{% include "accounting/report/include/balance-sheet-section.html" %}
{% endwith %}
<div class="d-flex justify-content-between accounting-report-table-row accounting-balance-sheet-subtotal">
<div>{{ A_("Total") }}</div>
<div class="accounting-amount {% if report.owner_s_equity.total < 0 %} text-danger {% endif %}">{{ report.owner_s_equity.total|accounting_report_format_amount }}</div>
<div class="d-flex justify-content-between accounting-report-table-row accounting-balance-sheet-subtotal" role="row">
<div role="cell">{{ A_("Total") }}</div>
<div class="accounting-amount {% if report.owner_s_equity.total < 0 %} text-danger {% endif %}" role="cell">{{ report.owner_s_equity.total|accounting_report_format_amount }}</div>
</div>
{% endif %}
<div class="d-md-none d-flex justify-content-between accounting-report-table-row accounting-balance-sheet-total">
<div>{{ A_("Total") }}</div>
<div class="accounting-amount {% if report.liabilities.total < 0 %} text-danger {% endif %}">{{ (report.liabilities.total + report.owner_s_equity.total)|accounting_report_format_amount }}</div>
<div class="d-md-none d-flex justify-content-between accounting-report-table-row accounting-balance-sheet-total" role="row">
<div role="cell">{{ A_("Total") }}</div>
<div class="accounting-amount {% if report.liabilities.total < 0 %} text-danger {% endif %}" role="cell">{{ (report.liabilities.total + report.owner_s_equity.total)|accounting_report_format_amount }}</div>
</div>
</div>
</div>
@@ -32,8 +32,8 @@ First written: 2023/2/25
{{ A_("Transfer") }}
</a>
</div>
<button id="accounting-btn-material-fab-speed-dial" class="btn btn-primary rounded-circle accounting-btn-material-fab" type="button" data-target="accounting-material-fab-speed-dial">
<i class="fas fa-plus"></i>
<button id="accounting-btn-material-fab-speed-dial" class="btn btn-primary rounded-circle accounting-btn-material-fab" type="button" data-target="accounting-material-fab-speed-dial" aria-label="{{ A_("New Journal Entry") }}">
<i class="fas fa-plus" aria-hidden="true"></i>
</button>
</div>
{% endif %}
@@ -19,24 +19,24 @@ balance-sheet-section.html: A section in the balance sheet.
Author: imacat@mail.imacat.idv.tw (imacat)
First written: 2023/3/8
#}
<div class="accounting-report-table-row accounting-balance-sheet-section">
<div>{{ section.title.title }}</div>
<div class="accounting-report-table-row accounting-balance-sheet-section" role="row">
<div role="cell">{{ section.title.title }}</div>
</div>
<div class="accounting-report-table-body">
{% for subsection in section.subsections %}
<div class="accounting-report-table-row accounting-balance-sheet-subsection">
<div>
<div class="accounting-report-table-row accounting-balance-sheet-subsection" role="row">
<div role="cell">
<span class="d-none d-md-inline">{{ subsection.title.code }}</span>
{{ subsection.title.title }}
</div>
</div>
{% for account in subsection.accounts %}
<a class="d-flex justify-content-between accounting-report-table-row accounting-balance-sheet-account" href="{{ account.url }}">
<div>
<a class="d-flex justify-content-between accounting-report-table-row accounting-balance-sheet-account" role="row" href="{{ account.url }}">
<div role="cell">
<span class="d-none d-md-inline">{{ account.account.code }}</span>
{{ account.account.title }}
</div>
<div class="accounting-amount {% if account.amount < 0 %} text-danger {% endif %}">{{ account.amount|accounting_report_format_amount }}</div>
<div class="accounting-amount {% if account.amount < 0 %} text-danger {% endif %}" role="cell">{{ account.amount|accounting_report_format_amount }}</div>
</a>
{% endfor %}
{% endfor %}
@@ -19,9 +19,9 @@ income-expenses-row-desktop.html: The row in the income and expenses log for the
Author: imacat@mail.imacat.idv.tw (imacat)
First written: 2023/3/8
#}
<div>{{ line_item.date|accounting_format_date }}</div>
<div>{{ line_item.account.title }}</div>
<div>{{ line_item.description|accounting_default }}</div>
<div class="accounting-amount">{{ line_item.income|accounting_format_amount|accounting_default }}</div>
<div class="accounting-amount">{{ line_item.expense|accounting_format_amount|accounting_default }}</div>
<div class="accounting-amount {% if line_item.balance < 0 %} text-danger {% endif %}">{{ line_item.balance|accounting_report_format_amount }}</div>
<div role="cell">{{ line_item.date|accounting_format_date }}</div>
<div role="cell">{{ line_item.account.title }}</div>
<div role="cell">{{ line_item.description|accounting_default }}</div>
<div class="accounting-amount" role="cell">{{ line_item.income|accounting_format_amount|accounting_default }}</div>
<div class="accounting-amount" role="cell">{{ line_item.expense|accounting_format_amount|accounting_default }}</div>
<div class="accounting-amount {% if line_item.balance < 0 %} text-danger {% endif %}" role="cell">{{ line_item.balance|accounting_report_format_amount }}</div>
@@ -19,10 +19,10 @@ ledger-row-desktop.html: The row in the ledger for the desktop computers
Author: imacat@mail.imacat.idv.tw (imacat)
First written: 2023/3/8
#}
<div>{{ line_item.date|accounting_format_date }}</div>
<div>{{ line_item.description|accounting_default }}</div>
<div class="accounting-amount">{{ line_item.debit|accounting_format_amount|accounting_default }}</div>
<div class="accounting-amount">{{ line_item.credit|accounting_format_amount|accounting_default }}</div>
<div role="cell">{{ line_item.date|accounting_format_date }}</div>
<div role="cell">{{ line_item.description|accounting_default }}</div>
<div class="accounting-amount" role="cell">{{ line_item.debit|accounting_format_amount|accounting_default }}</div>
<div class="accounting-amount" role="cell">{{ line_item.credit|accounting_format_amount|accounting_default }}</div>
{% if report.account.is_real %}
<div class="accounting-amount {% if line_item.balance < 0 %} text-danger {% endif %}">{{ line_item.balance|accounting_report_format_amount }}</div>
<div class="accounting-amount {% if line_item.balance < 0 %} text-danger {% endif %}" role="cell">{{ line_item.balance|accounting_report_format_amount }}</div>
{% endif %}
@@ -28,31 +28,31 @@ First written: 2023/3/4
</div>
<div class="modal-body">
{# Tab navigation #}
<ul class="nav nav-tabs mb-2">
<ul id="accounting-period-chooser-tab-list" class="nav nav-tabs mb-2" role="tablist" aria-label="{{ A_("Period Type") }}">
<li class="nav-item">
<span id="accounting-period-chooser-month-tab" class="nav-link {% if report.period.is_type_month %} active {% endif %} accounting-clickable" aria-current="{% if report.period.is_type_month %} page {% else %} false {% endif %}" data-tab-id="month">
<button id="accounting-period-chooser-month-tab" class="nav-link {% if report.period.is_type_month %} active {% endif %} accounting-clickable" type="button" tabindex="{% if report.period.is_type_month %}0{% else %}-1{% endif %}" role="tab" aria-controls="accounting-period-chooser-month-panel" aria-selected="{% if report.period.is_type_month %}true{% else %}false{% endif %}">
{{ A_("Month") }}
</span>
</button>
</li>
<li class="nav-item">
<span id="accounting-period-chooser-year-tab" class="nav-link {% if report.period.is_a_year %} active {% endif %} accounting-clickable" aria-current="{% if report.period.is_a_year %} page {% else %} false {% endif %}" data-tab-id="year">
<button id="accounting-period-chooser-year-tab" class="nav-link {% if report.period.is_a_year %} active {% endif %} accounting-clickable" type="button" tabindex="{% if report.period.is_a_year %}0{% else %}-1{% endif %}" role="tab" aria-controls="accounting-period-chooser-year-panel" aria-selected="{% if report.period.is_a_year %}true{% else %}false{% endif %}">
{{ A_("Year") }}
</span>
</button>
</li>
<li class="nav-item">
<span id="accounting-period-chooser-day-tab" class="nav-link {% if report.period.is_a_day %} active {% endif %} accounting-clickable" aria-current="{% if report.period.is_a_day %} page {% else %} false {% endif %}" data-tab-id="day">
<button id="accounting-period-chooser-day-tab" class="nav-link {% if report.period.is_a_day %} active {% endif %} accounting-clickable" type="button" tabindex="{% if report.period.is_a_day %}0{% else %}-1{% endif %}" role="tab" aria-controls="accounting-period-chooser-day-panel" aria-selected="{% if report.period.is_a_day %}true{% else %}false{% endif %}">
{{ A_("Day") }}
</span>
</button>
</li>
<li class="nav-item">
<span id="accounting-period-chooser-custom-tab" class="nav-link {% if report.period.is_type_arbitrary %} active {% endif %} accounting-clickable" aria-current="{% if report.period.is_type_arbitrary %} page {% else %} false {% endif %}" data-tab-id="custom">
<button id="accounting-period-chooser-custom-tab" class="nav-link {% if report.period.is_type_arbitrary %} active {% endif %} accounting-clickable" type="button" tabindex="{% if report.period.is_type_arbitrary %}0{% else %}-1{% endif %}" role="tab" aria-controls="accounting-period-chooser-custom-panel" aria-selected="{% if report.period.is_type_arbitrary %}true{% else %}false{% endif %}">
{{ A_("Custom") }}
</span>
</button>
</li>
</ul>
{# The month periods #}
<div id="accounting-period-chooser-month-page" {% if report.period.is_type_month %} aria-current="page" {% else %} class="d-none" aria-current="false" {% endif %} aria-labelledby="accounting-period-chooser-month-tab">
<div id="accounting-period-chooser-month-panel" {% if not report.period.is_type_month %} class="d-none" {% endif %} role="tabpanel" aria-labelledby="accounting-period-chooser-month-tab">
<div>
<a class="btn {% if report.period.is_this_month %} btn-primary {% else %} btn-outline-primary {% endif %}" role="button" href="{{ report.period_chooser.this_month_url }}">
{{ A_("This Month") }}
@@ -72,7 +72,7 @@ First written: 2023/3/4
</div>
{# The year periods #}
<div id="accounting-period-chooser-year-page" {% if report.period.is_a_year %} aria-current="page" {% else %} class="d-none" aria-current="false" {% endif %} aria-labelledby="accounting-period-chooser-year-tab">
<div id="accounting-period-chooser-year-panel" {% if not report.period.is_a_year %} class="d-none" {% endif %} role="tabpanel" aria-labelledby="accounting-period-chooser-year-tab">
<a class="btn {% if report.period.is_this_year %} btn-primary {% else %} btn-outline-primary {% endif %}" role="button" href="{{ report.period_chooser.this_year_url }}">
{{ A_("This Year") }}
</a>
@@ -93,7 +93,7 @@ First written: 2023/3/4
</div>
{# The day periods #}
<div id="accounting-period-chooser-day-page" {% if report.period.is_a_day %} aria-current="page" {% else %} class="d-none" aria-current="false" {% endif %} aria-labelledby="accounting-period-chooser-day-tab">
<div id="accounting-period-chooser-day-panel" {% if not report.period.is_a_day %} class="d-none" {% endif %} role="tabpanel" aria-labelledby="accounting-period-chooser-day-tab">
<div>
<a class="btn {% if report.period.is_today %} btn-primary {% else %} btn-outline-primary {% endif %}" role="button" href="{{ report.period_chooser.today_url }}">
{{ A_("Today") }}
@@ -109,14 +109,14 @@ First written: 2023/3/4
<div class="form-floating mb-3">
<input id="accounting-period-chooser-day-date" class="form-control" type="date" value="{{ report.period.start|accounting_default }}" min="{{ report.period_chooser.data_start }}" required="required">
<label for="accounting-period-chooser-day-date" class="form-label">{{ A_("Date") }}</label>
<div id="accounting-period-chooser-day-date-error" class="invalid-feedback"></div>
<div id="accounting-period-chooser-day-date-error" class="invalid-feedback" role="alert"></div>
</div>
</div>
{% endif %}
</div>
{# The custom periods #}
<div id="accounting-period-chooser-custom-page" {% if report.period.is_type_arbitrary %} aria-current="page" {% else %} class="d-none" aria-current="false" {% endif %} aria-labelledby="accounting-period-chooser-custom-tab">
<div id="accounting-period-chooser-custom-panel" {% if not report.period.is_type_arbitrary %} class="d-none" {% endif %} role="tabpanel" aria-labelledby="accounting-period-chooser-custom-tab">
<div>
<a class="btn {% if report.period.is_all %} btn-primary {% else %} btn-outline-primary {% endif %}" role="button" href="{{ report.period_chooser.all_url }}">
{{ A_("All") }}
@@ -127,13 +127,13 @@ First written: 2023/3/4
<div class="form-floating mb-3">
<input id="accounting-period-chooser-custom-start" class="form-control" type="date" value="{{ report.period.start|accounting_default }}" min="{{ report.period_chooser.data_start }}" max="{{ report.period.end }}" required="required">
<label for="accounting-period-chooser-custom-start" class="form-label">{{ A_("From") }}</label>
<div id="accounting-period-chooser-custom-start-error" class="invalid-feedback"></div>
<div id="accounting-period-chooser-custom-start-error" class="invalid-feedback" role="alert"></div>
</div>
<div class="form-floating mb-3">
<input id="accounting-period-chooser-custom-end" class="form-control" type="date" value="{{ report.period.end|accounting_default }}" min="{{ report.period.start }}" required="required">
<label for="accounting-period-chooser-custom-end" class="form-label">{{ A_("To") }}</label>
<div id="accounting-period-chooser-custom-end-error" class="invalid-feedback"></div>
<div id="accounting-period-chooser-custom-end-error" class="invalid-feedback" role="alert"></div>
</div>
<div>
@@ -22,7 +22,7 @@ First written: 2023/3/8
{% if accounting_can_edit() %}
<div class="btn-group d-none d-md-flex" role="group">
<button type="button" class="btn btn-primary dropdown-toggle" data-bs-toggle="dropdown" aria-expanded="false">
<i class="fa-solid fa-plus"></i>
<i class="fa-solid fa-plus" aria-hidden="true"></i>
<span class="d-none d-md-inline">{{ A_("New") }}</span>
</button>
<ul class="dropdown-menu">
@@ -46,7 +46,7 @@ First written: 2023/3/8
{% endif %}
<div class="btn-group" role="group">
<button id="accounting-choose-report" class="btn btn-primary dropdown-toggle" type="button" data-bs-toggle="dropdown" aria-expanded="false">
<i class="fa-solid fa-book"></i>
<i class="fa-solid fa-book" aria-hidden="true"></i>
<span class="d-none d-md-inline">{{ A_("Report") }}</span>
</button>
<ul class="dropdown-menu" aria-labelledby="accounting-choose-report">
@@ -59,17 +59,17 @@ First written: 2023/3/8
</li>
{% endfor %}
<li>
<span class="dropdown-item {% if report.report_chooser.is_search %} active {% endif %} accounting-clickable" data-bs-toggle="modal" data-bs-target="#accounting-search-modal">
<i class="fa-solid fa-magnifying-glass"></i>
<button class="dropdown-item {% if report.report_chooser.is_search %} active {% endif %}" type="button" data-bs-toggle="modal" data-bs-target="#accounting-search-modal">
<i class="fa-solid fa-magnifying-glass" aria-hidden="true"></i>
{{ A_("Search") }}
</span>
</button>
</li>
</ul>
</div>
{% if use_currency_chooser %}
<div class="btn-group" role="group">
<button id="accounting-choose-currency" class="btn btn-primary dropdown-toggle" type="button" data-bs-toggle="dropdown" aria-expanded="false">
<i class="fa-solid fa-money-bill-wave"></i>
<i class="fa-solid fa-money-bill-wave" aria-hidden="true"></i>
<span class="d-none d-md-inline">{{ A_("Currency") }}</span>
</button>
<ul class="dropdown-menu" aria-labelledby="accounting-choose-currency">
@@ -86,7 +86,7 @@ First written: 2023/3/8
{% if use_account_chooser %}
<div class="btn-group" role="group">
<button id="accounting-choose-account" class="btn btn-primary dropdown-toggle" type="button" data-bs-toggle="dropdown" aria-expanded="false">
<i class="fa-solid fa-clipboard"></i>
<i class="fa-solid fa-clipboard" aria-hidden="true"></i>
<span class="d-none d-md-inline">{{ A_("Account") }}</span>
</button>
<ul class="dropdown-menu accounting-toolbar-accounts" aria-labelledby="accounting-choose-account">
@@ -102,18 +102,18 @@ First written: 2023/3/8
{% endif %}
{% if use_period_chooser %}
<button class="btn btn-primary" type="button" data-bs-toggle="modal" data-bs-target="#accounting-period-chooser-modal">
<i class="fa-solid fa-calendar-day"></i>
<i class="fa-solid fa-calendar-day" aria-hidden="true"></i>
<span class="d-none d-md-inline">{{ A_("Period") }}</span>
</button>
{% endif %}
{% if report.has_data %}
<a class="btn btn-primary" role="button" href="{{ report.csv_uri }}">
<i class="fa-solid fa-download"></i>
<i class="fa-solid fa-download" aria-hidden="true"></i>
<span class="d-none d-md-inline">{{ A_("Download") }}</span>
</a>
{% else %}
<button class="btn btn-secondary" type="button" disabled="disabled">
<i class="fa-solid fa-download"></i>
<i class="fa-solid fa-download" aria-hidden="true"></i>
<span class="d-none d-md-inline">{{ A_("Download") }}</span>
</button>
{% endif %}
@@ -122,7 +122,7 @@ First written: 2023/3/8
<input id="accounting-toolbar-search" class="form-control form-control-sm" type="search" name="q" value="{{ request.args.q }}" placeholder=" " required="required">
<label id="accounting-toolbar-search-label" for="accounting-toolbar-search" class="input-group-text">
<button type="submit">
<i class="fa-solid fa-magnifying-glass"></i>
<i class="fa-solid fa-magnifying-glass" aria-hidden="true"></i>
<span class="d-none d-md-inline">{{ A_("Search") }}</span>
</button>
</label>
@@ -23,6 +23,7 @@ First written: 2023/3/5
{% block accounting_scripts %}
<script src="{{ url_for("accounting.static", filename="js/material-fab-speed-dial.js") }}"></script>
<script src="{{ url_for("accounting.static", filename="js/base-tablist.js") }}"></script>
<script src="{{ url_for("accounting.static", filename="js/period-chooser.js") }}"></script>
{% endblock %}
@@ -49,27 +50,27 @@ First written: 2023/3/5
{% include "accounting/include/pagination.html" %}
{% endwith %}
<div class="d-none d-md-block accounting-report-table accounting-income-expenses-table">
<div class="d-none d-md-block accounting-report-table accounting-income-expenses-table" role="table">
<div class="accounting-report-table-header">
<div class="accounting-report-table-row">
<div>{{ A_("Date") }}</div>
<div>{{ A_("Account") }}</div>
<div>{{ A_("Description") }}</div>
<div class="accounting-amount">{{ A_("Income") }}</div>
<div class="accounting-amount">{{ A_("Expense") }}</div>
<div class="accounting-amount">{{ A_("Balance") }}</div>
<div class="accounting-report-table-row" role="row">
<div role="columnheader">{{ A_("Date") }}</div>
<div role="columnheader">{{ A_("Account") }}</div>
<div role="columnheader">{{ A_("Description") }}</div>
<div class="accounting-amount" role="columnheader">{{ A_("Income") }}</div>
<div class="accounting-amount" role="columnheader">{{ A_("Expense") }}</div>
<div class="accounting-amount" role="columnheader">{{ A_("Balance") }}</div>
</div>
</div>
<div class="accounting-report-table-body">
{% if report.brought_forward %}
{% with line_item = report.brought_forward %}
<div class="accounting-report-table-row">
<div class="accounting-report-table-row" role="row">
{% include "accounting/report/include/income-expenses-row-desktop.html" %}
</div>
{% endwith %}
{% endif %}
{% for line_item in report.line_items %}
<a class="accounting-report-table-row" href="{{ line_item.url|accounting_append_next }}">
<a class="accounting-report-table-row" href="{{ line_item.url|accounting_append_next }}" role="row">
{% include "accounting/report/include/income-expenses-row-desktop.html" %}
</a>
{% endfor %}
@@ -77,11 +78,11 @@ First written: 2023/3/5
{% if report.total %}
{% with line_item = report.total %}
<div class="accounting-report-table-footer">
<div class="accounting-report-table-row">
<div>{{ A_("Total") }}</div>
<div class="accounting-amount">{{ line_item.income|accounting_format_amount }}</div>
<div class="accounting-amount">{{ line_item.expense|accounting_format_amount }}</div>
<div class="accounting-amount {% if line_item.balance < 0 %} text-danger {% endif %}">{{ line_item.balance|accounting_report_format_amount }}</div>
<div class="accounting-report-table-row" role="row">
<div role="cell">{{ A_("Total") }}</div>
<div class="accounting-amount" role="cell">{{ line_item.income|accounting_format_amount }}</div>
<div class="accounting-amount" role="cell">{{ line_item.expense|accounting_format_amount }}</div>
<div class="accounting-amount {% if line_item.balance < 0 %} text-danger {% endif %}" role="cell">{{ line_item.balance|accounting_report_format_amount }}</div>
</div>
</div>
{% endwith %}
@@ -23,6 +23,7 @@ First written: 2023/3/7
{% block accounting_scripts %}
<script src="{{ url_for("accounting.static", filename="js/material-fab-speed-dial.js") }}"></script>
<script src="{{ url_for("accounting.static", filename="js/base-tablist.js") }}"></script>
<script src="{{ url_for("accounting.static", filename="js/period-chooser.js") }}"></script>
{% endblock %}
@@ -55,44 +56,44 @@ First written: 2023/3/7
</h2>
</div>
<div class="accounting-report-table accounting-income-statement-table">
<div class="accounting-report-table accounting-income-statement-table" role="table">
<div class="accounting-report-table-header">
<div class="accounting-report-table-row">
<div class="accounting-amount">{{ A_("Amount") }}</div>
<div class="accounting-report-table-row" role="row">
<div class="accounting-amount" role="columnheader">{{ A_("Amount") }}</div>
</div>
</div>
<div class="accounting-report-table-body">
{% for section in report.sections %}
<div class="accounting-report-table-row accounting-income-statement-section">
<div>
<div class="accounting-report-table-row accounting-income-statement-section" role="row">
<div role="cell">
<span class="d-none d-md-inline">{{ section.title.code }}</span>
{{ section.title.title }}
</div>
</div>
{% for subsection in section.subsections %}
<div class="accounting-report-table-row accounting-income-statement-subsection">
<div>
<div class="accounting-report-table-row accounting-income-statement-subsection" role="row">
<div role="cell">
<span class="d-none d-md-inline">{{ subsection.title.code }}</span>
{{ subsection.title.title }}
</div>
</div>
{% for account in subsection.accounts %}
<a class="accounting-report-table-row accounting-income-statement-account" href="{{ account.url }}">
<div>
<a class="accounting-report-table-row accounting-income-statement-account" role="row" href="{{ account.url }}">
<div role="cell">
<span class="d-none d-md-inline">{{ account.account.code }}</span>
{{ account.account.title }}
</div>
<div class="accounting-amount {% if account.amount < 0 %} text-danger {% endif %}">{{ account.amount|accounting_report_format_amount }}</div>
<div class="accounting-amount {% if account.amount < 0 %} text-danger {% endif %}" role="cell">{{ account.amount|accounting_report_format_amount }}</div>
</a>
{% endfor %}
<div class="accounting-report-table-row accounting-income-statement-subtotal">
<div>{{ A_("Total") }}</div>
<div class="accounting-amount {% if subsection.total < 0 %} text-danger {% endif %}">{{ subsection.total|accounting_report_format_amount }}</div>
<div class="accounting-report-table-row accounting-income-statement-subtotal" role="row">
<div role="cell">{{ A_("Total") }}</div>
<div class="accounting-amount {% if subsection.total < 0 %} text-danger {% endif %}" role="cell">{{ subsection.total|accounting_report_format_amount }}</div>
</div>
{% endfor %}
<div class="accounting-report-table-row accounting-income-statement-total">
<div>{{ section.accumulated.title }}</div>
<div class="accounting-amount {% if section.accumulated.amount < 0 %} text-danger {% endif %}">{{ section.accumulated.amount|accounting_report_format_amount }}</div>
<div class="accounting-report-table-row accounting-income-statement-total" role="row">
<div role="cell">{{ section.accumulated.title }}</div>
<div class="accounting-amount {% if section.accumulated.amount < 0 %} text-danger {% endif %}" role="cell">{{ section.accumulated.amount|accounting_report_format_amount }}</div>
</div>
{% endfor %}
</div>
@@ -23,6 +23,7 @@ First written: 2023/3/4
{% block accounting_scripts %}
<script src="{{ url_for("accounting.static", filename="js/material-fab-speed-dial.js") }}"></script>
<script src="{{ url_for("accounting.static", filename="js/base-tablist.js") }}"></script>
<script src="{{ url_for("accounting.static", filename="js/period-chooser.js") }}"></script>
{% endblock %}
@@ -47,29 +48,29 @@ First written: 2023/3/4
{% include "accounting/include/pagination.html" %}
{% endwith %}
<div class="d-none d-md-block accounting-report-table accounting-journal-table">
<div class="d-none d-md-block accounting-report-table accounting-journal-table" role="table">
<div class="accounting-report-table-header">
<div class="accounting-report-table-row">
<div>{{ A_("Date") }}</div>
<div>{{ A_("Currency") }}</div>
<div>{{ A_("Account") }}</div>
<div>{{ A_("Description") }}</div>
<div class="accounting-amount">{{ A_("Debit") }}</div>
<div class="accounting-amount">{{ A_("Credit") }}</div>
<div class="accounting-report-table-row" role="row">
<div role="columnheader">{{ A_("Date") }}</div>
<div role="columnheader">{{ A_("Currency") }}</div>
<div role="columnheader">{{ A_("Account") }}</div>
<div role="columnheader">{{ A_("Description") }}</div>
<div class="accounting-amount" role="columnheader">{{ A_("Debit") }}</div>
<div class="accounting-amount" role="columnheader">{{ A_("Credit") }}</div>
</div>
</div>
<div class="accounting-report-table-body">
{% for line_item in report.line_items %}
<a class="accounting-report-table-row" href="{{ url_for("accounting.journal-entry.detail", journal_entry=line_item.journal_entry)|accounting_append_next }}">
<div>{{ line_item.journal_entry.date|accounting_format_date }}</div>
<div>{{ line_item.currency.name }}</div>
<div>
<a class="accounting-report-table-row" href="{{ url_for("accounting.journal-entry.detail", journal_entry=line_item.journal_entry)|accounting_append_next }}" role="row">
<div role="cell">{{ line_item.journal_entry.date|accounting_format_date }}</div>
<div role="cell">{{ line_item.currency.name }}</div>
<div role="cell">
<span class="d-none d-md-inline">{{ line_item.account.code }}</span>
{{ line_item.account.title }}
</div>
<div>{{ line_item.description|accounting_default }}</div>
<div class="accounting-amount">{{ line_item.debit|accounting_format_amount|accounting_default }}</div>
<div class="accounting-amount">{{ line_item.credit|accounting_format_amount|accounting_default }}</div>
<div role="cell">{{ line_item.description|accounting_default }}</div>
<div class="accounting-amount" role="cell">{{ line_item.debit|accounting_format_amount|accounting_default }}</div>
<div class="accounting-amount" role="cell">{{ line_item.credit|accounting_format_amount|accounting_default }}</div>
</a>
{% endfor %}
</div>
@@ -23,6 +23,7 @@ First written: 2023/3/5
{% block accounting_scripts %}
<script src="{{ url_for("accounting.static", filename="js/material-fab-speed-dial.js") }}"></script>
<script src="{{ url_for("accounting.static", filename="js/base-tablist.js") }}"></script>
<script src="{{ url_for("accounting.static", filename="js/period-chooser.js") }}"></script>
{% endblock %}
@@ -49,28 +50,28 @@ First written: 2023/3/5
{% include "accounting/include/pagination.html" %}
{% endwith %}
<div class="d-none d-md-block accounting-report-table {% if report.account.is_real %} accounting-ledger-real-table {% else %} accounting-ledger-nominal-table {% endif %}">
<div class="d-none d-md-block accounting-report-table {% if report.account.is_real %} accounting-ledger-real-table {% else %} accounting-ledger-nominal-table {% endif %}" role="table">
<div class="accounting-report-table-header">
<div class="accounting-report-table-row">
<div>{{ A_("Date") }}</div>
<div>{{ A_("Description") }}</div>
<div class="accounting-amount">{{ A_("Debit") }}</div>
<div class="accounting-amount">{{ A_("Credit") }}</div>
<div role="columnheader">{{ A_("Date") }}</div>
<div role="columnheader">{{ A_("Description") }}</div>
<div class="accounting-amount" role="columnheader">{{ A_("Debit") }}</div>
<div class="accounting-amount" role="columnheader">{{ A_("Credit") }}</div>
{% if report.account.is_real %}
<div class="accounting-amount">{{ A_("Balance") }}</div>
<div class="accounting-amount" role="columnheader">{{ A_("Balance") }}</div>
{% endif %}
</div>
</div>
<div class="accounting-report-table-body">
{% if report.brought_forward %}
{% with line_item = report.brought_forward %}
<div class="accounting-report-table-row">
<div class="accounting-report-table-row" role="row">
{% include "accounting/report/include/ledger-row-desktop.html" %}
</div>
{% endwith %}
{% endif %}
{% for line_item in report.line_items %}
<a class="accounting-report-table-row" href="{{ line_item.url|accounting_append_next }}">
<a class="accounting-report-table-row" href="{{ line_item.url|accounting_append_next }}" role="row">
{% include "accounting/report/include/ledger-row-desktop.html" %}
</a>
{% endfor %}
@@ -78,12 +79,12 @@ First written: 2023/3/5
{% if report.total %}
{% with line_item = report.total %}
<div class="accounting-report-table-footer">
<div class="accounting-report-table-row">
<div>{{ A_("Total") }}</div>
<div class="accounting-amount">{{ line_item.debit|accounting_format_amount|accounting_default }}</div>
<div class="accounting-amount">{{ line_item.credit|accounting_format_amount|accounting_default }}</div>
<div class="accounting-report-table-row" role="row">
<div role="cell">{{ A_("Total") }}</div>
<div class="accounting-amount" role="cell">{{ line_item.debit|accounting_format_amount|accounting_default }}</div>
<div class="accounting-amount" role="cell">{{ line_item.credit|accounting_format_amount|accounting_default }}</div>
{% if report.account.is_real %}
<div class="accounting-amount {% if line_item.balance < 0 %} text-danger {% endif %}">{{ line_item.balance|accounting_report_format_amount }}</div>
<div class="accounting-amount {% if line_item.balance < 0 %} text-danger {% endif %}" role="cell">{{ line_item.balance|accounting_report_format_amount }}</div>
{% endif %}
</div>
</div>
@@ -44,29 +44,29 @@ First written: 2023/3/8
{% include "accounting/include/pagination.html" %}
{% endwith %}
<div class="d-none d-md-block accounting-report-table accounting-journal-table">
<div class="d-none d-md-block accounting-report-table accounting-journal-table" role="table">
<div class="accounting-report-table-header">
<div class="accounting-report-table-row">
<div>{{ A_("Date") }}</div>
<div>{{ A_("Currency") }}</div>
<div>{{ A_("Account") }}</div>
<div>{{ A_("Description") }}</div>
<div class="accounting-amount">{{ A_("Debit") }}</div>
<div class="accounting-amount">{{ A_("Credit") }}</div>
<div class="accounting-report-table-row" role="row">
<div role="columnheader">{{ A_("Date") }}</div>
<div role="columnheader">{{ A_("Currency") }}</div>
<div role="columnheader">{{ A_("Account") }}</div>
<div role="columnheader">{{ A_("Description") }}</div>
<div class="accounting-amount" role="columnheader">{{ A_("Debit") }}</div>
<div class="accounting-amount" role="columnheader">{{ A_("Credit") }}</div>
</div>
</div>
<div class="accounting-report-table-body">
{% for line_item in report.line_items %}
<a class="accounting-report-table-row" href="{{ url_for("accounting.journal-entry.detail", journal_entry=line_item.journal_entry)|accounting_append_next }}">
<div>{{ line_item.journal_entry.date|accounting_format_date }}</div>
<div>{{ line_item.currency.name }}</div>
<div>
<a class="accounting-report-table-row" href="{{ url_for("accounting.journal-entry.detail", journal_entry=line_item.journal_entry)|accounting_append_next }}" role="row">
<div role="cell">{{ line_item.journal_entry.date|accounting_format_date }}</div>
<div role="cell">{{ line_item.currency.name }}</div>
<div role="cell">
<span class="d-none d-md-inline">{{ line_item.account.code }}</span>
{{ line_item.account.title }}
</div>
<div>{{ line_item.description|accounting_default }}</div>
<div class="accounting-amount">{{ line_item.debit|accounting_format_amount|accounting_default }}</div>
<div class="accounting-amount">{{ line_item.credit|accounting_format_amount|accounting_default }}</div>
<div role="cell">{{ line_item.description|accounting_default }}</div>
<div class="accounting-amount" role="cell">{{ line_item.debit|accounting_format_amount|accounting_default }}</div>
<div class="accounting-amount" role="cell">{{ line_item.credit|accounting_format_amount|accounting_default }}</div>
</a>
{% endfor %}
</div>
@@ -23,6 +23,7 @@ First written: 2023/3/5
{% block accounting_scripts %}
<script src="{{ url_for("accounting.static", filename="js/material-fab-speed-dial.js") }}"></script>
<script src="{{ url_for("accounting.static", filename="js/base-tablist.js") }}"></script>
<script src="{{ url_for("accounting.static", filename="js/period-chooser.js") }}"></script>
{% endblock %}
@@ -55,31 +56,31 @@ First written: 2023/3/5
</h2>
</div>
<div class="accounting-report-table accounting-trial-balance-table">
<div class="accounting-report-table accounting-trial-balance-table" role="table">
<div class="accounting-report-table-header">
<div class="accounting-report-table-row">
<div>{{ A_("Account") }}</div>
<div class="accounting-amount">{{ A_("Debit") }}</div>
<div class="accounting-amount">{{ A_("Credit") }}</div>
<div class="accounting-report-table-row" role="row">
<div role="columnheader">{{ A_("Account") }}</div>
<div class="accounting-amount" role="columnheader">{{ A_("Debit") }}</div>
<div class="accounting-amount" role="columnheader">{{ A_("Credit") }}</div>
</div>
</div>
<div class="accounting-report-table-body">
{% for account in report.accounts %}
<a class="accounting-report-table-row" href="{{ account.url }}">
<div>
<a class="accounting-report-table-row" href="{{ account.url }}" role="row">
<div role="cell">
<span class="d-none d-md-inline">{{ account.account.code }}</span>
{{ account.account.title }}
</div>
<div class="accounting-amount">{{ account.debit|accounting_format_amount|accounting_default }}</div>
<div class="accounting-amount">{{ account.credit|accounting_format_amount|accounting_default }}</div>
<div class="accounting-amount" role="cell">{{ account.debit|accounting_format_amount|accounting_default }}</div>
<div class="accounting-amount" role="cell">{{ account.credit|accounting_format_amount|accounting_default }}</div>
</a>
{% endfor %}
</div>
<div class="accounting-report-table-footer">
<div class="accounting-report-table-row">
<div>{{ A_("Total") }}</div>
<div class="accounting-amount">{{ report.total.debit|accounting_format_amount }}</div>
<div class="accounting-amount">{{ report.total.credit|accounting_format_amount }}</div>
<div class="accounting-report-table-row" role="row">
<div role="cell">{{ A_("Total") }}</div>
<div class="accounting-amount" role="cell">{{ report.total.debit|accounting_format_amount }}</div>
<div class="accounting-amount" role="cell">{{ report.total.credit|accounting_format_amount }}</div>
</div>
</div>
</div>
@@ -52,20 +52,20 @@ First written: 2023/4/8
</h2>
</div>
<div class="accounting-report-table accounting-unapplied-account-table">
<div class="accounting-report-table accounting-unapplied-account-table" role="table">
<div class="accounting-report-table-header">
<div class="accounting-report-table-row">
<div class="accounting-amount">{{ A_("Count") }}</div>
<div class="accounting-report-table-row" role="row">
<div class="accounting-amount" role="columnheader">{{ A_("Count") }}</div>
</div>
</div>
<div class="accounting-report-table-body">
{% for account in report.accounts %}
<a class="accounting-report-table-row" href="{{ url_for("accounting-report.unapplied", currency=report.currency, account=account, period=report.period) }}">
<div>
<a class="accounting-report-table-row" href="{{ url_for("accounting-report.unapplied", currency=report.currency, account=account, period=report.period) }}" role="row">
<div role="cell">
<span class="d-none d-md-inline">{{ account.code }}</span>
{{ account.title }}
</div>
<div class="accounting-amount">{{ account.count }}</div>
<div class="accounting-amount" role="cell">{{ account.count }}</div>
</a>
{% endfor %}
</div>
@@ -45,22 +45,22 @@ First written: 2023/4/7
{% include "accounting/include/pagination.html" %}
{% endwith %}
<div class="d-none d-md-block accounting-report-table accounting-unapplied-table">
<div class="d-none d-md-block accounting-report-table accounting-unapplied-table" role="table">
<div class="accounting-report-table-header">
<div class="accounting-report-table-row">
<div>{{ A_("Date") }}</div>
<div>{{ A_("Description") }}</div>
<div class="accounting-amount">{{ A_("Amount") }}</div>
<div class="accounting-amount">{{ A_("Net Balance") }}</div>
<div class="accounting-report-table-row" role="row">
<div role="columnheader">{{ A_("Date") }}</div>
<div role="columnheader">{{ A_("Description") }}</div>
<div class="accounting-amount" role="columnheader">{{ A_("Amount") }}</div>
<div class="accounting-amount" role="columnheader">{{ A_("Net Balance") }}</div>
</div>
</div>
<div class="accounting-report-table-body">
{% for line_item in report.line_items %}
<a class="accounting-report-table-row" href="{{ url_for("accounting.journal-entry.detail", journal_entry=line_item.journal_entry)|accounting_append_next }}">
<div>{{ line_item.journal_entry.date|accounting_format_date }}</div>
<div>{{ line_item.description|accounting_default }}</div>
<div class="accounting-amount">{{ line_item.amount|accounting_format_amount }}</div>
<div class="accounting-amount">{{ line_item.net_balance|accounting_format_amount }}</div>
<a class="accounting-report-table-row" href="{{ url_for("accounting.journal-entry.detail", journal_entry=line_item.journal_entry)|accounting_append_next }}" role="row">
<div role="cell">{{ line_item.journal_entry.date|accounting_format_date }}</div>
<div role="cell">{{ line_item.description|accounting_default }}</div>
<div class="accounting-amount" role="cell">{{ line_item.amount|accounting_format_amount }}</div>
<div class="accounting-amount" role="cell">{{ line_item.net_balance|accounting_format_amount }}</div>
</a>
{% endfor %}
</div>
@@ -52,20 +52,20 @@ First written: 2023/4/17
</h2>
</div>
<div class="accounting-report-table accounting-unapplied-account-table">
<div class="accounting-report-table accounting-unapplied-account-table" role="table">
<div class="accounting-report-table-header">
<div class="accounting-report-table-row">
<div class="accounting-amount">{{ A_("Count") }}</div>
<div class="accounting-report-table-row" role="row">
<div class="accounting-amount" role="columnheader">{{ A_("Count") }}</div>
</div>
</div>
<div class="accounting-report-table-body">
{% for account in report.accounts %}
<a class="accounting-report-table-row" href="{{ url_for("accounting-report.unmatched", currency=report.currency, account=account, period=report.period) }}">
<div>
<a class="accounting-report-table-row" href="{{ url_for("accounting-report.unmatched", currency=report.currency, account=account, period=report.period) }}" role="row">
<div role="cell">
<span class="d-none d-md-inline">{{ account.code }}</span>
{{ account.title }}
</div>
<div class="accounting-amount">{{ account.count }}</div>
<div class="accounting-amount" role="cell">{{ account.count }}</div>
</a>
{% endfor %}
</div>
@@ -42,7 +42,7 @@ First written: 2023/4/17
{% if report.matched_pairs %}
<button class="btn btn-primary" type="button" data-bs-toggle="modal" data-bs-target="#accounting-match-modal">
<i class="fa-solid fa-link"></i>
<i class="fa-solid fa-link" aria-hidden="true"></i>
{{ A_("Match") }}
</button>
@@ -86,29 +86,29 @@ First written: 2023/4/17
{% include "accounting/include/pagination.html" %}
{% endwith %}
<div class="d-none d-md-block accounting-report-table accounting-unmatched-table">
<div class="d-none d-md-block accounting-report-table accounting-unmatched-table" role="table">
<div class="accounting-report-table-header">
<div class="accounting-report-table-row">
<div>{{ A_("Date") }}</div>
<div>{{ A_("Description") }}</div>
<div class="accounting-amount">{{ A_("Debit") }}</div>
<div class="accounting-amount">{{ A_("Credit") }}</div>
<div class="accounting-amount">{{ A_("Balance") }}</div>
<div class="accounting-report-table-row" role="row">
<div role="columnheader">{{ A_("Date") }}</div>
<div role="columnheader">{{ A_("Description") }}</div>
<div class="accounting-amount" role="columnheader">{{ A_("Debit") }}</div>
<div class="accounting-amount" role="columnheader">{{ A_("Credit") }}</div>
<div class="accounting-amount" role="columnheader">{{ A_("Balance") }}</div>
</div>
</div>
<div class="accounting-report-table-body">
{% for line_item in report.line_items %}
<a class="accounting-report-table-row {% if not line_item.match %} accounting-report-table-row-danger {% endif %}" href="{{ url_for("accounting.journal-entry.detail", journal_entry=line_item.journal_entry)|accounting_append_next }}">
<div>{{ line_item.journal_entry.date|accounting_format_date }}</div>
<div>
<a class="accounting-report-table-row {% if not line_item.match %} accounting-report-table-row-danger {% endif %}" href="{{ url_for("accounting.journal-entry.detail", journal_entry=line_item.journal_entry)|accounting_append_next }}" role="row">
<div role="cell">{{ line_item.journal_entry.date|accounting_format_date }}</div>
<div role="cell">
{{ line_item.description|accounting_default }}
{% if line_item.match %}
<div class="small">{{ A_("Can match %(item)s", item=line_item.match) }}</div>
{% endif %}
</div>
<div class="accounting-amount">{{ line_item.debit|accounting_format_amount|accounting_default }}</div>
<div class="accounting-amount">{{ line_item.credit|accounting_format_amount|accounting_default }}</div>
<div class="accounting-amount {% if line_item.balance < 0 %} text-danger {% endif %}">{{ line_item.balance|accounting_report_format_amount }}</div>
<div class="accounting-amount" role="cell">{{ line_item.debit|accounting_format_amount|accounting_default }}</div>
<div class="accounting-amount" role="cell">{{ line_item.credit|accounting_format_amount|accounting_default }}</div>
<div class="accounting-amount {% if line_item.balance < 0 %} text-danger {% endif %}" role="cell">{{ line_item.balance|accounting_report_format_amount }}</div>
</a>
{% endfor %}
</div>