Compare commits
19 Commits
16e2a146db
...
v0.2.0
Author | SHA1 | Date | |
---|---|---|---|
8dc340dbf1 | |||
4b5b348270 | |||
d9585f0e53 | |||
5737d6cef4 | |||
1d61fa93d3 | |||
b1c7bc61c4 | |||
708a434b5d | |||
8e524674a3 | |||
699db20308 | |||
c3cedf714b | |||
c67ed4471c | |||
2d3b9f68b8 | |||
f82278b48a | |||
85480804e7 | |||
9e85c14431 | |||
31dc8fab04 | |||
dc24af1db0 | |||
59795635ee | |||
399afe56c8 |
53
docs/source/accounting.account.rst
Normal file
53
docs/source/accounting.account.rst
Normal file
@ -0,0 +1,53 @@
|
|||||||
|
accounting.account package
|
||||||
|
==========================
|
||||||
|
|
||||||
|
Submodules
|
||||||
|
----------
|
||||||
|
|
||||||
|
accounting.account.commands module
|
||||||
|
----------------------------------
|
||||||
|
|
||||||
|
.. automodule:: accounting.account.commands
|
||||||
|
:members:
|
||||||
|
:undoc-members:
|
||||||
|
:show-inheritance:
|
||||||
|
|
||||||
|
accounting.account.converters module
|
||||||
|
------------------------------------
|
||||||
|
|
||||||
|
.. automodule:: accounting.account.converters
|
||||||
|
:members:
|
||||||
|
:undoc-members:
|
||||||
|
:show-inheritance:
|
||||||
|
|
||||||
|
accounting.account.forms module
|
||||||
|
-------------------------------
|
||||||
|
|
||||||
|
.. automodule:: accounting.account.forms
|
||||||
|
:members:
|
||||||
|
:undoc-members:
|
||||||
|
:show-inheritance:
|
||||||
|
|
||||||
|
accounting.account.query module
|
||||||
|
-------------------------------
|
||||||
|
|
||||||
|
.. automodule:: accounting.account.query
|
||||||
|
:members:
|
||||||
|
:undoc-members:
|
||||||
|
:show-inheritance:
|
||||||
|
|
||||||
|
accounting.account.views module
|
||||||
|
-------------------------------
|
||||||
|
|
||||||
|
.. automodule:: accounting.account.views
|
||||||
|
:members:
|
||||||
|
:undoc-members:
|
||||||
|
:show-inheritance:
|
||||||
|
|
||||||
|
Module contents
|
||||||
|
---------------
|
||||||
|
|
||||||
|
.. automodule:: accounting.account
|
||||||
|
:members:
|
||||||
|
:undoc-members:
|
||||||
|
:show-inheritance:
|
@ -12,18 +12,10 @@ accounting.base\_account.commands module
|
|||||||
:undoc-members:
|
:undoc-members:
|
||||||
:show-inheritance:
|
:show-inheritance:
|
||||||
|
|
||||||
accounting.base\_account.database module
|
accounting.base\_account.converters module
|
||||||
----------------------------------------
|
------------------------------------------
|
||||||
|
|
||||||
.. automodule:: accounting.base_account.database
|
.. automodule:: accounting.base_account.converters
|
||||||
:members:
|
|
||||||
:undoc-members:
|
|
||||||
:show-inheritance:
|
|
||||||
|
|
||||||
accounting.base\_account.models module
|
|
||||||
--------------------------------------
|
|
||||||
|
|
||||||
.. automodule:: accounting.base_account.models
|
|
||||||
:members:
|
:members:
|
||||||
:undoc-members:
|
:undoc-members:
|
||||||
:show-inheritance:
|
:show-inheritance:
|
||||||
|
53
docs/source/accounting.currency.rst
Normal file
53
docs/source/accounting.currency.rst
Normal file
@ -0,0 +1,53 @@
|
|||||||
|
accounting.currency package
|
||||||
|
===========================
|
||||||
|
|
||||||
|
Submodules
|
||||||
|
----------
|
||||||
|
|
||||||
|
accounting.currency.commands module
|
||||||
|
-----------------------------------
|
||||||
|
|
||||||
|
.. automodule:: accounting.currency.commands
|
||||||
|
:members:
|
||||||
|
:undoc-members:
|
||||||
|
:show-inheritance:
|
||||||
|
|
||||||
|
accounting.currency.converters module
|
||||||
|
-------------------------------------
|
||||||
|
|
||||||
|
.. automodule:: accounting.currency.converters
|
||||||
|
:members:
|
||||||
|
:undoc-members:
|
||||||
|
:show-inheritance:
|
||||||
|
|
||||||
|
accounting.currency.forms module
|
||||||
|
--------------------------------
|
||||||
|
|
||||||
|
.. automodule:: accounting.currency.forms
|
||||||
|
:members:
|
||||||
|
:undoc-members:
|
||||||
|
:show-inheritance:
|
||||||
|
|
||||||
|
accounting.currency.query module
|
||||||
|
--------------------------------
|
||||||
|
|
||||||
|
.. automodule:: accounting.currency.query
|
||||||
|
:members:
|
||||||
|
:undoc-members:
|
||||||
|
:show-inheritance:
|
||||||
|
|
||||||
|
accounting.currency.views module
|
||||||
|
--------------------------------
|
||||||
|
|
||||||
|
.. automodule:: accounting.currency.views
|
||||||
|
:members:
|
||||||
|
:undoc-members:
|
||||||
|
:show-inheritance:
|
||||||
|
|
||||||
|
Module contents
|
||||||
|
---------------
|
||||||
|
|
||||||
|
.. automodule:: accounting.currency
|
||||||
|
:members:
|
||||||
|
:undoc-members:
|
||||||
|
:show-inheritance:
|
@ -7,12 +7,22 @@ Subpackages
|
|||||||
.. toctree::
|
.. toctree::
|
||||||
:maxdepth: 4
|
:maxdepth: 4
|
||||||
|
|
||||||
|
accounting.account
|
||||||
accounting.base_account
|
accounting.base_account
|
||||||
|
accounting.currency
|
||||||
accounting.utils
|
accounting.utils
|
||||||
|
|
||||||
Submodules
|
Submodules
|
||||||
----------
|
----------
|
||||||
|
|
||||||
|
accounting.database module
|
||||||
|
--------------------------
|
||||||
|
|
||||||
|
.. automodule:: accounting.database
|
||||||
|
:members:
|
||||||
|
:undoc-members:
|
||||||
|
:show-inheritance:
|
||||||
|
|
||||||
accounting.locale module
|
accounting.locale module
|
||||||
------------------------
|
------------------------
|
||||||
|
|
||||||
@ -21,6 +31,14 @@ accounting.locale module
|
|||||||
:undoc-members:
|
:undoc-members:
|
||||||
:show-inheritance:
|
:show-inheritance:
|
||||||
|
|
||||||
|
accounting.models module
|
||||||
|
------------------------
|
||||||
|
|
||||||
|
.. automodule:: accounting.models
|
||||||
|
:members:
|
||||||
|
:undoc-members:
|
||||||
|
:show-inheritance:
|
||||||
|
|
||||||
Module contents
|
Module contents
|
||||||
---------------
|
---------------
|
||||||
|
|
||||||
|
@ -4,6 +4,14 @@ accounting.utils package
|
|||||||
Submodules
|
Submodules
|
||||||
----------
|
----------
|
||||||
|
|
||||||
|
accounting.utils.next\_url module
|
||||||
|
---------------------------------
|
||||||
|
|
||||||
|
.. automodule:: accounting.utils.next_url
|
||||||
|
:members:
|
||||||
|
:undoc-members:
|
||||||
|
:show-inheritance:
|
||||||
|
|
||||||
accounting.utils.pagination module
|
accounting.utils.pagination module
|
||||||
----------------------------------
|
----------------------------------
|
||||||
|
|
||||||
@ -28,6 +36,30 @@ accounting.utils.query module
|
|||||||
:undoc-members:
|
:undoc-members:
|
||||||
:show-inheritance:
|
:show-inheritance:
|
||||||
|
|
||||||
|
accounting.utils.random\_id module
|
||||||
|
----------------------------------
|
||||||
|
|
||||||
|
.. automodule:: accounting.utils.random_id
|
||||||
|
:members:
|
||||||
|
:undoc-members:
|
||||||
|
:show-inheritance:
|
||||||
|
|
||||||
|
accounting.utils.strip\_text module
|
||||||
|
-----------------------------------
|
||||||
|
|
||||||
|
.. automodule:: accounting.utils.strip_text
|
||||||
|
:members:
|
||||||
|
:undoc-members:
|
||||||
|
:show-inheritance:
|
||||||
|
|
||||||
|
accounting.utils.user module
|
||||||
|
----------------------------
|
||||||
|
|
||||||
|
.. automodule:: accounting.utils.user
|
||||||
|
:members:
|
||||||
|
:undoc-members:
|
||||||
|
:show-inheritance:
|
||||||
|
|
||||||
Module contents
|
Module contents
|
||||||
---------------
|
---------------
|
||||||
|
|
||||||
|
@ -17,7 +17,7 @@
|
|||||||
|
|
||||||
[metadata]
|
[metadata]
|
||||||
name = mia-accounting-flask
|
name = mia-accounting-flask
|
||||||
version = 0.1.1
|
version = 0.2.0
|
||||||
author = imacat
|
author = imacat
|
||||||
author_email = imacat@mail.imacat.idv.tw
|
author_email = imacat@mail.imacat.idv.tw
|
||||||
description = The Mia! Accounting Flask project.
|
description = The Mia! Accounting Flask project.
|
||||||
|
@ -55,7 +55,7 @@ def init_app(app: Flask, user_utils: AbstractUserUtils,
|
|||||||
locale.init_app(app, bp)
|
locale.init_app(app, bp)
|
||||||
|
|
||||||
from .utils import permission
|
from .utils import permission
|
||||||
permission.init_app(app, can_view_func, can_edit_func)
|
permission.init_app(bp, can_view_func, can_edit_func)
|
||||||
|
|
||||||
from . import base_account
|
from . import base_account
|
||||||
base_account.init_app(app, bp)
|
base_account.init_app(app, bp)
|
||||||
@ -66,9 +66,7 @@ def init_app(app: Flask, user_utils: AbstractUserUtils,
|
|||||||
from . import currency
|
from . import currency
|
||||||
currency.init_app(app, bp)
|
currency.init_app(app, bp)
|
||||||
|
|
||||||
from .utils.next_url import append_next, inherit_next, or_next
|
from .utils import next_url
|
||||||
bp.add_app_template_filter(append_next, "append_next")
|
next_url.init_app(bp)
|
||||||
bp.add_app_template_filter(inherit_next, "inherit_next")
|
|
||||||
bp.add_app_template_filter(or_next, "or_next")
|
|
||||||
|
|
||||||
app.register_blueprint(bp)
|
app.register_blueprint(bp)
|
||||||
|
@ -23,8 +23,8 @@ from flask import Flask, Blueprint
|
|||||||
def init_app(app: Flask, bp: Blueprint) -> None:
|
def init_app(app: Flask, bp: Blueprint) -> None:
|
||||||
"""Initialize the application.
|
"""Initialize the application.
|
||||||
|
|
||||||
:param bp: The blueprint of the accounting application.
|
|
||||||
:param app: The Flask application.
|
:param app: The Flask application.
|
||||||
|
:param bp: The blueprint of the accounting application.
|
||||||
:return: None.
|
:return: None.
|
||||||
"""
|
"""
|
||||||
from .converters import AccountConverter
|
from .converters import AccountConverter
|
||||||
|
@ -23,8 +23,8 @@ from flask import Flask, Blueprint
|
|||||||
def init_app(app: Flask, bp: Blueprint) -> None:
|
def init_app(app: Flask, bp: Blueprint) -> None:
|
||||||
"""Initialize the application.
|
"""Initialize the application.
|
||||||
|
|
||||||
:param bp: The blueprint of the accounting application.
|
|
||||||
:param app: The Flask application.
|
:param app: The Flask application.
|
||||||
|
:param bp: The blueprint of the accounting application.
|
||||||
:return: None.
|
:return: None.
|
||||||
"""
|
"""
|
||||||
from .converters import BaseAccountConverter
|
from .converters import BaseAccountConverter
|
||||||
|
@ -23,8 +23,8 @@ from flask import Flask, Blueprint
|
|||||||
def init_app(app: Flask, bp: Blueprint) -> None:
|
def init_app(app: Flask, bp: Blueprint) -> None:
|
||||||
"""Initialize the application.
|
"""Initialize the application.
|
||||||
|
|
||||||
:param bp: The blueprint of the accounting application.
|
|
||||||
:param app: The Flask application.
|
:param app: The Flask application.
|
||||||
|
:param bp: The blueprint of the accounting application.
|
||||||
:return: None.
|
:return: None.
|
||||||
"""
|
"""
|
||||||
from .converters import CurrencyConverter
|
from .converters import CurrencyConverter
|
||||||
|
@ -25,7 +25,7 @@ time.
|
|||||||
|
|
||||||
from flask_sqlalchemy import SQLAlchemy
|
from flask_sqlalchemy import SQLAlchemy
|
||||||
|
|
||||||
db: SQLAlchemy
|
db: SQLAlchemy = SQLAlchemy()
|
||||||
"""The database instance."""
|
"""The database instance."""
|
||||||
|
|
||||||
|
|
||||||
|
@ -116,8 +116,8 @@ def __babel_js_catalog_view() -> Response:
|
|||||||
def init_app(app: Flask, bp: Blueprint) -> None:
|
def init_app(app: Flask, bp: Blueprint) -> None:
|
||||||
"""Initializes the application.
|
"""Initializes the application.
|
||||||
|
|
||||||
:param bp: The blueprint of the accounting application.
|
|
||||||
:param app: The Flask application.
|
:param app: The Flask application.
|
||||||
|
:param bp: The blueprint of the accounting application.
|
||||||
:return: None.
|
:return: None.
|
||||||
"""
|
"""
|
||||||
bp.add_url_rule("/_jstrans.js", "babel_catalog",
|
bp.add_url_rule("/_jstrans.js", "babel_catalog",
|
||||||
|
@ -64,6 +64,14 @@ class BaseAccount(db.Model):
|
|||||||
return l10n.title
|
return l10n.title
|
||||||
return self.title_l10n
|
return self.title_l10n
|
||||||
|
|
||||||
|
@property
|
||||||
|
def query_values(self) -> list[str]:
|
||||||
|
"""Returns the values to be queried.
|
||||||
|
|
||||||
|
:return: The values to be queried.
|
||||||
|
"""
|
||||||
|
return [self.code, self.title_l10n] + [x.title for x in self.l10n]
|
||||||
|
|
||||||
|
|
||||||
class BaseAccountL10n(db.Model):
|
class BaseAccountL10n(db.Model):
|
||||||
"""A localized base account title."""
|
"""A localized base account title."""
|
||||||
|
@ -21,65 +21,50 @@
|
|||||||
* First written: 2023/2/1
|
* First written: 2023/2/1
|
||||||
*/
|
*/
|
||||||
|
|
||||||
.clickable {
|
.accounting-clickable {
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
}
|
}
|
||||||
.btn-group .btn .search-input {
|
.btn-group .btn .accounting-search-input {
|
||||||
min-height: calc(1em + .5rem + 2px);
|
min-height: calc(1em + .5rem + 2px);
|
||||||
padding: 0 0.5rem;
|
padding: 0 0.5rem;
|
||||||
}
|
}
|
||||||
.btn-group .btn .search-label button {
|
.btn-group .btn .accounting-search-label button {
|
||||||
border: none;
|
border: none;
|
||||||
background-color: transparent;
|
background-color: transparent;
|
||||||
color: inherit;
|
color: inherit;
|
||||||
padding-right: 0;
|
padding-right: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
/** The account management */
|
/** The card layout */
|
||||||
.account {
|
.accounting-card {
|
||||||
padding: 2em 1.5em;
|
padding: 2em 1.5em;
|
||||||
margin: 1em;
|
margin: 1em;
|
||||||
background-color: #E9ECEF;
|
background-color: #E9ECEF;
|
||||||
border-radius: 0.3em;
|
border-radius: 0.3em;
|
||||||
box-shadow: 0 4px 8px 0 rgba(0, 0, 0, 0.2), 0 6px 20px 0 rgba(0, 0, 0, 0.19);
|
box-shadow: 0 4px 8px 0 rgba(0, 0, 0, 0.2), 0 6px 20px 0 rgba(0, 0, 0, 0.19);
|
||||||
}
|
}
|
||||||
.account .account-title {
|
.accounting-card-title {
|
||||||
font-size: 1.8rem;
|
font-size: 1.8rem;
|
||||||
font-weight: bolder;
|
font-weight: bolder;
|
||||||
}
|
}
|
||||||
.account .account-code {
|
.accounting-card-code {
|
||||||
font-size: 1.4rem;
|
font-size: 1.4rem;
|
||||||
color: #373b3e;
|
color: #373b3e;
|
||||||
}
|
}
|
||||||
.list-base-selector {
|
|
||||||
|
/** The option selector */
|
||||||
|
.accounting-selector-list {
|
||||||
height: 20rem;
|
height: 20rem;
|
||||||
overflow-y: scroll;
|
overflow-y: scroll;
|
||||||
}
|
}
|
||||||
|
|
||||||
/** The currency management. */
|
|
||||||
.currency {
|
|
||||||
padding: 2em 1.5em;
|
|
||||||
margin: 1em;
|
|
||||||
background-color: #E9ECEF;
|
|
||||||
border-radius: 0.3em;
|
|
||||||
box-shadow: 0 4px 8px 0 rgba(0, 0, 0, 0.2), 0 6px 20px 0 rgba(0, 0, 0, 0.19);
|
|
||||||
}
|
|
||||||
.currency .currency-name {
|
|
||||||
font-size: 1.8rem;
|
|
||||||
font-weight: bolder;
|
|
||||||
}
|
|
||||||
.currency .currency-code {
|
|
||||||
font-size: 1.4rem;
|
|
||||||
color: #373b3e;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* The Material Design text field (floating form control in Bootstrap) */
|
/* The Material Design text field (floating form control in Bootstrap) */
|
||||||
.material-text-field {
|
.accounting-material-text-field {
|
||||||
position: relative;
|
position: relative;
|
||||||
min-height: calc(3.5rem + 2px);
|
min-height: calc(3.5rem + 2px);
|
||||||
padding-top: 1.625rem;
|
padding-top: 1.625rem;
|
||||||
}
|
}
|
||||||
.material-text-field > .form-label {
|
.accounting-material-text-field > .form-label {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
top: 0;
|
top: 0;
|
||||||
left: 0;
|
left: 0;
|
||||||
@ -88,27 +73,27 @@
|
|||||||
transform-origin: 0 0;
|
transform-origin: 0 0;
|
||||||
transition: opacity .1s ease-in-out,transform .1s ease-in-out;
|
transition: opacity .1s ease-in-out,transform .1s ease-in-out;
|
||||||
}
|
}
|
||||||
.material-text-field.not-empty > .form-label {
|
.accounting-material-text-field.accounting-not-empty > .form-label {
|
||||||
opacity: 0.65;
|
opacity: 0.65;
|
||||||
transform: scale(.85) translateY(-.5rem) translateX(.15rem);
|
transform: scale(.85) translateY(-.5rem) translateX(.15rem);
|
||||||
}
|
}
|
||||||
|
|
||||||
/* The Material Design floating action buttons */
|
/* The Material Design floating action buttons */
|
||||||
.material-fab {
|
.accounting-material-fab {
|
||||||
position: fixed;
|
position: fixed;
|
||||||
right: 2rem;
|
right: 2rem;
|
||||||
bottom: 1rem;
|
bottom: 1rem;
|
||||||
z-index: 10;
|
z-index: 10;
|
||||||
flex-direction: column-reverse;
|
flex-direction: column-reverse;
|
||||||
}
|
}
|
||||||
.material-fab .btn {
|
.accounting-material-fab .btn {
|
||||||
border-radius: 50%;
|
border-radius: 50%;
|
||||||
transform: scale(1.5);
|
transform: scale(1.5);
|
||||||
box-shadow: 0 3px 5px -1px rgba(0, 0, 0, 0.2), 0 6px 10px 0 rgba(0, 0, 0, 0.14), 0 1px 18px 0 rgba(0,0,0,.12);
|
box-shadow: 0 3px 5px -1px rgba(0, 0, 0, 0.2), 0 6px 10px 0 rgba(0, 0, 0, 0.14), 0 1px 18px 0 rgba(0,0,0,.12);
|
||||||
display: block;
|
display: block;
|
||||||
margin-top: 2.5rem;
|
margin-top: 2.5rem;
|
||||||
}
|
}
|
||||||
.material-fab .btn:hover, .material-fab .btn:focus {
|
.accounting-material-fab .btn:hover, .accounting-material-fab .btn:focus {
|
||||||
box-shadow: 0 5px 5px -3px rgba(0, 0, 0, 0.2), 0 8px 10px 1px rgba(0, 0, 0, 0.14), 0 3px 14px 2px rgba(0,0,0,.12);
|
box-shadow: 0 5px 5px -3px rgba(0, 0, 0, 0.2), 0 8px 10px 1px rgba(0, 0, 0, 0.14), 0 3px 14px 2px rgba(0,0,0,.12);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -24,11 +24,11 @@
|
|||||||
// Initializes the page JavaScript.
|
// Initializes the page JavaScript.
|
||||||
document.addEventListener("DOMContentLoaded", function () {
|
document.addEventListener("DOMContentLoaded", function () {
|
||||||
initializeBaseAccountSelector();
|
initializeBaseAccountSelector();
|
||||||
document.getElementById("account-base-code")
|
document.getElementById("accounting-base-code")
|
||||||
.onchange = validateBase;
|
.onchange = validateBase;
|
||||||
document.getElementById("account-title")
|
document.getElementById("accounting-title")
|
||||||
.onchange = validateTitle;
|
.onchange = validateTitle;
|
||||||
document.getElementById("account-form")
|
document.getElementById("accounting-form")
|
||||||
.onsubmit = validateForm;
|
.onsubmit = validateForm;
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -38,25 +38,25 @@ document.addEventListener("DOMContentLoaded", function () {
|
|||||||
* @private
|
* @private
|
||||||
*/
|
*/
|
||||||
function initializeBaseAccountSelector() {
|
function initializeBaseAccountSelector() {
|
||||||
const selector = document.getElementById("select-base-modal");
|
const selector = document.getElementById("accounting-base-selector-model");
|
||||||
const base = document.getElementById("account-base");
|
const base = document.getElementById("accounting-base");
|
||||||
const baseCode = document.getElementById("account-base-code");
|
const baseCode = document.getElementById("accounting-base-code");
|
||||||
const baseContent = document.getElementById("account-base-content");
|
const baseContent = document.getElementById("accounting-base-content");
|
||||||
const options = Array.from(document.getElementsByClassName("list-group-item-base"));
|
const options = Array.from(document.getElementsByClassName("accounting-base-option"));
|
||||||
const btnClear = document.getElementById("btn-clear-base");
|
const btnClear = document.getElementById("accounting-btn-clear-base");
|
||||||
selector.addEventListener("show.bs.modal", function () {
|
selector.addEventListener("show.bs.modal", function () {
|
||||||
base.classList.add("not-empty");
|
base.classList.add("accounting-not-empty");
|
||||||
options.forEach(function (item) {
|
options.forEach(function (item) {
|
||||||
item.classList.remove("active");
|
item.classList.remove("active");
|
||||||
});
|
});
|
||||||
const selected = document.getElementById("list-group-item-base-" + baseCode.value);
|
const selected = document.getElementById("accounting-base-option-" + baseCode.value);
|
||||||
if (selected !== null) {
|
if (selected !== null) {
|
||||||
selected.classList.add("active");
|
selected.classList.add("active");
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
selector.addEventListener("hidden.bs.modal", function () {
|
selector.addEventListener("hidden.bs.modal", function () {
|
||||||
if (baseCode.value === "") {
|
if (baseCode.value === "") {
|
||||||
base.classList.remove("not-empty");
|
base.classList.remove("accounting-not-empty");
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
options.forEach(function (option) {
|
options.forEach(function (option) {
|
||||||
@ -79,6 +79,54 @@ function initializeBaseAccountSelector() {
|
|||||||
validateBase();
|
validateBase();
|
||||||
bootstrap.Modal.getInstance(selector).hide();
|
bootstrap.Modal.getInstance(selector).hide();
|
||||||
}
|
}
|
||||||
|
initializeBaseAccountQuery();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Initializes the query on the base account options.
|
||||||
|
*
|
||||||
|
* @private
|
||||||
|
*/
|
||||||
|
function initializeBaseAccountQuery() {
|
||||||
|
const query = document.getElementById("accounting-base-selector-query");
|
||||||
|
const optionList = document.getElementById("accounting-base-option-list");
|
||||||
|
const options = Array.from(document.getElementsByClassName("accounting-base-option"));
|
||||||
|
const queryNoResult = document.getElementById("accounting-base-option-no-result");
|
||||||
|
query.addEventListener("input", function () {
|
||||||
|
console.log(query.value);
|
||||||
|
if (query.value === "") {
|
||||||
|
options.forEach(function (option) {
|
||||||
|
option.classList.remove("d-none");
|
||||||
|
});
|
||||||
|
optionList.classList.remove("d-none");
|
||||||
|
queryNoResult.classList.add("d-none");
|
||||||
|
return
|
||||||
|
}
|
||||||
|
let hasAnyMatched = false;
|
||||||
|
options.forEach(function (option) {
|
||||||
|
const queryValues = JSON.parse(option.dataset.queryValues);
|
||||||
|
let isMatched = false;
|
||||||
|
for (let i = 0; i < queryValues.length; i++) {
|
||||||
|
if (queryValues[i].includes(query.value)) {
|
||||||
|
isMatched = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (isMatched) {
|
||||||
|
option.classList.remove("d-none");
|
||||||
|
hasAnyMatched = true;
|
||||||
|
} else {
|
||||||
|
option.classList.add("d-none");
|
||||||
|
}
|
||||||
|
});
|
||||||
|
if (!hasAnyMatched) {
|
||||||
|
optionList.classList.add("d-none");
|
||||||
|
queryNoResult.classList.remove("d-none");
|
||||||
|
} else {
|
||||||
|
optionList.classList.remove("d-none");
|
||||||
|
queryNoResult.classList.add("d-none");
|
||||||
|
}
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -101,9 +149,9 @@ function validateForm() {
|
|||||||
* @private
|
* @private
|
||||||
*/
|
*/
|
||||||
function validateBase() {
|
function validateBase() {
|
||||||
const field = document.getElementById("account-base-code");
|
const field = document.getElementById("accounting-base-code");
|
||||||
const error = document.getElementById("account-base-code-error");
|
const error = document.getElementById("accounting-base-code-error");
|
||||||
const displayField = document.getElementById("account-base");
|
const displayField = document.getElementById("accounting-base");
|
||||||
field.value = field.value.trim();
|
field.value = field.value.trim();
|
||||||
if (field.value === "") {
|
if (field.value === "") {
|
||||||
displayField.classList.add("is-invalid");
|
displayField.classList.add("is-invalid");
|
||||||
@ -122,8 +170,8 @@ function validateBase() {
|
|||||||
* @private
|
* @private
|
||||||
*/
|
*/
|
||||||
function validateTitle() {
|
function validateTitle() {
|
||||||
const field = document.getElementById("account-title");
|
const field = document.getElementById("accounting-title");
|
||||||
const error = document.getElementById("account-title-error");
|
const error = document.getElementById("accounting-title-error");
|
||||||
field.value = field.value.trim();
|
field.value = field.value.trim();
|
||||||
if (field.value === "") {
|
if (field.value === "") {
|
||||||
field.classList.add("is-invalid");
|
field.classList.add("is-invalid");
|
||||||
|
@ -23,13 +23,13 @@
|
|||||||
|
|
||||||
// Initializes the page JavaScript.
|
// Initializes the page JavaScript.
|
||||||
document.addEventListener("DOMContentLoaded", function () {
|
document.addEventListener("DOMContentLoaded", function () {
|
||||||
const list = document.getElementById("account-order-list");
|
const list = document.getElementById("accounting-order-list");
|
||||||
if (list !== null) {
|
if (list !== null) {
|
||||||
const onReorder = function () {
|
const onReorder = function () {
|
||||||
const accounts = Array.from(list.children);
|
const accounts = Array.from(list.children);
|
||||||
for (let i = 0; i < accounts.length; i++) {
|
for (let i = 0; i < accounts.length; i++) {
|
||||||
const no = document.getElementById("account-order-" + accounts[i].dataset.id + "-no");
|
const no = document.getElementById("accounting-order-" + accounts[i].dataset.id + "-no");
|
||||||
const code = document.getElementById("account-order-" + accounts[i].dataset.id + "-code");
|
const code = document.getElementById("accounting-order-" + accounts[i].dataset.id + "-code");
|
||||||
no.value = String(i + 1);
|
no.value = String(i + 1);
|
||||||
code.innerText = list.dataset.baseCode + "-" + ("000" + (i + 1)).slice(-3);
|
code.innerText = list.dataset.baseCode + "-" + ("000" + (i + 1)).slice(-3);
|
||||||
}
|
}
|
||||||
|
@ -23,11 +23,11 @@
|
|||||||
|
|
||||||
// Initializes the page JavaScript.
|
// Initializes the page JavaScript.
|
||||||
document.addEventListener("DOMContentLoaded", function () {
|
document.addEventListener("DOMContentLoaded", function () {
|
||||||
document.getElementById("currency-code")
|
document.getElementById("accounting-code")
|
||||||
.onchange = validateCode;
|
.onchange = validateCode;
|
||||||
document.getElementById("currency-name")
|
document.getElementById("accounting-name")
|
||||||
.onchange = validateName;
|
.onchange = validateName;
|
||||||
document.getElementById("currency-form")
|
document.getElementById("accounting-form")
|
||||||
.onsubmit = validateForm;
|
.onsubmit = validateForm;
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -69,7 +69,7 @@ function submitFormIfAllAsyncValid() {
|
|||||||
isValid = isAsyncValid[key] && isValid;
|
isValid = isAsyncValid[key] && isValid;
|
||||||
});
|
});
|
||||||
if (isValid) {
|
if (isValid) {
|
||||||
document.getElementById("currency-form").submit()
|
document.getElementById("accounting-form").submit()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -84,8 +84,8 @@ function validateCode(changeEvent = null) {
|
|||||||
const key = "code";
|
const key = "code";
|
||||||
const isSubmission = changeEvent === null;
|
const isSubmission = changeEvent === null;
|
||||||
let hasAsyncValidation = false;
|
let hasAsyncValidation = false;
|
||||||
const field = document.getElementById("currency-code");
|
const field = document.getElementById("accounting-code");
|
||||||
const error = document.getElementById("currency-code-error");
|
const error = document.getElementById("accounting-code-error");
|
||||||
field.value = field.value.trim();
|
field.value = field.value.trim();
|
||||||
if (field.value === "") {
|
if (field.value === "") {
|
||||||
field.classList.add("is-invalid");
|
field.classList.add("is-invalid");
|
||||||
@ -125,15 +125,15 @@ function validateCode(changeEvent = null) {
|
|||||||
* @private
|
* @private
|
||||||
*/
|
*/
|
||||||
function validateAsyncCodeIsDuplicated(isSubmission, key) {
|
function validateAsyncCodeIsDuplicated(isSubmission, key) {
|
||||||
const field = document.getElementById("currency-code");
|
const field = document.getElementById("accounting-code");
|
||||||
const error = document.getElementById("currency-code-error");
|
const error = document.getElementById("accounting-code-error");
|
||||||
const url = field.dataset.existsUrl;
|
const url = field.dataset.existsUrl;
|
||||||
const onLoad = function () {
|
const onLoad = function () {
|
||||||
if (this.status === 200) {
|
if (this.status === 200) {
|
||||||
const result = JSON.parse(this.responseText);
|
const result = JSON.parse(this.responseText);
|
||||||
if (result["exists"]) {
|
if (result["exists"]) {
|
||||||
field.classList.add("is-invalid");
|
field.classList.add("is-invalid");
|
||||||
error.innerText = _("Code conflicts with another currency.");
|
error.innerText = A_("Code conflicts with another currency.");
|
||||||
if (isSubmission) {
|
if (isSubmission) {
|
||||||
isAsyncValid[key] = false;
|
isAsyncValid[key] = false;
|
||||||
}
|
}
|
||||||
@ -160,8 +160,8 @@ function validateAsyncCodeIsDuplicated(isSubmission, key) {
|
|||||||
* @private
|
* @private
|
||||||
*/
|
*/
|
||||||
function validateName() {
|
function validateName() {
|
||||||
const field = document.getElementById("currency-name");
|
const field = document.getElementById("accounting-name");
|
||||||
const error = document.getElementById("currency-name-error");
|
const error = document.getElementById("accounting-name-error");
|
||||||
field.value = field.value.trim();
|
field.value = field.value.trim();
|
||||||
if (field.value === "") {
|
if (field.value === "") {
|
||||||
field.classList.add("is-invalid");
|
field.classList.add("is-invalid");
|
||||||
|
@ -26,47 +26,47 @@ First written: 2023/1/31
|
|||||||
{% block content %}
|
{% block content %}
|
||||||
|
|
||||||
<div class="btn-group mb-3">
|
<div class="btn-group mb-3">
|
||||||
<a class="btn btn-primary" href="{{ url_for("accounting.account.list")|or_next }}">
|
<a class="btn btn-primary" 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"></i>
|
||||||
{{ A_("Back") }}
|
{{ A_("Back") }}
|
||||||
</a>
|
</a>
|
||||||
{% if can_edit_accounting() %}
|
{% if accounting_can_edit() %}
|
||||||
<a class="btn btn-primary d-none d-md-inline" href="{{ url_for("accounting.account.edit", account=obj)|inherit_next }}">
|
<a class="btn btn-primary d-none d-md-inline" href="{{ url_for("accounting.account.edit", account=obj)|accounting_inherit_next }}">
|
||||||
<i class="fa-solid fa-gear"></i>
|
<i class="fa-solid fa-gear"></i>
|
||||||
{{ A_("Settings") }}
|
{{ A_("Settings") }}
|
||||||
</a>
|
</a>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
<a class="btn btn-primary" href="{{ url_for("accounting.account.order", base=obj.base)|append_next }}">
|
<a class="btn btn-primary" 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"></i>
|
||||||
{{ A_("Order") }}
|
{{ A_("Order") }}
|
||||||
</a>
|
</a>
|
||||||
{% if can_edit_accounting() %}
|
{% if accounting_can_edit() %}
|
||||||
<button class="btn btn-danger" type="button" data-bs-toggle="modal" data-bs-target="#delete-modal">
|
<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"></i>
|
||||||
{{ A_("Delete") }}
|
{{ A_("Delete") }}
|
||||||
</button>
|
</button>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{% if can_edit_accounting() %}
|
{% if accounting_can_edit() %}
|
||||||
<div class="d-md-none material-fab">
|
<div class="d-md-none accounting-material-fab">
|
||||||
<a class="btn btn-primary" href="{{ url_for("accounting.account.edit", account=obj)|inherit_next }}">
|
<a class="btn btn-primary" 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"></i>
|
||||||
</a>
|
</a>
|
||||||
</div>
|
</div>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
|
||||||
{% if can_edit_accounting() %}
|
{% if accounting_can_edit() %}
|
||||||
<form id="delete-form" action="{{ url_for("accounting.account.delete", account=obj) }}" method="post">
|
<form action="{{ url_for("accounting.account.delete", account=obj) }}" method="post">
|
||||||
<input id="csrf_token" type="hidden" name="csrf_token" value="{{ csrf_token() }}">
|
<input type="hidden" name="csrf_token" value="{{ csrf_token() }}">
|
||||||
{% if "next" in request.args %}
|
{% if "next" in request.args %}
|
||||||
<input type="hidden" name="next" value="{{ request.args["next"] }}">
|
<input type="hidden" name="next" value="{{ request.args["next"] }}">
|
||||||
{% endif %}
|
{% endif %}
|
||||||
<div class="modal fade" id="delete-modal" tabindex="-1" aria-labelledby="delete-model-label" aria-hidden="true">
|
<div class="modal fade" id="accounting-delete-modal" tabindex="-1" aria-labelledby="accounting-delete-model-label" aria-hidden="true">
|
||||||
<div class="modal-dialog">
|
<div class="modal-dialog">
|
||||||
<div class="modal-content">
|
<div class="modal-content">
|
||||||
<div class="modal-header">
|
<div class="modal-header">
|
||||||
<h1 class="modal-title fs-5" id="delete-model-label">{{ A_("Delete Account Confirmation") }}</h1>
|
<h1 class="modal-title fs-5" id="accounting-delete-model-label">{{ A_("Delete Account Confirmation") }}</h1>
|
||||||
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
|
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
|
||||||
</div>
|
</div>
|
||||||
<div class="modal-body">
|
<div class="modal-body">
|
||||||
@ -82,9 +82,9 @@ First written: 2023/1/31
|
|||||||
</form>
|
</form>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
|
||||||
<div class="account col-sm-6">
|
<div class="accounting-card col-sm-6">
|
||||||
<div class="account-title">{{ obj.title }}</div>
|
<div class="accounting-card-title">{{ obj.title }}</div>
|
||||||
<div class="account-code">{{ obj.code }}</div>
|
<div class="accounting-card-code">{{ obj.code }}</div>
|
||||||
{% if obj.is_offset_needed %}
|
{% if obj.is_offset_needed %}
|
||||||
<div>
|
<div>
|
||||||
<span class="badge rounded-pill bg-info">{{ A_("Offset needed") }}</span>
|
<span class="badge rounded-pill bg-info">{{ A_("Offset needed") }}</span>
|
||||||
|
@ -23,6 +23,6 @@ First written: 2023/2/1
|
|||||||
|
|
||||||
{% block header %}{% block title %}{{ A_("%(account)s Settings", account=account) }}{% endblock %}{% endblock %}
|
{% block header %}{% block title %}{{ A_("%(account)s Settings", account=account) }}{% endblock %}{% endblock %}
|
||||||
|
|
||||||
{% block back_url %}{{ url_for("accounting.account.detail", account=account)|inherit_next }}{% endblock %}
|
{% block back_url %}{{ url_for("accounting.account.detail", account=account)|accounting_inherit_next }}{% endblock %}
|
||||||
|
|
||||||
{% block action_url %}{{ url_for("accounting.account.update", account=account) }}{% endblock %}
|
{% block action_url %}{{ url_for("accounting.account.update", account=account) }}{% endblock %}
|
||||||
|
@ -34,16 +34,16 @@ First written: 2023/2/1
|
|||||||
</a>
|
</a>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<form id="account-form" action="{% block action_url %}{% endblock %}" method="post">
|
<form id="accounting-form" action="{% block action_url %}{% endblock %}" method="post">
|
||||||
{{ form.csrf_token }}
|
{{ form.csrf_token }}
|
||||||
{% if "next" in request.args %}
|
{% if "next" in request.args %}
|
||||||
<input type="hidden" name="next" value="{{ request.args["next"] }}">
|
<input type="hidden" name="next" value="{{ request.args["next"] }}">
|
||||||
{% endif %}
|
{% endif %}
|
||||||
<div class="form-floating mb-3">
|
<div class="form-floating mb-3">
|
||||||
<input id="account-base-code" type="hidden" name="base_code" value="{{ "" if form.base_code.data is none else form.base_code.data }}">
|
<input id="accounting-base-code" type="hidden" name="base_code" value="{{ "" if form.base_code.data is none else form.base_code.data }}">
|
||||||
<div id="account-base" class="form-control clickable material-text-field {% if form.base_code.data %} not-empty {% endif %} {% if form.base_code.errors %} is-invalid {% endif %}" data-bs-toggle="modal" data-bs-target="#select-base-modal">
|
<div id="accounting-base" 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-model">
|
||||||
<label id="account-base-label" class="form-label" for="account-base">{{ A_("Base account") }}</label>
|
<label class="form-label" for="accounting-base">{{ A_("Base account") }}</label>
|
||||||
<div id="account-base-content">
|
<div id="accounting-base-content">
|
||||||
{% if form.base_code.data %}
|
{% if form.base_code.data %}
|
||||||
{% if form.base_code.errors %}
|
{% if form.base_code.errors %}
|
||||||
{{ A_("(Unknown)") }}
|
{{ A_("(Unknown)") }}
|
||||||
@ -53,18 +53,18 @@ First written: 2023/2/1
|
|||||||
{% endif %}
|
{% endif %}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div id="account-base-code-error" class="invalid-feedback">{% if form.base_code.errors %}{{ form.base_code.errors[0] }}{% endif %}</div>
|
<div id="accounting-base-code-error" class="invalid-feedback">{% if form.base_code.errors %}{{ form.base_code.errors[0] }}{% endif %}</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="form-floating mb-3">
|
<div class="form-floating mb-3">
|
||||||
<input id="account-title" class="form-control {% if form.title.errors %} is-invalid {% endif %}" type="text" name="title" value="{{ "" if form.title.data is none else form.title.data }}" placeholder=" " required="required">
|
<input id="accounting-title" class="form-control {% if form.title.errors %} is-invalid {% endif %}" type="text" name="title" value="{{ "" if form.title.data is none else form.title.data }}" placeholder=" " required="required">
|
||||||
<label class="form-label" for="account-title">{{ A_("Title") }}</label>
|
<label class="form-label" for="accounting-title">{{ A_("Title") }}</label>
|
||||||
<div id="account-title-error" class="invalid-feedback">{% if form.title.errors %}{{ form.title.errors[0] }}{% endif %}</div>
|
<div id="accounting-title-error" class="invalid-feedback">{% if form.title.errors %}{{ form.title.errors[0] }}{% endif %}</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="form-check form-switch mb-3">
|
<div class="form-check form-switch mb-3">
|
||||||
<input id="account-is-offset-needed" class="form-check-input" type="checkbox" name="is_offset_needed" value="1" {% if form.is_offset_needed.data %} checked="checked" {% endif %}>
|
<input id="accounting-is-offset-needed" class="form-check-input" type="checkbox" name="is_offset_needed" value="1" {% if form.is_offset_needed.data %} checked="checked" {% endif %}>
|
||||||
<label class="form-check-label" for="account-is-offset-needed">
|
<label class="form-check-label" for="accounting-is-offset-needed">
|
||||||
{{ A_("The entries in the account need offsets.") }}
|
{{ A_("The entries in the account need offsets.") }}
|
||||||
</label>
|
</label>
|
||||||
</div>
|
</div>
|
||||||
@ -76,43 +76,44 @@ First written: 2023/2/1
|
|||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="d-md-none material-fab">
|
<div class="d-md-none accounting-material-fab">
|
||||||
<button class="btn btn-primary" type="submit">
|
<button class="btn btn-primary" type="submit">
|
||||||
<i class="fa-solid fa-floppy-disk"></i>
|
<i class="fa-solid fa-floppy-disk"></i>
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</form>
|
</form>
|
||||||
|
|
||||||
<div class="modal fade" id="select-base-modal" tabindex="-1" aria-labelledby="select-base-model-label" aria-hidden="true">
|
<div class="modal fade" id="accounting-base-selector-model" tabindex="-1" aria-labelledby="accounting-base-selector-model-label" aria-hidden="true">
|
||||||
<div class="modal-dialog">
|
<div class="modal-dialog">
|
||||||
<div class="modal-content">
|
<div class="modal-content">
|
||||||
<div class="modal-header">
|
<div class="modal-header">
|
||||||
<h1 class="modal-title fs-5" id="base-selector-model-label">{{ A_("Select Base Account") }}</h1>
|
<h1 class="modal-title fs-5" id="accounting-base-selector-model-label">{{ A_("Select Base Account") }}</h1>
|
||||||
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
|
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
|
||||||
</div>
|
</div>
|
||||||
<div class="modal-body">
|
<div class="modal-body">
|
||||||
<div class="input-group mb-2">
|
<div class="input-group mb-2">
|
||||||
<input id="select-base-query" class="form-control form-control-sm" type="search" placeholder=" " required="required" aria-label="Search">
|
<input id="accounting-base-selector-query" class="form-control form-control-sm" type="search" placeholder=" " required="required" aria-label="Search">
|
||||||
<label class="input-group-text" for="select-base-query">
|
<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"></i>
|
||||||
{{ A_("Search") }}
|
{{ A_("Search") }}
|
||||||
</label>
|
</label>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<ul class="list-group list-base-selector">
|
<ul id="accounting-base-option-list" class="list-group accounting-selector-list">
|
||||||
{% for base in form.base_options %}
|
{% for base in form.base_options %}
|
||||||
<li id="list-group-item-base-{{ base.code }}" class="list-group-item list-group-item-base clickable" data-code="{{ base.code }}" data-content="{{ base }}">
|
<li id="accounting-base-option-{{ base.code }}" class="list-group-item accounting-base-option accounting-clickable" data-code="{{ base.code }}" data-content="{{ base }}" data-query-values="{{ base.query_values|tojson|forceescape }}">
|
||||||
{{ base }}
|
{{ base }}
|
||||||
</li>
|
</li>
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
</ul>
|
</ul>
|
||||||
|
<p id="accounting-base-option-no-result" class="d-none">{{ A_("There is no data.") }}</p>
|
||||||
</div>
|
</div>
|
||||||
<div class="modal-footer">
|
<div class="modal-footer">
|
||||||
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">{{ A_("Cancel") }}</button>
|
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">{{ A_("Cancel") }}</button>
|
||||||
{% if form.base_code.data %}
|
{% if form.base_code.data %}
|
||||||
<button id="btn-clear-base" type="button" class="btn btn-danger">{{ A_("Clear") }}</button>
|
<button id="accounting-btn-clear-base" type="button" class="btn btn-danger">{{ A_("Clear") }}</button>
|
||||||
{% else %}
|
{% else %}
|
||||||
<button id="btn-clear-base" type="button" class="btn btn-secondary" disabled="disabled">{{ A_("Clear") }}</button>
|
<button id="accounting-btn-clear-base" type="button" class="btn btn-secondary" disabled="disabled">{{ A_("Clear") }}</button>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
@ -26,15 +26,15 @@ First written: 2023/1/30
|
|||||||
{% block content %}
|
{% block content %}
|
||||||
|
|
||||||
<div class="btn-group mb-2">
|
<div class="btn-group mb-2">
|
||||||
{% if can_edit_accounting() %}
|
{% if accounting_can_edit() %}
|
||||||
<a class="btn btn-primary text-nowrap d-none d-md-block" href="{{ url_for("accounting.account.create")|append_next }}">
|
<a class="btn btn-primary text-nowrap d-none d-md-block" href="{{ url_for("accounting.account.create")|accounting_append_next }}">
|
||||||
<i class="fa-solid fa-plus"></i>
|
<i class="fa-solid fa-plus"></i>
|
||||||
{{ A_("New") }}
|
{{ A_("New") }}
|
||||||
</a>
|
</a>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
<form class="btn btn-primary d-flex input-group" action="{{ url_for("accounting.account.list") }}" method="get" role="search">
|
<form class="btn btn-primary d-flex input-group" action="{{ url_for("accounting.account.list") }}" method="get" role="search">
|
||||||
<input id="search-input" class="form-control form-control-sm search-input" type="search" name="q" value="{{ request.args["q"] if "q" in request.args else "" }}" placeholder=" " required="required" aria-label="Search">
|
<input id="accounting-search" class="form-control form-control-sm accounting-search-input" type="search" name="q" value="{{ request.args["q"] if "q" in request.args else "" }}" placeholder=" " required="required" aria-label="Search">
|
||||||
<label for="search-input" class="search-label">
|
<label for="accounting-search" class="accounting-search-label">
|
||||||
<button type="submit">
|
<button type="submit">
|
||||||
<i class="fa-solid fa-magnifying-glass"></i>
|
<i class="fa-solid fa-magnifying-glass"></i>
|
||||||
{{ A_("Search") }}
|
{{ A_("Search") }}
|
||||||
@ -43,9 +43,9 @@ First written: 2023/1/30
|
|||||||
</form>
|
</form>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{% if can_edit_accounting() %}
|
{% if accounting_can_edit() %}
|
||||||
<div class="d-md-none material-fab">
|
<div class="d-md-none accounting-material-fab">
|
||||||
<a class="btn btn-primary" href="{{ url_for("accounting.account.create")|append_next }}">
|
<a class="btn btn-primary" href="{{ url_for("accounting.account.create")|accounting_append_next }}">
|
||||||
<i class="fa-solid fa-plus"></i>
|
<i class="fa-solid fa-plus"></i>
|
||||||
</a>
|
</a>
|
||||||
</div>
|
</div>
|
||||||
@ -56,7 +56,7 @@ First written: 2023/1/30
|
|||||||
|
|
||||||
<div class="list-group">
|
<div class="list-group">
|
||||||
{% for item in list %}
|
{% for item in list %}
|
||||||
<a class="list-group-item list-group-item-action" href="{{ url_for("accounting.account.detail", account=item)|append_next }}">
|
<a class="list-group-item list-group-item-action" href="{{ url_for("accounting.account.detail", account=item)|accounting_append_next }}">
|
||||||
{{ item }}
|
{{ item }}
|
||||||
{% if item.is_offset_needed %}
|
{% if item.is_offset_needed %}
|
||||||
<span class="badge rounded-pill bg-info">{{ A_("Offset needed") }}</span>
|
<span class="badge rounded-pill bg-info">{{ A_("Offset needed") }}</span>
|
||||||
|
@ -31,24 +31,24 @@ First written: 2023/2/2
|
|||||||
{% block content %}
|
{% block content %}
|
||||||
|
|
||||||
<div class="btn-group mb-3">
|
<div class="btn-group mb-3">
|
||||||
<a class="btn btn-primary" href="{{ url_for("accounting.account.list")|or_next }}">
|
<a class="btn btn-primary" 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"></i>
|
||||||
{{ A_("Back") }}
|
{{ A_("Back") }}
|
||||||
</a>
|
</a>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{% if base.accounts|length > 1 and can_edit_accounting() %}
|
{% if base.accounts|length > 1 and accounting_can_edit() %}
|
||||||
<form action="{{ url_for("accounting.account.sort", base=base) }}" method="post">
|
<form action="{{ url_for("accounting.account.sort", base=base) }}" method="post">
|
||||||
<input id="csrf_token" type="hidden" name="csrf_token" value="{{ csrf_token() }}">
|
<input type="hidden" name="csrf_token" value="{{ csrf_token() }}">
|
||||||
{% if "next" in request.args %}
|
{% if "next" in request.args %}
|
||||||
<input type="hidden" name="next" value="{{ request.args["next"] }}">
|
<input type="hidden" name="next" value="{{ request.args["next"] }}">
|
||||||
{% endif %}
|
{% endif %}
|
||||||
<ul id="account-order-list" class="list-group mb-3" data-base-code="{{ base.code }}">
|
<ul id="accounting-order-list" class="list-group mb-3" data-base-code="{{ base.code }}">
|
||||||
{% for account in base.accounts|sort(attribute="no") %}
|
{% for account in base.accounts|sort(attribute="no") %}
|
||||||
<li class="list-group-item d-flex justify-content-between" data-id="{{ account.id }}">
|
<li class="list-group-item d-flex justify-content-between" data-id="{{ account.id }}">
|
||||||
<input id="account-order-{{ account.id }}-no" type="hidden" name="{{ account.id }}-no" value="{{ loop.index }}">
|
<input id="accounting-order-{{ account.id }}-no" type="hidden" name="{{ account.id }}-no" value="{{ loop.index }}">
|
||||||
<div>
|
<div>
|
||||||
<span id="account-order-{{ account.id }}-code">{{ account.code }}</span>
|
<span id="accounting-order-{{ account.id }}-code">{{ account.code }}</span>
|
||||||
{{ account.title }}
|
{{ account.title }}
|
||||||
</div>
|
</div>
|
||||||
<i class="fa-solid fa-bars"></i>
|
<i class="fa-solid fa-bars"></i>
|
||||||
@ -63,7 +63,7 @@ First written: 2023/2/2
|
|||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="d-md-none material-fab">
|
<div class="d-md-none accounting-material-fab">
|
||||||
<button class="btn btn-primary" type="submit">
|
<button class="btn btn-primary" type="submit">
|
||||||
<i class="fa-solid fa-floppy-disk"></i>
|
<i class="fa-solid fa-floppy-disk"></i>
|
||||||
</button>
|
</button>
|
||||||
|
@ -26,19 +26,19 @@ First written: 2023/2/1
|
|||||||
{% block content %}
|
{% block content %}
|
||||||
|
|
||||||
<div class="btn-group mb-3">
|
<div class="btn-group mb-3">
|
||||||
<a class="btn btn-primary" href="{{ url_for("accounting.account.list")|or_next }}">
|
<a class="btn btn-primary" 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"></i>
|
||||||
{{ A_("Back") }}
|
{{ A_("Back") }}
|
||||||
</a>
|
</a>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="account col-sm-6">
|
<div class="accounting-card col-sm-6">
|
||||||
<div class="account-title">{{ obj.title }}</div>
|
<div class="accounting-card-title">{{ obj.title }}</div>
|
||||||
<div class="account-code">{{ obj.code }}</div>
|
<div class="accounting-card-code">{{ obj.code }}</div>
|
||||||
{% if obj.accounts %}
|
{% if obj.accounts %}
|
||||||
<div>
|
<div>
|
||||||
{% for account in obj.accounts %}
|
{% for account in obj.accounts %}
|
||||||
<a class="btn btn-primary" href="{{ url_for("accounting.account.detail", account=account)|append_next }}">
|
<a class="btn btn-primary" href="{{ url_for("accounting.account.detail", account=account)|accounting_append_next }}">
|
||||||
{{ account }}
|
{{ account }}
|
||||||
</a>
|
</a>
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
|
@ -27,8 +27,8 @@ First written: 2023/1/26
|
|||||||
|
|
||||||
<div class="btn-group mb-2">
|
<div class="btn-group mb-2">
|
||||||
<form class="btn btn-primary d-flex input-group" action="{{ url_for("accounting.base-account.list") }}" method="get" role="search">
|
<form class="btn btn-primary d-flex input-group" action="{{ url_for("accounting.base-account.list") }}" method="get" role="search">
|
||||||
<input id="search-input" class="form-control form-control-sm search-input" type="search" name="q" value="{{ request.args["q"] if "q" in request.args else "" }}" placeholder=" " required="required" aria-label="Search">
|
<input id="accounting-search" class="form-control form-control-sm accounting-search-input" type="search" name="q" value="{{ request.args["q"] if "q" in request.args else "" }}" placeholder=" " required="required" aria-label="Search">
|
||||||
<label for="search-input" class="search-label">
|
<label for="accounting-search" class="accounting-search-label">
|
||||||
<button type="submit">
|
<button type="submit">
|
||||||
<i class="fa-solid fa-magnifying-glass"></i>
|
<i class="fa-solid fa-magnifying-glass"></i>
|
||||||
{{ A_("Search") }}
|
{{ A_("Search") }}
|
||||||
@ -42,7 +42,7 @@ First written: 2023/1/26
|
|||||||
|
|
||||||
<div class="list-group">
|
<div class="list-group">
|
||||||
{% for item in list %}
|
{% for item in list %}
|
||||||
<a class="list-group-item list-group-item-action" href="{{ url_for("accounting.base-account.detail", account=item)|append_next }}">
|
<a class="list-group-item list-group-item-action" href="{{ url_for("accounting.base-account.detail", account=item)|accounting_append_next }}">
|
||||||
{{ item }}
|
{{ item }}
|
||||||
</a>
|
</a>
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
|
@ -26,43 +26,43 @@ First written: 2023/2/6
|
|||||||
{% block content %}
|
{% block content %}
|
||||||
|
|
||||||
<div class="btn-group mb-3">
|
<div class="btn-group mb-3">
|
||||||
<a class="btn btn-primary" href="{{ url_for("accounting.currency.list")|or_next }}">
|
<a class="btn btn-primary" 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"></i>
|
||||||
{{ A_("Back") }}
|
{{ A_("Back") }}
|
||||||
</a>
|
</a>
|
||||||
{% if can_edit_accounting() %}
|
{% if accounting_can_edit() %}
|
||||||
<a class="btn btn-primary d-none d-md-inline" href="{{ url_for("accounting.currency.edit", currency=obj)|inherit_next }}">
|
<a class="btn btn-primary d-none d-md-inline" href="{{ url_for("accounting.currency.edit", currency=obj)|accounting_inherit_next }}">
|
||||||
<i class="fa-solid fa-gear"></i>
|
<i class="fa-solid fa-gear"></i>
|
||||||
{{ A_("Settings") }}
|
{{ A_("Settings") }}
|
||||||
</a>
|
</a>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
{% if can_edit_accounting() %}
|
{% if accounting_can_edit() %}
|
||||||
<button class="btn btn-danger" type="button" data-bs-toggle="modal" data-bs-target="#delete-modal">
|
<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"></i>
|
||||||
{{ A_("Delete") }}
|
{{ A_("Delete") }}
|
||||||
</button>
|
</button>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{% if can_edit_accounting() %}
|
{% if accounting_can_edit() %}
|
||||||
<div class="d-md-none material-fab">
|
<div class="d-md-none accounting-material-fab">
|
||||||
<a class="btn btn-primary" href="{{ url_for("accounting.currency.edit", currency=obj)|inherit_next }}">
|
<a class="btn btn-primary" 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"></i>
|
||||||
</a>
|
</a>
|
||||||
</div>
|
</div>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
|
||||||
{% if can_edit_accounting() %}
|
{% if accounting_can_edit() %}
|
||||||
<form id="delete-form" action="{{ url_for("accounting.currency.delete", currency=obj) }}" method="post">
|
<form action="{{ url_for("accounting.currency.delete", currency=obj) }}" method="post">
|
||||||
<input id="csrf_token" type="hidden" name="csrf_token" value="{{ csrf_token() }}">
|
<input type="hidden" name="csrf_token" value="{{ csrf_token() }}">
|
||||||
{% if "next" in request.args %}
|
{% if "next" in request.args %}
|
||||||
<input type="hidden" name="next" value="{{ request.args["next"] }}">
|
<input type="hidden" name="next" value="{{ request.args["next"] }}">
|
||||||
{% endif %}
|
{% endif %}
|
||||||
<div class="modal fade" id="delete-modal" tabindex="-1" aria-labelledby="delete-model-label" aria-hidden="true">
|
<div class="modal fade" id="accounting-delete-modal" tabindex="-1" aria-labelledby="accounting-delete-model-label" aria-hidden="true">
|
||||||
<div class="modal-dialog">
|
<div class="modal-dialog">
|
||||||
<div class="modal-content">
|
<div class="modal-content">
|
||||||
<div class="modal-header">
|
<div class="modal-header">
|
||||||
<h1 class="modal-title fs-5" id="delete-model-label">{{ A_("Delete Currency Confirmation") }}</h1>
|
<h1 class="modal-title fs-5" id="accounting-delete-model-label">{{ A_("Delete Currency Confirmation") }}</h1>
|
||||||
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
|
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
|
||||||
</div>
|
</div>
|
||||||
<div class="modal-body">
|
<div class="modal-body">
|
||||||
@ -78,9 +78,9 @@ First written: 2023/2/6
|
|||||||
</form>
|
</form>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
|
||||||
<div class="currency col-sm-6">
|
<div class="accounting-card col-sm-6">
|
||||||
<div class="currency-name">{{ obj.name }}</div>
|
<div class="accounting-card-title">{{ obj.name }}</div>
|
||||||
<div class="currency-code">{{ obj.code }}</div>
|
<div class="accounting-card-code">{{ obj.code }}</div>
|
||||||
<div class="small text-secondary fst-italic">
|
<div class="small text-secondary fst-italic">
|
||||||
<div>{{ A_("Created") }} {{ obj.created_at }} {{ obj.created_by }}</div>
|
<div>{{ A_("Created") }} {{ obj.created_at }} {{ obj.created_by }}</div>
|
||||||
<div>{{ A_("Updated") }} {{ obj.updated_at }} {{ obj.updated_by }}</div>
|
<div>{{ A_("Updated") }} {{ obj.updated_at }} {{ obj.updated_by }}</div>
|
||||||
|
@ -23,7 +23,7 @@ First written: 2023/2/6
|
|||||||
|
|
||||||
{% block header %}{% block title %}{{ A_("%(currency)s Settings", currency=currency) }}{% endblock %}{% endblock %}
|
{% block header %}{% block title %}{{ A_("%(currency)s Settings", currency=currency) }}{% endblock %}{% endblock %}
|
||||||
|
|
||||||
{% block back_url %}{{ url_for("accounting.currency.detail", currency=currency)|inherit_next }}{% endblock %}
|
{% block back_url %}{{ url_for("accounting.currency.detail", currency=currency)|accounting_inherit_next }}{% endblock %}
|
||||||
|
|
||||||
{% block action_url %}{{ url_for("accounting.currency.update", currency=currency) }}{% endblock %}
|
{% block action_url %}{{ url_for("accounting.currency.update", currency=currency) }}{% endblock %}
|
||||||
|
|
||||||
|
@ -34,21 +34,21 @@ First written: 2023/2/6
|
|||||||
</a>
|
</a>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<form id="currency-form" action="{% block action_url %}{% endblock %}" method="post">
|
<form id="accounting-form" action="{% block action_url %}{% endblock %}" method="post">
|
||||||
{{ form.csrf_token }}
|
{{ form.csrf_token }}
|
||||||
{% if "next" in request.args %}
|
{% if "next" in request.args %}
|
||||||
<input type="hidden" name="next" value="{{ request.args["next"] }}">
|
<input type="hidden" name="next" value="{{ request.args["next"] }}">
|
||||||
{% endif %}
|
{% endif %}
|
||||||
<div class="form-floating mb-3">
|
<div class="form-floating mb-3">
|
||||||
<input id="currency-code" class="form-control {% if form.code.errors %} is-invalid {% endif %}" type="text" name="code" value="{{ "" if form.code.data is none else form.code.data }}" 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 }}">
|
<input id="accounting-code" class="form-control {% if form.code.errors %} is-invalid {% endif %}" type="text" name="code" value="{{ "" if form.code.data is none else form.code.data }}" 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="currency-code">{{ A_("Code") }}</label>
|
<label class="form-label" for="accounting-code">{{ A_("Code") }}</label>
|
||||||
<div id="currency-code-error" class="invalid-feedback">{% if form.code.errors %}{{ form.code.errors[0] }}{% endif %}</div>
|
<div id="accounting-code-error" class="invalid-feedback">{% if form.code.errors %}{{ form.code.errors[0] }}{% endif %}</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="form-floating mb-3">
|
<div class="form-floating mb-3">
|
||||||
<input id="currency-name" class="form-control {% if form.name.errors %} is-invalid {% endif %}" type="text" name="name" value="{{ "" if form.name.data is none else form.name.data }}" placeholder=" " required="required">
|
<input id="accounting-name" class="form-control {% if form.name.errors %} is-invalid {% endif %}" type="text" name="name" value="{{ "" if form.name.data is none else form.name.data }}" placeholder=" " required="required">
|
||||||
<label class="form-label" for="currency-name">{{ A_("Name") }}</label>
|
<label class="form-label" for="accounting-name">{{ A_("Name") }}</label>
|
||||||
<div id="currency-name-error" class="invalid-feedback">{% if form.name.errors %}{{ form.name.errors[0] }}{% endif %}</div>
|
<div id="accounting-name-error" class="invalid-feedback">{% if form.name.errors %}{{ form.name.errors[0] }}{% endif %}</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="d-none d-md-block">
|
<div class="d-none d-md-block">
|
||||||
@ -58,7 +58,7 @@ First written: 2023/2/6
|
|||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="d-md-none material-fab">
|
<div class="d-md-none accounting-material-fab">
|
||||||
<button class="btn btn-primary" type="submit">
|
<button class="btn btn-primary" type="submit">
|
||||||
<i class="fa-solid fa-floppy-disk"></i>
|
<i class="fa-solid fa-floppy-disk"></i>
|
||||||
</button>
|
</button>
|
||||||
|
@ -26,15 +26,15 @@ First written: 2023/2/6
|
|||||||
{% block content %}
|
{% block content %}
|
||||||
|
|
||||||
<div class="btn-group mb-2">
|
<div class="btn-group mb-2">
|
||||||
{% if can_edit_accounting() %}
|
{% if accounting_can_edit() %}
|
||||||
<a class="btn btn-primary text-nowrap d-none d-md-block" href="{{ url_for("accounting.currency.create")|append_next }}">
|
<a class="btn btn-primary text-nowrap d-none d-md-block" href="{{ url_for("accounting.currency.create")|accounting_append_next }}">
|
||||||
<i class="fa-solid fa-plus"></i>
|
<i class="fa-solid fa-plus"></i>
|
||||||
{{ A_("New") }}
|
{{ A_("New") }}
|
||||||
</a>
|
</a>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
<form class="btn btn-primary d-flex input-group" action="{{ url_for("accounting.currency.list") }}" method="get" role="search">
|
<form class="btn btn-primary d-flex input-group" action="{{ url_for("accounting.currency.list") }}" method="get" role="search">
|
||||||
<input id="search-input" class="form-control form-control-sm search-input" type="search" name="q" value="{{ request.args["q"] if "q" in request.args else "" }}" placeholder=" " required="required" aria-label="Search">
|
<input id="accounting-search" class="form-control form-control-sm accounting-search-input" type="search" name="q" value="{{ request.args["q"] if "q" in request.args else "" }}" placeholder=" " required="required" aria-label="Search">
|
||||||
<label for="search-input" class="search-label">
|
<label for="accounting-search" class="accounting-search-label">
|
||||||
<button type="submit">
|
<button type="submit">
|
||||||
<i class="fa-solid fa-magnifying-glass"></i>
|
<i class="fa-solid fa-magnifying-glass"></i>
|
||||||
{{ A_("Search") }}
|
{{ A_("Search") }}
|
||||||
@ -43,12 +43,20 @@ First written: 2023/2/6
|
|||||||
</form>
|
</form>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
{% if accounting_can_edit() %}
|
||||||
|
<div class="d-md-none accounting-material-fab">
|
||||||
|
<a class="btn btn-primary" href="{{ url_for("accounting.currency.create")|accounting_append_next }}">
|
||||||
|
<i class="fa-solid fa-plus"></i>
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
{% if list %}
|
{% if list %}
|
||||||
{% include "accounting/include/pagination.html" %}
|
{% include "accounting/include/pagination.html" %}
|
||||||
|
|
||||||
<div class="list-group">
|
<div class="list-group">
|
||||||
{% for item in list %}
|
{% for item in list %}
|
||||||
<a class="list-group-item list-group-item-action" href="{{ url_for("accounting.currency.detail", currency=item)|append_next }}">
|
<a class="list-group-item list-group-item-action" href="{{ url_for("accounting.currency.detail", currency=item)|accounting_append_next }}">
|
||||||
{{ item }}
|
{{ item }}
|
||||||
</a>
|
</a>
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
|
@ -19,7 +19,7 @@ nav.html: The navigation menu for the accounting application.
|
|||||||
Author: imacat@mail.imacat.idv.tw (imacat)
|
Author: imacat@mail.imacat.idv.tw (imacat)
|
||||||
First written: 2023/1/26
|
First written: 2023/1/26
|
||||||
#}
|
#}
|
||||||
{% if can_view_accounting() %}
|
{% if accounting_can_view() %}
|
||||||
<li class="nav-item dropdown">
|
<li class="nav-item dropdown">
|
||||||
<span class="nav-link dropdown-toggle" data-bs-toggle="dropdown">
|
<span class="nav-link dropdown-toggle" data-bs-toggle="dropdown">
|
||||||
<i class="fa-solid fa-gear"></i>
|
<i class="fa-solid fa-gear"></i>
|
||||||
@ -40,7 +40,7 @@ First written: 2023/1/26
|
|||||||
</li>
|
</li>
|
||||||
<li>
|
<li>
|
||||||
<a class="dropdown-item {% if request.endpoint.startswith("accounting.currency.") %} active {% endif %}" href="{{ url_for("accounting.currency.list") }}">
|
<a class="dropdown-item {% if request.endpoint.startswith("accounting.currency.") %} active {% endif %}" href="{{ url_for("accounting.currency.list") }}">
|
||||||
<i class="fa-solid fa-list"></i>
|
<i class="fa-solid fa-money-bill-wave"></i>
|
||||||
{{ A_("Currencies") }}
|
{{ A_("Currencies") }}
|
||||||
</a>
|
</a>
|
||||||
</li>
|
</li>
|
||||||
|
@ -8,8 +8,8 @@ msgid ""
|
|||||||
msgstr ""
|
msgstr ""
|
||||||
"Project-Id-Version: Mia! Accounting Flask 0.0.0\n"
|
"Project-Id-Version: Mia! Accounting Flask 0.0.0\n"
|
||||||
"Report-Msgid-Bugs-To: imacat@mail.imacat.idv.tw\n"
|
"Report-Msgid-Bugs-To: imacat@mail.imacat.idv.tw\n"
|
||||||
"POT-Creation-Date: 2023-02-06 09:47+0800\n"
|
"POT-Creation-Date: 2023-02-07 16:22+0800\n"
|
||||||
"PO-Revision-Date: 2023-02-06 09:48+0800\n"
|
"PO-Revision-Date: 2023-02-07 18:04+0800\n"
|
||||||
"Last-Translator: imacat <imacat@mail.imacat.idv.tw>\n"
|
"Last-Translator: imacat <imacat@mail.imacat.idv.tw>\n"
|
||||||
"Language: zh_Hant\n"
|
"Language: zh_Hant\n"
|
||||||
"Language-Team: zh_Hant <imacat@mail.imacat.idv.tw>\n"
|
"Language-Team: zh_Hant <imacat@mail.imacat.idv.tw>\n"
|
||||||
@ -23,12 +23,16 @@ msgstr ""
|
|||||||
msgid "The base account does not exist."
|
msgid "The base account does not exist."
|
||||||
msgstr "沒有這個基本科目。"
|
msgstr "沒有這個基本科目。"
|
||||||
|
|
||||||
#: src/accounting/account/forms.py:50
|
#: src/accounting/account/forms.py:52
|
||||||
|
msgid "The base account is not available."
|
||||||
|
msgstr "不能選這個基本科目。"
|
||||||
|
|
||||||
|
#: src/accounting/account/forms.py:61
|
||||||
#: src/accounting/static/js/account-form.js:110
|
#: src/accounting/static/js/account-form.js:110
|
||||||
msgid "Please select the base account."
|
msgid "Please select the base account."
|
||||||
msgstr "請選擇基本科目。"
|
msgstr "請選擇基本科目。"
|
||||||
|
|
||||||
#: src/accounting/account/forms.py:55
|
#: src/accounting/account/forms.py:67
|
||||||
msgid "Please fill in the title"
|
msgid "Please fill in the title"
|
||||||
msgstr "請填上標題。"
|
msgstr "請填上標題。"
|
||||||
|
|
||||||
@ -50,18 +54,59 @@ msgstr "科目未異動。"
|
|||||||
msgid "The account is updated successfully."
|
msgid "The account is updated successfully."
|
||||||
msgstr "科目存好了。"
|
msgstr "科目存好了。"
|
||||||
|
|
||||||
#: src/accounting/account/views.py:167
|
#: src/accounting/account/views.py:165
|
||||||
msgid "The account is deleted successfully."
|
msgid "The account is deleted successfully."
|
||||||
msgstr "科目刪掉了"
|
msgstr "科目刪掉了"
|
||||||
|
|
||||||
#: src/accounting/account/views.py:194
|
#: src/accounting/account/views.py:192
|
||||||
msgid "The order was not modified."
|
msgid "The order was not modified."
|
||||||
msgstr "順序未異動。"
|
msgstr "順序未異動。"
|
||||||
|
|
||||||
#: src/accounting/account/views.py:197
|
#: src/accounting/account/views.py:195
|
||||||
msgid "The order is updated successfully."
|
msgid "The order is updated successfully."
|
||||||
msgstr "順序存好了。"
|
msgstr "順序存好了。"
|
||||||
|
|
||||||
|
#: src/accounting/currency/forms.py:47
|
||||||
|
#: src/accounting/static/js/currency-form.js:136
|
||||||
|
msgid "Code conflicts with another currency."
|
||||||
|
msgstr "代碼與其它貨幣重複。"
|
||||||
|
|
||||||
|
#: src/accounting/currency/forms.py:52
|
||||||
|
#: src/accounting/static/js/currency-form.js:92
|
||||||
|
msgid "Please fill in the code."
|
||||||
|
msgstr "請填上代碼。"
|
||||||
|
|
||||||
|
#: src/accounting/currency/forms.py:54
|
||||||
|
#: src/accounting/static/js/currency-form.js:103
|
||||||
|
msgid "Code can only be composed of 3 upper-cased letters."
|
||||||
|
msgstr "代碼限為三個大寫英文字母。"
|
||||||
|
|
||||||
|
#: src/accounting/currency/forms.py:57
|
||||||
|
#: src/accounting/static/js/currency-form.js:98
|
||||||
|
msgid "This code is not available."
|
||||||
|
msgstr "不能用這個代碼。"
|
||||||
|
|
||||||
|
#: src/accounting/currency/forms.py:63
|
||||||
|
#: src/accounting/static/js/currency-form.js:168
|
||||||
|
msgid "Please fill in the name."
|
||||||
|
msgstr "請填上名稱。"
|
||||||
|
|
||||||
|
#: src/accounting/currency/views.py:90
|
||||||
|
msgid "The currency is added successfully"
|
||||||
|
msgstr "貨幣加好了。"
|
||||||
|
|
||||||
|
#: src/accounting/currency/views.py:146
|
||||||
|
msgid "The currency was not modified."
|
||||||
|
msgstr "貨幣未異動。"
|
||||||
|
|
||||||
|
#: src/accounting/currency/views.py:151
|
||||||
|
msgid "The currency is updated successfully."
|
||||||
|
msgstr "貨幣存好了。"
|
||||||
|
|
||||||
|
#: src/accounting/currency/views.py:167
|
||||||
|
msgid "The currency is deleted successfully."
|
||||||
|
msgstr "貨幣刪掉了"
|
||||||
|
|
||||||
#: src/accounting/static/js/account-form.js:130
|
#: src/accounting/static/js/account-form.js:130
|
||||||
msgid "Please fill in the title."
|
msgid "Please fill in the title."
|
||||||
msgstr "請填上標題。"
|
msgstr "請填上標題。"
|
||||||
@ -74,10 +119,13 @@ msgstr "新增科目"
|
|||||||
#: src/accounting/templates/accounting/account/include/form.html:33
|
#: src/accounting/templates/accounting/account/include/form.html:33
|
||||||
#: src/accounting/templates/accounting/account/order.html:36
|
#: src/accounting/templates/accounting/account/order.html:36
|
||||||
#: src/accounting/templates/accounting/base-account/detail.html:31
|
#: src/accounting/templates/accounting/base-account/detail.html:31
|
||||||
|
#: src/accounting/templates/accounting/currency/detail.html:31
|
||||||
|
#: src/accounting/templates/accounting/currency/include/form.html:33
|
||||||
msgid "Back"
|
msgid "Back"
|
||||||
msgstr "回上頁"
|
msgstr "回上頁"
|
||||||
|
|
||||||
#: src/accounting/templates/accounting/account/detail.html:36
|
#: src/accounting/templates/accounting/account/detail.html:36
|
||||||
|
#: src/accounting/templates/accounting/currency/detail.html:36
|
||||||
msgid "Settings"
|
msgid "Settings"
|
||||||
msgstr "設定"
|
msgstr "設定"
|
||||||
|
|
||||||
@ -86,6 +134,7 @@ msgid "Order"
|
|||||||
msgstr "次序"
|
msgstr "次序"
|
||||||
|
|
||||||
#: src/accounting/templates/accounting/account/detail.html:46
|
#: src/accounting/templates/accounting/account/detail.html:46
|
||||||
|
#: src/accounting/templates/accounting/currency/detail.html:42
|
||||||
msgid "Delete"
|
msgid "Delete"
|
||||||
msgstr "刪除"
|
msgstr "刪除"
|
||||||
|
|
||||||
@ -99,18 +148,22 @@ msgstr "你確定要刪掉這個科目嗎?"
|
|||||||
|
|
||||||
#: src/accounting/templates/accounting/account/detail.html:76
|
#: src/accounting/templates/accounting/account/detail.html:76
|
||||||
#: src/accounting/templates/accounting/account/include/form.html:111
|
#: src/accounting/templates/accounting/account/include/form.html:111
|
||||||
|
#: src/accounting/templates/accounting/currency/detail.html:72
|
||||||
msgid "Cancel"
|
msgid "Cancel"
|
||||||
msgstr "取消"
|
msgstr "取消"
|
||||||
|
|
||||||
#: src/accounting/templates/accounting/account/detail.html:77
|
#: src/accounting/templates/accounting/account/detail.html:77
|
||||||
|
#: src/accounting/templates/accounting/currency/detail.html:73
|
||||||
msgid "Confirm"
|
msgid "Confirm"
|
||||||
msgstr "確定"
|
msgstr "確定"
|
||||||
|
|
||||||
#: src/accounting/templates/accounting/account/detail.html:94
|
#: src/accounting/templates/accounting/account/detail.html:94
|
||||||
|
#: src/accounting/templates/accounting/currency/detail.html:85
|
||||||
msgid "Created"
|
msgid "Created"
|
||||||
msgstr "建檔"
|
msgstr "建檔"
|
||||||
|
|
||||||
#: src/accounting/templates/accounting/account/detail.html:95
|
#: src/accounting/templates/accounting/account/detail.html:95
|
||||||
|
#: src/accounting/templates/accounting/currency/detail.html:86
|
||||||
msgid "Updated"
|
msgid "Updated"
|
||||||
msgstr "更新"
|
msgstr "更新"
|
||||||
|
|
||||||
@ -121,6 +174,7 @@ msgstr "%(account)s設定"
|
|||||||
|
|
||||||
#: src/accounting/templates/accounting/account/list.html:24
|
#: src/accounting/templates/accounting/account/list.html:24
|
||||||
#: src/accounting/templates/accounting/base-account/list.html:24
|
#: src/accounting/templates/accounting/base-account/list.html:24
|
||||||
|
#: src/accounting/templates/accounting/currency/list.html:24
|
||||||
#, python-format
|
#, python-format
|
||||||
msgid "Search Result for \"%(query)s\""
|
msgid "Search Result for \"%(query)s\""
|
||||||
msgstr "「%(query)s」搜尋結果"
|
msgstr "「%(query)s」搜尋結果"
|
||||||
@ -130,18 +184,21 @@ msgid "Account Management"
|
|||||||
msgstr "科目管理"
|
msgstr "科目管理"
|
||||||
|
|
||||||
#: src/accounting/templates/accounting/account/list.html:32
|
#: src/accounting/templates/accounting/account/list.html:32
|
||||||
|
#: src/accounting/templates/accounting/currency/list.html:32
|
||||||
msgid "New"
|
msgid "New"
|
||||||
msgstr "新增"
|
msgstr "新增"
|
||||||
|
|
||||||
#: src/accounting/templates/accounting/account/include/form.html:98
|
#: src/accounting/templates/accounting/account/include/form.html:98
|
||||||
#: src/accounting/templates/accounting/account/list.html:40
|
#: src/accounting/templates/accounting/account/list.html:40
|
||||||
#: src/accounting/templates/accounting/base-account/list.html:34
|
#: src/accounting/templates/accounting/base-account/list.html:34
|
||||||
|
#: src/accounting/templates/accounting/currency/list.html:40
|
||||||
msgid "Search"
|
msgid "Search"
|
||||||
msgstr "搜尋"
|
msgstr "搜尋"
|
||||||
|
|
||||||
#: src/accounting/templates/accounting/account/list.html:68
|
#: src/accounting/templates/accounting/account/list.html:68
|
||||||
#: src/accounting/templates/accounting/account/order.html:81
|
#: src/accounting/templates/accounting/account/order.html:81
|
||||||
#: src/accounting/templates/accounting/base-account/list.html:51
|
#: src/accounting/templates/accounting/base-account/list.html:51
|
||||||
|
#: src/accounting/templates/accounting/currency/list.html:57
|
||||||
msgid "There is no data."
|
msgid "There is no data."
|
||||||
msgstr "沒有資料。"
|
msgstr "沒有資料。"
|
||||||
|
|
||||||
@ -152,6 +209,7 @@ msgstr "%(base)s下的科目"
|
|||||||
|
|
||||||
#: src/accounting/templates/accounting/account/include/form.html:75
|
#: src/accounting/templates/accounting/account/include/form.html:75
|
||||||
#: src/accounting/templates/accounting/account/order.html:62
|
#: src/accounting/templates/accounting/account/order.html:62
|
||||||
|
#: src/accounting/templates/accounting/currency/include/form.html:57
|
||||||
msgid "Save"
|
msgid "Save"
|
||||||
msgstr "儲存"
|
msgstr "儲存"
|
||||||
|
|
||||||
@ -184,6 +242,35 @@ msgstr "清除"
|
|||||||
msgid "Base Account Managements"
|
msgid "Base Account Managements"
|
||||||
msgstr "基本科目管理"
|
msgstr "基本科目管理"
|
||||||
|
|
||||||
|
#: src/accounting/templates/accounting/currency/create.html:24
|
||||||
|
msgid "Add a New Currency"
|
||||||
|
msgstr "新增貨幣"
|
||||||
|
|
||||||
|
#: src/accounting/templates/accounting/currency/detail.html:65
|
||||||
|
msgid "Delete Currency Confirmation"
|
||||||
|
msgstr "貨幣刪除確認"
|
||||||
|
|
||||||
|
#: src/accounting/templates/accounting/currency/detail.html:69
|
||||||
|
msgid "Do you really want to delete this currency?"
|
||||||
|
msgstr "你確定要刪掉這個貨幣嗎?"
|
||||||
|
|
||||||
|
#: src/accounting/templates/accounting/currency/edit.html:24
|
||||||
|
#, python-format
|
||||||
|
msgid "%(currency)s Settings"
|
||||||
|
msgstr "%(currency)s設定"
|
||||||
|
|
||||||
|
#: src/accounting/templates/accounting/currency/list.html:24
|
||||||
|
msgid "Currency Management"
|
||||||
|
msgstr "貨幣管理"
|
||||||
|
|
||||||
|
#: src/accounting/templates/accounting/currency/include/form.html:44
|
||||||
|
msgid "Code"
|
||||||
|
msgstr "代碼"
|
||||||
|
|
||||||
|
#: src/accounting/templates/accounting/currency/include/form.html:50
|
||||||
|
msgid "Name"
|
||||||
|
msgstr "名稱"
|
||||||
|
|
||||||
#: src/accounting/templates/accounting/include/nav.html:26
|
#: src/accounting/templates/accounting/include/nav.html:26
|
||||||
msgid "Accounting"
|
msgid "Accounting"
|
||||||
msgstr "記帳"
|
msgstr "記帳"
|
||||||
@ -196,6 +283,10 @@ msgstr "科目"
|
|||||||
msgid "Base Accounts"
|
msgid "Base Accounts"
|
||||||
msgstr "基本科目"
|
msgstr "基本科目"
|
||||||
|
|
||||||
|
#: src/accounting/templates/accounting/include/nav.html:44
|
||||||
|
msgid "Currencies"
|
||||||
|
msgstr "貨幣"
|
||||||
|
|
||||||
#: src/accounting/utils/pagination.py:206
|
#: src/accounting/utils/pagination.py:206
|
||||||
msgctxt "Pagination|"
|
msgctxt "Pagination|"
|
||||||
msgid "Previous"
|
msgid "Previous"
|
||||||
|
@ -22,7 +22,7 @@ This module should not import any other module from the application.
|
|||||||
from urllib.parse import urlparse, parse_qsl, ParseResult, urlencode, \
|
from urllib.parse import urlparse, parse_qsl, ParseResult, urlencode, \
|
||||||
urlunparse
|
urlunparse
|
||||||
|
|
||||||
from flask import request
|
from flask import request, Blueprint
|
||||||
|
|
||||||
|
|
||||||
def append_next(uri: str) -> str:
|
def append_next(uri: str) -> str:
|
||||||
@ -73,3 +73,14 @@ def __set_next(uri: str, next_uri: str) -> str:
|
|||||||
parts: list[str] = list(uri_p)
|
parts: list[str] = list(uri_p)
|
||||||
parts[4] = urlencode(params)
|
parts[4] = urlencode(params)
|
||||||
return urlunparse(parts)
|
return urlunparse(parts)
|
||||||
|
|
||||||
|
|
||||||
|
def init_app(bp: Blueprint) -> None:
|
||||||
|
"""Initializes the application.
|
||||||
|
|
||||||
|
:param bp: The blueprint of the accounting application.
|
||||||
|
:return: None.
|
||||||
|
"""
|
||||||
|
bp.add_app_template_filter(append_next, "accounting_append_next")
|
||||||
|
bp.add_app_template_filter(inherit_next, "accounting_inherit_next")
|
||||||
|
bp.add_app_template_filter(or_next, "accounting_or_next")
|
||||||
|
@ -21,7 +21,9 @@ This module should not import any other module from the application.
|
|||||||
"""
|
"""
|
||||||
import typing as t
|
import typing as t
|
||||||
|
|
||||||
from flask import Flask, abort
|
from flask import abort, Blueprint
|
||||||
|
|
||||||
|
from accounting.utils.user import get_current_user
|
||||||
|
|
||||||
|
|
||||||
def has_permission(rule: t.Callable[[], bool]) -> t.Callable:
|
def has_permission(rule: t.Callable[[], bool]) -> t.Callable:
|
||||||
@ -75,17 +77,22 @@ def can_view() -> bool:
|
|||||||
def can_edit() -> bool:
|
def can_edit() -> bool:
|
||||||
"""Returns whether the current user can edit the account data.
|
"""Returns whether the current user can edit the account data.
|
||||||
|
|
||||||
|
The user has to log in.
|
||||||
|
|
||||||
:return: True if the current user can edit the accounting data, or False
|
:return: True if the current user can edit the accounting data, or False
|
||||||
otherwise.
|
otherwise.
|
||||||
"""
|
"""
|
||||||
|
if get_current_user() is None:
|
||||||
|
return False
|
||||||
return __can_edit_func()
|
return __can_edit_func()
|
||||||
|
|
||||||
|
|
||||||
def init_app(app: Flask, can_view_func: t.Callable[[], bool] | None = None,
|
def init_app(bp: Blueprint,
|
||||||
|
can_view_func: t.Callable[[], bool] | None = None,
|
||||||
can_edit_func: t.Callable[[], bool] | None = None) -> None:
|
can_edit_func: t.Callable[[], bool] | None = None) -> None:
|
||||||
"""Initializes the application.
|
"""Initializes the application.
|
||||||
|
|
||||||
:param app: The Flask application.
|
:param bp: The blueprint of the accounting application.
|
||||||
:param can_view_func: A callback that returns whether the current user can
|
:param can_view_func: A callback that returns whether the current user can
|
||||||
view the accounting data.
|
view the accounting data.
|
||||||
:param can_edit_func: A callback that returns whether the current user can
|
:param can_edit_func: A callback that returns whether the current user can
|
||||||
@ -97,5 +104,5 @@ def init_app(app: Flask, can_view_func: t.Callable[[], bool] | None = None,
|
|||||||
__can_view_func = can_view_func
|
__can_view_func = can_view_func
|
||||||
if can_edit_func is not None:
|
if can_edit_func is not None:
|
||||||
__can_edit_func = can_edit_func
|
__can_edit_func = can_edit_func
|
||||||
app.jinja_env.globals["can_view_accounting"] = __can_view_func
|
bp.add_app_template_global(can_view, "accounting_can_view")
|
||||||
app.jinja_env.globals["can_edit_accounting"] = __can_edit_func
|
bp.add_app_template_global(can_edit, "accounting_can_edit")
|
||||||
|
@ -23,6 +23,7 @@ import typing as t
|
|||||||
from abc import ABC, abstractmethod
|
from abc import ABC, abstractmethod
|
||||||
|
|
||||||
import sqlalchemy as sa
|
import sqlalchemy as sa
|
||||||
|
from flask import g
|
||||||
from flask_sqlalchemy.model import Model
|
from flask_sqlalchemy.model import Model
|
||||||
|
|
||||||
T = t.TypeVar("T", bound=Model)
|
T = t.TypeVar("T", bound=Model)
|
||||||
@ -49,10 +50,11 @@ class AbstractUserUtils(t.Generic[T], ABC):
|
|||||||
|
|
||||||
@property
|
@property
|
||||||
@abstractmethod
|
@abstractmethod
|
||||||
def current_user(self) -> T:
|
def current_user(self) -> T | None:
|
||||||
"""Returns the current user.
|
"""Returns the currently logged-in user.
|
||||||
|
|
||||||
:return: The current user.
|
:return: The currently logged-in user, or None if the user has not
|
||||||
|
logged in
|
||||||
"""
|
"""
|
||||||
|
|
||||||
@abstractmethod
|
@abstractmethod
|
||||||
@ -72,9 +74,9 @@ class AbstractUserUtils(t.Generic[T], ABC):
|
|||||||
|
|
||||||
__user_utils: AbstractUserUtils
|
__user_utils: AbstractUserUtils
|
||||||
"""The user utilities."""
|
"""The user utilities."""
|
||||||
user_cls: t.Type[Model]
|
user_cls: t.Type[Model] = Model
|
||||||
"""The user class."""
|
"""The user class."""
|
||||||
user_pk_column: sa.Column
|
user_pk_column: sa.Column = sa.Column(sa.Integer)
|
||||||
"""The primary key column of the user class."""
|
"""The primary key column of the user class."""
|
||||||
|
|
||||||
|
|
||||||
@ -95,7 +97,7 @@ def get_current_user_pk() -> int:
|
|||||||
|
|
||||||
:return: The primary key value of the currently logged-in user.
|
:return: The primary key value of the currently logged-in user.
|
||||||
"""
|
"""
|
||||||
return __user_utils.get_pk(__user_utils.current_user)
|
return __user_utils.get_pk(get_current_user())
|
||||||
|
|
||||||
|
|
||||||
def has_user(username: str) -> bool:
|
def has_user(username: str) -> bool:
|
||||||
@ -114,3 +116,14 @@ def get_user_pk(username: str) -> int:
|
|||||||
:return: The primary key value of the user by the username.
|
:return: The primary key value of the user by the username.
|
||||||
"""
|
"""
|
||||||
return __user_utils.get_pk(__user_utils.get_by_username(username))
|
return __user_utils.get_pk(__user_utils.get_by_username(username))
|
||||||
|
|
||||||
|
|
||||||
|
def get_current_user() -> user_cls | None:
|
||||||
|
"""Returns the currently logged-in user. The result is cached in the
|
||||||
|
current request.
|
||||||
|
|
||||||
|
:return: The currently logged-in user.
|
||||||
|
"""
|
||||||
|
if not hasattr(g, "_accounting_user"):
|
||||||
|
setattr(g, "_accounting_user", __user_utils.current_user)
|
||||||
|
return getattr(g, "_accounting_user")
|
||||||
|
@ -80,7 +80,7 @@ def create_app(is_testing: bool = False) -> Flask:
|
|||||||
return auth.User.id
|
return auth.User.id
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def current_user(self) -> auth.User:
|
def current_user(self) -> auth.User | None:
|
||||||
return auth.current_user()
|
return auth.current_user()
|
||||||
|
|
||||||
def get_by_username(self, username: str) -> auth.User | None:
|
def get_by_username(self, username: str) -> auth.User | None:
|
||||||
|
Reference in New Issue
Block a user