Compare commits
	
		
			51 Commits
		
	
	
		
			v0.9.1
			...
			5ae1ab95ae
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
| 5ae1ab95ae | |||
| 7a5b3b78fc | |||
| 7df4051452 | |||
| 85084c68fd | |||
| 0185c16654 | |||
| 7dd007f3cf | |||
| 38b8a028d5 | |||
| 213981a8b2 | |||
| a4d1789b58 | |||
| 91620d7db2 | |||
| 02fcabb0ce | |||
| 4c2dcc5070 | |||
| c9166fda4d | |||
| 3a0f0873e2 | |||
| a17395b43e | |||
| 17c8d9d1a9 | |||
| fa94cd407e | |||
| 9a704c8185 | |||
| 8286c0c6d8 | |||
| f7efacad75 | |||
| 9263ae0274 | |||
| 78a9d7794c | |||
| f3ae37a409 | |||
| ddc1081252 | |||
| 202d51a032 | |||
| 562bc47be7 | |||
| f3d43a66cc | |||
| c3fc6d9a87 | |||
| e1a0380628 | |||
| f2a2fcdd32 | |||
| ab29166f1e | |||
| 8033921181 | |||
| 08732c1e66 | |||
| 4adc464d3d | |||
| 2f9d2e36cb | |||
| 5bb10bf6ba | |||
| 06e7b6ddff | |||
| 20e1982984 | |||
| a70720be50 | |||
| cb6de08152 | |||
| 211821b4d7 | |||
| 0faca49540 | |||
| 14e79df571 | |||
| 04fbb725d2 | |||
| a1d6844e52 | |||
| 94391b02a6 | |||
| 1cb8a7563e | |||
| 63f0f28948 | |||
| 3431922f12 | |||
| d5a9e1af18 | |||
| 73f5d63f44 | 
							
								
								
									
										2
									
								
								.gitignore
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										2
									
								
								.gitignore
									
									
									
									
										vendored
									
									
								
							@@ -1,4 +1,4 @@
 | 
			
		||||
# The Mia! Accounting Flask Project.
 | 
			
		||||
# The Mia! Accounting Project.
 | 
			
		||||
# Author: imacat@mail.imacat.idv.tw (imacat), 2023/1/27
 | 
			
		||||
 | 
			
		||||
#  Copyright (c) 2022 imacat.
 | 
			
		||||
 
 | 
			
		||||
@@ -1,4 +1,4 @@
 | 
			
		||||
# The Mia! Flask Project.
 | 
			
		||||
# The Mia! Accounting Project.
 | 
			
		||||
# Author: imacat@mail.imacat.idv.tw (imacat), 2022/8/21
 | 
			
		||||
 | 
			
		||||
#  Copyright (c) 2022-2023 imacat.
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										26
									
								
								README.rst
									
									
									
									
									
								
							
							
						
						
									
										26
									
								
								README.rst
									
									
									
									
									
								
							@@ -1,24 +1,33 @@
 | 
			
		||||
=====================
 | 
			
		||||
Mia! Accounting Flask
 | 
			
		||||
=====================
 | 
			
		||||
===============
 | 
			
		||||
Mia! Accounting
 | 
			
		||||
===============
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
Description
 | 
			
		||||
===========
 | 
			
		||||
 | 
			
		||||
This is the Mia! Accounting Flask project.  It is an accounting
 | 
			
		||||
This is the Mia! Accounting project.  It is an accounting
 | 
			
		||||
module for the Flask_ applications.
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
Install
 | 
			
		||||
=======
 | 
			
		||||
 | 
			
		||||
Install the latest source from the
 | 
			
		||||
`Mia! Accounting Flask repository`_.
 | 
			
		||||
Install ``mia-accounting`` with ``pip``.
 | 
			
		||||
 | 
			
		||||
::
 | 
			
		||||
 | 
			
		||||
    pip install git+https://gitea.imacat.idv.tw/imacat/mia-accounting-flask.git
 | 
			
		||||
    pip install mia-accounting
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
Usage
 | 
			
		||||
=====
 | 
			
		||||
 | 
			
		||||
This needs to be done.  Currently, you can refer to the test site
 | 
			
		||||
located in the test directory on the `Mia! Accounting repository`_.
 | 
			
		||||
 | 
			
		||||
The test site is running as the
 | 
			
		||||
`live demonstration for Mia! Accounting`_.
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
Copyright
 | 
			
		||||
@@ -47,4 +56,5 @@ Authors
 | 
			
		||||
| 2023/1/27
 | 
			
		||||
 | 
			
		||||
.. _Flask: https://flask.palletsprojects.com
 | 
			
		||||
.. _Mia! Accounting Flask repository: https://gitea.imacat.idv.tw/imacat/mia-accounting-flask
 | 
			
		||||
.. _Mia! Accounting repository: https://github.com/imacat/mia-accounting
 | 
			
		||||
.. _live demonstration for Mia! Accounting: https://accounting.imacat.idv.tw
 | 
			
		||||
 
 | 
			
		||||
@@ -10,10 +10,10 @@ sys.path.insert(0, os.path.abspath('../../src/'))
 | 
			
		||||
# -- Project information -----------------------------------------------------
 | 
			
		||||
# https://www.sphinx-doc.org/en/master/usage/configuration.html#project-information
 | 
			
		||||
 | 
			
		||||
project = 'Mia! Accounting Flask'
 | 
			
		||||
project = 'Mia! Accounting'
 | 
			
		||||
copyright = '2023, imacat'
 | 
			
		||||
author = 'imacat'
 | 
			
		||||
release = '0.9.1'
 | 
			
		||||
release = '0.11.1'
 | 
			
		||||
 | 
			
		||||
# -- General configuration ---------------------------------------------------
 | 
			
		||||
# https://www.sphinx-doc.org/en/master/usage/configuration.html#general-configuration
 | 
			
		||||
 
 | 
			
		||||
@@ -1,10 +1,10 @@
 | 
			
		||||
.. Mia! Accounting Flask documentation master file, created by
 | 
			
		||||
.. Mia! Accounting documentation master file, created by
 | 
			
		||||
   sphinx-quickstart on Fri Jan 27 12:20:04 2023.
 | 
			
		||||
   You can adapt this file completely to your liking, but it should at least
 | 
			
		||||
   contain the root `toctree` directive.
 | 
			
		||||
 | 
			
		||||
Welcome to Mia! Accounting Flask's documentation!
 | 
			
		||||
=================================================
 | 
			
		||||
Welcome to Mia! Accounting's documentation!
 | 
			
		||||
===========================================
 | 
			
		||||
 | 
			
		||||
.. toctree::
 | 
			
		||||
   :maxdepth: 2
 | 
			
		||||
 
 | 
			
		||||
@@ -1,4 +1,4 @@
 | 
			
		||||
# The Mia! Flask Project.
 | 
			
		||||
# The Mia! Accounting Project.
 | 
			
		||||
# Author: imacat@mail.imacat.idv.tw (imacat), 2022/8/21
 | 
			
		||||
 | 
			
		||||
#  Copyright (c) 2022 imacat.
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										12
									
								
								setup.cfg
									
									
									
									
									
								
							
							
						
						
									
										12
									
								
								setup.cfg
									
									
									
									
									
								
							@@ -1,4 +1,4 @@
 | 
			
		||||
# The Mia! Flask Project.
 | 
			
		||||
# The Mia! Accounting Project.
 | 
			
		||||
# Author: imacat@mail.imacat.idv.tw (imacat), 2022/8/21
 | 
			
		||||
 | 
			
		||||
#  Copyright (c) 2022-2023 imacat.
 | 
			
		||||
@@ -16,16 +16,16 @@
 | 
			
		||||
#  limitations under the License.
 | 
			
		||||
 | 
			
		||||
[metadata]
 | 
			
		||||
name = mia-accounting-flask
 | 
			
		||||
version = 0.9.1
 | 
			
		||||
name = mia-accounting
 | 
			
		||||
version = 0.11.1
 | 
			
		||||
author = imacat
 | 
			
		||||
author_email = imacat@mail.imacat.idv.tw
 | 
			
		||||
description = The Mia! Accounting Flask project.
 | 
			
		||||
description = The Mia! Accounting project.
 | 
			
		||||
long_description = file: README.rst
 | 
			
		||||
long_description_content_type = text/x-rst
 | 
			
		||||
url = https://github.com/imacat/mia-accounting-flask
 | 
			
		||||
url = https://github.com/imacat/mia-accounting
 | 
			
		||||
project_urls =
 | 
			
		||||
    Bug Tracker = https://github.com/imacat/mia-accounting-flask/issues
 | 
			
		||||
    Bug Tracker = https://github.com/imacat/mia-accounting/issues
 | 
			
		||||
classifiers =
 | 
			
		||||
    Programming Language :: Python :: 3
 | 
			
		||||
    License :: OSI Approved :: Apache Software License
 | 
			
		||||
 
 | 
			
		||||
@@ -1,4 +1,4 @@
 | 
			
		||||
# The Mia! Accounting Flask Project.
 | 
			
		||||
# The Mia! Accounting Project.
 | 
			
		||||
# Author: imacat@mail.imacat.idv.tw (imacat), 2023/1/25
 | 
			
		||||
 | 
			
		||||
#  Copyright (c) 2023 imacat.
 | 
			
		||||
@@ -47,7 +47,6 @@ def init_app(app: Flask, user_utils: UserUtilityInterface,
 | 
			
		||||
    init_user_utils(user_utils)
 | 
			
		||||
 | 
			
		||||
    bp: Blueprint = Blueprint("accounting", __name__,
 | 
			
		||||
                              url_prefix=url_prefix,
 | 
			
		||||
                              template_folder="templates",
 | 
			
		||||
                              static_folder="static")
 | 
			
		||||
 | 
			
		||||
@@ -84,9 +83,9 @@ def init_app(app: Flask, user_utils: UserUtilityInterface,
 | 
			
		||||
    journal_entry.init_app(app, bp)
 | 
			
		||||
 | 
			
		||||
    from . import report
 | 
			
		||||
    report.init_app(app, bp)
 | 
			
		||||
    report.init_app(app, url_prefix)
 | 
			
		||||
 | 
			
		||||
    from . import option
 | 
			
		||||
    option.init_app(bp)
 | 
			
		||||
 | 
			
		||||
    app.register_blueprint(bp)
 | 
			
		||||
    app.register_blueprint(bp, url_prefix=url_prefix)
 | 
			
		||||
 
 | 
			
		||||
@@ -1,4 +1,4 @@
 | 
			
		||||
# The Mia! Accounting Flask Project.
 | 
			
		||||
# The Mia! Accounting Project.
 | 
			
		||||
# Author: imacat@mail.imacat.idv.tw (imacat), 2023/1/30
 | 
			
		||||
 | 
			
		||||
#  Copyright (c) 2023 imacat.
 | 
			
		||||
 
 | 
			
		||||
@@ -1,4 +1,4 @@
 | 
			
		||||
# The Mia! Accounting Flask Project.
 | 
			
		||||
# The Mia! Accounting Project.
 | 
			
		||||
# Author: imacat@mail.imacat.idv.tw (imacat), 2023/1/30
 | 
			
		||||
 | 
			
		||||
#  Copyright (c) 2023 imacat.
 | 
			
		||||
 
 | 
			
		||||
@@ -1,4 +1,4 @@
 | 
			
		||||
# The Mia! Accounting Flask Project.
 | 
			
		||||
# The Mia! Accounting Project.
 | 
			
		||||
# Author: imacat@mail.imacat.idv.tw (imacat), 2023/1/31
 | 
			
		||||
 | 
			
		||||
#  Copyright (c) 2023 imacat.
 | 
			
		||||
 
 | 
			
		||||
@@ -1,4 +1,4 @@
 | 
			
		||||
# The Mia! Accounting Flask Project.
 | 
			
		||||
# The Mia! Accounting Project.
 | 
			
		||||
# Author: imacat@mail.imacat.idv.tw (imacat), 2023/2/1
 | 
			
		||||
 | 
			
		||||
#  Copyright (c) 2023 imacat.
 | 
			
		||||
 
 | 
			
		||||
@@ -1,4 +1,4 @@
 | 
			
		||||
# The Mia! Accounting Flask Project.
 | 
			
		||||
# The Mia! Accounting Project.
 | 
			
		||||
# Author: imacat@mail.imacat.idv.tw (imacat), 2023/1/30
 | 
			
		||||
 | 
			
		||||
#  Copyright (c) 2023 imacat.
 | 
			
		||||
 
 | 
			
		||||
@@ -1,4 +1,4 @@
 | 
			
		||||
# The Mia! Accounting Flask Project.
 | 
			
		||||
# The Mia! Accounting Project.
 | 
			
		||||
# Author: imacat@mail.imacat.idv.tw (imacat), 2023/1/30
 | 
			
		||||
 | 
			
		||||
#  Copyright (c) 2023 imacat.
 | 
			
		||||
 
 | 
			
		||||
@@ -1,4 +1,4 @@
 | 
			
		||||
# The Mia! Accounting Flask Project.
 | 
			
		||||
# The Mia! Accounting Project.
 | 
			
		||||
# Author: imacat@mail.imacat.idv.tw (imacat), 2023/1/25
 | 
			
		||||
 | 
			
		||||
#  Copyright (c) 2023 imacat.
 | 
			
		||||
 
 | 
			
		||||
@@ -1,4 +1,4 @@
 | 
			
		||||
# The Mia! Accounting Flask Project.
 | 
			
		||||
# The Mia! Accounting Project.
 | 
			
		||||
# Author: imacat@mail.imacat.idv.tw (imacat), 2023/1/25
 | 
			
		||||
 | 
			
		||||
#  Copyright (c) 2023 imacat.
 | 
			
		||||
 
 | 
			
		||||
@@ -1,4 +1,4 @@
 | 
			
		||||
# The Mia! Accounting Flask Project.
 | 
			
		||||
# The Mia! Accounting Project.
 | 
			
		||||
# Author: imacat@mail.imacat.idv.tw (imacat), 2023/2/1
 | 
			
		||||
 | 
			
		||||
#  Copyright (c) 2023 imacat.
 | 
			
		||||
 
 | 
			
		||||
@@ -1,4 +1,4 @@
 | 
			
		||||
# The Mia! Accounting Flask Project.
 | 
			
		||||
# The Mia! Accounting Project.
 | 
			
		||||
# Author: imacat@mail.imacat.idv.tw (imacat), 2023/1/26
 | 
			
		||||
 | 
			
		||||
#  Copyright (c) 2023 imacat.
 | 
			
		||||
 
 | 
			
		||||
@@ -1,4 +1,4 @@
 | 
			
		||||
# The Mia! Accounting Flask Project.
 | 
			
		||||
# The Mia! Accounting Project.
 | 
			
		||||
# Author: imacat@mail.imacat.idv.tw (imacat), 2023/1/26
 | 
			
		||||
 | 
			
		||||
#  Copyright (c) 2023 imacat.
 | 
			
		||||
 
 | 
			
		||||
@@ -1,4 +1,4 @@
 | 
			
		||||
# The Mia! Accounting Flask Project.
 | 
			
		||||
# The Mia! Accounting Project.
 | 
			
		||||
# Author: imacat@mail.imacat.idv.tw (imacat), 2023/2/6
 | 
			
		||||
 | 
			
		||||
#  Copyright (c) 2023 imacat.
 | 
			
		||||
 
 | 
			
		||||
@@ -1,4 +1,4 @@
 | 
			
		||||
# The Mia! Accounting Flask Project.
 | 
			
		||||
# The Mia! Accounting Project.
 | 
			
		||||
# Author: imacat@mail.imacat.idv.tw (imacat), 2023/2/6
 | 
			
		||||
 | 
			
		||||
#  Copyright (c) 2023 imacat.
 | 
			
		||||
 
 | 
			
		||||
@@ -1,4 +1,4 @@
 | 
			
		||||
# The Mia! Accounting Flask Project.
 | 
			
		||||
# The Mia! Accounting Project.
 | 
			
		||||
# Author: imacat@mail.imacat.idv.tw (imacat), 2023/2/6
 | 
			
		||||
 | 
			
		||||
#  Copyright (c) 2023 imacat.
 | 
			
		||||
 
 | 
			
		||||
@@ -1,4 +1,4 @@
 | 
			
		||||
# The Mia! Accounting Flask Project.
 | 
			
		||||
# The Mia! Accounting Project.
 | 
			
		||||
# Author: imacat@mail.imacat.idv.tw (imacat), 2023/2/6
 | 
			
		||||
 | 
			
		||||
#  Copyright (c) 2023 imacat.
 | 
			
		||||
 
 | 
			
		||||
@@ -1,4 +1,4 @@
 | 
			
		||||
# The Mia! Accounting Flask Project.
 | 
			
		||||
# The Mia! Accounting Project.
 | 
			
		||||
# Author: imacat@mail.imacat.idv.tw (imacat), 2023/2/6
 | 
			
		||||
 | 
			
		||||
#  Copyright (c) 2023 imacat.
 | 
			
		||||
 
 | 
			
		||||
@@ -1,4 +1,4 @@
 | 
			
		||||
# The Mia! Accounting Flask Project.
 | 
			
		||||
# The Mia! Accounting Project.
 | 
			
		||||
# Author: imacat@mail.imacat.idv.tw (imacat), 2023/2/6
 | 
			
		||||
 | 
			
		||||
#  Copyright (c) 2023 imacat.
 | 
			
		||||
 
 | 
			
		||||
@@ -1,4 +1,4 @@
 | 
			
		||||
# The Mia! Accounting Flask Project.
 | 
			
		||||
# The Mia! Accounting Project.
 | 
			
		||||
# Author: imacat@mail.imacat.idv.tw (imacat), 2023/3/22
 | 
			
		||||
 | 
			
		||||
#  Copyright (c) 2023 imacat.
 | 
			
		||||
 
 | 
			
		||||
@@ -1,4 +1,4 @@
 | 
			
		||||
# The Mia! Accounting Flask Project.
 | 
			
		||||
# The Mia! Accounting Project.
 | 
			
		||||
# Author: imacat@mail.imacat.idv.tw (imacat), 2023/2/18
 | 
			
		||||
 | 
			
		||||
#  Copyright (c) 2023 imacat.
 | 
			
		||||
 
 | 
			
		||||
@@ -1,4 +1,4 @@
 | 
			
		||||
# The Mia! Accounting Flask Project.
 | 
			
		||||
# The Mia! Accounting Project.
 | 
			
		||||
# Author: imacat@mail.imacat.idv.tw (imacat), 2023/2/19
 | 
			
		||||
 | 
			
		||||
#  Copyright (c) 2023 imacat.
 | 
			
		||||
 
 | 
			
		||||
@@ -1,4 +1,4 @@
 | 
			
		||||
# The Mia! Accounting Flask Project.
 | 
			
		||||
# The Mia! Accounting Project.
 | 
			
		||||
# Author: imacat@mail.imacat.idv.tw (imacat), 2023/3/10
 | 
			
		||||
 | 
			
		||||
#  Copyright (c) 2023 imacat.
 | 
			
		||||
 
 | 
			
		||||
@@ -1,4 +1,4 @@
 | 
			
		||||
# The Mia! Accounting Flask Project.
 | 
			
		||||
# The Mia! Accounting Project.
 | 
			
		||||
# Author: imacat@mail.imacat.idv.tw (imacat), 2023/3/10
 | 
			
		||||
 | 
			
		||||
#  Copyright (c) 2023 imacat.
 | 
			
		||||
 
 | 
			
		||||
@@ -1,4 +1,4 @@
 | 
			
		||||
# The Mia! Accounting Flask Project.
 | 
			
		||||
# The Mia! Accounting Project.
 | 
			
		||||
# Author: imacat@mail.imacat.idv.tw (imacat), 2023/2/18
 | 
			
		||||
 | 
			
		||||
#  Copyright (c) 2023 imacat.
 | 
			
		||||
 
 | 
			
		||||
@@ -1,4 +1,4 @@
 | 
			
		||||
# The Mia! Accounting Flask Project.
 | 
			
		||||
# The Mia! Accounting Project.
 | 
			
		||||
# Author: imacat@mail.imacat.idv.tw (imacat), 2023/3/10
 | 
			
		||||
 | 
			
		||||
#  Copyright (c) 2023 imacat.
 | 
			
		||||
 
 | 
			
		||||
@@ -1,4 +1,4 @@
 | 
			
		||||
# The Mia! Accounting Flask Project.
 | 
			
		||||
# The Mia! Accounting Project.
 | 
			
		||||
# Author: imacat@mail.imacat.idv.tw (imacat), 2023/3/10
 | 
			
		||||
 | 
			
		||||
#  Copyright (c) 2023 imacat.
 | 
			
		||||
 
 | 
			
		||||
@@ -1,4 +1,4 @@
 | 
			
		||||
# The Mia! Accounting Flask Project.
 | 
			
		||||
# The Mia! Accounting Project.
 | 
			
		||||
# Author: imacat@mail.imacat.idv.tw (imacat), 2023/2/25
 | 
			
		||||
 | 
			
		||||
#  Copyright (c) 2023 imacat.
 | 
			
		||||
 
 | 
			
		||||
@@ -1,4 +1,4 @@
 | 
			
		||||
# The Mia! Accounting Flask Project.
 | 
			
		||||
# The Mia! Accounting Project.
 | 
			
		||||
# Author: imacat@mail.imacat.idv.tw (imacat), 2023/3/10
 | 
			
		||||
 | 
			
		||||
#  Copyright (c) 2023 imacat.
 | 
			
		||||
 
 | 
			
		||||
@@ -1,4 +1,4 @@
 | 
			
		||||
# The Mia! Accounting Flask Project.
 | 
			
		||||
# The Mia! Accounting Project.
 | 
			
		||||
# Author: imacat@mail.imacat.idv.tw (imacat), 2023/3/10
 | 
			
		||||
 | 
			
		||||
#  Copyright (c) 2023 imacat.
 | 
			
		||||
 
 | 
			
		||||
@@ -1,4 +1,4 @@
 | 
			
		||||
# The Mia! Accounting Flask Project.
 | 
			
		||||
# The Mia! Accounting Project.
 | 
			
		||||
# Author: imacat@mail.imacat.idv.tw (imacat), 2023/2/27
 | 
			
		||||
 | 
			
		||||
#  Copyright (c) 2023 imacat.
 | 
			
		||||
@@ -36,12 +36,14 @@ class DescriptionAccount:
 | 
			
		||||
        :param account: The account.
 | 
			
		||||
        :param freq: The frequency of the tag with the account.
 | 
			
		||||
        """
 | 
			
		||||
        self.account: Account = account
 | 
			
		||||
        self.__account: Account = account
 | 
			
		||||
        """The account."""
 | 
			
		||||
        self.id: int = account.id
 | 
			
		||||
        """The account ID."""
 | 
			
		||||
        self.code: str = account.code
 | 
			
		||||
        """The account code."""
 | 
			
		||||
        self.is_need_offset: bool = account.is_need_offset
 | 
			
		||||
        """Whether the journal entry line items of this account need offset."""
 | 
			
		||||
        self.freq: int = freq
 | 
			
		||||
        """The frequency of the tag with the account."""
 | 
			
		||||
 | 
			
		||||
@@ -50,7 +52,7 @@ class DescriptionAccount:
 | 
			
		||||
 | 
			
		||||
        :return: The string representation of the account.
 | 
			
		||||
        """
 | 
			
		||||
        return str(self.account)
 | 
			
		||||
        return str(self.__account)
 | 
			
		||||
 | 
			
		||||
    def add_freq(self, freq: int) -> None:
 | 
			
		||||
        """Adds the frequency of an account.
 | 
			
		||||
 
 | 
			
		||||
@@ -1,4 +1,4 @@
 | 
			
		||||
# The Mia! Accounting Flask Project.
 | 
			
		||||
# The Mia! Accounting Project.
 | 
			
		||||
# Author: imacat@mail.imacat.idv.tw (imacat), 2023/3/15
 | 
			
		||||
 | 
			
		||||
#  Copyright (c) 2023 imacat.
 | 
			
		||||
 
 | 
			
		||||
@@ -1,4 +1,4 @@
 | 
			
		||||
# The Mia! Accounting Flask Project.
 | 
			
		||||
# The Mia! Accounting Project.
 | 
			
		||||
# Author: imacat@mail.imacat.idv.tw (imacat), 2023/2/19
 | 
			
		||||
 | 
			
		||||
#  Copyright (c) 2023 imacat.
 | 
			
		||||
 
 | 
			
		||||
@@ -1,4 +1,4 @@
 | 
			
		||||
# The Mia! Accounting Flask Project.
 | 
			
		||||
# The Mia! Accounting Project.
 | 
			
		||||
# Author: imacat@mail.imacat.idv.tw (imacat), 2023/3/10
 | 
			
		||||
 | 
			
		||||
#  Copyright (c) 2023 imacat.
 | 
			
		||||
@@ -72,11 +72,12 @@ def get_selectable_original_line_items(
 | 
			
		||||
    line_items: list[JournalEntryLineItem] = JournalEntryLineItem.query\
 | 
			
		||||
        .filter(JournalEntryLineItem.id.in_({x for x in net_balances}))\
 | 
			
		||||
        .join(JournalEntry)\
 | 
			
		||||
        .order_by(JournalEntry.date, JournalEntryLineItem.is_debit,
 | 
			
		||||
                  JournalEntryLineItem.no)\
 | 
			
		||||
        .order_by(JournalEntry.date, JournalEntry.no,
 | 
			
		||||
                  JournalEntryLineItem.is_debit, JournalEntryLineItem.no)\
 | 
			
		||||
        .options(selectinload(JournalEntryLineItem.currency),
 | 
			
		||||
                 selectinload(JournalEntryLineItem.account),
 | 
			
		||||
                 selectinload(JournalEntryLineItem.journal_entry)).all()
 | 
			
		||||
    line_items.reverse()
 | 
			
		||||
    for line_item in line_items:
 | 
			
		||||
        line_item.net_balance = line_item.amount \
 | 
			
		||||
            if net_balances[line_item.id] is None \
 | 
			
		||||
 
 | 
			
		||||
@@ -1,4 +1,4 @@
 | 
			
		||||
# The Mia! Accounting Flask Project.
 | 
			
		||||
# The Mia! Accounting Project.
 | 
			
		||||
# Author: imacat@mail.imacat.idv.tw (imacat), 2023/2/18
 | 
			
		||||
 | 
			
		||||
#  Copyright (c) 2023 imacat.
 | 
			
		||||
@@ -235,4 +235,4 @@ def __get_default_page_uri() -> str:
 | 
			
		||||
 | 
			
		||||
    :return: The URI for the default page.
 | 
			
		||||
    """
 | 
			
		||||
    return url_for("accounting.report.default")
 | 
			
		||||
    return url_for("accounting-report.default")
 | 
			
		||||
 
 | 
			
		||||
@@ -1,4 +1,4 @@
 | 
			
		||||
# The Mia! Accounting Flask Project.
 | 
			
		||||
# The Mia! Accounting Project.
 | 
			
		||||
# Author: imacat@mail.imacat.idv.tw (imacat), 2023/1/25
 | 
			
		||||
 | 
			
		||||
#  Copyright (c) 2023 imacat.
 | 
			
		||||
 
 | 
			
		||||
@@ -1,4 +1,4 @@
 | 
			
		||||
# The Mia! Accounting Flask Project.
 | 
			
		||||
# The Mia! Accounting Project.
 | 
			
		||||
# Author: imacat@mail.imacat.idv.tw (imacat), 2023/1/25
 | 
			
		||||
 | 
			
		||||
#  Copyright (c) 2023 imacat.
 | 
			
		||||
@@ -21,7 +21,6 @@ from __future__ import annotations
 | 
			
		||||
 | 
			
		||||
import re
 | 
			
		||||
import typing as t
 | 
			
		||||
from datetime import date
 | 
			
		||||
from decimal import Decimal
 | 
			
		||||
 | 
			
		||||
import sqlalchemy as sa
 | 
			
		||||
 
 | 
			
		||||
@@ -1,4 +1,4 @@
 | 
			
		||||
# The Mia! Accounting Flask Project.
 | 
			
		||||
# The Mia! Accounting Project.
 | 
			
		||||
# Author: imacat@mail.imacat.idv.tw (imacat), 2023/3/22
 | 
			
		||||
 | 
			
		||||
#  Copyright (c) 2023 imacat.
 | 
			
		||||
 
 | 
			
		||||
@@ -1,4 +1,4 @@
 | 
			
		||||
# The Mia! Accounting Flask Project.
 | 
			
		||||
# The Mia! Accounting Project.
 | 
			
		||||
# Author: imacat@mail.imacat.idv.tw (imacat), 2023/3/22
 | 
			
		||||
 | 
			
		||||
#  Copyright (c) 2023 imacat.
 | 
			
		||||
 
 | 
			
		||||
@@ -1,4 +1,4 @@
 | 
			
		||||
# The Mia! Accounting Flask Project.
 | 
			
		||||
# The Mia! Accounting Project.
 | 
			
		||||
# Author: imacat@mail.imacat.idv.tw (imacat), 2023/3/22
 | 
			
		||||
 | 
			
		||||
#  Copyright (c) 2023 imacat.
 | 
			
		||||
 
 | 
			
		||||
@@ -1,4 +1,4 @@
 | 
			
		||||
# The Mia! Accounting Flask Project.
 | 
			
		||||
# The Mia! Accounting Project.
 | 
			
		||||
# Author: imacat@mail.imacat.idv.tw (imacat), 2023/3/3
 | 
			
		||||
 | 
			
		||||
#  Copyright (c) 2023 imacat.
 | 
			
		||||
@@ -17,14 +17,14 @@
 | 
			
		||||
"""The report management.
 | 
			
		||||
 | 
			
		||||
"""
 | 
			
		||||
from flask import Flask, Blueprint
 | 
			
		||||
from flask import Flask
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def init_app(app: Flask, bp: Blueprint) -> None:
 | 
			
		||||
def init_app(app: Flask, url_prefix: str) -> None:
 | 
			
		||||
    """Initialize the application.
 | 
			
		||||
 | 
			
		||||
    :param app: The Flask application.
 | 
			
		||||
    :param bp: The blueprint of the accounting application.
 | 
			
		||||
    :param url_prefix: The URL prefix of the accounting application.
 | 
			
		||||
    :return: None.
 | 
			
		||||
    """
 | 
			
		||||
    from .converters import PeriodConverter, IncomeExpensesAccountConverter
 | 
			
		||||
@@ -32,4 +32,4 @@ def init_app(app: Flask, bp: Blueprint) -> None:
 | 
			
		||||
    app.url_map.converters["ieAccount"] = IncomeExpensesAccountConverter
 | 
			
		||||
 | 
			
		||||
    from .views import bp as report_bp
 | 
			
		||||
    bp.register_blueprint(report_bp, url_prefix="/reports")
 | 
			
		||||
    app.register_blueprint(report_bp, url_prefix=url_prefix)
 | 
			
		||||
 
 | 
			
		||||
@@ -1,4 +1,4 @@
 | 
			
		||||
# The Mia! Accounting Flask Project.
 | 
			
		||||
# The Mia! Accounting Project.
 | 
			
		||||
# Author: imacat@mail.imacat.idv.tw (imacat), 2023/3/3
 | 
			
		||||
 | 
			
		||||
#  Copyright (c) 2023 imacat.
 | 
			
		||||
 
 | 
			
		||||
@@ -1,4 +1,4 @@
 | 
			
		||||
# The Mia! Accounting Flask Project.
 | 
			
		||||
# The Mia! Accounting Project.
 | 
			
		||||
# Author: imacat@mail.imacat.idv.tw (imacat), 2023/3/9
 | 
			
		||||
 | 
			
		||||
#  Copyright (c) 2023 imacat.
 | 
			
		||||
 
 | 
			
		||||
@@ -1,4 +1,4 @@
 | 
			
		||||
# The Mia! Accounting Flask Project.
 | 
			
		||||
# The Mia! Accounting Project.
 | 
			
		||||
# Author: imacat@mail.imacat.idv.tw (imacat), 2023/3/4
 | 
			
		||||
 | 
			
		||||
#  Copyright (c) 2023 imacat.
 | 
			
		||||
 
 | 
			
		||||
@@ -1,4 +1,4 @@
 | 
			
		||||
# The Mia! Accounting Flask Project.
 | 
			
		||||
# The Mia! Accounting Project.
 | 
			
		||||
# Author: imacat@mail.imacat.idv.tw (imacat), 2023/3/4
 | 
			
		||||
 | 
			
		||||
#  Copyright (c) 2023 imacat.
 | 
			
		||||
 
 | 
			
		||||
@@ -1,4 +1,4 @@
 | 
			
		||||
# The Mia! Accounting Flask Project.
 | 
			
		||||
# The Mia! Accounting Project.
 | 
			
		||||
# Author: imacat@mail.imacat.idv.tw (imacat), 2023/3/4
 | 
			
		||||
 | 
			
		||||
#  Copyright (c) 2023 imacat.
 | 
			
		||||
 
 | 
			
		||||
@@ -1,4 +1,4 @@
 | 
			
		||||
# The Mia! Accounting Flask Project.
 | 
			
		||||
# The Mia! Accounting Project.
 | 
			
		||||
# Author: imacat@mail.imacat.idv.tw (imacat), 2023/3/4
 | 
			
		||||
 | 
			
		||||
#  Copyright (c) 2023 imacat.
 | 
			
		||||
 
 | 
			
		||||
@@ -1,4 +1,4 @@
 | 
			
		||||
# The Mia! Accounting Flask Project.
 | 
			
		||||
# The Mia! Accounting Project.
 | 
			
		||||
# Author: imacat@mail.imacat.idv.tw (imacat), 2023/3/4
 | 
			
		||||
 | 
			
		||||
#  Copyright (c) 2023 imacat.
 | 
			
		||||
 
 | 
			
		||||
@@ -1,4 +1,4 @@
 | 
			
		||||
# The Mia! Accounting Flask Project.
 | 
			
		||||
# The Mia! Accounting Project.
 | 
			
		||||
# Author: imacat@mail.imacat.idv.tw (imacat), 2023/3/4
 | 
			
		||||
 | 
			
		||||
#  Copyright (c) 2023 imacat.
 | 
			
		||||
 
 | 
			
		||||
@@ -1,4 +1,4 @@
 | 
			
		||||
# The Mia! Accounting Flask Project.
 | 
			
		||||
# The Mia! Accounting Project.
 | 
			
		||||
# Author: imacat@mail.imacat.idv.tw (imacat), 2023/3/4
 | 
			
		||||
 | 
			
		||||
#  Copyright (c) 2023 imacat.
 | 
			
		||||
 
 | 
			
		||||
@@ -1,4 +1,4 @@
 | 
			
		||||
# The Mia! Accounting Flask Project.
 | 
			
		||||
# The Mia! Accounting Project.
 | 
			
		||||
# Author: imacat@mail.imacat.idv.tw (imacat), 2023/3/7
 | 
			
		||||
 | 
			
		||||
#  Copyright (c) 2023 imacat.
 | 
			
		||||
 
 | 
			
		||||
@@ -1,4 +1,4 @@
 | 
			
		||||
# The Mia! Accounting Flask Project.
 | 
			
		||||
# The Mia! Accounting Project.
 | 
			
		||||
# Author: imacat@mail.imacat.idv.tw (imacat), 2023/3/7
 | 
			
		||||
 | 
			
		||||
#  Copyright (c) 2023 imacat.
 | 
			
		||||
@@ -137,6 +137,7 @@ class AccountCollector:
 | 
			
		||||
            .join(JournalEntry).join(Account)\
 | 
			
		||||
            .filter(*conditions)\
 | 
			
		||||
            .group_by(Account.id, Account.base_code, Account.no)\
 | 
			
		||||
            .having(balance_func != 0)\
 | 
			
		||||
            .order_by(Account.base_code, Account.no)
 | 
			
		||||
        account_balances: list[sa.Row] \
 | 
			
		||||
            = db.session.execute(select_balance).all()
 | 
			
		||||
 
 | 
			
		||||
@@ -1,4 +1,4 @@
 | 
			
		||||
# The Mia! Accounting Flask Project.
 | 
			
		||||
# The Mia! Accounting Project.
 | 
			
		||||
# Author: imacat@mail.imacat.idv.tw (imacat), 2023/3/7
 | 
			
		||||
 | 
			
		||||
#  Copyright (c) 2023 imacat.
 | 
			
		||||
 
 | 
			
		||||
@@ -1,4 +1,4 @@
 | 
			
		||||
# The Mia! Accounting Flask Project.
 | 
			
		||||
# The Mia! Accounting Project.
 | 
			
		||||
# Author: imacat@mail.imacat.idv.tw (imacat), 2023/3/7
 | 
			
		||||
 | 
			
		||||
#  Copyright (c) 2023 imacat.
 | 
			
		||||
@@ -269,6 +269,7 @@ class IncomeStatement(BaseReport):
 | 
			
		||||
            .join(JournalEntry).join(Account)\
 | 
			
		||||
            .filter(*conditions)\
 | 
			
		||||
            .group_by(Account.id)\
 | 
			
		||||
            .having(balance_func != 0)\
 | 
			
		||||
            .order_by(Account.base_code, Account.no)
 | 
			
		||||
        balances: list[sa.Row] = db.session.execute(select_balances).all()
 | 
			
		||||
        accounts: dict[int, Account] \
 | 
			
		||||
 
 | 
			
		||||
@@ -1,4 +1,4 @@
 | 
			
		||||
# The Mia! Accounting Flask Project.
 | 
			
		||||
# The Mia! Accounting Project.
 | 
			
		||||
# Author: imacat@mail.imacat.idv.tw (imacat), 2023/3/7
 | 
			
		||||
 | 
			
		||||
#  Copyright (c) 2023 imacat.
 | 
			
		||||
 
 | 
			
		||||
@@ -1,4 +1,4 @@
 | 
			
		||||
# The Mia! Accounting Flask Project.
 | 
			
		||||
# The Mia! Accounting Project.
 | 
			
		||||
# Author: imacat@mail.imacat.idv.tw (imacat), 2023/3/7
 | 
			
		||||
 | 
			
		||||
#  Copyright (c) 2023 imacat.
 | 
			
		||||
 
 | 
			
		||||
@@ -1,4 +1,4 @@
 | 
			
		||||
# The Mia! Accounting Flask Project.
 | 
			
		||||
# The Mia! Accounting Project.
 | 
			
		||||
# Author: imacat@mail.imacat.idv.tw (imacat), 2023/3/8
 | 
			
		||||
 | 
			
		||||
#  Copyright (c) 2023 imacat.
 | 
			
		||||
 
 | 
			
		||||
@@ -1,4 +1,4 @@
 | 
			
		||||
# The Mia! Accounting Flask Project.
 | 
			
		||||
# The Mia! Accounting Project.
 | 
			
		||||
# Author: imacat@mail.imacat.idv.tw (imacat), 2023/3/7
 | 
			
		||||
 | 
			
		||||
#  Copyright (c) 2023 imacat.
 | 
			
		||||
@@ -191,6 +191,7 @@ class TrialBalance(BaseReport):
 | 
			
		||||
            .join(JournalEntry).join(Account)\
 | 
			
		||||
            .filter(*conditions)\
 | 
			
		||||
            .group_by(Account.id)\
 | 
			
		||||
            .having(balance_func != 0)\
 | 
			
		||||
            .order_by(Account.base_code, Account.no)
 | 
			
		||||
        balances: list[sa.Row] = db.session.execute(select_balances).all()
 | 
			
		||||
        accounts: dict[int, Account] \
 | 
			
		||||
 
 | 
			
		||||
@@ -1,4 +1,4 @@
 | 
			
		||||
# The Mia! Accounting Flask Project.
 | 
			
		||||
# The Mia! Accounting Project.
 | 
			
		||||
# Author: imacat@mail.imacat.idv.tw (imacat), 2023/3/7
 | 
			
		||||
 | 
			
		||||
#  Copyright (c) 2023 imacat.
 | 
			
		||||
 
 | 
			
		||||
@@ -1,4 +1,4 @@
 | 
			
		||||
# The Mia! Accounting Flask Project.
 | 
			
		||||
# The Mia! Accounting Project.
 | 
			
		||||
# Author: imacat@mail.imacat.idv.tw (imacat), 2023/3/7
 | 
			
		||||
 | 
			
		||||
#  Copyright (c) 2023 imacat.
 | 
			
		||||
 
 | 
			
		||||
@@ -1,4 +1,4 @@
 | 
			
		||||
# The Mia! Accounting Flask Project.
 | 
			
		||||
# The Mia! Accounting Project.
 | 
			
		||||
# Author: imacat@mail.imacat.idv.tw (imacat), 2023/3/6
 | 
			
		||||
 | 
			
		||||
#  Copyright (c) 2023 imacat.
 | 
			
		||||
 
 | 
			
		||||
@@ -1,4 +1,4 @@
 | 
			
		||||
# The Mia! Accounting Flask Project.
 | 
			
		||||
# The Mia! Accounting Project.
 | 
			
		||||
# Author: imacat@mail.imacat.idv.tw (imacat), 2023/3/8
 | 
			
		||||
 | 
			
		||||
#  Copyright (c) 2023 imacat.
 | 
			
		||||
 
 | 
			
		||||
@@ -1,4 +1,4 @@
 | 
			
		||||
# The Mia! Accounting Flask Project.
 | 
			
		||||
# The Mia! Accounting Project.
 | 
			
		||||
# Author: imacat@mail.imacat.idv.tw (imacat), 2023/3/7
 | 
			
		||||
 | 
			
		||||
#  Copyright (c) 2023 imacat.
 | 
			
		||||
 
 | 
			
		||||
@@ -1,4 +1,4 @@
 | 
			
		||||
# The Mia! Accounting Flask Project.
 | 
			
		||||
# The Mia! Accounting Project.
 | 
			
		||||
# Author: imacat@mail.imacat.idv.tw (imacat), 2023/3/5
 | 
			
		||||
 | 
			
		||||
#  Copyright (c) 2023 imacat.
 | 
			
		||||
 
 | 
			
		||||
@@ -1,4 +1,4 @@
 | 
			
		||||
# The Mia! Accounting Flask Project.
 | 
			
		||||
# The Mia! Accounting Project.
 | 
			
		||||
# Author: imacat@mail.imacat.idv.tw (imacat), 2023/3/4
 | 
			
		||||
 | 
			
		||||
#  Copyright (c) 2023 imacat.
 | 
			
		||||
@@ -68,9 +68,9 @@ class ReportChooser:
 | 
			
		||||
        """The title of the current report."""
 | 
			
		||||
        self.is_search: bool = active_report == ReportType.SEARCH
 | 
			
		||||
        """Whether the current report is the search page."""
 | 
			
		||||
        self.__reports.append(self.__journal)
 | 
			
		||||
        self.__reports.append(self.__ledger)
 | 
			
		||||
        self.__reports.append(self.__income_expenses)
 | 
			
		||||
        self.__reports.append(self.__ledger)
 | 
			
		||||
        self.__reports.append(self.__journal)
 | 
			
		||||
        self.__reports.append(self.__trial_balance)
 | 
			
		||||
        self.__reports.append(self.__income_statement)
 | 
			
		||||
        self.__reports.append(self.__balance_sheet)
 | 
			
		||||
@@ -80,28 +80,6 @@ class ReportChooser:
 | 
			
		||||
        if self.is_search:
 | 
			
		||||
            self.current_report = gettext("Search")
 | 
			
		||||
 | 
			
		||||
    @property
 | 
			
		||||
    def __journal(self) -> OptionLink:
 | 
			
		||||
        """Returns the journal.
 | 
			
		||||
 | 
			
		||||
        :return: The journal.
 | 
			
		||||
        """
 | 
			
		||||
        return OptionLink(gettext("Journal"), journal_url(self.__period),
 | 
			
		||||
                          self.__active_report == ReportType.JOURNAL,
 | 
			
		||||
                          fa_icon="fa-solid fa-book")
 | 
			
		||||
 | 
			
		||||
    @property
 | 
			
		||||
    def __ledger(self) -> OptionLink:
 | 
			
		||||
        """Returns the ledger.
 | 
			
		||||
 | 
			
		||||
        :return: The ledger.
 | 
			
		||||
        """
 | 
			
		||||
        return OptionLink(gettext("Ledger"),
 | 
			
		||||
                          ledger_url(self.__currency, self.__account,
 | 
			
		||||
                                     self.__period),
 | 
			
		||||
                          self.__active_report == ReportType.LEDGER,
 | 
			
		||||
                          fa_icon="fa-solid fa-clipboard")
 | 
			
		||||
 | 
			
		||||
    @property
 | 
			
		||||
    def __income_expenses(self) -> OptionLink:
 | 
			
		||||
        """Returns the income and expenses log.
 | 
			
		||||
@@ -118,6 +96,28 @@ class ReportChooser:
 | 
			
		||||
                          self.__active_report == ReportType.INCOME_EXPENSES,
 | 
			
		||||
                          fa_icon="fa-solid fa-money-bill-wave")
 | 
			
		||||
 | 
			
		||||
    @property
 | 
			
		||||
    def __ledger(self) -> OptionLink:
 | 
			
		||||
        """Returns the ledger.
 | 
			
		||||
 | 
			
		||||
        :return: The ledger.
 | 
			
		||||
        """
 | 
			
		||||
        return OptionLink(gettext("Ledger"),
 | 
			
		||||
                          ledger_url(self.__currency, self.__account,
 | 
			
		||||
                                     self.__period),
 | 
			
		||||
                          self.__active_report == ReportType.LEDGER,
 | 
			
		||||
                          fa_icon="fa-solid fa-clipboard")
 | 
			
		||||
 | 
			
		||||
    @property
 | 
			
		||||
    def __journal(self) -> OptionLink:
 | 
			
		||||
        """Returns the journal.
 | 
			
		||||
 | 
			
		||||
        :return: The journal.
 | 
			
		||||
        """
 | 
			
		||||
        return OptionLink(gettext("Journal"), journal_url(self.__period),
 | 
			
		||||
                          self.__active_report == ReportType.JOURNAL,
 | 
			
		||||
                          fa_icon="fa-solid fa-book")
 | 
			
		||||
 | 
			
		||||
    @property
 | 
			
		||||
    def __trial_balance(self) -> OptionLink:
 | 
			
		||||
        """Returns the trial balance.
 | 
			
		||||
 
 | 
			
		||||
@@ -1,4 +1,4 @@
 | 
			
		||||
# The Mia! Accounting Flask Project.
 | 
			
		||||
# The Mia! Accounting Project.
 | 
			
		||||
# Author: imacat@mail.imacat.idv.tw (imacat), 2023/3/4
 | 
			
		||||
 | 
			
		||||
#  Copyright (c) 2023 imacat.
 | 
			
		||||
 
 | 
			
		||||
@@ -1,4 +1,4 @@
 | 
			
		||||
# The Mia! Accounting Flask Project.
 | 
			
		||||
# The Mia! Accounting Project.
 | 
			
		||||
# Author: imacat@mail.imacat.idv.tw (imacat), 2023/3/9
 | 
			
		||||
 | 
			
		||||
#  Copyright (c) 2023 imacat.
 | 
			
		||||
@@ -34,8 +34,8 @@ def journal_url(period: Period) \
 | 
			
		||||
    :return: The URL of the journal.
 | 
			
		||||
    """
 | 
			
		||||
    if period.is_default:
 | 
			
		||||
        return url_for("accounting.report.journal-default")
 | 
			
		||||
    return url_for("accounting.report.journal", period=period)
 | 
			
		||||
        return url_for("accounting-report.journal-default")
 | 
			
		||||
    return url_for("accounting-report.journal", period=period)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def ledger_url(currency: Currency, account: Account, period: Period) \
 | 
			
		||||
@@ -48,9 +48,9 @@ def ledger_url(currency: Currency, account: Account, period: Period) \
 | 
			
		||||
    :return: The URL of the ledger.
 | 
			
		||||
    """
 | 
			
		||||
    if period.is_default:
 | 
			
		||||
        return url_for("accounting.report.ledger-default",
 | 
			
		||||
        return url_for("accounting-report.ledger-default",
 | 
			
		||||
                       currency=currency, account=account)
 | 
			
		||||
    return url_for("accounting.report.ledger",
 | 
			
		||||
    return url_for("accounting-report.ledger",
 | 
			
		||||
                   currency=currency, account=account,
 | 
			
		||||
                   period=period)
 | 
			
		||||
 | 
			
		||||
@@ -67,11 +67,11 @@ def income_expenses_url(currency: Currency, account: CurrentAccount,
 | 
			
		||||
    if currency.code == default_currency_code() \
 | 
			
		||||
            and account.code == options.default_ie_account_code \
 | 
			
		||||
            and period.is_default:
 | 
			
		||||
        return url_for("accounting.report.default")
 | 
			
		||||
        return url_for("accounting-report.default")
 | 
			
		||||
    if period.is_default:
 | 
			
		||||
        return url_for("accounting.report.income-expenses-default",
 | 
			
		||||
        return url_for("accounting-report.income-expenses-default",
 | 
			
		||||
                       currency=currency, account=account)
 | 
			
		||||
    return url_for("accounting.report.income-expenses",
 | 
			
		||||
    return url_for("accounting-report.income-expenses",
 | 
			
		||||
                   currency=currency, account=account,
 | 
			
		||||
                   period=period)
 | 
			
		||||
 | 
			
		||||
@@ -84,9 +84,9 @@ def trial_balance_url(currency: Currency, period: Period) -> str:
 | 
			
		||||
    :return: The URL of the trial balance.
 | 
			
		||||
    """
 | 
			
		||||
    if period.is_default:
 | 
			
		||||
        return url_for("accounting.report.trial-balance-default",
 | 
			
		||||
        return url_for("accounting-report.trial-balance-default",
 | 
			
		||||
                       currency=currency)
 | 
			
		||||
    return url_for("accounting.report.trial-balance",
 | 
			
		||||
    return url_for("accounting-report.trial-balance",
 | 
			
		||||
                   currency=currency, period=period)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@@ -98,9 +98,9 @@ def income_statement_url(currency: Currency, period: Period) -> str:
 | 
			
		||||
    :return: The URL of the income statement.
 | 
			
		||||
    """
 | 
			
		||||
    if period.is_default:
 | 
			
		||||
        return url_for("accounting.report.income-statement-default",
 | 
			
		||||
        return url_for("accounting-report.income-statement-default",
 | 
			
		||||
                       currency=currency)
 | 
			
		||||
    return url_for("accounting.report.income-statement",
 | 
			
		||||
    return url_for("accounting-report.income-statement",
 | 
			
		||||
                   currency=currency, period=period)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@@ -112,7 +112,7 @@ def balance_sheet_url(currency: Currency, period: Period) -> str:
 | 
			
		||||
    :return: The URL of the balance sheet.
 | 
			
		||||
    """
 | 
			
		||||
    if period.is_default:
 | 
			
		||||
        return url_for("accounting.report.balance-sheet-default",
 | 
			
		||||
        return url_for("accounting-report.balance-sheet-default",
 | 
			
		||||
                       currency=currency)
 | 
			
		||||
    return url_for("accounting.report.balance-sheet",
 | 
			
		||||
    return url_for("accounting-report.balance-sheet",
 | 
			
		||||
                   currency=currency, period=period)
 | 
			
		||||
 
 | 
			
		||||
@@ -1,4 +1,4 @@
 | 
			
		||||
# The Mia! Accounting Flask Project.
 | 
			
		||||
# The Mia! Accounting Project.
 | 
			
		||||
# Author: imacat@mail.imacat.idv.tw (imacat), 2023/3/3
 | 
			
		||||
 | 
			
		||||
#  Copyright (c) 2023 imacat.
 | 
			
		||||
@@ -30,7 +30,7 @@ from .reports import Journal, Ledger, IncomeExpenses, TrialBalance, \
 | 
			
		||||
    IncomeStatement, BalanceSheet, Search
 | 
			
		||||
from .template_filters import format_amount
 | 
			
		||||
 | 
			
		||||
bp: Blueprint = Blueprint("report", __name__)
 | 
			
		||||
bp: Blueprint = Blueprint("accounting-report", __name__)
 | 
			
		||||
"""The view blueprint for the reports."""
 | 
			
		||||
bp.add_app_template_filter(format_amount, "accounting_report_format_amount")
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -1,4 +1,4 @@
 | 
			
		||||
/* The Mia! Accounting Flask Project
 | 
			
		||||
/* The Mia! Accounting Project
 | 
			
		||||
 * style.css: The style sheet for the accounting application.
 | 
			
		||||
 */
 | 
			
		||||
 | 
			
		||||
@@ -316,6 +316,10 @@ a.accounting-report-table-row {
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/* The description editor */
 | 
			
		||||
.accounting-description-editor-buttons {
 | 
			
		||||
    max-height: 7rem;
 | 
			
		||||
    overflow-y: scroll;
 | 
			
		||||
}
 | 
			
		||||
.accounting-description-editor-buttons .btn {
 | 
			
		||||
    margin-bottom: 0.3rem;
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -1,4 +1,4 @@
 | 
			
		||||
/* The Mia! Accounting Flask Project
 | 
			
		||||
/* The Mia! Accounting Project
 | 
			
		||||
 * account-form.js: The JavaScript for the account form
 | 
			
		||||
 */
 | 
			
		||||
 | 
			
		||||
@@ -342,12 +342,6 @@ class BaseAccountSelector {
 | 
			
		||||
 */
 | 
			
		||||
class BaseAccountOption {
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * The base account selector
 | 
			
		||||
     * @type {BaseAccountSelector}
 | 
			
		||||
     */
 | 
			
		||||
    #selector;
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * The element
 | 
			
		||||
     * @type {HTMLLIElement}
 | 
			
		||||
@@ -379,13 +373,12 @@ class BaseAccountOption {
 | 
			
		||||
     * @param element {HTMLLIElement} the element
 | 
			
		||||
     */
 | 
			
		||||
    constructor(selector, element) {
 | 
			
		||||
        this.#selector = selector;
 | 
			
		||||
        this.#element = element;
 | 
			
		||||
        this.code = element.dataset.code;
 | 
			
		||||
        this.text = element.dataset.text;
 | 
			
		||||
        this.#queryValues = JSON.parse(element.dataset.queryValues);
 | 
			
		||||
 | 
			
		||||
        this.#element.onclick = () => this.#selector.form.saveBaseAccount(this);
 | 
			
		||||
        this.#element.onclick = () => selector.form.saveBaseAccount(this);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
 
 | 
			
		||||
@@ -1,4 +1,4 @@
 | 
			
		||||
/* The Mia! Accounting Flask Project
 | 
			
		||||
/* The Mia! Accounting Project
 | 
			
		||||
 * account-order.js: The JavaScript for the account order
 | 
			
		||||
 */
 | 
			
		||||
 | 
			
		||||
@@ -29,10 +29,11 @@ document.addEventListener("DOMContentLoaded", () => {
 | 
			
		||||
        const onReorder = () => {
 | 
			
		||||
            const accounts = Array.from(list.children);
 | 
			
		||||
            for (let i = 0; i < accounts.length; i++) {
 | 
			
		||||
                const no = document.getElementById("accounting-order-" + accounts[i].dataset.id + "-no");
 | 
			
		||||
                const code = document.getElementById("accounting-order-" + accounts[i].dataset.id + "-code");
 | 
			
		||||
                const no = document.getElementById(`accounting-order-${accounts[i].dataset.id}-no`);
 | 
			
		||||
                const code = document.getElementById(`accounting-order-${accounts[i].dataset.id}-code`);
 | 
			
		||||
                no.value = String(i + 1);
 | 
			
		||||
                code.innerText = list.dataset.baseCode + "-" + ("000" + (i + 1)).slice(-3);
 | 
			
		||||
                const zeroPaddedNo = `000${no.value}`.slice(-3)
 | 
			
		||||
                code.innerText = `${list.dataset.baseCode}-${zeroPaddedNo}`;
 | 
			
		||||
            }
 | 
			
		||||
        };
 | 
			
		||||
        initializeDragAndDropReordering(list, onReorder);
 | 
			
		||||
 
 | 
			
		||||
@@ -1,4 +1,4 @@
 | 
			
		||||
/* The Mia! Accounting Flask Project
 | 
			
		||||
/* The Mia! Accounting Project
 | 
			
		||||
 * currency-form.js: The JavaScript for the currency form
 | 
			
		||||
 */
 | 
			
		||||
 | 
			
		||||
@@ -128,7 +128,7 @@ class CurrencyForm {
 | 
			
		||||
        }
 | 
			
		||||
        const original = this.#code.dataset.original;
 | 
			
		||||
        if (original === "" || this.#code.value !== original) {
 | 
			
		||||
            const response = await fetch(this.#code.dataset.existsUrl + "?q=" + encodeURIComponent(this.#code.value));
 | 
			
		||||
            const response = await fetch(`${this.#code.dataset.existsUrl}?q=${encodeURIComponent(this.#code.value)}`);
 | 
			
		||||
            const data = await response.json();
 | 
			
		||||
            if (data["exists"]) {
 | 
			
		||||
                this.#code.classList.add("is-invalid");
 | 
			
		||||
 
 | 
			
		||||
@@ -1,4 +1,4 @@
 | 
			
		||||
/* The Mia! Accounting Flask Project
 | 
			
		||||
/* The Mia! Accounting Project
 | 
			
		||||
 * description-editor.js: The JavaScript for the description editor
 | 
			
		||||
 */
 | 
			
		||||
 | 
			
		||||
@@ -60,7 +60,7 @@ class DescriptionEditor {
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * The current tab
 | 
			
		||||
     * @type {TabPlane}
 | 
			
		||||
     * @type {DescriptionEditorTabPlane}
 | 
			
		||||
     */
 | 
			
		||||
    currentTab;
 | 
			
		||||
 | 
			
		||||
@@ -68,7 +68,7 @@ class DescriptionEditor {
 | 
			
		||||
     * The description input
 | 
			
		||||
     * @type {HTMLInputElement}
 | 
			
		||||
     */
 | 
			
		||||
    description;
 | 
			
		||||
    #descriptionInput;
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * The button to the original line item selector
 | 
			
		||||
@@ -89,20 +89,44 @@ class DescriptionEditor {
 | 
			
		||||
    note;
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * The account buttons
 | 
			
		||||
     * @type {HTMLButtonElement[]}
 | 
			
		||||
     * The placeholder of the confirmed account
 | 
			
		||||
     * @type {DescriptionEditorConfirmedAccount}
 | 
			
		||||
     */
 | 
			
		||||
    #accountButtons;
 | 
			
		||||
    #confirmedAccountPlaceholder;
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * The selected account button
 | 
			
		||||
     * @type {HTMLButtonElement|null}
 | 
			
		||||
     * All the suggested accounts
 | 
			
		||||
     * @type {DescriptionEditorSuggestedAccount[]}
 | 
			
		||||
     */
 | 
			
		||||
    #selectedAccount = null;
 | 
			
		||||
    #allSuggestedAccounts;
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * The current suggested accounts
 | 
			
		||||
     * @type {DescriptionEditorSuggestedAccount[]}
 | 
			
		||||
     */
 | 
			
		||||
    #currentSuggestedAccounts;
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * The account that the user specified or confirmed
 | 
			
		||||
     * @type {DescriptionEditorConfirmedAccount|null}
 | 
			
		||||
     */
 | 
			
		||||
    #confirmedAccount = null;
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Whether the user has confirmed the account
 | 
			
		||||
     * @type {boolean}
 | 
			
		||||
     */
 | 
			
		||||
    isAccountConfirmed = false;
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * The selected account.
 | 
			
		||||
     * @type {DescriptionEditorAccount|null}
 | 
			
		||||
     */
 | 
			
		||||
    selectedAccount = null;
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * The tab planes
 | 
			
		||||
     * @type {{general: GeneralTagTab, travel: GeneralTripTab, bus: BusTripTab, recurring: RecurringTransactionTab, annotation: AnnotationTab}}
 | 
			
		||||
     * @type {{general: DescriptionEditorGeneralTagTab, travel: DescriptionEditorGeneralTripTab, bus: DescriptionEditorBusTripTab, recurring: DescriptionEditorRecurringTab, annotation: DescriptionEditorAnnotationTab}}
 | 
			
		||||
     */
 | 
			
		||||
    tabPlanes = {};
 | 
			
		||||
 | 
			
		||||
@@ -115,23 +139,22 @@ class DescriptionEditor {
 | 
			
		||||
    constructor(lineItemEditor, debitCredit) {
 | 
			
		||||
        this.lineItemEditor = lineItemEditor;
 | 
			
		||||
        this.debitCredit = debitCredit;
 | 
			
		||||
        this.prefix = "accounting-description-editor-" + debitCredit;
 | 
			
		||||
        this.prefix = `accounting-description-editor-${debitCredit}`;
 | 
			
		||||
        this.#form = document.getElementById(this.prefix);
 | 
			
		||||
        this.#modal = document.getElementById(this.prefix + "-modal");
 | 
			
		||||
        this.description = 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");
 | 
			
		||||
        // noinspection JSValidateTypes
 | 
			
		||||
        this.#accountButtons = Array.from(document.getElementsByClassName(this.prefix + "-account"));
 | 
			
		||||
        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));
 | 
			
		||||
 | 
			
		||||
        for (const cls of [GeneralTagTab, GeneralTripTab, BusTripTab, RecurringTransactionTab, AnnotationTab]) {
 | 
			
		||||
        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.#initializeSuggestedAccounts();
 | 
			
		||||
        this.description.onchange = () => this.#onDescriptionChange();
 | 
			
		||||
        this.#descriptionInput.onchange = () => this.#onDescriptionChange();
 | 
			
		||||
        this.#offsetButton.onclick = () => this.lineItemEditor.originalLineItemSelector.onOpen();
 | 
			
		||||
        this.#form.onsubmit = () => {
 | 
			
		||||
            if (this.currentTab.validate()) {
 | 
			
		||||
@@ -141,12 +164,44 @@ class DescriptionEditor {
 | 
			
		||||
        };
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Returns the description.
 | 
			
		||||
     *
 | 
			
		||||
     * @return {string} the description
 | 
			
		||||
     */
 | 
			
		||||
    get description() {
 | 
			
		||||
        return this.#descriptionInput.value;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Sets the description.
 | 
			
		||||
     *
 | 
			
		||||
     * @param description {string} the description
 | 
			
		||||
     */
 | 
			
		||||
    set description(description) {
 | 
			
		||||
        this.#descriptionInput.value = description;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Returns the current account options.
 | 
			
		||||
     *
 | 
			
		||||
     * @return {DescriptionEditorAccount[]} the current account options.
 | 
			
		||||
     */
 | 
			
		||||
    get #currentAccountOptions() {
 | 
			
		||||
        if (this.#confirmedAccount === null) {
 | 
			
		||||
            return this.#currentSuggestedAccounts;
 | 
			
		||||
        }
 | 
			
		||||
        return [this.#confirmedAccount].concat(this.#currentSuggestedAccounts);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * The callback when the description input is changed.
 | 
			
		||||
     *
 | 
			
		||||
     */
 | 
			
		||||
    #onDescriptionChange() {
 | 
			
		||||
        this.description.value = this.description.value.trim();
 | 
			
		||||
        this.#resetTabPlanes();
 | 
			
		||||
        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()) {
 | 
			
		||||
                break;
 | 
			
		||||
@@ -156,20 +211,49 @@ class DescriptionEditor {
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Filters the suggested accounts.
 | 
			
		||||
     * Resets the tab planes.
 | 
			
		||||
     *
 | 
			
		||||
     */
 | 
			
		||||
    #resetTabPlanes() {
 | 
			
		||||
        for (const tabPlane of Object.values(this.tabPlanes)) {
 | 
			
		||||
            tabPlane.reset();
 | 
			
		||||
        }
 | 
			
		||||
        this.tabPlanes.general.switchToMe();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Updates the current suggested accounts.
 | 
			
		||||
     *
 | 
			
		||||
     * @param tagButton {HTMLButtonElement} the tag button
 | 
			
		||||
     */
 | 
			
		||||
    filterSuggestedAccounts(tagButton) {
 | 
			
		||||
    updateCurrentSuggestedAccounts(tagButton) {
 | 
			
		||||
        this.clearSuggestedAccounts();
 | 
			
		||||
        const suggested = JSON.parse(tagButton.dataset.accounts);
 | 
			
		||||
        for (const accountButton of this.#accountButtons) {
 | 
			
		||||
            if (suggested.includes(accountButton.dataset.code)) {
 | 
			
		||||
                accountButton.classList.remove("d-none");
 | 
			
		||||
                if (accountButton.dataset.code === suggested[0]) {
 | 
			
		||||
                    this.#selectAccount(accountButton);
 | 
			
		||||
        const suggestedAccountCodes = JSON.parse(tagButton.dataset.accounts);
 | 
			
		||||
        this.#currentSuggestedAccounts = this.#allSuggestedAccounts.filter((account) => {
 | 
			
		||||
            if (this.#confirmedAccount !== null && account.code === this.#confirmedAccount.code) {
 | 
			
		||||
                return false;
 | 
			
		||||
            }
 | 
			
		||||
            return suggestedAccountCodes.includes(account.code);
 | 
			
		||||
        });
 | 
			
		||||
        for (const account of this.#currentSuggestedAccounts) {
 | 
			
		||||
            account.setShown(true);
 | 
			
		||||
        }
 | 
			
		||||
        this.#selectSuggestedAccount(suggestedAccountCodes[0]);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Selects the suggested account.
 | 
			
		||||
     *
 | 
			
		||||
     * @param code {string} the code of the most-frequent suggested account
 | 
			
		||||
     */
 | 
			
		||||
    #selectSuggestedAccount(code) {
 | 
			
		||||
        if (this.isAccountConfirmed) {
 | 
			
		||||
            return;
 | 
			
		||||
        }
 | 
			
		||||
        for (const account of this.#currentAccountOptions) {
 | 
			
		||||
            if (account.code === code) {
 | 
			
		||||
                this.selectAccount(account);
 | 
			
		||||
                return;
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
@@ -179,37 +263,29 @@ class DescriptionEditor {
 | 
			
		||||
     *
 | 
			
		||||
     */
 | 
			
		||||
    clearSuggestedAccounts() {
 | 
			
		||||
        for (const accountButton of this.#accountButtons) {
 | 
			
		||||
            accountButton.classList.add("d-none");
 | 
			
		||||
        for (const account of this.#allSuggestedAccounts) {
 | 
			
		||||
            account.setShown(false);
 | 
			
		||||
            account.setActive(false);
 | 
			
		||||
        }
 | 
			
		||||
        this.#selectAccount(null);
 | 
			
		||||
        this.#currentSuggestedAccounts = [];
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Initializes the suggested accounts.
 | 
			
		||||
     * Select an account.
 | 
			
		||||
     *
 | 
			
		||||
     * @param selectedAccount {DescriptionEditorAccount|null} the account, or null to deselect the account
 | 
			
		||||
     */
 | 
			
		||||
    #initializeSuggestedAccounts() {
 | 
			
		||||
        for (const accountButton of this.#accountButtons) {
 | 
			
		||||
            accountButton.onclick = () => this.#selectAccount(accountButton);
 | 
			
		||||
    selectAccount(selectedAccount) {
 | 
			
		||||
        for (const account of this.#currentAccountOptions) {
 | 
			
		||||
            account.setActive(false);
 | 
			
		||||
        }
 | 
			
		||||
        if (selectedAccount !== null) {
 | 
			
		||||
            selectedAccount.setActive(true);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Select a suggested account.
 | 
			
		||||
     *
 | 
			
		||||
     * @param selectedAccountButton {HTMLButtonElement|null} the account button, or null to deselect the account
 | 
			
		||||
     */
 | 
			
		||||
    #selectAccount(selectedAccountButton) {
 | 
			
		||||
        for (const accountButton of this.#accountButtons) {
 | 
			
		||||
            accountButton.classList.remove("btn-primary");
 | 
			
		||||
            accountButton.classList.add("btn-outline-primary");
 | 
			
		||||
        this.selectedAccount = selectedAccount;
 | 
			
		||||
        if (this.selectedAccount !== null) {
 | 
			
		||||
            this.isAccountConfirmed &&= this.selectedAccount.isConfirmedAccount;
 | 
			
		||||
        }
 | 
			
		||||
        if (selectedAccountButton !== null) {
 | 
			
		||||
            selectedAccountButton.classList.remove("btn-outline-primary");
 | 
			
		||||
            selectedAccountButton.classList.add("btn-primary");
 | 
			
		||||
        }
 | 
			
		||||
        this.#selectedAccount = selectedAccountButton;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
@@ -218,11 +294,7 @@ class DescriptionEditor {
 | 
			
		||||
     */
 | 
			
		||||
    #submit() {
 | 
			
		||||
        bootstrap.Modal.getOrCreateInstance(this.#modal).hide();
 | 
			
		||||
        if (this.#selectedAccount !== null) {
 | 
			
		||||
            this.lineItemEditor.saveDescriptionWithAccount(this.description.value, this.#selectedAccount.dataset.code, this.#selectedAccount.dataset.text, this.#selectedAccount.classList.contains("accounting-account-is-need-offset"));
 | 
			
		||||
        } else {
 | 
			
		||||
            this.lineItemEditor.saveDescription(this.description.value);
 | 
			
		||||
        }
 | 
			
		||||
        this.lineItemEditor.saveDescription(this);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
@@ -230,21 +302,27 @@ class DescriptionEditor {
 | 
			
		||||
     *
 | 
			
		||||
     */
 | 
			
		||||
    onOpen() {
 | 
			
		||||
        this.#reset();
 | 
			
		||||
        this.description.value = this.lineItemEditor.description === null? "": this.lineItemEditor.description;
 | 
			
		||||
        this.description = this.lineItemEditor.description === null? "": this.lineItemEditor.description;
 | 
			
		||||
        this.#setConfirmedAccount();
 | 
			
		||||
        this.#onDescriptionChange();
 | 
			
		||||
        if (this.isAccountConfirmed) {
 | 
			
		||||
            this.selectAccount(this.#confirmedAccount);
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Resets the description editor.
 | 
			
		||||
     * Sets the confirmed account.
 | 
			
		||||
     *
 | 
			
		||||
     */
 | 
			
		||||
    #reset() {
 | 
			
		||||
        this.description.value = "";
 | 
			
		||||
        for (const tabPlane of Object.values(this.tabPlanes)) {
 | 
			
		||||
            tabPlane.reset();
 | 
			
		||||
    #setConfirmedAccount() {
 | 
			
		||||
        this.isAccountConfirmed = this.lineItemEditor.isAccountConfirmed;
 | 
			
		||||
        this.#confirmedAccountPlaceholder.setShown(this.isAccountConfirmed);
 | 
			
		||||
        if (this.isAccountConfirmed) {
 | 
			
		||||
            this.#confirmedAccountPlaceholder.initializeFrom(this.lineItemEditor.account);
 | 
			
		||||
            this.#confirmedAccount = this.#confirmedAccountPlaceholder;
 | 
			
		||||
        } else {
 | 
			
		||||
            this.#confirmedAccount = null;
 | 
			
		||||
        }
 | 
			
		||||
        this.tabPlanes.general.switchToMe();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
@@ -263,13 +341,130 @@ class DescriptionEditor {
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * An account option in the description editor.
 | 
			
		||||
 *
 | 
			
		||||
 */
 | 
			
		||||
class DescriptionEditorAccount extends JournalEntryAccount {
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * The account button
 | 
			
		||||
     * @type {HTMLButtonElement}
 | 
			
		||||
     */
 | 
			
		||||
    #element;
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Whether this is the account specified or confirmed by the user
 | 
			
		||||
     * @type {boolean}
 | 
			
		||||
     */
 | 
			
		||||
    isConfirmedAccount = false;
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Constructs an account option in the description editor.
 | 
			
		||||
     *
 | 
			
		||||
     * @param editor {DescriptionEditor} the description editor
 | 
			
		||||
     * @param code {string} the account code
 | 
			
		||||
     * @param text {string} the account text
 | 
			
		||||
     * @param isNeedOffset {boolean} true if the line items in the account needs offset, or false otherwise
 | 
			
		||||
     * @param button {HTMLButtonElement} the account button
 | 
			
		||||
     */
 | 
			
		||||
    constructor(editor, code, text, isNeedOffset, button) {
 | 
			
		||||
        super(code, text, isNeedOffset);
 | 
			
		||||
        this.#element = button;
 | 
			
		||||
        this.#element.onclick = () => editor.selectAccount(this);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * 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("btn-primary");
 | 
			
		||||
            this.#element.classList.remove("btn-outline-primary");
 | 
			
		||||
        } else {
 | 
			
		||||
            this.#element.classList.remove("btn-primary");
 | 
			
		||||
            this.#element.classList.add("btn-outline-primary");
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Sets the content of the account button.
 | 
			
		||||
     *
 | 
			
		||||
     */
 | 
			
		||||
    resetContent() {
 | 
			
		||||
        this.#element.innerText = this.text;
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * A suggested account.
 | 
			
		||||
 *
 | 
			
		||||
 */
 | 
			
		||||
class DescriptionEditorSuggestedAccount extends DescriptionEditorAccount {
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Constructs a suggested account.
 | 
			
		||||
     *
 | 
			
		||||
     * @param editor {DescriptionEditor} the description editor
 | 
			
		||||
     * @param button {HTMLButtonElement} the account button
 | 
			
		||||
     */
 | 
			
		||||
    constructor(editor, button) {
 | 
			
		||||
        super(editor, button.dataset.code, button.dataset.text, button.classList.contains("accounting-account-is-need-offset"), button);
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * The account option that is specified or confirmed by the user.
 | 
			
		||||
 *
 | 
			
		||||
 */
 | 
			
		||||
class DescriptionEditorConfirmedAccount extends DescriptionEditorAccount {
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Constructs the account option that is specified or confirmed by the user.
 | 
			
		||||
     *
 | 
			
		||||
     * @param editor {DescriptionEditor} the description editor
 | 
			
		||||
     * @param button {HTMLButtonElement} the account button
 | 
			
		||||
     */
 | 
			
		||||
    constructor(editor, button) {
 | 
			
		||||
        super(editor, "", "", false, button);
 | 
			
		||||
        this.isConfirmedAccount = true;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Initializes the confirmed account from the line item editor.
 | 
			
		||||
     *
 | 
			
		||||
     * @param account {JournalEntryAccount} the confirmed account from the line item editor
 | 
			
		||||
     */
 | 
			
		||||
    initializeFrom(account) {
 | 
			
		||||
        this.code = account.code;
 | 
			
		||||
        this.text = account.text;
 | 
			
		||||
        this.isNeedOffset = account.isNeedOffset;
 | 
			
		||||
        this.resetContent();
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * A tab plane.
 | 
			
		||||
 *
 | 
			
		||||
 * @abstract
 | 
			
		||||
 * @private
 | 
			
		||||
 */
 | 
			
		||||
class TabPlane {
 | 
			
		||||
class DescriptionEditorTabPlane {
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * The parent description editor
 | 
			
		||||
@@ -302,9 +497,9 @@ class TabPlane {
 | 
			
		||||
     */
 | 
			
		||||
    constructor(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.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();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
@@ -364,7 +559,7 @@ class TabPlane {
 | 
			
		||||
 * @abstract
 | 
			
		||||
 * @private
 | 
			
		||||
 */
 | 
			
		||||
class TagTabPlane extends TabPlane {
 | 
			
		||||
class DescriptionEditorTagTabPlane extends DescriptionEditorTabPlane {
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * The tag input
 | 
			
		||||
@@ -392,10 +587,10 @@ class TagTabPlane extends TabPlane {
 | 
			
		||||
     */
 | 
			
		||||
    constructor(editor) {
 | 
			
		||||
        super(editor);
 | 
			
		||||
        this.tag = document.getElementById(this.prefix + "-tag");
 | 
			
		||||
        this.tagError = document.getElementById(this.prefix + "-tag-error");
 | 
			
		||||
        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();
 | 
			
		||||
@@ -414,7 +609,7 @@ class TagTabPlane extends TabPlane {
 | 
			
		||||
            if (tagButton.dataset.value === this.tag.value) {
 | 
			
		||||
                tagButton.classList.remove("btn-outline-primary");
 | 
			
		||||
                tagButton.classList.add("btn-primary");
 | 
			
		||||
                this.editor.filterSuggestedAccounts(tagButton);
 | 
			
		||||
                this.editor.updateCurrentSuggestedAccounts(tagButton);
 | 
			
		||||
                isMatched = true;
 | 
			
		||||
            } else {
 | 
			
		||||
                tagButton.classList.remove("btn-primary");
 | 
			
		||||
@@ -442,7 +637,7 @@ class TagTabPlane extends TabPlane {
 | 
			
		||||
        super.switchToMe();
 | 
			
		||||
        for (const tagButton of this.tagButtons) {
 | 
			
		||||
            if (tagButton.classList.contains("btn-primary")) {
 | 
			
		||||
                this.editor.filterSuggestedAccounts(tagButton);
 | 
			
		||||
                this.editor.updateCurrentSuggestedAccounts(tagButton);
 | 
			
		||||
                return;
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
@@ -463,7 +658,7 @@ class TagTabPlane extends TabPlane {
 | 
			
		||||
                tagButton.classList.remove("btn-outline-primary");
 | 
			
		||||
                tagButton.classList.add("btn-primary");
 | 
			
		||||
                this.tag.value = tagButton.dataset.value;
 | 
			
		||||
                this.editor.filterSuggestedAccounts(tagButton);
 | 
			
		||||
                this.editor.updateCurrentSuggestedAccounts(tagButton);
 | 
			
		||||
                this.updateDescription();
 | 
			
		||||
            };
 | 
			
		||||
        }
 | 
			
		||||
@@ -522,7 +717,7 @@ class TagTabPlane extends TabPlane {
 | 
			
		||||
 *
 | 
			
		||||
 * @private
 | 
			
		||||
 */
 | 
			
		||||
class GeneralTagTab extends TagTabPlane {
 | 
			
		||||
class DescriptionEditorGeneralTagTab extends DescriptionEditorTagTabPlane {
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * The tab ID
 | 
			
		||||
@@ -540,12 +735,12 @@ class GeneralTagTab extends TagTabPlane {
 | 
			
		||||
     * @override
 | 
			
		||||
     */
 | 
			
		||||
    updateDescription() {
 | 
			
		||||
        const pos = this.editor.description.value.indexOf("—");
 | 
			
		||||
        const prefix = this.tag.value === ""? "": this.tag.value + "—";
 | 
			
		||||
        const pos = this.editor.description.indexOf("—");
 | 
			
		||||
        const prefix = this.tag.value === ""? "": `${this.tag.value}—`;
 | 
			
		||||
        if (pos === -1) {
 | 
			
		||||
            this.editor.description.value = prefix + this.editor.description.value;
 | 
			
		||||
            this.editor.description = `${prefix}${this.editor.description}`;
 | 
			
		||||
        } else {
 | 
			
		||||
            this.editor.description.value = prefix + this.editor.description.value.substring(pos + 1);
 | 
			
		||||
            this.editor.description = `${prefix}${this.editor.description.substring(pos + 1)}`;
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
@@ -556,7 +751,7 @@ class GeneralTagTab extends TagTabPlane {
 | 
			
		||||
     * @override
 | 
			
		||||
     */
 | 
			
		||||
    populate() {
 | 
			
		||||
        const found = this.editor.description.value.match(/^([^—]+)—/);
 | 
			
		||||
        const found = this.editor.description.match(/^([^—]+)—/);
 | 
			
		||||
        if (found === null) {
 | 
			
		||||
            return false;
 | 
			
		||||
        }
 | 
			
		||||
@@ -583,7 +778,7 @@ class GeneralTagTab extends TagTabPlane {
 | 
			
		||||
 *
 | 
			
		||||
 * @private
 | 
			
		||||
 */
 | 
			
		||||
class GeneralTripTab extends TagTabPlane {
 | 
			
		||||
class DescriptionEditorGeneralTripTab extends DescriptionEditorTagTabPlane {
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * The origin
 | 
			
		||||
@@ -623,12 +818,12 @@ class GeneralTripTab extends TagTabPlane {
 | 
			
		||||
     */
 | 
			
		||||
    constructor(editor) {
 | 
			
		||||
        super(editor);
 | 
			
		||||
        this.#from = document.getElementById(this.prefix + "-from");
 | 
			
		||||
        this.#fromError = document.getElementById(this.prefix + "-from-error");
 | 
			
		||||
        this.#to = document.getElementById(this.prefix + "-to");
 | 
			
		||||
        this.#toError = document.getElementById(this.prefix + "-to-error")
 | 
			
		||||
        this.#from = document.getElementById(`${this.prefix}-from`);
 | 
			
		||||
        this.#fromError = document.getElementById(`${this.prefix}-from-error`);
 | 
			
		||||
        this.#to = document.getElementById(`${this.prefix}-to`);
 | 
			
		||||
        this.#toError = document.getElementById(`${this.prefix}-to-error`)
 | 
			
		||||
        // noinspection JSValidateTypes
 | 
			
		||||
        this.#directionButtons = Array.from(document.getElementsByClassName(this.prefix + "-direction"));
 | 
			
		||||
        this.#directionButtons = Array.from(document.getElementsByClassName(`${this.prefix}-direction`));
 | 
			
		||||
        this.#from.onchange = () => {
 | 
			
		||||
            this.#from.value = this.#from.value.trim();
 | 
			
		||||
            this.updateDescription();
 | 
			
		||||
@@ -675,7 +870,7 @@ class GeneralTripTab extends TagTabPlane {
 | 
			
		||||
                break;
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
        this.editor.description.value = this.tag.value + "—" + this.#from.value + direction + this.#to.value;
 | 
			
		||||
        this.editor.description = `${this.tag.value}—${this.#from.value}${direction}${this.#to.value}`;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
@@ -709,7 +904,7 @@ class GeneralTripTab extends TagTabPlane {
 | 
			
		||||
     * @override
 | 
			
		||||
     */
 | 
			
		||||
    populate() {
 | 
			
		||||
        const found = this.editor.description.value.match(/^([^—]+)—([^—→↔]+)([→↔])(.+?)(?:[*×]\d+)?(?:\([^()]+\))?$/);
 | 
			
		||||
        const found = this.editor.description.match(/^([^—]+)—([^—→↔]+)([→↔])(.+?)(?:[*×]\d+)?(?:\([^()]+\))?$/);
 | 
			
		||||
        if (found === null) {
 | 
			
		||||
            return false;
 | 
			
		||||
        }
 | 
			
		||||
@@ -782,7 +977,7 @@ class GeneralTripTab extends TagTabPlane {
 | 
			
		||||
 *
 | 
			
		||||
 * @private
 | 
			
		||||
 */
 | 
			
		||||
class BusTripTab extends TagTabPlane {
 | 
			
		||||
class DescriptionEditorBusTripTab extends DescriptionEditorTagTabPlane {
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * The route
 | 
			
		||||
@@ -828,12 +1023,12 @@ class BusTripTab extends TagTabPlane {
 | 
			
		||||
     */
 | 
			
		||||
    constructor(editor) {
 | 
			
		||||
        super(editor);
 | 
			
		||||
        this.#route = document.getElementById(this.prefix + "-route");
 | 
			
		||||
        this.#routeError = document.getElementById(this.prefix + "-route-error");
 | 
			
		||||
        this.#from = document.getElementById(this.prefix + "-from");
 | 
			
		||||
        this.#fromError = document.getElementById(this.prefix + "-from-error");
 | 
			
		||||
        this.#to = document.getElementById(this.prefix + "-to");
 | 
			
		||||
        this.#toError = document.getElementById(this.prefix + "-to-error")
 | 
			
		||||
        this.#route = document.getElementById(`${this.prefix}-route`);
 | 
			
		||||
        this.#routeError = document.getElementById(`${this.prefix}-route-error`);
 | 
			
		||||
        this.#from = document.getElementById(`${this.prefix}-from`);
 | 
			
		||||
        this.#fromError = document.getElementById(`${this.prefix}-from-error`);
 | 
			
		||||
        this.#to = document.getElementById(`${this.prefix}-to`);
 | 
			
		||||
        this.#toError = document.getElementById(`${this.prefix}-to-error`)
 | 
			
		||||
        this.#route.onchange = () => {
 | 
			
		||||
            this.#route.value = this.#route.value.trim();
 | 
			
		||||
            this.updateDescription();
 | 
			
		||||
@@ -867,7 +1062,7 @@ class BusTripTab extends TagTabPlane {
 | 
			
		||||
     * @override
 | 
			
		||||
     */
 | 
			
		||||
    updateDescription() {
 | 
			
		||||
        this.editor.description.value = this.tag.value + "—" + this.#route.value + "—" + this.#from.value + "→" + this.#to.value;
 | 
			
		||||
        this.editor.description = `${this.tag.value}—${this.#route.value}—${this.#from.value}→${this.#to.value}`;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
@@ -895,7 +1090,7 @@ class BusTripTab extends TagTabPlane {
 | 
			
		||||
     * @override
 | 
			
		||||
     */
 | 
			
		||||
    populate() {
 | 
			
		||||
        const found = this.editor.description.value.match(/^([^—]+)—([^—]+)—([^—→]+)→(.+?)(?:[*×]\d+)?(?:\([^()]+\))?$/);
 | 
			
		||||
        const found = this.editor.description.match(/^([^—]+)—([^—]+)—([^—→]+)→(.+?)(?:[*×]\d+)?(?:\([^()]+\))?$/);
 | 
			
		||||
        if (found === null) {
 | 
			
		||||
            return false;
 | 
			
		||||
        }
 | 
			
		||||
@@ -970,7 +1165,7 @@ class BusTripTab extends TagTabPlane {
 | 
			
		||||
 *
 | 
			
		||||
 * @private
 | 
			
		||||
 */
 | 
			
		||||
class RecurringTransactionTab extends TabPlane {
 | 
			
		||||
class DescriptionEditorRecurringTab extends DescriptionEditorTabPlane {
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * The month names
 | 
			
		||||
@@ -999,14 +1194,14 @@ class RecurringTransactionTab extends TabPlane {
 | 
			
		||||
            A_("September"), A_("October"), A_("November"), A_("December"),
 | 
			
		||||
        ];
 | 
			
		||||
        // noinspection JSValidateTypes
 | 
			
		||||
        this.#itemButtons = Array.from(document.getElementsByClassName(this.prefix + "-item"));
 | 
			
		||||
        this.#itemButtons = Array.from(document.getElementsByClassName(`${this.prefix}-item`));
 | 
			
		||||
        for (const itemButton of this.#itemButtons) {
 | 
			
		||||
            itemButton.onclick = () => {
 | 
			
		||||
                this.reset();
 | 
			
		||||
                itemButton.classList.add("btn-primary");
 | 
			
		||||
                itemButton.classList.remove("btn-outline-primary");
 | 
			
		||||
                this.editor.description.value = this.#getDescription(itemButton);
 | 
			
		||||
                this.editor.filterSuggestedAccounts(itemButton);
 | 
			
		||||
                this.editor.description = this.#getDescription(itemButton);
 | 
			
		||||
                this.editor.updateCurrentSuggestedAccounts(itemButton);
 | 
			
		||||
            };
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
@@ -1028,8 +1223,8 @@ class RecurringTransactionTab extends TabPlane {
 | 
			
		||||
            .replaceAll("{this_month_name}", this.#monthNames[thisMonth])
 | 
			
		||||
            .replaceAll("{last_month_number}", String(lastMonth))
 | 
			
		||||
            .replaceAll("{last_month_name}", this.#monthNames[lastMonth])
 | 
			
		||||
            .replaceAll("{last_bimonthly_number}", String(lastBimonthlyFrom) + "–" + String(lastBimonthlyTo))
 | 
			
		||||
            .replaceAll("{last_bimonthly_name}", this.#monthNames[lastBimonthlyFrom] + "–" + this.#monthNames[lastBimonthlyTo]);
 | 
			
		||||
            .replaceAll("{last_bimonthly_number}", `${String(lastBimonthlyFrom)}–${String(lastBimonthlyTo)}`)
 | 
			
		||||
            .replaceAll("{last_bimonthly_name}", `${this.#monthNames[lastBimonthlyFrom]}–${this.#monthNames[lastBimonthlyTo]}`);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
@@ -1062,7 +1257,7 @@ class RecurringTransactionTab extends TabPlane {
 | 
			
		||||
     */
 | 
			
		||||
    populate() {
 | 
			
		||||
        for (const itemButton of this.#itemButtons) {
 | 
			
		||||
            if (this.#getDescription(itemButton) === this.editor.description.value) {
 | 
			
		||||
            if (this.#getDescription(itemButton) === this.editor.description) {
 | 
			
		||||
                itemButton.classList.add("btn-primary");
 | 
			
		||||
                itemButton.classList.remove("btn-outline-primary");
 | 
			
		||||
                this.switchToMe();
 | 
			
		||||
@@ -1080,7 +1275,7 @@ class RecurringTransactionTab extends TabPlane {
 | 
			
		||||
        super.switchToMe();
 | 
			
		||||
        for (const itemButton of this.#itemButtons) {
 | 
			
		||||
            if (itemButton.classList.contains("btn-primary")) {
 | 
			
		||||
                this.editor.filterSuggestedAccounts(itemButton);
 | 
			
		||||
                this.editor.updateCurrentSuggestedAccounts(itemButton);
 | 
			
		||||
                return;
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
@@ -1103,7 +1298,7 @@ class RecurringTransactionTab extends TabPlane {
 | 
			
		||||
 *
 | 
			
		||||
 * @private
 | 
			
		||||
 */
 | 
			
		||||
class AnnotationTab extends TabPlane {
 | 
			
		||||
class DescriptionEditorAnnotationTab extends DescriptionEditorTabPlane {
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Constructs a tab plane.
 | 
			
		||||
@@ -1136,15 +1331,15 @@ class AnnotationTab extends TabPlane {
 | 
			
		||||
     * @override
 | 
			
		||||
     */
 | 
			
		||||
    updateDescription() {
 | 
			
		||||
        const found = this.editor.description.value.match(/^(.*?)(?:[*×]\d+)?(?:\([^()]+\))?$/);
 | 
			
		||||
        const found = this.editor.description.match(/^(.*?)(?:[*×]\d+)?(?:\([^()]+\))?$/);
 | 
			
		||||
        if (found !== null) {
 | 
			
		||||
            this.editor.description.value = found[1];
 | 
			
		||||
            this.editor.description = found[1];
 | 
			
		||||
        }
 | 
			
		||||
        if (parseInt(this.editor.number.value) > 1) {
 | 
			
		||||
            this.editor.description.value = this.editor.description.value + "×" + this.editor.number.value;
 | 
			
		||||
            this.editor.description = `${this.editor.description}×${this.editor.number.value}`;
 | 
			
		||||
        }
 | 
			
		||||
        if (this.editor.note.value !== "") {
 | 
			
		||||
            this.editor.description.value = this.editor.description.value + "(" + this.editor.note.value + ")";
 | 
			
		||||
            this.editor.description = `${this.editor.description}(${this.editor.note.value})`;
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
@@ -1165,19 +1360,19 @@ class AnnotationTab extends TabPlane {
 | 
			
		||||
     * @override
 | 
			
		||||
     */
 | 
			
		||||
    populate() {
 | 
			
		||||
        const found = this.editor.description.value.match(/^(.*?)(?:[*×](\d+))?(?:\(([^()]+)\))?$/);
 | 
			
		||||
        this.editor.description.value = found[1];
 | 
			
		||||
        const found = this.editor.description.match(/^(.*?)(?:[*×](\d+))?(?:\(([^()]+)\))?$/);
 | 
			
		||||
        this.editor.description = found[1];
 | 
			
		||||
        if (found[2] === undefined || parseInt(found[2]) === 1) {
 | 
			
		||||
            this.editor.number.value = "";
 | 
			
		||||
        } else {
 | 
			
		||||
            this.editor.number.value = found[2];
 | 
			
		||||
            this.editor.description.value = this.editor.description.value + "×" + this.editor.number.value;
 | 
			
		||||
            this.editor.description = `${this.editor.description}×${this.editor.number.value}`;
 | 
			
		||||
        }
 | 
			
		||||
        if (found[3] === undefined) {
 | 
			
		||||
            this.editor.note.value = "";
 | 
			
		||||
        } else {
 | 
			
		||||
            this.editor.note.value = found[3];
 | 
			
		||||
            this.editor.description.value = this.editor.description.value + "(" + this.editor.note.value + ")";
 | 
			
		||||
            this.editor.description = `${this.editor.description}(${this.editor.note.value})`;
 | 
			
		||||
        }
 | 
			
		||||
        return true;
 | 
			
		||||
    }
 | 
			
		||||
 
 | 
			
		||||
@@ -1,4 +1,4 @@
 | 
			
		||||
/* The Mia! Accounting Flask Project
 | 
			
		||||
/* The Mia! Accounting Project
 | 
			
		||||
 * drag-and-drop-reorder.js: The JavaScript for the reorder a list with drag-and-drop
 | 
			
		||||
 */
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -1,4 +1,4 @@
 | 
			
		||||
/* The Mia! Accounting Flask Project
 | 
			
		||||
/* The Mia! Accounting Project
 | 
			
		||||
 * journal-entry-account-selector.js: The JavaScript for the account selector of the journal entry form
 | 
			
		||||
 */
 | 
			
		||||
 | 
			
		||||
@@ -91,13 +91,13 @@ class JournalEntryAccountSelector {
 | 
			
		||||
    constructor(lineItemEditor, debitCredit) {
 | 
			
		||||
        this.lineItemEditor = lineItemEditor
 | 
			
		||||
        this.#debitCredit = debitCredit;
 | 
			
		||||
        const prefix = "accounting-account-selector-" + debitCredit;
 | 
			
		||||
        this.#query = document.getElementById(prefix + "-query");
 | 
			
		||||
        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");
 | 
			
		||||
        const prefix = `accounting-account-selector-${debitCredit}`;
 | 
			
		||||
        this.#query = document.getElementById(`${prefix}-query`);
 | 
			
		||||
        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 = () => {
 | 
			
		||||
            this.#isShowMore = true;
 | 
			
		||||
@@ -139,8 +139,8 @@ class JournalEntryAccountSelector {
 | 
			
		||||
     */
 | 
			
		||||
    #getCodesUsedInForm() {
 | 
			
		||||
        const inUse = this.lineItemEditor.form.getAccountCodesUsed(this.#debitCredit);
 | 
			
		||||
        if (this.lineItemEditor.accountCode !== null) {
 | 
			
		||||
            inUse.push(this.lineItemEditor.accountCode);
 | 
			
		||||
        if (this.lineItemEditor.account !== null) {
 | 
			
		||||
            inUse.push(this.lineItemEditor.account.code);
 | 
			
		||||
        }
 | 
			
		||||
        return inUse
 | 
			
		||||
    }
 | 
			
		||||
@@ -155,9 +155,9 @@ class JournalEntryAccountSelector {
 | 
			
		||||
        this.#more.classList.remove("d-none");
 | 
			
		||||
        this.#filterOptions();
 | 
			
		||||
        for (const option of this.#options) {
 | 
			
		||||
            option.setActive(option.code === this.lineItemEditor.accountCode);
 | 
			
		||||
            option.setActive(this.lineItemEditor.account !== null && option.code === this.lineItemEditor.account.code);
 | 
			
		||||
        }
 | 
			
		||||
        if (this.lineItemEditor.accountCode === null) {
 | 
			
		||||
        if (this.lineItemEditor.account === null) {
 | 
			
		||||
            this.#clearButton.classList.add("btn-secondary");
 | 
			
		||||
            this.#clearButton.classList.remove("btn-danger");
 | 
			
		||||
            this.#clearButton.disabled = true;
 | 
			
		||||
@@ -190,12 +190,6 @@ class JournalEntryAccountSelector {
 | 
			
		||||
 */
 | 
			
		||||
class JournalEntryAccountOption {
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * The account selector
 | 
			
		||||
     * @type {JournalEntryAccountSelector}
 | 
			
		||||
     */
 | 
			
		||||
    #selector;
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * The element
 | 
			
		||||
     * @type {HTMLLIElement}
 | 
			
		||||
@@ -239,7 +233,6 @@ class JournalEntryAccountOption {
 | 
			
		||||
     * @param element {HTMLLIElement} the element
 | 
			
		||||
     */
 | 
			
		||||
    constructor(selector, element) {
 | 
			
		||||
        this.#selector = selector;
 | 
			
		||||
        this.#element = element;
 | 
			
		||||
        this.code = element.dataset.code;
 | 
			
		||||
        this.text = element.dataset.text;
 | 
			
		||||
@@ -247,7 +240,7 @@ class JournalEntryAccountOption {
 | 
			
		||||
        this.isNeedOffset = element.classList.contains("accounting-account-is-need-offset");
 | 
			
		||||
        this.#queryValues = JSON.parse(element.dataset.queryValues);
 | 
			
		||||
 | 
			
		||||
        this.#element.onclick = () => this.#selector.lineItemEditor.saveAccount(this);
 | 
			
		||||
        this.#element.onclick = () => selector.lineItemEditor.saveAccount(this);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
 
 | 
			
		||||
@@ -1,4 +1,4 @@
 | 
			
		||||
/* The Mia! Accounting Flask Project
 | 
			
		||||
/* The Mia! Accounting Project
 | 
			
		||||
 * journal-entry-form.js: The JavaScript for the journal entry form
 | 
			
		||||
 */
 | 
			
		||||
 | 
			
		||||
@@ -128,7 +128,7 @@ class JournalEntryForm {
 | 
			
		||||
            const html = this.#element.dataset.currencyTemplate
 | 
			
		||||
                .replaceAll("CURRENCY_INDEX", escapeHtml(String(newIndex)));
 | 
			
		||||
            this.#currencyList.insertAdjacentHTML("beforeend", html);
 | 
			
		||||
            const element = document.getElementById("accounting-currency-" + String(newIndex));
 | 
			
		||||
            const element = document.getElementById(`accounting-currency-${String(newIndex)}`);
 | 
			
		||||
            this.#currencies.push(new CurrencySubForm(this, element));
 | 
			
		||||
            this.#resetDeleteCurrencyButtons();
 | 
			
		||||
            this.#initializeDragAndDropReordering();
 | 
			
		||||
@@ -207,8 +207,8 @@ class JournalEntryForm {
 | 
			
		||||
     * @return {string[]} the account codes used in the form
 | 
			
		||||
     */
 | 
			
		||||
    getAccountCodesUsed(debitCredit) {
 | 
			
		||||
        return this.getLineItems(debitCredit).map((lineItem) => lineItem.accountCode)
 | 
			
		||||
            .filter((code) => code !== null);
 | 
			
		||||
        return this.getLineItems(debitCredit).filter((lineItem) => lineItem.account !== null)
 | 
			
		||||
            .map((lineItem) => lineItem.account.code);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
@@ -415,16 +415,16 @@ class CurrencySubForm {
 | 
			
		||||
        this.#element = element;
 | 
			
		||||
        this.form = form;
 | 
			
		||||
        this.index = parseInt(this.#element.dataset.index);
 | 
			
		||||
        const prefix = "accounting-currency-" + String(this.index);
 | 
			
		||||
        this.#control = document.getElementById(prefix + "-control");
 | 
			
		||||
        this.#error = document.getElementById(prefix + "-error");
 | 
			
		||||
        this.#no = document.getElementById(prefix + "-no");
 | 
			
		||||
        this.#code = document.getElementById(prefix + "-code");
 | 
			
		||||
        this.#codeSelect = document.getElementById(prefix + "-code-select");
 | 
			
		||||
        this.#deleteButton = document.getElementById(prefix + "-delete");
 | 
			
		||||
        const debitElement = document.getElementById(prefix + "-debit");
 | 
			
		||||
        const prefix = `accounting-currency-${String(this.index)}`;
 | 
			
		||||
        this.#control = document.getElementById(`${prefix}-control`);
 | 
			
		||||
        this.#error = document.getElementById(`${prefix}-error`);
 | 
			
		||||
        this.#no = document.getElementById(`${prefix}-no`);
 | 
			
		||||
        this.#code = document.getElementById(`${prefix}-code`);
 | 
			
		||||
        this.#codeSelect = document.getElementById(`${prefix}-code-select`);
 | 
			
		||||
        this.#deleteButton = document.getElementById(`${prefix}-delete`);
 | 
			
		||||
        const debitElement = document.getElementById(`${prefix}-debit`);
 | 
			
		||||
        this.#debit = debitElement === null? null: new DebitCreditSubForm(this, debitElement, "debit");
 | 
			
		||||
        const creditElement = document.getElementById(prefix + "-credit");
 | 
			
		||||
        const creditElement = document.getElementById(`${prefix}-credit`);
 | 
			
		||||
        this.#credit = creditElement == null? null: new DebitCreditSubForm(this, creditElement, "credit");
 | 
			
		||||
        this.#codeSelect.onchange = () => this.#code.value = this.#codeSelect.value;
 | 
			
		||||
        this.#deleteButton.onclick = () => {
 | 
			
		||||
@@ -457,11 +457,7 @@ class CurrencySubForm {
 | 
			
		||||
     * @param isShown {boolean} true to show, or false otherwise
 | 
			
		||||
     */
 | 
			
		||||
    setDeleteButtonShown(isShown) {
 | 
			
		||||
        if (isShown) {
 | 
			
		||||
            this.#deleteButton.classList.remove("d-none");
 | 
			
		||||
        } else {
 | 
			
		||||
            this.#deleteButton.classList.add("d-none");
 | 
			
		||||
        }
 | 
			
		||||
        setElementShown(this.#deleteButton, isShown);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
@@ -617,13 +613,13 @@ class DebitCreditSubForm {
 | 
			
		||||
        this.#element = element;
 | 
			
		||||
        this.#currencyIndex = currency.index;
 | 
			
		||||
        this.debitCredit = debitCredit;
 | 
			
		||||
        this.#prefix = "accounting-currency-" + String(this.#currencyIndex) + "-" + debitCredit;
 | 
			
		||||
        this.#content = document.getElementById(this.#prefix + "-content");
 | 
			
		||||
        this.#error = document.getElementById(this.#prefix + "-error");
 | 
			
		||||
        this.#lineItemList = document.getElementById(this.#prefix + "-list");
 | 
			
		||||
        this.#prefix = `accounting-currency-${String(this.#currencyIndex)}-${debitCredit}`;
 | 
			
		||||
        this.#content = document.getElementById(`${this.#prefix}-content`);
 | 
			
		||||
        this.#error = document.getElementById(`${this.#prefix}-error`);
 | 
			
		||||
        this.#lineItemList = document.getElementById(`${this.#prefix}-list`);
 | 
			
		||||
        this.lineItems = Array.from(document.getElementsByClassName(this.#prefix)).map((element) => new LineItemSubForm(this, element));
 | 
			
		||||
        this.#total = document.getElementById(this.#prefix + "-total");
 | 
			
		||||
        this.#addLineItemButton = document.getElementById(this.#prefix + "-add-line-item");
 | 
			
		||||
        this.#total = document.getElementById(`${this.#prefix}-total`);
 | 
			
		||||
        this.#addLineItemButton = document.getElementById(`${this.#prefix}-add-line-item`);
 | 
			
		||||
 | 
			
		||||
        this.#resetContent();
 | 
			
		||||
        this.#addLineItemButton.onclick = () => this.currency.form.lineItemEditor.onAddNew(this);
 | 
			
		||||
@@ -653,7 +649,7 @@ class DebitCreditSubForm {
 | 
			
		||||
            .replaceAll("DEBIT_CREDIT", escapeHtml(this.debitCredit))
 | 
			
		||||
            .replaceAll("LINE_ITEM_INDEX", escapeHtml(String(newIndex)));
 | 
			
		||||
        this.#lineItemList.insertAdjacentHTML("beforeend", html);
 | 
			
		||||
        const lineItem = new LineItemSubForm(this, document.getElementById(this.#prefix + "-" + String(newIndex)));
 | 
			
		||||
        const lineItem = new LineItemSubForm(this, document.getElementById(`${this.#prefix}-${String(newIndex)}`));
 | 
			
		||||
        this.lineItems.push(lineItem);
 | 
			
		||||
        this.#resetContent();
 | 
			
		||||
        this.#resetDeleteLineItemButtons();
 | 
			
		||||
@@ -700,20 +696,19 @@ class DebitCreditSubForm {
 | 
			
		||||
            this.#element.classList.remove("accounting-not-empty");
 | 
			
		||||
            this.#element.classList.add("accounting-clickable");
 | 
			
		||||
            this.#element.dataset.bsToggle = "modal"
 | 
			
		||||
            this.#element.dataset.bsTarget = "#" + this.currency.form.lineItemEditor.modal.id;
 | 
			
		||||
            this.#element.dataset.bsTarget = `#${this.currency.form.lineItemEditor.modal.id}`;
 | 
			
		||||
            this.#element.onclick = () => {
 | 
			
		||||
                this.#element.classList.add("accounting-not-empty");
 | 
			
		||||
                this.currency.form.lineItemEditor.onAddNew(this);
 | 
			
		||||
            };
 | 
			
		||||
            this.#content.classList.add("d-none");
 | 
			
		||||
        } else {
 | 
			
		||||
            this.#element.classList.add("accounting-not-empty");
 | 
			
		||||
            this.#element.classList.remove("accounting-clickable");
 | 
			
		||||
            delete this.#element.dataset.bsToggle;
 | 
			
		||||
            delete this.#element.dataset.bsTarget;
 | 
			
		||||
            this.#element.onclick = null;
 | 
			
		||||
            this.#content.classList.remove("d-none");
 | 
			
		||||
        }
 | 
			
		||||
        setElementShown(this.#content, this.lineItems.length !== 0);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
@@ -784,6 +779,53 @@ class DebitCreditSubForm {
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * A journal entry account.
 | 
			
		||||
 *
 | 
			
		||||
 */
 | 
			
		||||
class JournalEntryAccount {
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * The account code
 | 
			
		||||
     * @type {string}
 | 
			
		||||
     */
 | 
			
		||||
    code;
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * The account text
 | 
			
		||||
     * @type {string}
 | 
			
		||||
     */
 | 
			
		||||
    text;
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Whether the line items in the account needs offset
 | 
			
		||||
     * @type {boolean}
 | 
			
		||||
     */
 | 
			
		||||
    isNeedOffset;
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Constructs a journal entry account.
 | 
			
		||||
     *
 | 
			
		||||
     * @param code {string} the account code
 | 
			
		||||
     * @param text {string} the account text
 | 
			
		||||
     * @param isNeedOffset {boolean} true if the line items in the account needs offset, or false otherwise
 | 
			
		||||
     */
 | 
			
		||||
    constructor(code, text, isNeedOffset) {
 | 
			
		||||
        this.code = code;
 | 
			
		||||
        this.text = text;
 | 
			
		||||
        this.isNeedOffset = isNeedOffset;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Returns a copy of the account.
 | 
			
		||||
     *
 | 
			
		||||
     * @return {JournalEntryAccount} the copy of the account
 | 
			
		||||
     */
 | 
			
		||||
    copy() {
 | 
			
		||||
        return new JournalEntryAccount(this.code, this.text, this.isNeedOffset);
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * The line item sub-form.
 | 
			
		||||
 *
 | 
			
		||||
@@ -910,20 +952,20 @@ class LineItemSubForm {
 | 
			
		||||
        this.debitCredit = element.dataset.debitCredit;
 | 
			
		||||
        this.index = parseInt(element.dataset.lineItemIndex);
 | 
			
		||||
        this.isMatched = element.classList.contains("accounting-matched-line-item");
 | 
			
		||||
        const prefix = "accounting-currency-" + element.dataset.currencyIndex + "-" + this.debitCredit + "-" + String(this.index);
 | 
			
		||||
        this.#control = document.getElementById(prefix + "-control");
 | 
			
		||||
        this.#error = document.getElementById(prefix + "-error");
 | 
			
		||||
        this.#no = document.getElementById(prefix + "-no");
 | 
			
		||||
        this.#accountCode = document.getElementById(prefix + "-account-code");
 | 
			
		||||
        this.#accountText = document.getElementById(prefix + "-account-text");
 | 
			
		||||
        this.#description = document.getElementById(prefix + "-description");
 | 
			
		||||
        this.#descriptionText = document.getElementById(prefix + "-description-text");
 | 
			
		||||
        this.#originalLineItemId = document.getElementById(prefix + "-original-line-item-id");
 | 
			
		||||
        this.#originalLineItemText = document.getElementById(prefix + "-original-line-item-text");
 | 
			
		||||
        this.#offsets = document.getElementById(prefix + "-offsets");
 | 
			
		||||
        this.#amount = document.getElementById(prefix + "-amount");
 | 
			
		||||
        this.#amountText = document.getElementById(prefix + "-amount-text");
 | 
			
		||||
        this.#deleteButton = document.getElementById(prefix + "-delete");
 | 
			
		||||
        const prefix = `accounting-currency-${element.dataset.currencyIndex}-${this.debitCredit}-${String(this.index)}`;
 | 
			
		||||
        this.#control = document.getElementById(`${prefix}-control`);
 | 
			
		||||
        this.#error = document.getElementById(`${prefix}-error`);
 | 
			
		||||
        this.#no = document.getElementById(`${prefix}-no`);
 | 
			
		||||
        this.#accountCode = document.getElementById(`${prefix}-account-code`);
 | 
			
		||||
        this.#accountText = document.getElementById(`${prefix}-account-text`);
 | 
			
		||||
        this.#description = document.getElementById(`${prefix}-description`);
 | 
			
		||||
        this.#descriptionText = document.getElementById(`${prefix}-description-text`);
 | 
			
		||||
        this.#originalLineItemId = document.getElementById(`${prefix}-original-line-item-id`);
 | 
			
		||||
        this.#originalLineItemText = document.getElementById(`${prefix}-original-line-item-text`);
 | 
			
		||||
        this.#offsets = document.getElementById(`${prefix}-offsets`);
 | 
			
		||||
        this.#amount = document.getElementById(`${prefix}-amount`);
 | 
			
		||||
        this.#amountText = document.getElementById(`${prefix}-amount-text`);
 | 
			
		||||
        this.#deleteButton = document.getElementById(`${prefix}-delete`);
 | 
			
		||||
        this.#control.onclick = () => this.debitCreditSubForm.currency.form.lineItemEditor.onEdit(this);
 | 
			
		||||
        this.#deleteButton.onclick = () => {
 | 
			
		||||
            this.#element.parentElement.removeChild(this.#element);
 | 
			
		||||
@@ -940,15 +982,6 @@ class LineItemSubForm {
 | 
			
		||||
        this.#no.value = String(siblings.indexOf(this.#element) + 1);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Returns whether the line item needs offset.
 | 
			
		||||
     *
 | 
			
		||||
     * @return {boolean} true if the line item needs offset, or false otherwise
 | 
			
		||||
     */
 | 
			
		||||
    get isNeedOffset() {
 | 
			
		||||
        return "isNeedOffset" in this.#element.dataset;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Returns the ID of the original line item.
 | 
			
		||||
     *
 | 
			
		||||
@@ -986,21 +1019,12 @@ class LineItemSubForm {
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Returns the account code.
 | 
			
		||||
     * Returns the account.
 | 
			
		||||
     *
 | 
			
		||||
     * @return {string|null} the account code
 | 
			
		||||
     * @return {JournalEntryAccount|null} the account
 | 
			
		||||
     */
 | 
			
		||||
    get accountCode() {
 | 
			
		||||
        return this.#accountCode.value === ""? null: this.#accountCode.value;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Returns the account text.
 | 
			
		||||
     *
 | 
			
		||||
     * @return {string|null} the account text
 | 
			
		||||
     */
 | 
			
		||||
    get accountText() {
 | 
			
		||||
        return this.#accountCode.dataset.text === ""? null: this.#accountCode.dataset.text;
 | 
			
		||||
    get account() {
 | 
			
		||||
        return this.#accountCode.value === null? null: new JournalEntryAccount(this.#accountCode.value, this.#accountCode.dataset.text, this.#accountCode.classList.contains("accounting-account-is-need-offset"));
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
@@ -1027,11 +1051,7 @@ class LineItemSubForm {
 | 
			
		||||
     * @param isShown {boolean} true to show, or false otherwise
 | 
			
		||||
     */
 | 
			
		||||
    setDeleteButtonShown(isShown) {
 | 
			
		||||
        if (isShown) {
 | 
			
		||||
            this.#deleteButton.classList.remove("d-none");
 | 
			
		||||
        } else {
 | 
			
		||||
            this.#deleteButton.classList.add("d-none");
 | 
			
		||||
        }
 | 
			
		||||
        setElementShown(this.#deleteButton, isShown);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
@@ -1061,24 +1081,24 @@ class LineItemSubForm {
 | 
			
		||||
     * @param editor {JournalEntryLineItemEditor} the line item editor
 | 
			
		||||
     */
 | 
			
		||||
    save(editor) {
 | 
			
		||||
        if (editor.isNeedOffset) {
 | 
			
		||||
            this.#offsets.classList.remove("d-none");
 | 
			
		||||
        } else {
 | 
			
		||||
            this.#offsets.classList.add("d-none");
 | 
			
		||||
        }
 | 
			
		||||
        setElementShown(this.#offsets, editor.account.isNeedOffset);
 | 
			
		||||
        this.#originalLineItemId.value = editor.originalLineItemId === null? "": editor.originalLineItemId;
 | 
			
		||||
        this.#originalLineItemId.dataset.date = editor.originalLineItemDate === null? "": editor.originalLineItemDate;
 | 
			
		||||
        this.#originalLineItemId.dataset.text = editor.originalLineItemText === null? "": editor.originalLineItemText;
 | 
			
		||||
        setElementShown(this.#originalLineItemText, editor.originalLineItemText !== null);
 | 
			
		||||
        if (editor.originalLineItemText === null) {
 | 
			
		||||
            this.#originalLineItemText.classList.add("d-none");
 | 
			
		||||
            this.#originalLineItemText.innerText = "";
 | 
			
		||||
        } else {
 | 
			
		||||
            this.#originalLineItemText.classList.remove("d-none");
 | 
			
		||||
            this.#originalLineItemText.innerText = A_("Offset %(item)s", {item: editor.originalLineItemText});
 | 
			
		||||
        }
 | 
			
		||||
        this.#accountCode.value = editor.accountCode === null? "": editor.accountCode;
 | 
			
		||||
        this.#accountCode.dataset.text = editor.accountText === null? "": editor.accountText;
 | 
			
		||||
        this.#accountText.innerText = editor.accountText === null? "": editor.accountText;
 | 
			
		||||
        this.#accountCode.value = editor.account.code;
 | 
			
		||||
        this.#accountCode.dataset.text = editor.account.text;
 | 
			
		||||
        if (editor.account.isNeedOffset) {
 | 
			
		||||
            this.#accountCode.classList.add("accounting-account-is-need-offset");
 | 
			
		||||
        } else {
 | 
			
		||||
            this.#accountCode.classList.remove("accounting-account-is-need-offset");
 | 
			
		||||
        }
 | 
			
		||||
        this.#accountText.innerText = editor.account.text;
 | 
			
		||||
        this.#description.value = editor.description === null? "": editor.description;
 | 
			
		||||
        this.#descriptionText.innerText = editor.description === null? "": editor.description;
 | 
			
		||||
        this.#amount.value = editor.amount;
 | 
			
		||||
@@ -1119,3 +1139,18 @@ function formatDecimal(number) {
 | 
			
		||||
    const whole = Number(number.minus(frac)).toLocaleString();
 | 
			
		||||
    return whole + String(frac).substring(1);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Sets whether an element is shown.
 | 
			
		||||
 *
 | 
			
		||||
 * @param element {HTMLElement} the element
 | 
			
		||||
 * @param isShown {boolean} true to show, or false otherwise
 | 
			
		||||
 * @private
 | 
			
		||||
 */
 | 
			
		||||
function setElementShown(element, isShown) {
 | 
			
		||||
    if (isShown) {
 | 
			
		||||
        element.classList.remove("d-none");
 | 
			
		||||
    } else {
 | 
			
		||||
        element.classList.add("d-none");
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -1,4 +1,4 @@
 | 
			
		||||
/* The Mia! Accounting Flask Project
 | 
			
		||||
/* The Mia! Accounting Project
 | 
			
		||||
 * journal-entry-line-item-editor.js: The JavaScript for the journal entry line item editor
 | 
			
		||||
 */
 | 
			
		||||
 | 
			
		||||
@@ -148,12 +148,6 @@ class JournalEntryLineItemEditor {
 | 
			
		||||
     */
 | 
			
		||||
    #debitCreditSubForm;
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Whether the journal entry line item needs offset
 | 
			
		||||
     * @type {boolean}
 | 
			
		||||
     */
 | 
			
		||||
    isNeedOffset = false;
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * The ID of the original line item
 | 
			
		||||
     * @type {string|null}
 | 
			
		||||
@@ -173,16 +167,16 @@ class JournalEntryLineItemEditor {
 | 
			
		||||
    originalLineItemText = null;
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * The account code
 | 
			
		||||
     * @type {string|null}
 | 
			
		||||
     * The account
 | 
			
		||||
     * @type {JournalEntryAccount|null}
 | 
			
		||||
     */
 | 
			
		||||
    accountCode = null;
 | 
			
		||||
    account = null;
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * The account text
 | 
			
		||||
     * @type {string|null}
 | 
			
		||||
     * Whether the user has confirmed the account
 | 
			
		||||
     * @type {boolean}
 | 
			
		||||
     */
 | 
			
		||||
    accountText = null;
 | 
			
		||||
    isAccountConfirmed = false;
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * The description
 | 
			
		||||
@@ -216,20 +210,20 @@ class JournalEntryLineItemEditor {
 | 
			
		||||
    constructor(form) {
 | 
			
		||||
        this.form = form;
 | 
			
		||||
        this.#element = document.getElementById(this.#prefix);
 | 
			
		||||
        this.modal = document.getElementById(this.#prefix + "-modal");
 | 
			
		||||
        this.#originalLineItemContainer = document.getElementById(this.#prefix + "-original-line-item-container");
 | 
			
		||||
        this.#originalLineItemControl = document.getElementById(this.#prefix + "-original-line-item-control");
 | 
			
		||||
        this.#originalLineItemText = document.getElementById(this.#prefix + "-original-line-item");
 | 
			
		||||
        this.#originalLineItemError = document.getElementById(this.#prefix + "-original-line-item-error");
 | 
			
		||||
        this.#originalLineItemDelete = document.getElementById(this.#prefix + "-original-line-item-delete");
 | 
			
		||||
        this.#descriptionControl = document.getElementById(this.#prefix + "-description-control");
 | 
			
		||||
        this.#descriptionText = document.getElementById(this.#prefix + "-description");
 | 
			
		||||
        this.#descriptionError = document.getElementById(this.#prefix + "-description-error");
 | 
			
		||||
        this.#accountControl = document.getElementById(this.#prefix + "-account-control");
 | 
			
		||||
        this.#accountText = document.getElementById(this.#prefix + "-account");
 | 
			
		||||
        this.#accountError = document.getElementById(this.#prefix + "-account-error")
 | 
			
		||||
        this.#amountInput = document.getElementById(this.#prefix + "-amount");
 | 
			
		||||
        this.#amountError = document.getElementById(this.#prefix + "-amount-error");
 | 
			
		||||
        this.modal = document.getElementById(`${this.#prefix}-modal`);
 | 
			
		||||
        this.#originalLineItemContainer = document.getElementById(`${this.#prefix}-original-line-item-container`);
 | 
			
		||||
        this.#originalLineItemControl = document.getElementById(`${this.#prefix}-original-line-item-control`);
 | 
			
		||||
        this.#originalLineItemText = document.getElementById(`${this.#prefix}-original-line-item`);
 | 
			
		||||
        this.#originalLineItemError = document.getElementById(`${this.#prefix}-original-line-item-error`);
 | 
			
		||||
        this.#originalLineItemDelete = document.getElementById(`${this.#prefix}-original-line-item-delete`);
 | 
			
		||||
        this.#descriptionControl = document.getElementById(`${this.#prefix}-description-control`);
 | 
			
		||||
        this.#descriptionText = document.getElementById(`${this.#prefix}-description`);
 | 
			
		||||
        this.#descriptionError = document.getElementById(`${this.#prefix}-description-error`);
 | 
			
		||||
        this.#accountControl = document.getElementById(`${this.#prefix}-account-control`);
 | 
			
		||||
        this.#accountText = document.getElementById(`${this.#prefix}-account`);
 | 
			
		||||
        this.#accountError = document.getElementById(`${this.#prefix}-account-error`)
 | 
			
		||||
        this.#amountInput = document.getElementById(`${this.#prefix}-amount`);
 | 
			
		||||
        this.#amountError = document.getElementById(`${this.#prefix}-amount-error`);
 | 
			
		||||
        this.#descriptionEditors = DescriptionEditor.getInstances(this);
 | 
			
		||||
        this.#accountSelectors = JournalEntryAccountSelector.getInstances(this);
 | 
			
		||||
        this.originalLineItemSelector = new OriginalLineItemSelector(this);
 | 
			
		||||
@@ -276,7 +270,6 @@ class JournalEntryLineItemEditor {
 | 
			
		||||
     * @param originalLineItem {OriginalLineItem} the original line item
 | 
			
		||||
     */
 | 
			
		||||
    saveOriginalLineItem(originalLineItem) {
 | 
			
		||||
        this.isNeedOffset = false;
 | 
			
		||||
        this.#originalLineItemContainer.classList.remove("d-none");
 | 
			
		||||
        this.#originalLineItemControl.classList.add("accounting-not-empty");
 | 
			
		||||
        this.originalLineItemId = originalLineItem.id;
 | 
			
		||||
@@ -292,9 +285,9 @@ class JournalEntryLineItemEditor {
 | 
			
		||||
        this.description = originalLineItem.description === ""? null: originalLineItem.description;
 | 
			
		||||
        this.#descriptionText.innerText = originalLineItem.description;
 | 
			
		||||
        this.#accountControl.classList.add("accounting-not-empty");
 | 
			
		||||
        this.accountCode = originalLineItem.accountCode;
 | 
			
		||||
        this.accountText = originalLineItem.accountText;
 | 
			
		||||
        this.#accountText.innerText = originalLineItem.accountText;
 | 
			
		||||
        this.account = originalLineItem.account.copy();
 | 
			
		||||
        this.isAccountConfirmed = false;
 | 
			
		||||
        this.#accountText.innerText = this.account.text;
 | 
			
		||||
        this.#amountInput.value = String(originalLineItem.netBalance);
 | 
			
		||||
        this.#amountInput.max = String(originalLineItem.netBalance);
 | 
			
		||||
        this.#amountInput.min = "0";
 | 
			
		||||
@@ -306,7 +299,6 @@ class JournalEntryLineItemEditor {
 | 
			
		||||
     *
 | 
			
		||||
     */
 | 
			
		||||
    clearOriginalLineItem() {
 | 
			
		||||
        this.isNeedOffset = false;
 | 
			
		||||
        this.#originalLineItemContainer.classList.add("d-none");
 | 
			
		||||
        this.#originalLineItemControl.classList.remove("accounting-not-empty");
 | 
			
		||||
        this.originalLineItemId = null;
 | 
			
		||||
@@ -315,8 +307,8 @@ class JournalEntryLineItemEditor {
 | 
			
		||||
        this.#originalLineItemText.innerText = "";
 | 
			
		||||
        this.#setEnableDescriptionAccount(true);
 | 
			
		||||
        this.#accountControl.classList.remove("accounting-not-empty");
 | 
			
		||||
        this.accountCode = null;
 | 
			
		||||
        this.accountText = null;
 | 
			
		||||
        this.account = null;
 | 
			
		||||
        this.isAccountConfirmed = false;
 | 
			
		||||
        this.#accountText.innerText = "";
 | 
			
		||||
        this.#amountInput.max = "";
 | 
			
		||||
    }
 | 
			
		||||
@@ -324,47 +316,35 @@ class JournalEntryLineItemEditor {
 | 
			
		||||
    /**
 | 
			
		||||
     * Saves the description from the description editor.
 | 
			
		||||
     *
 | 
			
		||||
     * @param description {string} the description
 | 
			
		||||
     * @param editor {DescriptionEditor} the description editor
 | 
			
		||||
     */
 | 
			
		||||
    saveDescription(description) {
 | 
			
		||||
        if (description === "") {
 | 
			
		||||
    saveDescription(editor) {
 | 
			
		||||
        if (editor.selectedAccount !== null) {
 | 
			
		||||
            this.#accountControl.classList.add("accounting-not-empty");
 | 
			
		||||
            this.account = editor.selectedAccount.copy();
 | 
			
		||||
            this.#accountText.innerText = editor.selectedAccount.text;
 | 
			
		||||
            this.isAccountConfirmed = editor.isAccountConfirmed;
 | 
			
		||||
            this.#validateAccount();
 | 
			
		||||
        }
 | 
			
		||||
        if (editor.description === "") {
 | 
			
		||||
            this.#descriptionControl.classList.remove("accounting-not-empty");
 | 
			
		||||
        } else {
 | 
			
		||||
            this.#descriptionControl.classList.add("accounting-not-empty");
 | 
			
		||||
        }
 | 
			
		||||
        this.description = description === ""? null: description;
 | 
			
		||||
        this.#descriptionText.innerText = description;
 | 
			
		||||
        this.description = editor.description === ""? null: editor.description;
 | 
			
		||||
        this.#descriptionText.innerText = editor.description;
 | 
			
		||||
        this.#validateDescription();
 | 
			
		||||
        bootstrap.Modal.getOrCreateInstance(this.modal).show();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Saves the description with the suggested account from the description editor.
 | 
			
		||||
     *
 | 
			
		||||
     * @param description {string} the description
 | 
			
		||||
     * @param accountCode {string} the account code
 | 
			
		||||
     * @param accountText {string} the account text
 | 
			
		||||
     * @param isAccountNeedOffset {boolean} true if the line items in the account need offset, or false otherwise
 | 
			
		||||
     */
 | 
			
		||||
    saveDescriptionWithAccount(description, accountCode, accountText, isAccountNeedOffset) {
 | 
			
		||||
        this.isNeedOffset = isAccountNeedOffset;
 | 
			
		||||
        this.#accountControl.classList.add("accounting-not-empty");
 | 
			
		||||
        this.accountCode = accountCode;
 | 
			
		||||
        this.accountText = accountText;
 | 
			
		||||
        this.#accountText.innerText = accountText;
 | 
			
		||||
        this.#validateAccount();
 | 
			
		||||
        this.saveDescription(description)
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Clears the account.
 | 
			
		||||
     *
 | 
			
		||||
     */
 | 
			
		||||
    clearAccount() {
 | 
			
		||||
        this.isNeedOffset = false;
 | 
			
		||||
        this.#accountControl.classList.remove("accounting-not-empty");
 | 
			
		||||
        this.accountCode = null;
 | 
			
		||||
        this.accountText = null;
 | 
			
		||||
        this.account = null;
 | 
			
		||||
        this.isAccountConfirmed = false;
 | 
			
		||||
        this.#accountText.innerText = "";
 | 
			
		||||
        this.#validateAccount();
 | 
			
		||||
    }
 | 
			
		||||
@@ -375,10 +355,9 @@ class JournalEntryLineItemEditor {
 | 
			
		||||
     * @param account {JournalEntryAccountOption} the selected account
 | 
			
		||||
     */
 | 
			
		||||
    saveAccount(account) {
 | 
			
		||||
        this.isNeedOffset = account.isNeedOffset;
 | 
			
		||||
        this.#accountControl.classList.add("accounting-not-empty");
 | 
			
		||||
        this.accountCode = account.code;
 | 
			
		||||
        this.accountText = account.text;
 | 
			
		||||
        this.account = new JournalEntryAccount(account.code, account.text, account.isNeedOffset);
 | 
			
		||||
        this.isAccountConfirmed = true;
 | 
			
		||||
        this.#accountText.innerText = account.text;
 | 
			
		||||
        this.#validateAccount();
 | 
			
		||||
    }
 | 
			
		||||
@@ -427,7 +406,7 @@ class JournalEntryLineItemEditor {
 | 
			
		||||
     * @return {boolean} true if valid, or false otherwise
 | 
			
		||||
     */
 | 
			
		||||
    #validateAccount() {
 | 
			
		||||
        if (this.accountCode === null) {
 | 
			
		||||
        if (this.account === null) {
 | 
			
		||||
            this.#accountControl.classList.add("is-invalid");
 | 
			
		||||
            this.#accountError.innerText = A_("Please select the account.");
 | 
			
		||||
            return false;
 | 
			
		||||
@@ -486,7 +465,6 @@ class JournalEntryLineItemEditor {
 | 
			
		||||
        this.lineItem = null;
 | 
			
		||||
        this.#debitCreditSubForm = debitCredit;
 | 
			
		||||
        this.debitCredit = this.#debitCreditSubForm.debitCredit;
 | 
			
		||||
        this.isNeedOffset = false;
 | 
			
		||||
        this.#originalLineItemContainer.classList.add("d-none");
 | 
			
		||||
        this.#originalLineItemControl.classList.remove("accounting-not-empty");
 | 
			
		||||
        this.#originalLineItemControl.classList.remove("is-invalid");
 | 
			
		||||
@@ -502,8 +480,8 @@ class JournalEntryLineItemEditor {
 | 
			
		||||
        this.#descriptionError.innerText = ""
 | 
			
		||||
        this.#accountControl.classList.remove("accounting-not-empty");
 | 
			
		||||
        this.#accountControl.classList.remove("is-invalid");
 | 
			
		||||
        this.accountCode = null;
 | 
			
		||||
        this.accountText = null;
 | 
			
		||||
        this.account = null;
 | 
			
		||||
        this.isAccountConfirmed = false;
 | 
			
		||||
        this.#accountText.innerText = "";
 | 
			
		||||
        this.#accountError.innerText = "";
 | 
			
		||||
        this.#amountInput.value = "";
 | 
			
		||||
@@ -522,7 +500,6 @@ class JournalEntryLineItemEditor {
 | 
			
		||||
        this.lineItem = lineItem;
 | 
			
		||||
        this.#debitCreditSubForm = lineItem.debitCreditSubForm;
 | 
			
		||||
        this.debitCredit = this.#debitCreditSubForm.debitCredit;
 | 
			
		||||
        this.isNeedOffset = lineItem.isNeedOffset;
 | 
			
		||||
        this.originalLineItemId = lineItem.originalLineItemId;
 | 
			
		||||
        this.originalLineItemDate = lineItem.originalLineItemDate;
 | 
			
		||||
        this.originalLineItemText = lineItem.originalLineItemText;
 | 
			
		||||
@@ -542,14 +519,14 @@ class JournalEntryLineItemEditor {
 | 
			
		||||
            this.#descriptionControl.classList.add("accounting-not-empty");
 | 
			
		||||
        }
 | 
			
		||||
        this.#descriptionText.innerText = this.description === null? "": this.description;
 | 
			
		||||
        if (lineItem.accountCode === null) {
 | 
			
		||||
        this.account = lineItem.account;
 | 
			
		||||
        this.isAccountConfirmed = true;
 | 
			
		||||
        if (this.account === null) {
 | 
			
		||||
            this.#accountControl.classList.remove("accounting-not-empty");
 | 
			
		||||
        } else {
 | 
			
		||||
            this.#accountControl.classList.add("accounting-not-empty");
 | 
			
		||||
        }
 | 
			
		||||
        this.accountCode = lineItem.accountCode;
 | 
			
		||||
        this.accountText = lineItem.accountText;
 | 
			
		||||
        this.#accountText.innerText = this.accountText;
 | 
			
		||||
        this.#accountText.innerText = this.account.text;
 | 
			
		||||
        this.#amountInput.value = lineItem.amount === null? "": String(lineItem.amount);
 | 
			
		||||
        const maxAmount = this.#getMaxAmount();
 | 
			
		||||
        this.#amountInput.max = maxAmount === null? "": maxAmount;
 | 
			
		||||
@@ -577,11 +554,11 @@ class JournalEntryLineItemEditor {
 | 
			
		||||
    #setEnableDescriptionAccount(isEnabled) {
 | 
			
		||||
        if (isEnabled) {
 | 
			
		||||
            this.#descriptionControl.dataset.bsToggle = "modal";
 | 
			
		||||
            this.#descriptionControl.dataset.bsTarget = "#accounting-description-editor-" + this.#debitCreditSubForm.debitCredit + "-modal";
 | 
			
		||||
            this.#descriptionControl.dataset.bsTarget = `#accounting-description-editor-${this.#debitCreditSubForm.debitCredit}-modal`;
 | 
			
		||||
            this.#descriptionControl.classList.remove("accounting-disabled");
 | 
			
		||||
            this.#descriptionControl.classList.add("accounting-clickable");
 | 
			
		||||
            this.#accountControl.dataset.bsToggle = "modal";
 | 
			
		||||
            this.#accountControl.dataset.bsTarget = "#accounting-account-selector-" + this.#debitCreditSubForm.debitCredit + "-modal";
 | 
			
		||||
            this.#accountControl.dataset.bsTarget = `#accounting-account-selector-${this.#debitCreditSubForm.debitCredit}-modal`;
 | 
			
		||||
            this.#accountControl.classList.remove("accounting-disabled");
 | 
			
		||||
            this.#accountControl.classList.add("accounting-clickable");
 | 
			
		||||
        } else {
 | 
			
		||||
@@ -596,3 +573,4 @@ class JournalEntryLineItemEditor {
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -1,4 +1,4 @@
 | 
			
		||||
/* The Mia! Accounting Flask Project
 | 
			
		||||
/* The Mia! Accounting Project
 | 
			
		||||
 * journal-entry-order.js: The JavaScript for the journal entry order
 | 
			
		||||
 */
 | 
			
		||||
 | 
			
		||||
@@ -29,7 +29,7 @@ document.addEventListener("DOMContentLoaded", () => {
 | 
			
		||||
        const onReorder = () => {
 | 
			
		||||
            const accounts = Array.from(list.children);
 | 
			
		||||
            for (let i = 0; i < accounts.length; i++) {
 | 
			
		||||
                const no = document.getElementById("accounting-order-" + accounts[i].dataset.id + "-no");
 | 
			
		||||
                const no = document.getElementById(`accounting-order-${accounts[i].dataset.id}-no`);
 | 
			
		||||
                no.value = String(i + 1);
 | 
			
		||||
            }
 | 
			
		||||
        };
 | 
			
		||||
 
 | 
			
		||||
@@ -1,4 +1,4 @@
 | 
			
		||||
/* The Mia! Accounting Flask Project
 | 
			
		||||
/* The Mia! Accounting Project
 | 
			
		||||
 * material-fab-speed-dial.js: The JavaScript for the speed dial for the material floating buttons
 | 
			
		||||
 */
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -1,4 +1,4 @@
 | 
			
		||||
/* The Mia! Accounting Flask Project
 | 
			
		||||
/* The Mia! Accounting Project
 | 
			
		||||
 * account-form.js: The JavaScript for the account form
 | 
			
		||||
 */
 | 
			
		||||
 | 
			
		||||
@@ -242,12 +242,12 @@ class RecurringExpenseIncomeSubForm {
 | 
			
		||||
        this.#form = form;
 | 
			
		||||
        this.expenseIncome = expenseIncome;
 | 
			
		||||
        this.editor = new RecurringItemEditor(this);
 | 
			
		||||
        this.#prefix = "accounting-recurring-" + expenseIncome;
 | 
			
		||||
        this.#prefix = `accounting-recurring-${expenseIncome}`;
 | 
			
		||||
        this.#element = document.getElementById(this.#prefix);
 | 
			
		||||
        this.#content = document.getElementById(this.#prefix + "-content");
 | 
			
		||||
        this.#itemList = document.getElementById(this.#prefix + "-list");
 | 
			
		||||
        this.#items = Array.from(document.getElementsByClassName(this.#prefix + "-item")).map((element) => new RecurringItemSubForm(this, element));
 | 
			
		||||
        this.#addButton = document.getElementById(this.#prefix + "-add");
 | 
			
		||||
        this.#content = document.getElementById(`${this.#prefix}-content`);
 | 
			
		||||
        this.#itemList = document.getElementById(`${this.#prefix}-list`);
 | 
			
		||||
        this.#items = Array.from(document.getElementsByClassName(`${this.#prefix}-item`)).map((element) => new RecurringItemSubForm(this, element));
 | 
			
		||||
        this.#addButton = document.getElementById(`${this.#prefix}-add`);
 | 
			
		||||
 | 
			
		||||
        this.#resetContent();
 | 
			
		||||
        this.#addButton.onclick = () => this.editor.onAddNew();
 | 
			
		||||
@@ -265,7 +265,7 @@ class RecurringExpenseIncomeSubForm {
 | 
			
		||||
            .replaceAll("EXPENSE_INCOME", escapeHtml(this.expenseIncome))
 | 
			
		||||
            .replaceAll("ITEM_INDEX", escapeHtml(String(newIndex)));
 | 
			
		||||
        this.#itemList.insertAdjacentHTML("beforeend", html);
 | 
			
		||||
        const element = document.getElementById(this.#prefix + "-" + String(newIndex))
 | 
			
		||||
        const element = document.getElementById(`${this.#prefix}-${String(newIndex)}`)
 | 
			
		||||
        const item = new RecurringItemSubForm(this, element);
 | 
			
		||||
        this.#items.push(item);
 | 
			
		||||
        this.#resetContent();
 | 
			
		||||
@@ -294,7 +294,7 @@ class RecurringExpenseIncomeSubForm {
 | 
			
		||||
            this.#element.classList.remove("accounting-not-empty");
 | 
			
		||||
            this.#element.classList.add("accounting-clickable");
 | 
			
		||||
            this.#element.dataset.bsToggle = "modal"
 | 
			
		||||
            this.#element.dataset.bsTarget = "#" + this.editor.modal.id;
 | 
			
		||||
            this.#element.dataset.bsTarget = `#${this.editor.modal.id}`;
 | 
			
		||||
            this.#element.onclick = () => this.editor.onAddNew();
 | 
			
		||||
            this.#content.classList.add("d-none");
 | 
			
		||||
        } else {
 | 
			
		||||
@@ -441,17 +441,17 @@ class RecurringItemSubForm {
 | 
			
		||||
        this.#expenseIncomeSubForm = expenseIncomeSubForm
 | 
			
		||||
        this.#element = element;
 | 
			
		||||
        this.itemIndex = parseInt(element.dataset.itemIndex);
 | 
			
		||||
        const prefix = "accounting-recurring-" + expenseIncomeSubForm.expenseIncome + "-" + element.dataset.itemIndex;
 | 
			
		||||
        this.#control = document.getElementById(prefix + "-control");
 | 
			
		||||
        this.#error = document.getElementById(prefix + "-error");
 | 
			
		||||
        this.#no = document.getElementById(prefix + "-no");
 | 
			
		||||
        this.#name = document.getElementById(prefix + "-name");
 | 
			
		||||
        this.#nameText = document.getElementById(prefix + "-name-text");
 | 
			
		||||
        this.#accountCode = document.getElementById(prefix + "-account-code");
 | 
			
		||||
        this.#accountText = document.getElementById(prefix + "-account-text");
 | 
			
		||||
        this.#descriptionTemplate = document.getElementById(prefix + "-description-template");
 | 
			
		||||
        this.#descriptionTemplateText = document.getElementById(prefix + "-description-template-text");
 | 
			
		||||
        this.deleteButton = document.getElementById(prefix + "-delete");
 | 
			
		||||
        const prefix = `accounting-recurring-${expenseIncomeSubForm.expenseIncome}-${element.dataset.itemIndex}`;
 | 
			
		||||
        this.#control = document.getElementById(`${prefix}-control`);
 | 
			
		||||
        this.#error = document.getElementById(`${prefix}-error`);
 | 
			
		||||
        this.#no = document.getElementById(`${prefix}-no`);
 | 
			
		||||
        this.#name = document.getElementById(`${prefix}-name`);
 | 
			
		||||
        this.#nameText = document.getElementById(`${prefix}-name-text`);
 | 
			
		||||
        this.#accountCode = document.getElementById(`${prefix}-account-code`);
 | 
			
		||||
        this.#accountText = document.getElementById(`${prefix}-account-text`);
 | 
			
		||||
        this.#descriptionTemplate = document.getElementById(`${prefix}-description-template`);
 | 
			
		||||
        this.#descriptionTemplateText = document.getElementById(`${prefix}-description-template-text`);
 | 
			
		||||
        this.deleteButton = document.getElementById(`${prefix}-delete`);
 | 
			
		||||
 | 
			
		||||
        this.#control.onclick = () => this.#expenseIncomeSubForm.editor.onEdit(this);
 | 
			
		||||
        this.deleteButton.onclick = () => {
 | 
			
		||||
@@ -652,16 +652,16 @@ class RecurringItemEditor {
 | 
			
		||||
    constructor(subForm) {
 | 
			
		||||
        this.#subForm = subForm;
 | 
			
		||||
        this.expenseIncome = subForm.expenseIncome;
 | 
			
		||||
        const prefix = "accounting-recurring-item-editor-" + subForm.expenseIncome;
 | 
			
		||||
        const prefix = `accounting-recurring-item-editor-${subForm.expenseIncome}`;
 | 
			
		||||
        this.#form = document.getElementById(prefix);
 | 
			
		||||
        this.modal = document.getElementById(prefix + "-modal");
 | 
			
		||||
        this.#name = document.getElementById(prefix + "-name");
 | 
			
		||||
        this.#nameError = document.getElementById(prefix + "-name-error");
 | 
			
		||||
        this.#accountControl = document.getElementById(prefix + "-account-control");
 | 
			
		||||
        this.#accountContainer = document.getElementById(prefix + "-account");
 | 
			
		||||
        this.#accountError = document.getElementById(prefix + "-account-error");
 | 
			
		||||
        this.#descriptionTemplate = document.getElementById(prefix + "-description-template");
 | 
			
		||||
        this.#descriptionTemplateError = document.getElementById(prefix + "-description-template-error");
 | 
			
		||||
        this.modal = document.getElementById(`${prefix}-modal`);
 | 
			
		||||
        this.#name = document.getElementById(`${prefix}-name`);
 | 
			
		||||
        this.#nameError = document.getElementById(`${prefix}-name-error`);
 | 
			
		||||
        this.#accountControl = document.getElementById(`${prefix}-account-control`);
 | 
			
		||||
        this.#accountContainer = document.getElementById(`${prefix}-account`);
 | 
			
		||||
        this.#accountError = document.getElementById(`${prefix}-account-error`);
 | 
			
		||||
        this.#descriptionTemplate = document.getElementById(`${prefix}-description-template`);
 | 
			
		||||
        this.#descriptionTemplateError = document.getElementById(`${prefix}-description-template-error`);
 | 
			
		||||
        this.#accountSelector = new RecurringAccountSelector(this);
 | 
			
		||||
 | 
			
		||||
        this.#name.onchange = () => this.#validateName();
 | 
			
		||||
@@ -882,12 +882,12 @@ class RecurringAccountSelector {
 | 
			
		||||
    constructor(editor) {
 | 
			
		||||
        this.editor = editor;
 | 
			
		||||
        this.#expenseIncome = editor.expenseIncome;
 | 
			
		||||
        const prefix = "accounting-recurring-accounting-selector-" + editor.expenseIncome;
 | 
			
		||||
        this.#query = document.getElementById(prefix + "-query");
 | 
			
		||||
        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");
 | 
			
		||||
        const prefix = `accounting-recurring-accounting-selector-${editor.expenseIncome}`;
 | 
			
		||||
        this.#query = document.getElementById(`${prefix}-query`);
 | 
			
		||||
        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();
 | 
			
		||||
@@ -944,12 +944,6 @@ class RecurringAccountSelector {
 | 
			
		||||
 */
 | 
			
		||||
class RecurringAccount {
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * The account selector for the recurring item editor
 | 
			
		||||
     * @type {RecurringAccountSelector}
 | 
			
		||||
     */
 | 
			
		||||
    #selector;
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * The element
 | 
			
		||||
     * @type {HTMLLIElement}
 | 
			
		||||
@@ -981,13 +975,12 @@ class RecurringAccount {
 | 
			
		||||
     * @param element {HTMLLIElement} the element
 | 
			
		||||
     */
 | 
			
		||||
    constructor(selector, element) {
 | 
			
		||||
        this.#selector = selector;
 | 
			
		||||
        this.#element = element;
 | 
			
		||||
        this.code = element.dataset.code;
 | 
			
		||||
        this.text = element.dataset.text;
 | 
			
		||||
        this.#queryValues = JSON.parse(element.dataset.queryValues);
 | 
			
		||||
 | 
			
		||||
        this.#element.onclick = () => this.#selector.editor.saveAccount(this);
 | 
			
		||||
        this.#element.onclick = () => selector.editor.saveAccount(this);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
 
 | 
			
		||||
@@ -1,4 +1,4 @@
 | 
			
		||||
/* The Mia! Accounting Flask Project
 | 
			
		||||
/* The Mia! Accounting Project
 | 
			
		||||
 * original-line-item-selector.js: The JavaScript for the original line item selector
 | 
			
		||||
 */
 | 
			
		||||
 | 
			
		||||
@@ -88,10 +88,10 @@ class OriginalLineItemSelector {
 | 
			
		||||
     */
 | 
			
		||||
    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));
 | 
			
		||||
        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));
 | 
			
		||||
        this.#optionById = {};
 | 
			
		||||
        for (const option of this.#options) {
 | 
			
		||||
            this.#optionById[option.id] = option;
 | 
			
		||||
@@ -194,10 +194,10 @@ class OriginalLineItemSelector {
 | 
			
		||||
class OriginalLineItem {
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * The original line item selector
 | 
			
		||||
     * @type {OriginalLineItemSelector}
 | 
			
		||||
     * The journal entry form
 | 
			
		||||
     * @type {JournalEntryForm}
 | 
			
		||||
     */
 | 
			
		||||
    #selector;
 | 
			
		||||
    #form;
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * The element
 | 
			
		||||
@@ -230,16 +230,10 @@ class OriginalLineItem {
 | 
			
		||||
    #currencyCode;
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * The account code
 | 
			
		||||
     * @type {string}
 | 
			
		||||
     * The account
 | 
			
		||||
     * @type {JournalEntryAccount}
 | 
			
		||||
     */
 | 
			
		||||
    accountCode;
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * The account text
 | 
			
		||||
     * @type {string}
 | 
			
		||||
     */
 | 
			
		||||
    accountText;
 | 
			
		||||
    account;
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * The description
 | 
			
		||||
@@ -284,21 +278,20 @@ class OriginalLineItem {
 | 
			
		||||
     * @param element {HTMLLIElement} the element
 | 
			
		||||
     */
 | 
			
		||||
    constructor(selector, element) {
 | 
			
		||||
        this.#selector = selector;
 | 
			
		||||
        this.#form = selector.lineItemEditor.form;
 | 
			
		||||
        this.#element = element;
 | 
			
		||||
        this.id = element.dataset.id;
 | 
			
		||||
        this.date = element.dataset.date;
 | 
			
		||||
        this.#debitCredit = element.dataset.debitCredit;
 | 
			
		||||
        this.#currencyCode = element.dataset.currencyCode;
 | 
			
		||||
        this.accountCode = element.dataset.accountCode;
 | 
			
		||||
        this.accountText = element.dataset.accountText;
 | 
			
		||||
        this.account = new JournalEntryAccount(element.dataset.accountCode, element.dataset.accountText, false);
 | 
			
		||||
        this.description = element.dataset.description;
 | 
			
		||||
        this.bareNetBalance = new Decimal(element.dataset.netBalance);
 | 
			
		||||
        this.netBalance = this.bareNetBalance;
 | 
			
		||||
        this.netBalanceText = document.getElementById("accounting-original-line-item-selector-option-" + this.id + "-net-balance");
 | 
			
		||||
        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 = () => this.#selector.lineItemEditor.saveOriginalLineItem(this);
 | 
			
		||||
        this.#element.onclick = () => selector.lineItemEditor.saveOriginalLineItem(this);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
@@ -339,7 +332,7 @@ class OriginalLineItem {
 | 
			
		||||
     */
 | 
			
		||||
    isMatched(debitCredit, currencyCode, query = null) {
 | 
			
		||||
        return this.netBalance.greaterThan(0)
 | 
			
		||||
            && this.date <= this.#selector.lineItemEditor.form.date
 | 
			
		||||
            && this.date <= this.#form.date
 | 
			
		||||
            && this.#isDebitCreditMatched(debitCredit)
 | 
			
		||||
            && this.#currencyCode === currencyCode
 | 
			
		||||
            && this.#isQueryMatched(query);
 | 
			
		||||
 
 | 
			
		||||
@@ -1,4 +1,4 @@
 | 
			
		||||
/* The Mia! Accounting Flask Project
 | 
			
		||||
/* The Mia! Accounting Project
 | 
			
		||||
 * period-chooser.js: The JavaScript for the period chooser
 | 
			
		||||
 */
 | 
			
		||||
 | 
			
		||||
@@ -51,7 +51,7 @@ class PeriodChooser {
 | 
			
		||||
     */
 | 
			
		||||
    constructor() {
 | 
			
		||||
        const prefix = "accounting-period-chooser";
 | 
			
		||||
        this.modal = document.getElementById(prefix + "-modal");
 | 
			
		||||
        this.modal = document.getElementById(`${prefix}-modal`);
 | 
			
		||||
        for (const cls of [MonthTab, YearTab, DayTab, CustomTab]) {
 | 
			
		||||
            const tab = new cls(this);
 | 
			
		||||
            this.tabPlanes[tab.tabId()] = tab;
 | 
			
		||||
@@ -112,9 +112,9 @@ class TabPlane {
 | 
			
		||||
     */
 | 
			
		||||
    constructor(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.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();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
@@ -164,12 +164,11 @@ class MonthTab extends TabPlane {
 | 
			
		||||
     */
 | 
			
		||||
    constructor(chooser) {
 | 
			
		||||
        super(chooser);
 | 
			
		||||
        const monthChooser = document.getElementById(this.prefix + "-chooser");
 | 
			
		||||
        const monthChooser = document.getElementById(`${this.prefix}-chooser`);
 | 
			
		||||
        if (monthChooser !== null) {
 | 
			
		||||
            let start = monthChooser.dataset.start;
 | 
			
		||||
            this.#monthChooser = new tempusDominus.TempusDominus(monthChooser, {
 | 
			
		||||
                restrictions: {
 | 
			
		||||
                    minDate: new Date(start),
 | 
			
		||||
                    minDate: new Date(monthChooser.dataset.start),
 | 
			
		||||
                },
 | 
			
		||||
                display: {
 | 
			
		||||
                    inline: true,
 | 
			
		||||
@@ -182,9 +181,8 @@ class MonthTab extends TabPlane {
 | 
			
		||||
            });
 | 
			
		||||
            monthChooser.addEventListener(tempusDominus.Namespace.events.change, (e) => {
 | 
			
		||||
                const date = e.detail.date;
 | 
			
		||||
                const year = date.year;
 | 
			
		||||
                const month = date.month + 1;
 | 
			
		||||
                const period = month < 10? year + "-0" + month: year + "-" + month;
 | 
			
		||||
                const zeroPaddedMonth = `0${date.month + 1}`.slice(-2)
 | 
			
		||||
                const period = `${date.year}-${zeroPaddedMonth}`;
 | 
			
		||||
                window.location = chooser.modal.dataset.urlTemplate
 | 
			
		||||
                    .replaceAll("PERIOD", period);
 | 
			
		||||
            });
 | 
			
		||||
@@ -244,8 +242,8 @@ class DayTab extends TabPlane {
 | 
			
		||||
     */
 | 
			
		||||
    constructor(chooser) {
 | 
			
		||||
        super(chooser);
 | 
			
		||||
        this.#date = document.getElementById(this.prefix + "-date");
 | 
			
		||||
        this.#dateError = document.getElementById(this.prefix + "-date-error");
 | 
			
		||||
        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()) {
 | 
			
		||||
@@ -331,11 +329,11 @@ class CustomTab extends TabPlane {
 | 
			
		||||
     */
 | 
			
		||||
    constructor(chooser) {
 | 
			
		||||
        super(chooser);
 | 
			
		||||
        this.#start = document.getElementById(this.prefix + "-start");
 | 
			
		||||
        this.#startError = document.getElementById(this.prefix + "-start-error");
 | 
			
		||||
        this.#end = document.getElementById(this.prefix + "-end");
 | 
			
		||||
        this.#endError = document.getElementById(this.prefix + "-end-error");
 | 
			
		||||
        this.#conform = document.getElementById(this.prefix + "-confirm");
 | 
			
		||||
        this.#start = document.getElementById(`${this.prefix}-start`);
 | 
			
		||||
        this.#startError = document.getElementById(`${this.prefix}-start-error`);
 | 
			
		||||
        this.#end = document.getElementById(`${this.prefix}-end`);
 | 
			
		||||
        this.#endError = document.getElementById(`${this.prefix}-end-error`);
 | 
			
		||||
        this.#conform = document.getElementById(`${this.prefix}-confirm`);
 | 
			
		||||
        if (this.#start !== null) {
 | 
			
		||||
            this.#start.onchange = () => {
 | 
			
		||||
                if (this.#validateStart()) {
 | 
			
		||||
@@ -353,7 +351,7 @@ class CustomTab extends TabPlane {
 | 
			
		||||
                isValid = this.#validateEnd() && isValid;
 | 
			
		||||
                if (isValid) {
 | 
			
		||||
                    window.location = chooser.modal.dataset.urlTemplate
 | 
			
		||||
                        .replaceAll("PERIOD", this.#start.value + "-" + this.#end.value);
 | 
			
		||||
                        .replaceAll("PERIOD", `${this.#start.value}-${this.#end.value}`);
 | 
			
		||||
                }
 | 
			
		||||
            };
 | 
			
		||||
        }
 | 
			
		||||
 
 | 
			
		||||
@@ -1,4 +1,4 @@
 | 
			
		||||
# The Mia! Accounting Flask Project.
 | 
			
		||||
# The Mia! Accounting Project.
 | 
			
		||||
# Author: imacat@mail.imacat.idv.tw (imacat), 2023/2/25
 | 
			
		||||
 | 
			
		||||
#  Copyright (c) 2023 imacat.
 | 
			
		||||
 
 | 
			
		||||
@@ -1,4 +1,4 @@
 | 
			
		||||
# The Mia! Accounting Flask Project.
 | 
			
		||||
# The Mia! Accounting Project.
 | 
			
		||||
# Author: imacat@mail.imacat.idv.tw (imacat), 2023/3/3
 | 
			
		||||
 | 
			
		||||
#  Copyright (c) 2023 imacat.
 | 
			
		||||
 
 | 
			
		||||
@@ -1,5 +1,5 @@
 | 
			
		||||
{#
 | 
			
		||||
The Mia! Accounting Flask Project
 | 
			
		||||
The Mia! Accounting Project
 | 
			
		||||
create.html: The account creation form
 | 
			
		||||
 | 
			
		||||
 Copyright (c) 2023 imacat.
 | 
			
		||||
 
 | 
			
		||||
@@ -1,5 +1,5 @@
 | 
			
		||||
{#
 | 
			
		||||
The Mia! Accounting Flask Project
 | 
			
		||||
The Mia! Accounting Project
 | 
			
		||||
detail.html: The account detail
 | 
			
		||||
 | 
			
		||||
 Copyright (c) 2023 imacat.
 | 
			
		||||
 
 | 
			
		||||
@@ -1,5 +1,5 @@
 | 
			
		||||
{#
 | 
			
		||||
The Mia! Accounting Flask Project
 | 
			
		||||
The Mia! Accounting Project
 | 
			
		||||
edit.html: The account edit form
 | 
			
		||||
 | 
			
		||||
 Copyright (c) 2023 imacat.
 | 
			
		||||
 
 | 
			
		||||
@@ -1,5 +1,5 @@
 | 
			
		||||
{#
 | 
			
		||||
The Mia! Accounting Flask Project
 | 
			
		||||
The Mia! Accounting Project
 | 
			
		||||
form.html: The account form
 | 
			
		||||
 | 
			
		||||
 Copyright (c) 2023 imacat.
 | 
			
		||||
 
 | 
			
		||||
@@ -1,5 +1,5 @@
 | 
			
		||||
{#
 | 
			
		||||
The Mia! Accounting Flask Project
 | 
			
		||||
The Mia! Accounting Project
 | 
			
		||||
list.html: The account list
 | 
			
		||||
 | 
			
		||||
 Copyright (c) 2023 imacat.
 | 
			
		||||
 
 | 
			
		||||
@@ -1,5 +1,5 @@
 | 
			
		||||
{#
 | 
			
		||||
The Mia! Accounting Flask Project
 | 
			
		||||
The Mia! Accounting Project
 | 
			
		||||
order.html: The order of the accounts under a same base account
 | 
			
		||||
 | 
			
		||||
 Copyright (c) 2023 imacat.
 | 
			
		||||
 
 | 
			
		||||
@@ -1,5 +1,5 @@
 | 
			
		||||
{#
 | 
			
		||||
The Mia! Accounting Flask Project
 | 
			
		||||
The Mia! Accounting Project
 | 
			
		||||
detail.html: The base account detail
 | 
			
		||||
 | 
			
		||||
 Copyright (c) 2023 imacat.
 | 
			
		||||
 
 | 
			
		||||
@@ -1,5 +1,5 @@
 | 
			
		||||
{#
 | 
			
		||||
The Mia! Accounting Flask Project
 | 
			
		||||
The Mia! Accounting Project
 | 
			
		||||
list.html: The base account list
 | 
			
		||||
 | 
			
		||||
 Copyright (c) 2023 imacat.
 | 
			
		||||
 
 | 
			
		||||
@@ -1,5 +1,5 @@
 | 
			
		||||
{#
 | 
			
		||||
The Mia! Accounting Flask Project
 | 
			
		||||
The Mia! Accounting Project
 | 
			
		||||
base.html: The application-wide base template.
 | 
			
		||||
 | 
			
		||||
 Copyright (c) 2023 imacat.
 | 
			
		||||
 
 | 
			
		||||
@@ -1,5 +1,5 @@
 | 
			
		||||
{#
 | 
			
		||||
The Mia! Accounting Flask Project
 | 
			
		||||
The Mia! Accounting Project
 | 
			
		||||
create.html: The currency creation form
 | 
			
		||||
 | 
			
		||||
 Copyright (c) 2023 imacat.
 | 
			
		||||
 
 | 
			
		||||
Some files were not shown because too many files have changed in this diff Show More
		Reference in New Issue
	
	Block a user