Compare commits
	
		
			92 Commits
		
	
	
		
			v0.10.0
			...
			1224d6f83e
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
| 1224d6f83e | |||
| 3a8618f7c3 | |||
| 5d87205659 | |||
| 04de4f5c5e | |||
| f8ea863b80 | |||
| 5ae0d03b32 | |||
| a9a3ad5871 | |||
| 5edc95afce | |||
| 943ace6fc7 | |||
| a63bc977e9 | |||
| dabe6ddbca | |||
| f47e9b3150 | |||
| bb5383febe | |||
| 87f9063ceb | |||
| 51f0185bcf | |||
| 7ca08d6cc8 | |||
| c8e9e562be | |||
| ba43bd7e90 | |||
| 4e550413ba | |||
| 59a3cbb472 | |||
| d1b64d069e | |||
| d823d3254f | |||
| 5e9a2fb0c3 | |||
| 3f2e659ba5 | |||
| 9f7bb6b9de | |||
| 6857164702 | |||
| 6bac76be64 | |||
| 370d2668e5 | |||
| 5e3e695e62 | |||
| 510d369e9c | |||
| b65cae9252 | |||
| 285c12406b | |||
| df240472a4 | |||
| 1218b224fc | |||
| 79689ac0e5 | |||
| 1660e66766 | |||
| 12d00c9c7d | |||
| 428018e4a9 | |||
| a8f318b0bb | |||
| a3507494e5 | |||
| 3aa6c8d6f6 | |||
| 052b62cdd4 | |||
| 3728a4037d | |||
| 6eee17d44f | |||
| e5cc2b5a2f | |||
| ac3b5523b1 | |||
| 5af6fd9619 | |||
| 71a20cba29 | |||
| 4a4cf1ea40 | |||
| e9824808ec | |||
| c984d2d596 | |||
| 720e77c814 | |||
| 0f0412827d | |||
| 3a0e978f76 | |||
| 8c10d42d7b | |||
| 04ec51afbe | |||
| fe7a8842ce | |||
| 66daa5c42c | |||
| 27fb44937d | |||
| 7026ed3a65 | |||
| fdd3e93778 | |||
| def7559457 | |||
| 7905820d68 | |||
| 7ae332c975 | |||
| 86c5b91697 | |||
| 9168840e64 | |||
| 21b9cfa8b8 | |||
| b0b3b3acb1 | |||
| cb1d254cf0 | |||
| eb9ad57e72 | |||
| ec26f8ef4d | |||
| 7ed29115ed | |||
| 95955197ac | |||
| d5a0f79e4b | |||
| 6aa655aa64 | |||
| 6e532af26e | |||
| fa1818d124 | |||
| f21ecc2aa9 | |||
| 5ae1ab95ae | |||
| 7a5b3b78fc | |||
| 7df4051452 | |||
| 85084c68fd | |||
| 0185c16654 | |||
| 7dd007f3cf | |||
| 38b8a028d5 | |||
| 213981a8b2 | |||
| a4d1789b58 | |||
| 91620d7db2 | |||
| 02fcabb0ce | |||
| 4c2dcc5070 | |||
| c9166fda4d | |||
| 3a0f0873e2 | 
							
								
								
									
										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
 | 
					# Author: imacat@mail.imacat.idv.tw (imacat), 2023/1/27
 | 
				
			||||||
 | 
					
 | 
				
			||||||
#  Copyright (c) 2022 imacat.
 | 
					#  Copyright (c) 2022 imacat.
 | 
				
			||||||
 
 | 
				
			|||||||
							
								
								
									
										40
									
								
								.readthedocs.yaml
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										40
									
								
								.readthedocs.yaml
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,40 @@
 | 
				
			|||||||
 | 
					# The Mia! Accounting Project.
 | 
				
			||||||
 | 
					# Author: imacat@mail.imacat.idv.tw (imacat), 2023/4/5
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#  Copyright (c) 2023 imacat.
 | 
				
			||||||
 | 
					#
 | 
				
			||||||
 | 
					#  Licensed under the Apache License, Version 2.0 (the "License");
 | 
				
			||||||
 | 
					#  you may not use this file except in compliance with the License.
 | 
				
			||||||
 | 
					#  You may obtain a copy of the License at
 | 
				
			||||||
 | 
					#
 | 
				
			||||||
 | 
					#      http://www.apache.org/licenses/LICENSE-2.0
 | 
				
			||||||
 | 
					#
 | 
				
			||||||
 | 
					#  Unless required by applicable law or agreed to in writing, software
 | 
				
			||||||
 | 
					#  distributed under the License is distributed on an "AS IS" BASIS,
 | 
				
			||||||
 | 
					#  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 | 
				
			||||||
 | 
					#  See the License for the specific language governing permissions and
 | 
				
			||||||
 | 
					#  limitations under the License.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					# .readthedocs.yaml
 | 
				
			||||||
 | 
					# Read the Docs configuration file
 | 
				
			||||||
 | 
					# See https://docs.readthedocs.io/en/stable/config-file/v2.html for details
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					# Required
 | 
				
			||||||
 | 
					version: 2
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					# Set the version of Python and other tools you might need
 | 
				
			||||||
 | 
					build:
 | 
				
			||||||
 | 
					  os: ubuntu-22.04
 | 
				
			||||||
 | 
					  tools:
 | 
				
			||||||
 | 
					    python: "3.11"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					# Build documentation in the docs/ directory with Sphinx
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					# If using Sphinx, optionally build your docs in additional formats such as PDF
 | 
				
			||||||
 | 
					formats: all
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					# Optionally declare the Python requirements required to build your docs
 | 
				
			||||||
 | 
					python:
 | 
				
			||||||
 | 
					   install:
 | 
				
			||||||
 | 
					   - method: pip
 | 
				
			||||||
 | 
					     path: .
 | 
				
			||||||
							
								
								
									
										23
									
								
								MANIFEST.in
									
									
									
									
									
								
							
							
						
						
									
										23
									
								
								MANIFEST.in
									
									
									
									
									
								
							@@ -1,4 +1,4 @@
 | 
				
			|||||||
# The Mia! Flask Project.
 | 
					# The Mia! Accounting Project.
 | 
				
			||||||
# Author: imacat@mail.imacat.idv.tw (imacat), 2022/8/21
 | 
					# Author: imacat@mail.imacat.idv.tw (imacat), 2022/8/21
 | 
				
			||||||
 | 
					
 | 
				
			||||||
#  Copyright (c) 2022-2023 imacat.
 | 
					#  Copyright (c) 2022-2023 imacat.
 | 
				
			||||||
@@ -15,17 +15,14 @@
 | 
				
			|||||||
#  See the License for the specific language governing permissions and
 | 
					#  See the License for the specific language governing permissions and
 | 
				
			||||||
#  limitations under the License.
 | 
					#  limitations under the License.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					recursive-include src/accounting/static *
 | 
				
			||||||
exclude src/accounting/static/js/dummy.js
 | 
					exclude src/accounting/static/js/dummy.js
 | 
				
			||||||
include src/accounting/translations/*
 | 
					recursive-include src/accounting/templates *
 | 
				
			||||||
include src/accounting/translations/*/LC_MESSAGES/*
 | 
					recursive-include src/accounting/translations *
 | 
				
			||||||
include docs/*
 | 
					recursive-include src/accounting/data *
 | 
				
			||||||
include docs/source/*
 | 
					recursive-include docs *
 | 
				
			||||||
include docs/source/_static/*
 | 
					recursive-exclude docs/build *
 | 
				
			||||||
include docs/source/_templates/*
 | 
					recursive-include tests *
 | 
				
			||||||
include tests/*
 | 
					 | 
				
			||||||
exclude tests/test_temp.py
 | 
					exclude tests/test_temp.py
 | 
				
			||||||
include tests/test_site/*
 | 
					recursive-exclude tests *.pyc
 | 
				
			||||||
include tests/test_site/static/*
 | 
					recursive-exclude tests/instance *
 | 
				
			||||||
include tests/test_site/templates/*
 | 
					 | 
				
			||||||
include tests/test_site/translations/*
 | 
					 | 
				
			||||||
include tests/test_site/translations/*/LC_MESSAGES/*
 | 
					 | 
				
			||||||
 
 | 
				
			|||||||
							
								
								
									
										191
									
								
								README.rst
									
									
									
									
									
								
							
							
						
						
									
										191
									
								
								README.rst
									
									
									
									
									
								
							@@ -1,24 +1,176 @@
 | 
				
			|||||||
=====================
 | 
					===============
 | 
				
			||||||
Mia! Accounting Flask
 | 
					Mia! Accounting
 | 
				
			||||||
=====================
 | 
					===============
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
Description
 | 
					Description
 | 
				
			||||||
===========
 | 
					===========
 | 
				
			||||||
 | 
					
 | 
				
			||||||
This is the Mia! Accounting Flask project.  It is an accounting
 | 
					*Mia! Accounting* is an accounting module for Flask_ applications.
 | 
				
			||||||
module for the Flask_ applications.
 | 
					It implements `double-entry bookkeeping`_, and generates the following
 | 
				
			||||||
 | 
					accounting reports:
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					* Trial balance
 | 
				
			||||||
 | 
					* Income statement
 | 
				
			||||||
 | 
					* Balance sheet
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					In addition, *Mia! Accounting* tracks offsets for unpaid payables and
 | 
				
			||||||
 | 
					receivables.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
Install
 | 
					Installation
 | 
				
			||||||
=======
 | 
					============
 | 
				
			||||||
 | 
					
 | 
				
			||||||
Install the latest source from the
 | 
					Install *Mia! Accounting* with ``pip``:
 | 
				
			||||||
`Mia! Accounting Flask repository`_.
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
::
 | 
					::
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    pip install git+https://gitea.imacat.idv.tw/imacat/mia-accounting-flask.git
 | 
					    pip install mia-accounting
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					You may also download the from the `PyPI project page`_ or the
 | 
				
			||||||
 | 
					`release page`_ on the `Git repository`_.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					Test Site and Live Demonstration
 | 
				
			||||||
 | 
					================================
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					You may find a working example in the `test site`_ in the
 | 
				
			||||||
 | 
					`source distribution`_.  It is the simplest website that works with
 | 
				
			||||||
 | 
					*Mia! Accounting*.  It is used in the automatic tests.  It is the same
 | 
				
			||||||
 | 
					code run for `live demonstration`_.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					If you do not have a running Flask application or do not know how to
 | 
				
			||||||
 | 
					start one, you may start with the test site.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					Prerequisites
 | 
				
			||||||
 | 
					=============
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					You need a running Flask application with database user login.
 | 
				
			||||||
 | 
					The primary key of the user data model must be integer.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					The following front-end JavaScript libraries must be loaded.  You may
 | 
				
			||||||
 | 
					download it locally or use CDN_.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					* Bootstrap_ 5.2.3 or above
 | 
				
			||||||
 | 
					* FontAwesome_ 6.2.1 or above
 | 
				
			||||||
 | 
					* `Decimal.js`_ 6.4.3 or above
 | 
				
			||||||
 | 
					* `Tempus-Dominus`_ 6.4.3 or above
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					Configuration
 | 
				
			||||||
 | 
					=============
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					You need to pass the Flask *app* and an implementation of
 | 
				
			||||||
 | 
					`UserUtilityInterface`_ to the `init_app`_ function.
 | 
				
			||||||
 | 
					``UserUtilityInterface`` contains everything *Mia! Accounting* needs.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					The following is an example configuration for *Mia! Accounting*.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					::
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    from flask import Response, redirect
 | 
				
			||||||
 | 
					    from .auth import current_user()
 | 
				
			||||||
 | 
					    from .modules import User
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def create_app(test_config=None) -> Flask:
 | 
				
			||||||
 | 
					        app: Flask = Flask(__name__)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        ... (Configuration of SQLAlchemy, CSRF, Babel_JS, ... etc) ...
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        import accounting
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        class UserUtils(accounting.UserUtilityInterface[User]):
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            def can_view(self) -> bool:
 | 
				
			||||||
 | 
					                return True
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            def can_edit(self) -> bool:
 | 
				
			||||||
 | 
					                return "editor" in current_user().roles
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            def can_admin(self) -> bool:
 | 
				
			||||||
 | 
					                return current_user().is_admin
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            def unauthorized(self) -> Response:
 | 
				
			||||||
 | 
					                return redirect("/login")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            @property
 | 
				
			||||||
 | 
					            def cls(self) -> t.Type[User]:
 | 
				
			||||||
 | 
					                return User
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            @property
 | 
				
			||||||
 | 
					            def pk_column(self) -> Column:
 | 
				
			||||||
 | 
					                return User.id
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            @property
 | 
				
			||||||
 | 
					            def current_user(self) -> User | None:
 | 
				
			||||||
 | 
					                return current_user()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            def get_by_username(self, username: str) -> User | None:
 | 
				
			||||||
 | 
					                return User.query.filter(User.username == username).first()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            def get_pk(self, user: User) -> int:
 | 
				
			||||||
 | 
					                return user.id
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        accounting.init_app(app, UserUtils())
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        ... (Any other configuration) ...
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        return app
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					Database Initialization
 | 
				
			||||||
 | 
					=======================
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					After the configuration, you need to run
 | 
				
			||||||
 | 
					`flask_sqlalchemy.SQLAlchemy.create_all`_ to create the
 | 
				
			||||||
 | 
					database tables that *Mia! Accounting* uses.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					*Mia! Accounting* adds three console commands:
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					* ``accounting-init-base``
 | 
				
			||||||
 | 
					* ``accounting-init-accounts``
 | 
				
			||||||
 | 
					* ``accounting-init-currencies``
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					After database tables are created, run
 | 
				
			||||||
 | 
					``accounting-init-base`` first, and then the other two commands.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					::
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    % flask --app myapp accounting-init-base
 | 
				
			||||||
 | 
					    % flask --app myapp accounting-init-accounts
 | 
				
			||||||
 | 
					    % flask --app myapp accounting-init-currencies
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					Navigation Menu
 | 
				
			||||||
 | 
					===============
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					Include the navigation menu in the `Bootstrap navigation bar`_ in your
 | 
				
			||||||
 | 
					base template:
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					::
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    <nav class="navbar navbar-expand-lg bg-body-tertiary bg-dark navbar-dark">
 | 
				
			||||||
 | 
					      <div class="container-fluid">
 | 
				
			||||||
 | 
					        ...
 | 
				
			||||||
 | 
					        <div id="collapsible-navbar" class="collapse navbar-collapse">
 | 
				
			||||||
 | 
					          <ul class="navbar-nav me-auto mb-2 mb-lg-0">
 | 
				
			||||||
 | 
					            ...
 | 
				
			||||||
 | 
					            {% include "accounting/include/nav.html" %}
 | 
				
			||||||
 | 
					            ...
 | 
				
			||||||
 | 
					          </ul>
 | 
				
			||||||
 | 
					          ...
 | 
				
			||||||
 | 
					        </div>
 | 
				
			||||||
 | 
					      </div>
 | 
				
			||||||
 | 
					    </nav>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					Check your Flask application and see how it works.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					Documentation
 | 
				
			||||||
 | 
					=============
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					Refer to the `documentation on Read the Docs`_.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
Copyright
 | 
					Copyright
 | 
				
			||||||
@@ -46,5 +198,22 @@ Authors
 | 
				
			|||||||
| imacat@mail.imacat.idv.tw
 | 
					| imacat@mail.imacat.idv.tw
 | 
				
			||||||
| 2023/1/27
 | 
					| 2023/1/27
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
.. _Flask: https://flask.palletsprojects.com
 | 
					.. _Flask: https://flask.palletsprojects.com
 | 
				
			||||||
.. _Mia! Accounting Flask repository: https://gitea.imacat.idv.tw/imacat/mia-accounting-flask
 | 
					.. _double-entry bookkeeping: https://en.wikipedia.org/wiki/Double-entry_bookkeeping
 | 
				
			||||||
 | 
					.. _test site: https://github.com/imacat/mia-accounting/tree/main/tests/test_site
 | 
				
			||||||
 | 
					.. _source distribution: https://pypi.org/project/mia-accounting/#files
 | 
				
			||||||
 | 
					.. _live demonstration: https://accounting.imacat.idv.tw
 | 
				
			||||||
 | 
					.. _PyPI project page: https://pypi.org/project/mia-accounting
 | 
				
			||||||
 | 
					.. _release page: https://github.com/imacat/mia-accounting/releases
 | 
				
			||||||
 | 
					.. _Git repository: https://github.com/imacat/mia-accounting
 | 
				
			||||||
 | 
					.. _CDN: https://en.wikipedia.org/wiki/Content_delivery_network
 | 
				
			||||||
 | 
					.. _Bootstrap: https://getbootstrap.com
 | 
				
			||||||
 | 
					.. _FontAwesome: https://fontawesome.com
 | 
				
			||||||
 | 
					.. _Decimal.js: https://mikemcl.github.io/decimal.js
 | 
				
			||||||
 | 
					.. _Tempus-Dominus: https://getdatepicker.com
 | 
				
			||||||
 | 
					.. _UserUtilityInterface: https://mia-accounting.readthedocs.io/en/latest/accounting.utils.html#accounting.utils.user.UserUtilityInterface
 | 
				
			||||||
 | 
					.. _init_app: https://mia-accounting.readthedocs.io/en/latest/accounting.html#accounting.init_app
 | 
				
			||||||
 | 
					.. _flask_sqlalchemy.SQLAlchemy.create_all: https://flask-sqlalchemy.palletsprojects.com/en/3.0.x/api/#flask_sqlalchemy.SQLAlchemy.create_all
 | 
				
			||||||
 | 
					.. _Bootstrap navigation bar: https://getbootstrap.com/docs/5.3/components/navbar/
 | 
				
			||||||
 | 
					.. _documentation on Read the Docs: https://mia-accounting.readthedocs.io
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -20,14 +20,6 @@ accounting.journal\_entry.utils.description\_editor module
 | 
				
			|||||||
   :undoc-members:
 | 
					   :undoc-members:
 | 
				
			||||||
   :show-inheritance:
 | 
					   :show-inheritance:
 | 
				
			||||||
 | 
					
 | 
				
			||||||
accounting.journal\_entry.utils.offset\_alias module
 | 
					 | 
				
			||||||
----------------------------------------------------
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
.. automodule:: accounting.journal_entry.utils.offset_alias
 | 
					 | 
				
			||||||
   :members:
 | 
					 | 
				
			||||||
   :undoc-members:
 | 
					 | 
				
			||||||
   :show-inheritance:
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
accounting.journal\_entry.utils.operators module
 | 
					accounting.journal\_entry.utils.operators module
 | 
				
			||||||
------------------------------------------------
 | 
					------------------------------------------------
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -60,6 +60,22 @@ accounting.report.reports.trial\_balance module
 | 
				
			|||||||
   :undoc-members:
 | 
					   :undoc-members:
 | 
				
			||||||
   :show-inheritance:
 | 
					   :show-inheritance:
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					accounting.report.reports.unapplied module
 | 
				
			||||||
 | 
					------------------------------------------
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					.. automodule:: accounting.report.reports.unapplied
 | 
				
			||||||
 | 
					   :members:
 | 
				
			||||||
 | 
					   :undoc-members:
 | 
				
			||||||
 | 
					   :show-inheritance:
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					accounting.report.reports.unapplied\_accounts module
 | 
				
			||||||
 | 
					----------------------------------------------------
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					.. automodule:: accounting.report.reports.unapplied_accounts
 | 
				
			||||||
 | 
					   :members:
 | 
				
			||||||
 | 
					   :undoc-members:
 | 
				
			||||||
 | 
					   :show-inheritance:
 | 
				
			||||||
 | 
					
 | 
				
			||||||
Module contents
 | 
					Module contents
 | 
				
			||||||
---------------
 | 
					---------------
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -52,6 +52,14 @@ accounting.report.utils.report\_type module
 | 
				
			|||||||
   :undoc-members:
 | 
					   :undoc-members:
 | 
				
			||||||
   :show-inheritance:
 | 
					   :show-inheritance:
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					accounting.report.utils.unapplied module
 | 
				
			||||||
 | 
					----------------------------------------
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					.. automodule:: accounting.report.utils.unapplied
 | 
				
			||||||
 | 
					   :members:
 | 
				
			||||||
 | 
					   :undoc-members:
 | 
				
			||||||
 | 
					   :show-inheritance:
 | 
				
			||||||
 | 
					
 | 
				
			||||||
accounting.report.utils.urls module
 | 
					accounting.report.utils.urls module
 | 
				
			||||||
-----------------------------------
 | 
					-----------------------------------
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -13,6 +13,7 @@ Subpackages
 | 
				
			|||||||
   accounting.journal_entry
 | 
					   accounting.journal_entry
 | 
				
			||||||
   accounting.option
 | 
					   accounting.option
 | 
				
			||||||
   accounting.report
 | 
					   accounting.report
 | 
				
			||||||
 | 
					   accounting.unmatched_offset
 | 
				
			||||||
   accounting.utils
 | 
					   accounting.utils
 | 
				
			||||||
 | 
					
 | 
				
			||||||
Submodules
 | 
					Submodules
 | 
				
			||||||
 
 | 
				
			|||||||
							
								
								
									
										29
									
								
								docs/source/accounting.unmatched_offset.rst
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										29
									
								
								docs/source/accounting.unmatched_offset.rst
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,29 @@
 | 
				
			|||||||
 | 
					accounting.unmatched\_offset package
 | 
				
			||||||
 | 
					====================================
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					Submodules
 | 
				
			||||||
 | 
					----------
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					accounting.unmatched\_offset.queries module
 | 
				
			||||||
 | 
					-------------------------------------------
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					.. automodule:: accounting.unmatched_offset.queries
 | 
				
			||||||
 | 
					   :members:
 | 
				
			||||||
 | 
					   :undoc-members:
 | 
				
			||||||
 | 
					   :show-inheritance:
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					accounting.unmatched\_offset.views module
 | 
				
			||||||
 | 
					-----------------------------------------
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					.. automodule:: accounting.unmatched_offset.views
 | 
				
			||||||
 | 
					   :members:
 | 
				
			||||||
 | 
					   :undoc-members:
 | 
				
			||||||
 | 
					   :show-inheritance:
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					Module contents
 | 
				
			||||||
 | 
					---------------
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					.. automodule:: accounting.unmatched_offset
 | 
				
			||||||
 | 
					   :members:
 | 
				
			||||||
 | 
					   :undoc-members:
 | 
				
			||||||
 | 
					   :show-inheritance:
 | 
				
			||||||
@@ -44,6 +44,22 @@ accounting.utils.next\_uri module
 | 
				
			|||||||
   :undoc-members:
 | 
					   :undoc-members:
 | 
				
			||||||
   :show-inheritance:
 | 
					   :show-inheritance:
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					accounting.utils.offset\_alias module
 | 
				
			||||||
 | 
					-------------------------------------
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					.. automodule:: accounting.utils.offset_alias
 | 
				
			||||||
 | 
					   :members:
 | 
				
			||||||
 | 
					   :undoc-members:
 | 
				
			||||||
 | 
					   :show-inheritance:
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					accounting.utils.offset\_matcher module
 | 
				
			||||||
 | 
					---------------------------------------
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					.. automodule:: accounting.utils.offset_matcher
 | 
				
			||||||
 | 
					   :members:
 | 
				
			||||||
 | 
					   :undoc-members:
 | 
				
			||||||
 | 
					   :show-inheritance:
 | 
				
			||||||
 | 
					
 | 
				
			||||||
accounting.utils.options module
 | 
					accounting.utils.options module
 | 
				
			||||||
-------------------------------
 | 
					-------------------------------
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -92,6 +108,14 @@ accounting.utils.strip\_text module
 | 
				
			|||||||
   :undoc-members:
 | 
					   :undoc-members:
 | 
				
			||||||
   :show-inheritance:
 | 
					   :show-inheritance:
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					accounting.utils.unapplied module
 | 
				
			||||||
 | 
					---------------------------------
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					.. automodule:: accounting.utils.unapplied
 | 
				
			||||||
 | 
					   :members:
 | 
				
			||||||
 | 
					   :undoc-members:
 | 
				
			||||||
 | 
					   :show-inheritance:
 | 
				
			||||||
 | 
					
 | 
				
			||||||
accounting.utils.user module
 | 
					accounting.utils.user module
 | 
				
			||||||
----------------------------
 | 
					----------------------------
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -10,10 +10,10 @@ sys.path.insert(0, os.path.abspath('../../src/'))
 | 
				
			|||||||
# -- Project information -----------------------------------------------------
 | 
					# -- Project information -----------------------------------------------------
 | 
				
			||||||
# https://www.sphinx-doc.org/en/master/usage/configuration.html#project-information
 | 
					# https://www.sphinx-doc.org/en/master/usage/configuration.html#project-information
 | 
				
			||||||
 | 
					
 | 
				
			||||||
project = 'Mia! Accounting Flask'
 | 
					project = 'Mia! Accounting'
 | 
				
			||||||
copyright = '2023, imacat'
 | 
					copyright = '2023, imacat'
 | 
				
			||||||
author = 'imacat'
 | 
					author = 'imacat'
 | 
				
			||||||
release = '0.10.0'
 | 
					release = '1.1.0'
 | 
				
			||||||
 | 
					
 | 
				
			||||||
# -- General configuration ---------------------------------------------------
 | 
					# -- General configuration ---------------------------------------------------
 | 
				
			||||||
# https://www.sphinx-doc.org/en/master/usage/configuration.html#general-configuration
 | 
					# https://www.sphinx-doc.org/en/master/usage/configuration.html#general-configuration
 | 
				
			||||||
 
 | 
				
			|||||||
							
								
								
									
										61
									
								
								docs/source/examples.rst
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										61
									
								
								docs/source/examples.rst
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,61 @@
 | 
				
			|||||||
 | 
					Examples
 | 
				
			||||||
 | 
					========
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					.. _example-userutils:
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					An Example Configuration
 | 
				
			||||||
 | 
					------------------------
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					The following is an example configuration for *Mia! Accounting*.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					::
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    from flask import Response, redirect
 | 
				
			||||||
 | 
					    from .auth import current_user()
 | 
				
			||||||
 | 
					    from .modules import User
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def create_app(test_config=None) -> Flask:
 | 
				
			||||||
 | 
					        app: Flask = Flask(__name__)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        ... (Configuration of SQLAlchemy, CSRF, Babel_JS, ... etc) ...
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        import accounting
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        class UserUtils(accounting.UserUtilityInterface[User]):
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            def can_view(self) -> bool:
 | 
				
			||||||
 | 
					                return True
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            def can_edit(self) -> bool:
 | 
				
			||||||
 | 
					                return "editor" in current_user().roles
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            def can_admin(self) -> bool:
 | 
				
			||||||
 | 
					                return current_user().is_admin
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            def unauthorized(self) -> Response:
 | 
				
			||||||
 | 
					                return redirect("/login")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            @property
 | 
				
			||||||
 | 
					            def cls(self) -> t.Type[User]:
 | 
				
			||||||
 | 
					                return User
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            @property
 | 
				
			||||||
 | 
					            def pk_column(self) -> Column:
 | 
				
			||||||
 | 
					                return User.id
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            @property
 | 
				
			||||||
 | 
					            def current_user(self) -> User | None:
 | 
				
			||||||
 | 
					                return current_user()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            def get_by_username(self, username: str) -> User | None:
 | 
				
			||||||
 | 
					                return User.query.filter(User.username == username).first()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            def get_pk(self, user: User) -> int:
 | 
				
			||||||
 | 
					                return user.id
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        accounting.init_app(app, UserUtils())
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        ... (Any other configuration) ...
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        return app
 | 
				
			||||||
							
								
								
									
										57
									
								
								docs/source/history.rst
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										57
									
								
								docs/source/history.rst
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,57 @@
 | 
				
			|||||||
 | 
					History
 | 
				
			||||||
 | 
					=======
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					I created my own private accounting application in Perl_/mod_perl_ in
 | 
				
			||||||
 | 
					2007, as part of my personal website.  The first revision was made
 | 
				
			||||||
 | 
					using Perl/Mojolicious_ in 2019, with the aim of making it
 | 
				
			||||||
 | 
					mobile-friendly using Bootstrap_, and with modern back-end and
 | 
				
			||||||
 | 
					front-end technologies such as jQuery.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					The second revision was done in Python_/Django_ in 2020, as I was
 | 
				
			||||||
 | 
					looking to change my career from PHP_/Laravel_ to Python, but lacked
 | 
				
			||||||
 | 
					experience with large Python projects.  I needed something in my
 | 
				
			||||||
 | 
					portfolio and decided to work on the somewhat outdated Mojolicious
 | 
				
			||||||
 | 
					project.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					Despite having no prior experience with Django, I spent two months
 | 
				
			||||||
 | 
					working late nights to create the `Mia! Accounting Django`_
 | 
				
			||||||
 | 
					application.  It took me another 1.5 months to make it an independent
 | 
				
			||||||
 | 
					module, which I later released as an open source project on PyPI.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					The application worked nicely for my household bookkeeping for two
 | 
				
			||||||
 | 
					years.  However, new demands arose over time, especially with tracking
 | 
				
			||||||
 | 
					payables and receivables.  This was critical `during the pandemic`_ as
 | 
				
			||||||
 | 
					more payments were made online with credit cards.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					The biggest issue I encountered was with
 | 
				
			||||||
 | 
					`Django's MTV architectural pattern`_.  Django takes over the control
 | 
				
			||||||
 | 
					flow.  I had to override several parts of the `class-based views`_ for
 | 
				
			||||||
 | 
					different but yet simple control flow logic.  In the end, it became
 | 
				
			||||||
 | 
					very difficult to track whether things went wrong because I overrode
 | 
				
			||||||
 | 
					something or because it just wouldn't work with the basic assumption
 | 
				
			||||||
 | 
					of the class-based views.  By the time I realized it, it was too late
 | 
				
			||||||
 | 
					for me to drop Django's MTV and rewrite everything from class-based
 | 
				
			||||||
 | 
					views to function-based views.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					Therefore, I decided to turn to microframeworks_ like Flask_.   After
 | 
				
			||||||
 | 
					working with modularized Flask and FastAPI_ applications for two
 | 
				
			||||||
 | 
					years, I returned to the project and wrote its third revision using
 | 
				
			||||||
 | 
					Flask in 2023.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					.. _Perl: https://www.perl.org
 | 
				
			||||||
 | 
					.. _mod_perl: https://perl.apache.org
 | 
				
			||||||
 | 
					.. _Mojolicious: https://mojolicious.org
 | 
				
			||||||
 | 
					.. _Bootstrap: https://getbootstrap.com
 | 
				
			||||||
 | 
					.. _jQuery: https://jquery.com
 | 
				
			||||||
 | 
					.. _Python: https://www.python.org
 | 
				
			||||||
 | 
					.. _Django: https://www.djangoproject.com
 | 
				
			||||||
 | 
					.. _PHP: https://www.php.net
 | 
				
			||||||
 | 
					.. _Laravel: https://laravel.com
 | 
				
			||||||
 | 
					.. _Mia! Accounting Django: https://github.com/imacat/mia-accounting-django
 | 
				
			||||||
 | 
					.. _during the pandemic: https://en.wikipedia.org/wiki/COVID-19_pandemic
 | 
				
			||||||
 | 
					.. _FastAPI: https://fastapi.tiangolo.com
 | 
				
			||||||
 | 
					.. _Django's MTV architectural pattern: https://docs.djangoproject.com/en/dev/faq/general/#django-appears-to-be-a-mvc-framework-but-you-call-the-controller-the-view-and-the-view-the-template-how-come-you-don-t-use-the-standard-names
 | 
				
			||||||
 | 
					.. _class-based views: https://docs.djangoproject.com/en/4.2/topics/class-based-views/
 | 
				
			||||||
 | 
					.. _microframeworks: https://en.wikipedia.org/wiki/Microframework
 | 
				
			||||||
 | 
					.. _Flask: https://flask.palletsprojects.com
 | 
				
			||||||
@@ -1,15 +1,20 @@
 | 
				
			|||||||
.. 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.
 | 
					   sphinx-quickstart on Fri Jan 27 12:20:04 2023.
 | 
				
			||||||
   You can adapt this file completely to your liking, but it should at least
 | 
					   You can adapt this file completely to your liking, but it should at least
 | 
				
			||||||
   contain the root `toctree` directive.
 | 
					   contain the root `toctree` directive.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
Welcome to Mia! Accounting Flask's documentation!
 | 
					Welcome to Mia! Accounting's documentation!
 | 
				
			||||||
=================================================
 | 
					===========================================
 | 
				
			||||||
 | 
					
 | 
				
			||||||
.. toctree::
 | 
					.. toctree::
 | 
				
			||||||
   :maxdepth: 2
 | 
					   :maxdepth: 2
 | 
				
			||||||
   :caption: Contents:
 | 
					   :caption: Contents:
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					   intro
 | 
				
			||||||
 | 
					   accounting
 | 
				
			||||||
 | 
					   examples
 | 
				
			||||||
 | 
					   history
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
Indices and tables
 | 
					Indices and tables
 | 
				
			||||||
 
 | 
				
			|||||||
							
								
								
									
										136
									
								
								docs/source/intro.rst
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										136
									
								
								docs/source/intro.rst
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,136 @@
 | 
				
			|||||||
 | 
					Introduction
 | 
				
			||||||
 | 
					============
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					*Mia! Accounting* is an accounting module for Flask_ applications.
 | 
				
			||||||
 | 
					It implements `double-entry bookkeeping`_, and generates the following
 | 
				
			||||||
 | 
					accounting reports:
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					* Trial balance
 | 
				
			||||||
 | 
					* Income statement
 | 
				
			||||||
 | 
					* Balance sheet
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					In addition, *Mia! Accounting* tracks offsets for unpaid payables and
 | 
				
			||||||
 | 
					receivables.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					Installation
 | 
				
			||||||
 | 
					------------
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					Install *Mia! Accounting* with ``pip``:
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					::
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    pip install mia-accounting
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					You may also download the from the `PyPI project page`_ or the
 | 
				
			||||||
 | 
					`release page`_ on the `Git repository`_.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					Test Site and Live Demonstration
 | 
				
			||||||
 | 
					--------------------------------
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					You may find a working example in the `test site`_ in the
 | 
				
			||||||
 | 
					`source distribution`_.  It is the simplest website that works with
 | 
				
			||||||
 | 
					*Mia! Accounting*.  It is used in the automatic tests.  It is the same
 | 
				
			||||||
 | 
					code run for `live demonstration`_.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					If you do not have a running Flask application or do not know how to
 | 
				
			||||||
 | 
					start one, you may start with the test site.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					Prerequisites
 | 
				
			||||||
 | 
					-------------
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					You need a running Flask application with database user login.
 | 
				
			||||||
 | 
					The primary key of the user data model must be integer.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					The following front-end JavaScript libraries must be loaded.  You may
 | 
				
			||||||
 | 
					download it locally or use CDN_.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					* Bootstrap_ 5.2.3 or above
 | 
				
			||||||
 | 
					* FontAwesome_ 6.2.1 or above
 | 
				
			||||||
 | 
					* `Decimal.js`_ 6.4.3 or above
 | 
				
			||||||
 | 
					* `Tempus-Dominus`_ 6.4.3 or above
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					Configuration
 | 
				
			||||||
 | 
					-------------
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					You need to pass the Flask *app* and an implementation of
 | 
				
			||||||
 | 
					:py:class:`accounting.utils.user.UserUtilityInterface` to the
 | 
				
			||||||
 | 
					:py:func:`accounting.init_app` function.  ``UserUtilityInterface``
 | 
				
			||||||
 | 
					contains everything *Mia! Accounting* needs.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					See an example in :ref:`example-userutils`.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					Database Initialization
 | 
				
			||||||
 | 
					-----------------------
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					After the configuration, you need to run
 | 
				
			||||||
 | 
					`flask_sqlalchemy.SQLAlchemy.create_all`_ to create the
 | 
				
			||||||
 | 
					database tables that *Mia! Accounting* uses.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					*Mia! Accounting* adds three console commands:
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					* ``accounting-init-base``
 | 
				
			||||||
 | 
					* ``accounting-init-accounts``
 | 
				
			||||||
 | 
					* ``accounting-init-currencies``
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					After database tables are created, run
 | 
				
			||||||
 | 
					``accounting-init-base`` first, and then the other two commands.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					::
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    % flask --app myapp accounting-init-base
 | 
				
			||||||
 | 
					    % flask --app myapp accounting-init-accounts
 | 
				
			||||||
 | 
					    % flask --app myapp accounting-init-currencies
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					Navigation Menu
 | 
				
			||||||
 | 
					---------------
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					Include the navigation menu in the `Bootstrap navigation bar`_ in your
 | 
				
			||||||
 | 
					base template:
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					::
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    <nav class="navbar navbar-expand-lg bg-body-tertiary bg-dark navbar-dark">
 | 
				
			||||||
 | 
					      <div class="container-fluid">
 | 
				
			||||||
 | 
					        ...
 | 
				
			||||||
 | 
					        <div id="collapsible-navbar" class="collapse navbar-collapse">
 | 
				
			||||||
 | 
					          <ul class="navbar-nav me-auto mb-2 mb-lg-0">
 | 
				
			||||||
 | 
					            ...
 | 
				
			||||||
 | 
					            {% include "accounting/include/nav.html" %}
 | 
				
			||||||
 | 
					            ...
 | 
				
			||||||
 | 
					          </ul>
 | 
				
			||||||
 | 
					          ...
 | 
				
			||||||
 | 
					        </div>
 | 
				
			||||||
 | 
					      </div>
 | 
				
			||||||
 | 
					    </nav>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					Check your Flask application and see how it works.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					Documentation
 | 
				
			||||||
 | 
					-------------
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					Refer to the `documentation on Read the Docs`_.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					.. _Flask: https://flask.palletsprojects.com
 | 
				
			||||||
 | 
					.. _double-entry bookkeeping: https://en.wikipedia.org/wiki/Double-entry_bookkeeping
 | 
				
			||||||
 | 
					.. _test site: https://github.com/imacat/mia-accounting/tree/main/tests/test_site
 | 
				
			||||||
 | 
					.. _source distribution: https://pypi.org/project/mia-accounting/#files
 | 
				
			||||||
 | 
					.. _live demonstration: https://accounting.imacat.idv.tw
 | 
				
			||||||
 | 
					.. _PyPI project page: https://pypi.org/project/mia-accounting
 | 
				
			||||||
 | 
					.. _release page: https://github.com/imacat/mia-accounting/releases
 | 
				
			||||||
 | 
					.. _Git repository: https://github.com/imacat/mia-accounting
 | 
				
			||||||
 | 
					.. _CDN: https://en.wikipedia.org/wiki/Content_delivery_network
 | 
				
			||||||
 | 
					.. _Bootstrap: https://getbootstrap.com
 | 
				
			||||||
 | 
					.. _FontAwesome: https://fontawesome.com
 | 
				
			||||||
 | 
					.. _Decimal.js: https://mikemcl.github.io/decimal.js
 | 
				
			||||||
 | 
					.. _Tempus-Dominus: https://getdatepicker.com
 | 
				
			||||||
 | 
					.. _flask_sqlalchemy.SQLAlchemy.create_all: https://flask-sqlalchemy.palletsprojects.com/en/3.0.x/api/#flask_sqlalchemy.SQLAlchemy.create_all
 | 
				
			||||||
 | 
					.. _Bootstrap navigation bar: https://getbootstrap.com/docs/5.3/components/navbar/
 | 
				
			||||||
 | 
					.. _documentation on Read the Docs: https://mia-accounting.readthedocs.io
 | 
				
			||||||
@@ -1,7 +1,7 @@
 | 
				
			|||||||
# The Mia! Flask Project.
 | 
					# The Mia! Accounting Project.
 | 
				
			||||||
# Author: imacat@mail.imacat.idv.tw (imacat), 2022/8/21
 | 
					# Author: imacat@mail.imacat.idv.tw (imacat), 2022/8/21
 | 
				
			||||||
 | 
					
 | 
				
			||||||
#  Copyright (c) 2022 imacat.
 | 
					#  Copyright (c) 2022-2023 imacat.
 | 
				
			||||||
#
 | 
					#
 | 
				
			||||||
#  Licensed under the Apache License, Version 2.0 (the "License");
 | 
					#  Licensed under the Apache License, Version 2.0 (the "License");
 | 
				
			||||||
#  you may not use this file except in compliance with the License.
 | 
					#  you may not use this file except in compliance with the License.
 | 
				
			||||||
@@ -15,6 +15,51 @@
 | 
				
			|||||||
#  See the License for the specific language governing permissions and
 | 
					#  See the License for the specific language governing permissions and
 | 
				
			||||||
#  limitations under the License.
 | 
					#  limitations under the License.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					[project]
 | 
				
			||||||
 | 
					name = "mia-accounting"
 | 
				
			||||||
 | 
					version = "1.1.0"
 | 
				
			||||||
 | 
					description = "A Flask accounting module."
 | 
				
			||||||
 | 
					readme = "README.rst"
 | 
				
			||||||
 | 
					requires-python = ">=3.11"
 | 
				
			||||||
 | 
					authors = [
 | 
				
			||||||
 | 
					    {name = "imacat", email = "imacat@mail.imacat.idv.tw"},
 | 
				
			||||||
 | 
					]
 | 
				
			||||||
 | 
					keywords = ["mia", "accounting", "flask"]
 | 
				
			||||||
 | 
					classifiers = [
 | 
				
			||||||
 | 
					    "Programming Language :: Python :: 3",
 | 
				
			||||||
 | 
					    "License :: OSI Approved :: Apache Software License",
 | 
				
			||||||
 | 
					    "Operating System :: OS Independent",
 | 
				
			||||||
 | 
					    "Framework :: Flask",
 | 
				
			||||||
 | 
					    "Topic :: Office/Business :: Financial :: Accounting",
 | 
				
			||||||
 | 
					]
 | 
				
			||||||
 | 
					dependencies = [
 | 
				
			||||||
 | 
					    "flask",
 | 
				
			||||||
 | 
					    "Flask-SQLAlchemy",
 | 
				
			||||||
 | 
					    "Flask-WTF",
 | 
				
			||||||
 | 
					    "Flask-Babel >= 3",
 | 
				
			||||||
 | 
					    "Flask-Babel-JS",
 | 
				
			||||||
 | 
					]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					[project.optional-dependencies]
 | 
				
			||||||
 | 
					test = [
 | 
				
			||||||
 | 
					    "unittest",
 | 
				
			||||||
 | 
					    "httpx",
 | 
				
			||||||
 | 
					    "OpenCC",
 | 
				
			||||||
 | 
					]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					[project.urls]
 | 
				
			||||||
 | 
					"Documentation" = "https://mia-accounting.readthedocs.io"
 | 
				
			||||||
 | 
					"Repository" = "https://github.com/imacat/mia-accounting"
 | 
				
			||||||
 | 
					"Bug Tracker" = "https://github.com/imacat/mia-accounting/issues"
 | 
				
			||||||
 | 
					"Demonstration" = "https://accounting.imacat.idv.tw"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
[build-system]
 | 
					[build-system]
 | 
				
			||||||
requires = ["setuptools>=42"]
 | 
					requires = ["setuptools>=42"]
 | 
				
			||||||
build-backend = "setuptools.build_meta"
 | 
					build-backend = "setuptools.build_meta"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					[tool.setuptools.exclude-package-data]
 | 
				
			||||||
 | 
					"*" = [
 | 
				
			||||||
 | 
					    "babel.cfg",
 | 
				
			||||||
 | 
					    "*.pot",
 | 
				
			||||||
 | 
					    "*.po",
 | 
				
			||||||
 | 
					]
 | 
				
			||||||
 
 | 
				
			|||||||
							
								
								
									
										56
									
								
								setup.cfg
									
									
									
									
									
								
							
							
						
						
									
										56
									
								
								setup.cfg
									
									
									
									
									
								
							@@ -1,56 +0,0 @@
 | 
				
			|||||||
# The Mia! Flask Project.
 | 
					 | 
				
			||||||
# Author: imacat@mail.imacat.idv.tw (imacat), 2022/8/21
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
#  Copyright (c) 2022-2023 imacat.
 | 
					 | 
				
			||||||
#
 | 
					 | 
				
			||||||
#  Licensed under the Apache License, Version 2.0 (the "License");
 | 
					 | 
				
			||||||
#  you may not use this file except in compliance with the License.
 | 
					 | 
				
			||||||
#  You may obtain a copy of the License at
 | 
					 | 
				
			||||||
#
 | 
					 | 
				
			||||||
#      http://www.apache.org/licenses/LICENSE-2.0
 | 
					 | 
				
			||||||
#
 | 
					 | 
				
			||||||
#  Unless required by applicable law or agreed to in writing, software
 | 
					 | 
				
			||||||
#  distributed under the License is distributed on an "AS IS" BASIS,
 | 
					 | 
				
			||||||
#  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 | 
					 | 
				
			||||||
#  See the License for the specific language governing permissions and
 | 
					 | 
				
			||||||
#  limitations under the License.
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
[metadata]
 | 
					 | 
				
			||||||
name = mia-accounting-flask
 | 
					 | 
				
			||||||
version = 0.10.0
 | 
					 | 
				
			||||||
author = imacat
 | 
					 | 
				
			||||||
author_email = imacat@mail.imacat.idv.tw
 | 
					 | 
				
			||||||
description = The Mia! Accounting Flask project.
 | 
					 | 
				
			||||||
long_description = file: README.rst
 | 
					 | 
				
			||||||
long_description_content_type = text/x-rst
 | 
					 | 
				
			||||||
url = https://github.com/imacat/mia-accounting-flask
 | 
					 | 
				
			||||||
project_urls =
 | 
					 | 
				
			||||||
    Bug Tracker = https://github.com/imacat/mia-accounting-flask/issues
 | 
					 | 
				
			||||||
classifiers =
 | 
					 | 
				
			||||||
    Programming Language :: Python :: 3
 | 
					 | 
				
			||||||
    License :: OSI Approved :: Apache Software License
 | 
					 | 
				
			||||||
    Operating System :: OS Independent
 | 
					 | 
				
			||||||
    Framework :: Flask
 | 
					 | 
				
			||||||
    Topic :: Office/Business :: Financial :: Accounting
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
[options]
 | 
					 | 
				
			||||||
package_dir =
 | 
					 | 
				
			||||||
    = src
 | 
					 | 
				
			||||||
python_requires = >=3.11
 | 
					 | 
				
			||||||
install_requires =
 | 
					 | 
				
			||||||
  flask
 | 
					 | 
				
			||||||
  Flask-SQLAlchemy
 | 
					 | 
				
			||||||
  Flask-WTF
 | 
					 | 
				
			||||||
  Flask-Babel >= 3
 | 
					 | 
				
			||||||
  Flask-Babel-JS
 | 
					 | 
				
			||||||
tests_require =
 | 
					 | 
				
			||||||
  unittest
 | 
					 | 
				
			||||||
  httpx
 | 
					 | 
				
			||||||
  OpenCC
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
[options.package_data]
 | 
					 | 
				
			||||||
accounting =
 | 
					 | 
				
			||||||
  static/**
 | 
					 | 
				
			||||||
  templates/**
 | 
					 | 
				
			||||||
  translations/*/LC_MESSAGES/*.mo
 | 
					 | 
				
			||||||
  data/**
 | 
					 | 
				
			||||||
@@ -1,4 +1,4 @@
 | 
				
			|||||||
# The Mia! Accounting Flask Project.
 | 
					# The Mia! Accounting Project.
 | 
				
			||||||
# Author: imacat@mail.imacat.idv.tw (imacat), 2023/1/25
 | 
					# Author: imacat@mail.imacat.idv.tw (imacat), 2023/1/25
 | 
				
			||||||
 | 
					
 | 
				
			||||||
#  Copyright (c) 2023 imacat.
 | 
					#  Copyright (c) 2023 imacat.
 | 
				
			||||||
@@ -47,7 +47,6 @@ def init_app(app: Flask, user_utils: UserUtilityInterface,
 | 
				
			|||||||
    init_user_utils(user_utils)
 | 
					    init_user_utils(user_utils)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    bp: Blueprint = Blueprint("accounting", __name__,
 | 
					    bp: Blueprint = Blueprint("accounting", __name__,
 | 
				
			||||||
                              url_prefix=url_prefix,
 | 
					 | 
				
			||||||
                              template_folder="templates",
 | 
					                              template_folder="templates",
 | 
				
			||||||
                              static_folder="static")
 | 
					                              static_folder="static")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -84,9 +83,12 @@ def init_app(app: Flask, user_utils: UserUtilityInterface,
 | 
				
			|||||||
    journal_entry.init_app(app, bp)
 | 
					    journal_entry.init_app(app, bp)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    from . import report
 | 
					    from . import report
 | 
				
			||||||
    report.init_app(app, bp)
 | 
					    report.init_app(app, url_prefix)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    from . import option
 | 
					    from . import option
 | 
				
			||||||
    option.init_app(bp)
 | 
					    option.init_app(bp)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    app.register_blueprint(bp)
 | 
					    from . import unmatched_offset
 | 
				
			||||||
 | 
					    unmatched_offset.init_app(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
 | 
					# Author: imacat@mail.imacat.idv.tw (imacat), 2023/1/30
 | 
				
			||||||
 | 
					
 | 
				
			||||||
#  Copyright (c) 2023 imacat.
 | 
					#  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
 | 
					# Author: imacat@mail.imacat.idv.tw (imacat), 2023/1/30
 | 
				
			||||||
 | 
					
 | 
				
			||||||
#  Copyright (c) 2023 imacat.
 | 
					#  Copyright (c) 2023 imacat.
 | 
				
			||||||
@@ -108,15 +108,15 @@ def __is_need_offset(base_code: str) -> bool:
 | 
				
			|||||||
    """
 | 
					    """
 | 
				
			||||||
    # Assets
 | 
					    # Assets
 | 
				
			||||||
    if base_code[0] == "1":
 | 
					    if base_code[0] == "1":
 | 
				
			||||||
        if base_code[:3] in {"113", "114", "118", "184"}:
 | 
					        if base_code[:3] in {"113", "114", "118", "184", "186"}:
 | 
				
			||||||
            return True
 | 
					            return True
 | 
				
			||||||
        if base_code in {"1411", "1421", "1431", "1441", "1511", "1521",
 | 
					        if base_code in {"1286", "1411", "1421", "1431", "1441", "1511",
 | 
				
			||||||
                         "1581", "1611", "1851", ""}:
 | 
					                         "1521", "1581", "1611", "1851"}:
 | 
				
			||||||
            return True
 | 
					            return True
 | 
				
			||||||
        return False
 | 
					        return False
 | 
				
			||||||
    # Liabilities
 | 
					    # Liabilities
 | 
				
			||||||
    if base_code[0] == "2":
 | 
					    if base_code[0] == "2":
 | 
				
			||||||
        if base_code in {"2111", "2114", "2284", "2293"}:
 | 
					        if base_code in {"2111", "2114", "2284", "2293", "2861"}:
 | 
				
			||||||
            return False
 | 
					            return False
 | 
				
			||||||
        return True
 | 
					        return True
 | 
				
			||||||
    # Only assets and liabilities need offset
 | 
					    # Only assets and liabilities need offset
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1,4 +1,4 @@
 | 
				
			|||||||
# The Mia! Accounting Flask Project.
 | 
					# The Mia! Accounting Project.
 | 
				
			||||||
# Author: imacat@mail.imacat.idv.tw (imacat), 2023/1/31
 | 
					# Author: imacat@mail.imacat.idv.tw (imacat), 2023/1/31
 | 
				
			||||||
 | 
					
 | 
				
			||||||
#  Copyright (c) 2023 imacat.
 | 
					#  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
 | 
					# Author: imacat@mail.imacat.idv.tw (imacat), 2023/2/1
 | 
				
			||||||
 | 
					
 | 
				
			||||||
#  Copyright (c) 2023 imacat.
 | 
					#  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
 | 
					# Author: imacat@mail.imacat.idv.tw (imacat), 2023/1/30
 | 
				
			||||||
 | 
					
 | 
				
			||||||
#  Copyright (c) 2023 imacat.
 | 
					#  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
 | 
					# Author: imacat@mail.imacat.idv.tw (imacat), 2023/1/30
 | 
				
			||||||
 | 
					
 | 
				
			||||||
#  Copyright (c) 2023 imacat.
 | 
					#  Copyright (c) 2023 imacat.
 | 
				
			||||||
@@ -53,7 +53,7 @@ def list_accounts() -> str:
 | 
				
			|||||||
                           list=pagination.list, pagination=pagination)
 | 
					                           list=pagination.list, pagination=pagination)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@bp.get("/create", endpoint="create")
 | 
					@bp.get("create", endpoint="create")
 | 
				
			||||||
@has_permission(can_edit)
 | 
					@has_permission(can_edit)
 | 
				
			||||||
def show_add_account_form() -> str:
 | 
					def show_add_account_form() -> str:
 | 
				
			||||||
    """Shows the form to add an account.
 | 
					    """Shows the form to add an account.
 | 
				
			||||||
@@ -70,7 +70,7 @@ def show_add_account_form() -> str:
 | 
				
			|||||||
                           form=form)
 | 
					                           form=form)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@bp.post("/store", endpoint="store")
 | 
					@bp.post("store", endpoint="store")
 | 
				
			||||||
@has_permission(can_edit)
 | 
					@has_permission(can_edit)
 | 
				
			||||||
def add_account() -> redirect:
 | 
					def add_account() -> redirect:
 | 
				
			||||||
    """Adds an account.
 | 
					    """Adds an account.
 | 
				
			||||||
@@ -91,7 +91,7 @@ def add_account() -> redirect:
 | 
				
			|||||||
    return redirect(inherit_next(__get_detail_uri(account)))
 | 
					    return redirect(inherit_next(__get_detail_uri(account)))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@bp.get("/<account:account>", endpoint="detail")
 | 
					@bp.get("<account:account>", endpoint="detail")
 | 
				
			||||||
@has_permission(can_view)
 | 
					@has_permission(can_view)
 | 
				
			||||||
def show_account_detail(account: Account) -> str:
 | 
					def show_account_detail(account: Account) -> str:
 | 
				
			||||||
    """Shows the account detail.
 | 
					    """Shows the account detail.
 | 
				
			||||||
@@ -102,7 +102,7 @@ def show_account_detail(account: Account) -> str:
 | 
				
			|||||||
    return render_template("accounting/account/detail.html", obj=account)
 | 
					    return render_template("accounting/account/detail.html", obj=account)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@bp.get("/<account:account>/edit", endpoint="edit")
 | 
					@bp.get("<account:account>/edit", endpoint="edit")
 | 
				
			||||||
@has_permission(can_edit)
 | 
					@has_permission(can_edit)
 | 
				
			||||||
def show_account_edit_form(account: Account) -> str:
 | 
					def show_account_edit_form(account: Account) -> str:
 | 
				
			||||||
    """Shows the form to edit an account.
 | 
					    """Shows the form to edit an account.
 | 
				
			||||||
@@ -121,7 +121,7 @@ def show_account_edit_form(account: Account) -> str:
 | 
				
			|||||||
                           account=account, form=form)
 | 
					                           account=account, form=form)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@bp.post("/<account:account>/update", endpoint="update")
 | 
					@bp.post("<account:account>/update", endpoint="update")
 | 
				
			||||||
@has_permission(can_edit)
 | 
					@has_permission(can_edit)
 | 
				
			||||||
def update_account(account: Account) -> redirect:
 | 
					def update_account(account: Account) -> redirect:
 | 
				
			||||||
    """Updates an account.
 | 
					    """Updates an account.
 | 
				
			||||||
@@ -148,7 +148,7 @@ def update_account(account: Account) -> redirect:
 | 
				
			|||||||
    return redirect(inherit_next(__get_detail_uri(account)))
 | 
					    return redirect(inherit_next(__get_detail_uri(account)))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@bp.post("/<account:account>/delete", endpoint="delete")
 | 
					@bp.post("<account:account>/delete", endpoint="delete")
 | 
				
			||||||
@has_permission(can_edit)
 | 
					@has_permission(can_edit)
 | 
				
			||||||
def delete_account(account: Account) -> redirect:
 | 
					def delete_account(account: Account) -> redirect:
 | 
				
			||||||
    """Deletes an account.
 | 
					    """Deletes an account.
 | 
				
			||||||
@@ -167,7 +167,7 @@ def delete_account(account: Account) -> redirect:
 | 
				
			|||||||
    return redirect(or_next(__get_list_uri()))
 | 
					    return redirect(or_next(__get_list_uri()))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@bp.get("/bases/<baseAccount:base>", endpoint="order")
 | 
					@bp.get("bases/<baseAccount:base>", endpoint="order")
 | 
				
			||||||
@has_permission(can_view)
 | 
					@has_permission(can_view)
 | 
				
			||||||
def show_account_order(base: BaseAccount) -> str:
 | 
					def show_account_order(base: BaseAccount) -> str:
 | 
				
			||||||
    """Shows the order of the accounts under a same base account.
 | 
					    """Shows the order of the accounts under a same base account.
 | 
				
			||||||
@@ -178,7 +178,7 @@ def show_account_order(base: BaseAccount) -> str:
 | 
				
			|||||||
    return render_template("accounting/account/order.html", base=base)
 | 
					    return render_template("accounting/account/order.html", base=base)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@bp.post("/bases/<baseAccount:base>", endpoint="sort")
 | 
					@bp.post("bases/<baseAccount:base>", endpoint="sort")
 | 
				
			||||||
@has_permission(can_edit)
 | 
					@has_permission(can_edit)
 | 
				
			||||||
def sort_accounts(base: BaseAccount) -> redirect:
 | 
					def sort_accounts(base: BaseAccount) -> redirect:
 | 
				
			||||||
    """Reorders the accounts under a base account.
 | 
					    """Reorders the accounts under a base account.
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1,4 +1,4 @@
 | 
				
			|||||||
# The Mia! Accounting Flask Project.
 | 
					# The Mia! Accounting Project.
 | 
				
			||||||
# Author: imacat@mail.imacat.idv.tw (imacat), 2023/1/25
 | 
					# Author: imacat@mail.imacat.idv.tw (imacat), 2023/1/25
 | 
				
			||||||
 | 
					
 | 
				
			||||||
#  Copyright (c) 2023 imacat.
 | 
					#  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
 | 
					# Author: imacat@mail.imacat.idv.tw (imacat), 2023/1/25
 | 
				
			||||||
 | 
					
 | 
				
			||||||
#  Copyright (c) 2023 imacat.
 | 
					#  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
 | 
					# Author: imacat@mail.imacat.idv.tw (imacat), 2023/2/1
 | 
				
			||||||
 | 
					
 | 
				
			||||||
#  Copyright (c) 2023 imacat.
 | 
					#  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
 | 
					# Author: imacat@mail.imacat.idv.tw (imacat), 2023/1/26
 | 
				
			||||||
 | 
					
 | 
				
			||||||
#  Copyright (c) 2023 imacat.
 | 
					#  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
 | 
					# Author: imacat@mail.imacat.idv.tw (imacat), 2023/1/26
 | 
				
			||||||
 | 
					
 | 
				
			||||||
#  Copyright (c) 2023 imacat.
 | 
					#  Copyright (c) 2023 imacat.
 | 
				
			||||||
@@ -22,6 +22,7 @@ from flask import Blueprint, render_template
 | 
				
			|||||||
from accounting.models import BaseAccount
 | 
					from accounting.models import BaseAccount
 | 
				
			||||||
from accounting.utils.pagination import Pagination
 | 
					from accounting.utils.pagination import Pagination
 | 
				
			||||||
from accounting.utils.permission import has_permission, can_view
 | 
					from accounting.utils.permission import has_permission, can_view
 | 
				
			||||||
 | 
					from .queries import get_base_account_query
 | 
				
			||||||
 | 
					
 | 
				
			||||||
bp: Blueprint = Blueprint("base-account", __name__)
 | 
					bp: Blueprint = Blueprint("base-account", __name__)
 | 
				
			||||||
"""The view blueprint for the base account management."""
 | 
					"""The view blueprint for the base account management."""
 | 
				
			||||||
@@ -34,14 +35,13 @@ def list_accounts() -> str:
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
    :return: The account list.
 | 
					    :return: The account list.
 | 
				
			||||||
    """
 | 
					    """
 | 
				
			||||||
    from .queries import get_base_account_query
 | 
					 | 
				
			||||||
    accounts: list[BaseAccount] = get_base_account_query()
 | 
					    accounts: list[BaseAccount] = get_base_account_query()
 | 
				
			||||||
    pagination: Pagination = Pagination[BaseAccount](accounts)
 | 
					    pagination: Pagination = Pagination[BaseAccount](accounts)
 | 
				
			||||||
    return render_template("accounting/base-account/list.html",
 | 
					    return render_template("accounting/base-account/list.html",
 | 
				
			||||||
                           list=pagination.list, pagination=pagination)
 | 
					                           list=pagination.list, pagination=pagination)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@bp.get("/<baseAccount:account>", endpoint="detail")
 | 
					@bp.get("<baseAccount:account>", endpoint="detail")
 | 
				
			||||||
@has_permission(can_view)
 | 
					@has_permission(can_view)
 | 
				
			||||||
def show_account_detail(account: BaseAccount) -> str:
 | 
					def show_account_detail(account: BaseAccount) -> str:
 | 
				
			||||||
    """Shows the account detail.
 | 
					    """Shows the account detail.
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1,4 +1,4 @@
 | 
				
			|||||||
# The Mia! Accounting Flask Project.
 | 
					# The Mia! Accounting Project.
 | 
				
			||||||
# Author: imacat@mail.imacat.idv.tw (imacat), 2023/2/6
 | 
					# Author: imacat@mail.imacat.idv.tw (imacat), 2023/2/6
 | 
				
			||||||
 | 
					
 | 
				
			||||||
#  Copyright (c) 2023 imacat.
 | 
					#  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
 | 
					# Author: imacat@mail.imacat.idv.tw (imacat), 2023/2/6
 | 
				
			||||||
 | 
					
 | 
				
			||||||
#  Copyright (c) 2023 imacat.
 | 
					#  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
 | 
					# Author: imacat@mail.imacat.idv.tw (imacat), 2023/2/6
 | 
				
			||||||
 | 
					
 | 
				
			||||||
#  Copyright (c) 2023 imacat.
 | 
					#  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
 | 
					# Author: imacat@mail.imacat.idv.tw (imacat), 2023/2/6
 | 
				
			||||||
 | 
					
 | 
				
			||||||
#  Copyright (c) 2023 imacat.
 | 
					#  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
 | 
					# Author: imacat@mail.imacat.idv.tw (imacat), 2023/2/6
 | 
				
			||||||
 | 
					
 | 
				
			||||||
#  Copyright (c) 2023 imacat.
 | 
					#  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
 | 
					# Author: imacat@mail.imacat.idv.tw (imacat), 2023/2/6
 | 
				
			||||||
 | 
					
 | 
				
			||||||
#  Copyright (c) 2023 imacat.
 | 
					#  Copyright (c) 2023 imacat.
 | 
				
			||||||
@@ -34,6 +34,7 @@ from accounting.utils.pagination import Pagination
 | 
				
			|||||||
from accounting.utils.permission import has_permission, can_view, can_edit
 | 
					from accounting.utils.permission import has_permission, can_view, can_edit
 | 
				
			||||||
from accounting.utils.user import get_current_user_pk
 | 
					from accounting.utils.user import get_current_user_pk
 | 
				
			||||||
from .forms import CurrencyForm
 | 
					from .forms import CurrencyForm
 | 
				
			||||||
 | 
					from .queries import get_currency_query
 | 
				
			||||||
 | 
					
 | 
				
			||||||
bp: Blueprint = Blueprint("currency", __name__)
 | 
					bp: Blueprint = Blueprint("currency", __name__)
 | 
				
			||||||
"""The view blueprint for the currency management."""
 | 
					"""The view blueprint for the currency management."""
 | 
				
			||||||
@@ -48,14 +49,13 @@ def list_currencies() -> str:
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
    :return: The currency list.
 | 
					    :return: The currency list.
 | 
				
			||||||
    """
 | 
					    """
 | 
				
			||||||
    from .queries import get_currency_query
 | 
					 | 
				
			||||||
    currencies: list[Currency] = get_currency_query()
 | 
					    currencies: list[Currency] = get_currency_query()
 | 
				
			||||||
    pagination: Pagination = Pagination[Currency](currencies)
 | 
					    pagination: Pagination = Pagination[Currency](currencies)
 | 
				
			||||||
    return render_template("accounting/currency/list.html",
 | 
					    return render_template("accounting/currency/list.html",
 | 
				
			||||||
                           list=pagination.list, pagination=pagination)
 | 
					                           list=pagination.list, pagination=pagination)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@bp.get("/create", endpoint="create")
 | 
					@bp.get("create", endpoint="create")
 | 
				
			||||||
@has_permission(can_edit)
 | 
					@has_permission(can_edit)
 | 
				
			||||||
def show_add_currency_form() -> str:
 | 
					def show_add_currency_form() -> str:
 | 
				
			||||||
    """Shows the form to add a currency.
 | 
					    """Shows the form to add a currency.
 | 
				
			||||||
@@ -72,7 +72,7 @@ def show_add_currency_form() -> str:
 | 
				
			|||||||
                           form=form)
 | 
					                           form=form)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@bp.post("/store", endpoint="store")
 | 
					@bp.post("store", endpoint="store")
 | 
				
			||||||
@has_permission(can_edit)
 | 
					@has_permission(can_edit)
 | 
				
			||||||
def add_currency() -> redirect:
 | 
					def add_currency() -> redirect:
 | 
				
			||||||
    """Adds a currency.
 | 
					    """Adds a currency.
 | 
				
			||||||
@@ -93,7 +93,7 @@ def add_currency() -> redirect:
 | 
				
			|||||||
    return redirect(inherit_next(__get_detail_uri(currency)))
 | 
					    return redirect(inherit_next(__get_detail_uri(currency)))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@bp.get("/<currency:currency>", endpoint="detail")
 | 
					@bp.get("<currency:currency>", endpoint="detail")
 | 
				
			||||||
@has_permission(can_view)
 | 
					@has_permission(can_view)
 | 
				
			||||||
def show_currency_detail(currency: Currency) -> str:
 | 
					def show_currency_detail(currency: Currency) -> str:
 | 
				
			||||||
    """Shows the currency detail.
 | 
					    """Shows the currency detail.
 | 
				
			||||||
@@ -104,7 +104,7 @@ def show_currency_detail(currency: Currency) -> str:
 | 
				
			|||||||
    return render_template("accounting/currency/detail.html", obj=currency)
 | 
					    return render_template("accounting/currency/detail.html", obj=currency)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@bp.get("/<currency:currency>/edit", endpoint="edit")
 | 
					@bp.get("<currency:currency>/edit", endpoint="edit")
 | 
				
			||||||
@has_permission(can_edit)
 | 
					@has_permission(can_edit)
 | 
				
			||||||
def show_currency_edit_form(currency: Currency) -> str:
 | 
					def show_currency_edit_form(currency: Currency) -> str:
 | 
				
			||||||
    """Shows the form to edit a currency.
 | 
					    """Shows the form to edit a currency.
 | 
				
			||||||
@@ -123,7 +123,7 @@ def show_currency_edit_form(currency: Currency) -> str:
 | 
				
			|||||||
                           currency=currency, form=form)
 | 
					                           currency=currency, form=form)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@bp.post("/<currency:currency>/update", endpoint="update")
 | 
					@bp.post("<currency:currency>/update", endpoint="update")
 | 
				
			||||||
@has_permission(can_edit)
 | 
					@has_permission(can_edit)
 | 
				
			||||||
def update_currency(currency: Currency) -> redirect:
 | 
					def update_currency(currency: Currency) -> redirect:
 | 
				
			||||||
    """Updates a currency.
 | 
					    """Updates a currency.
 | 
				
			||||||
@@ -151,7 +151,7 @@ def update_currency(currency: Currency) -> redirect:
 | 
				
			|||||||
    return redirect(inherit_next(__get_detail_uri(currency)))
 | 
					    return redirect(inherit_next(__get_detail_uri(currency)))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@bp.post("/<currency:currency>/delete", endpoint="delete")
 | 
					@bp.post("<currency:currency>/delete", endpoint="delete")
 | 
				
			||||||
@has_permission(can_edit)
 | 
					@has_permission(can_edit)
 | 
				
			||||||
def delete_currency(currency: Currency) -> redirect:
 | 
					def delete_currency(currency: Currency) -> redirect:
 | 
				
			||||||
    """Deletes a currency.
 | 
					    """Deletes a currency.
 | 
				
			||||||
@@ -169,7 +169,7 @@ def delete_currency(currency: Currency) -> redirect:
 | 
				
			|||||||
    return redirect(or_next(url_for("accounting.currency.list")))
 | 
					    return redirect(or_next(url_for("accounting.currency.list")))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@api_bp.get("/exists-code", endpoint="exists")
 | 
					@api_bp.get("exists-code", endpoint="exists")
 | 
				
			||||||
@has_permission(can_edit)
 | 
					@has_permission(can_edit)
 | 
				
			||||||
def exists_code() -> dict[str, bool]:
 | 
					def exists_code() -> dict[str, bool]:
 | 
				
			||||||
    """Validates whether a currency code exists.
 | 
					    """Validates whether a currency code exists.
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1,4 +1,4 @@
 | 
				
			|||||||
# The Mia! Accounting Flask Project.
 | 
					# The Mia! Accounting Project.
 | 
				
			||||||
# Author: imacat@mail.imacat.idv.tw (imacat), 2023/3/22
 | 
					# Author: imacat@mail.imacat.idv.tw (imacat), 2023/3/22
 | 
				
			||||||
 | 
					
 | 
				
			||||||
#  Copyright (c) 2023 imacat.
 | 
					#  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
 | 
					# Author: imacat@mail.imacat.idv.tw (imacat), 2023/2/18
 | 
				
			||||||
 | 
					
 | 
				
			||||||
#  Copyright (c) 2023 imacat.
 | 
					#  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
 | 
					# Author: imacat@mail.imacat.idv.tw (imacat), 2023/2/19
 | 
				
			||||||
 | 
					
 | 
				
			||||||
#  Copyright (c) 2023 imacat.
 | 
					#  Copyright (c) 2023 imacat.
 | 
				
			||||||
@@ -23,6 +23,7 @@ from flask import abort
 | 
				
			|||||||
from sqlalchemy.orm import selectinload
 | 
					from sqlalchemy.orm import selectinload
 | 
				
			||||||
from werkzeug.routing import BaseConverter
 | 
					from werkzeug.routing import BaseConverter
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					from accounting import db
 | 
				
			||||||
from accounting.models import JournalEntry, JournalEntryLineItem
 | 
					from accounting.models import JournalEntry, JournalEntryLineItem
 | 
				
			||||||
from accounting.utils.journal_entry_types import JournalEntryType
 | 
					from accounting.utils.journal_entry_types import JournalEntryType
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -37,13 +38,7 @@ class JournalEntryConverter(BaseConverter):
 | 
				
			|||||||
        :param value: The journal entry ID.
 | 
					        :param value: The journal entry ID.
 | 
				
			||||||
        :return: The corresponding journal entry.
 | 
					        :return: The corresponding journal entry.
 | 
				
			||||||
        """
 | 
					        """
 | 
				
			||||||
        journal_entry: JournalEntry | None = JournalEntry.query\
 | 
					        journal_entry: JournalEntry | None = db.session.get(JournalEntry, value)
 | 
				
			||||||
            .join(JournalEntryLineItem)\
 | 
					 | 
				
			||||||
            .filter(JournalEntry.id == value)\
 | 
					 | 
				
			||||||
            .options(selectinload(JournalEntry.line_items)
 | 
					 | 
				
			||||||
                     .selectinload(JournalEntryLineItem.offsets)
 | 
					 | 
				
			||||||
                     .selectinload(JournalEntryLineItem.journal_entry))\
 | 
					 | 
				
			||||||
            .first()
 | 
					 | 
				
			||||||
        if journal_entry is None:
 | 
					        if journal_entry is None:
 | 
				
			||||||
            abort(404)
 | 
					            abort(404)
 | 
				
			||||||
        return journal_entry
 | 
					        return journal_entry
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1,4 +1,4 @@
 | 
				
			|||||||
# The Mia! Accounting Flask Project.
 | 
					# The Mia! Accounting Project.
 | 
				
			||||||
# Author: imacat@mail.imacat.idv.tw (imacat), 2023/3/10
 | 
					# Author: imacat@mail.imacat.idv.tw (imacat), 2023/3/10
 | 
				
			||||||
 | 
					
 | 
				
			||||||
#  Copyright (c) 2023 imacat.
 | 
					#  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
 | 
					# Author: imacat@mail.imacat.idv.tw (imacat), 2023/3/10
 | 
				
			||||||
 | 
					
 | 
				
			||||||
#  Copyright (c) 2023 imacat.
 | 
					#  Copyright (c) 2023 imacat.
 | 
				
			||||||
@@ -28,14 +28,13 @@ from wtforms.validators import DataRequired
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
from accounting import db
 | 
					from accounting import db
 | 
				
			||||||
from accounting.forms import CurrencyExists
 | 
					from accounting.forms import CurrencyExists
 | 
				
			||||||
from accounting.journal_entry.utils.offset_alias import offset_alias
 | 
					 | 
				
			||||||
from accounting.locale import lazy_gettext
 | 
					from accounting.locale import lazy_gettext
 | 
				
			||||||
from accounting.models import JournalEntryLineItem
 | 
					from accounting.models import JournalEntryLineItem
 | 
				
			||||||
from accounting.utils.cast import be
 | 
					from accounting.utils.cast import be
 | 
				
			||||||
 | 
					from accounting.utils.offset_alias import offset_alias
 | 
				
			||||||
from accounting.utils.strip_text import strip_text
 | 
					from accounting.utils.strip_text import strip_text
 | 
				
			||||||
from .line_item import LineItemForm, CreditLineItemForm, DebitLineItemForm
 | 
					from .line_item import LineItemForm, CreditLineItemForm, DebitLineItemForm
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					 | 
				
			||||||
CURRENCY_REQUIRED: DataRequired = DataRequired(
 | 
					CURRENCY_REQUIRED: DataRequired = DataRequired(
 | 
				
			||||||
    lazy_gettext("Please select the currency."))
 | 
					    lazy_gettext("Please select the currency."))
 | 
				
			||||||
"""The validator to check if the currency code is empty."""
 | 
					"""The validator to check if the currency code is empty."""
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1,4 +1,4 @@
 | 
				
			|||||||
# The Mia! Accounting Flask Project.
 | 
					# The Mia! Accounting Project.
 | 
				
			||||||
# Author: imacat@mail.imacat.idv.tw (imacat), 2023/2/18
 | 
					# Author: imacat@mail.imacat.idv.tw (imacat), 2023/2/18
 | 
				
			||||||
 | 
					
 | 
				
			||||||
#  Copyright (c) 2023 imacat.
 | 
					#  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
 | 
					# Author: imacat@mail.imacat.idv.tw (imacat), 2023/3/10
 | 
				
			||||||
 | 
					
 | 
				
			||||||
#  Copyright (c) 2023 imacat.
 | 
					#  Copyright (c) 2023 imacat.
 | 
				
			||||||
@@ -31,7 +31,7 @@ from accounting import db
 | 
				
			|||||||
from accounting.forms import ACCOUNT_REQUIRED, AccountExists, IsDebitAccount, \
 | 
					from accounting.forms import ACCOUNT_REQUIRED, AccountExists, IsDebitAccount, \
 | 
				
			||||||
    IsCreditAccount
 | 
					    IsCreditAccount
 | 
				
			||||||
from accounting.locale import lazy_gettext
 | 
					from accounting.locale import lazy_gettext
 | 
				
			||||||
from accounting.models import Account, JournalEntryLineItem
 | 
					from accounting.models import Account, JournalEntry, JournalEntryLineItem
 | 
				
			||||||
from accounting.template_filters import format_amount
 | 
					from accounting.template_filters import format_amount
 | 
				
			||||||
from accounting.utils.cast import be
 | 
					from accounting.utils.cast import be
 | 
				
			||||||
from accounting.utils.random_id import new_id
 | 
					from accounting.utils.random_id import new_id
 | 
				
			||||||
@@ -127,10 +127,8 @@ class KeepAccountWhenHavingOffset:
 | 
				
			|||||||
        assert isinstance(form, LineItemForm)
 | 
					        assert isinstance(form, LineItemForm)
 | 
				
			||||||
        if field.data is None or form.id.data is None:
 | 
					        if field.data is None or form.id.data is None:
 | 
				
			||||||
            return
 | 
					            return
 | 
				
			||||||
        line_item: JournalEntryLineItem | None = db.session\
 | 
					        line_item: JournalEntryLineItem | None \
 | 
				
			||||||
            .query(JournalEntryLineItem)\
 | 
					            = db.session.get(JournalEntryLineItem, form.id.data)
 | 
				
			||||||
            .filter(JournalEntryLineItem.id == form.id.data)\
 | 
					 | 
				
			||||||
            .options(selectinload(JournalEntryLineItem.offsets)).first()
 | 
					 | 
				
			||||||
        if line_item is None or len(line_item.offsets) == 0:
 | 
					        if line_item is None or len(line_item.offsets) == 0:
 | 
				
			||||||
            return
 | 
					            return
 | 
				
			||||||
        if field.data != line_item.account_code:
 | 
					        if field.data != line_item.account_code:
 | 
				
			||||||
@@ -344,14 +342,13 @@ class LineItemForm(FlaskForm):
 | 
				
			|||||||
            def get_offsets() -> list[JournalEntryLineItem]:
 | 
					            def get_offsets() -> list[JournalEntryLineItem]:
 | 
				
			||||||
                if not self.is_need_offset or self.id.data is None:
 | 
					                if not self.is_need_offset or self.id.data is None:
 | 
				
			||||||
                    return []
 | 
					                    return []
 | 
				
			||||||
                return JournalEntryLineItem.query\
 | 
					                return JournalEntryLineItem.query.join(JournalEntry)\
 | 
				
			||||||
                    .filter(JournalEntryLineItem.original_line_item_id
 | 
					                    .filter(JournalEntryLineItem.original_line_item_id
 | 
				
			||||||
                            == self.id.data)\
 | 
					                            == self.id.data)\
 | 
				
			||||||
 | 
					                    .order_by(JournalEntry.date, JournalEntry.no,
 | 
				
			||||||
 | 
					                              JournalEntryLineItem.no)\
 | 
				
			||||||
                    .options(selectinload(JournalEntryLineItem.journal_entry),
 | 
					                    .options(selectinload(JournalEntryLineItem.journal_entry),
 | 
				
			||||||
                             selectinload(JournalEntryLineItem.account),
 | 
					                             selectinload(JournalEntryLineItem.account)).all()
 | 
				
			||||||
                             selectinload(JournalEntryLineItem.offsets)
 | 
					 | 
				
			||||||
                             .selectinload(
 | 
					 | 
				
			||||||
                                 JournalEntryLineItem.journal_entry)).all()
 | 
					 | 
				
			||||||
            setattr(self, "__offsets", get_offsets())
 | 
					            setattr(self, "__offsets", get_offsets())
 | 
				
			||||||
        return getattr(self, "__offsets")
 | 
					        return getattr(self, "__offsets")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1,4 +1,4 @@
 | 
				
			|||||||
# The Mia! Accounting Flask Project.
 | 
					# The Mia! Accounting Project.
 | 
				
			||||||
# Author: imacat@mail.imacat.idv.tw (imacat), 2023/3/10
 | 
					# Author: imacat@mail.imacat.idv.tw (imacat), 2023/3/10
 | 
				
			||||||
 | 
					
 | 
				
			||||||
#  Copyright (c) 2023 imacat.
 | 
					#  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
 | 
					# Author: imacat@mail.imacat.idv.tw (imacat), 2023/2/25
 | 
				
			||||||
 | 
					
 | 
				
			||||||
#  Copyright (c) 2023 imacat.
 | 
					#  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
 | 
					# Author: imacat@mail.imacat.idv.tw (imacat), 2023/3/10
 | 
				
			||||||
 | 
					
 | 
				
			||||||
#  Copyright (c) 2023 imacat.
 | 
					#  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
 | 
					# Author: imacat@mail.imacat.idv.tw (imacat), 2023/3/10
 | 
				
			||||||
 | 
					
 | 
				
			||||||
#  Copyright (c) 2023 imacat.
 | 
					#  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
 | 
					# Author: imacat@mail.imacat.idv.tw (imacat), 2023/2/27
 | 
				
			||||||
 | 
					
 | 
				
			||||||
#  Copyright (c) 2023 imacat.
 | 
					#  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
 | 
					# Author: imacat@mail.imacat.idv.tw (imacat), 2023/2/19
 | 
				
			||||||
 | 
					
 | 
				
			||||||
#  Copyright (c) 2023 imacat.
 | 
					#  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
 | 
					# Author: imacat@mail.imacat.idv.tw (imacat), 2023/3/10
 | 
				
			||||||
 | 
					
 | 
				
			||||||
#  Copyright (c) 2023 imacat.
 | 
					#  Copyright (c) 2023 imacat.
 | 
				
			||||||
@@ -25,7 +25,7 @@ from sqlalchemy.orm import selectinload
 | 
				
			|||||||
from accounting import db
 | 
					from accounting import db
 | 
				
			||||||
from accounting.models import Account, JournalEntry, JournalEntryLineItem
 | 
					from accounting.models import Account, JournalEntry, JournalEntryLineItem
 | 
				
			||||||
from accounting.utils.cast import be
 | 
					from accounting.utils.cast import be
 | 
				
			||||||
from .offset_alias import offset_alias
 | 
					from accounting.utils.offset_alias import offset_alias
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
def get_selectable_original_line_items(
 | 
					def get_selectable_original_line_items(
 | 
				
			||||||
@@ -72,11 +72,12 @@ def get_selectable_original_line_items(
 | 
				
			|||||||
    line_items: list[JournalEntryLineItem] = JournalEntryLineItem.query\
 | 
					    line_items: list[JournalEntryLineItem] = JournalEntryLineItem.query\
 | 
				
			||||||
        .filter(JournalEntryLineItem.id.in_({x for x in net_balances}))\
 | 
					        .filter(JournalEntryLineItem.id.in_({x for x in net_balances}))\
 | 
				
			||||||
        .join(JournalEntry)\
 | 
					        .join(JournalEntry)\
 | 
				
			||||||
        .order_by(JournalEntry.date, JournalEntryLineItem.is_debit,
 | 
					        .order_by(JournalEntry.date, JournalEntry.no,
 | 
				
			||||||
                  JournalEntryLineItem.no)\
 | 
					                  JournalEntryLineItem.is_debit, JournalEntryLineItem.no)\
 | 
				
			||||||
        .options(selectinload(JournalEntryLineItem.currency),
 | 
					        .options(selectinload(JournalEntryLineItem.currency),
 | 
				
			||||||
                 selectinload(JournalEntryLineItem.account),
 | 
					                 selectinload(JournalEntryLineItem.account),
 | 
				
			||||||
                 selectinload(JournalEntryLineItem.journal_entry)).all()
 | 
					                 selectinload(JournalEntryLineItem.journal_entry)).all()
 | 
				
			||||||
 | 
					    line_items.reverse()
 | 
				
			||||||
    for line_item in line_items:
 | 
					    for line_item in line_items:
 | 
				
			||||||
        line_item.net_balance = line_item.amount \
 | 
					        line_item.net_balance = line_item.amount \
 | 
				
			||||||
            if net_balances[line_item.id] is None \
 | 
					            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
 | 
					# Author: imacat@mail.imacat.idv.tw (imacat), 2023/2/18
 | 
				
			||||||
 | 
					
 | 
				
			||||||
#  Copyright (c) 2023 imacat.
 | 
					#  Copyright (c) 2023 imacat.
 | 
				
			||||||
@@ -49,7 +49,7 @@ bp.add_app_template_filter(format_amount_input,
 | 
				
			|||||||
bp.add_app_template_filter(text2html, "accounting_journal_entry_text2html")
 | 
					bp.add_app_template_filter(text2html, "accounting_journal_entry_text2html")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@bp.get("/create/<journalEntryType:journal_entry_type>", endpoint="create")
 | 
					@bp.get("create/<journalEntryType:journal_entry_type>", endpoint="create")
 | 
				
			||||||
@has_permission(can_edit)
 | 
					@has_permission(can_edit)
 | 
				
			||||||
def show_add_journal_entry_form(journal_entry_type: JournalEntryType) -> str:
 | 
					def show_add_journal_entry_form(journal_entry_type: JournalEntryType) -> str:
 | 
				
			||||||
    """Shows the form to add a journal entry.
 | 
					    """Shows the form to add a journal entry.
 | 
				
			||||||
@@ -71,7 +71,7 @@ def show_add_journal_entry_form(journal_entry_type: JournalEntryType) -> str:
 | 
				
			|||||||
    return journal_entry_op.render_create_template(form)
 | 
					    return journal_entry_op.render_create_template(form)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@bp.post("/store/<journalEntryType:journal_entry_type>", endpoint="store")
 | 
					@bp.post("store/<journalEntryType:journal_entry_type>", endpoint="store")
 | 
				
			||||||
@has_permission(can_edit)
 | 
					@has_permission(can_edit)
 | 
				
			||||||
def add_journal_entry(journal_entry_type: JournalEntryType) -> redirect:
 | 
					def add_journal_entry(journal_entry_type: JournalEntryType) -> redirect:
 | 
				
			||||||
    """Adds a journal entry.
 | 
					    """Adds a journal entry.
 | 
				
			||||||
@@ -98,7 +98,7 @@ def add_journal_entry(journal_entry_type: JournalEntryType) -> redirect:
 | 
				
			|||||||
    return redirect(inherit_next(__get_detail_uri(journal_entry)))
 | 
					    return redirect(inherit_next(__get_detail_uri(journal_entry)))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@bp.get("/<journalEntry:journal_entry>", endpoint="detail")
 | 
					@bp.get("<journalEntry:journal_entry>", endpoint="detail")
 | 
				
			||||||
@has_permission(can_view)
 | 
					@has_permission(can_view)
 | 
				
			||||||
def show_journal_entry_detail(journal_entry: JournalEntry) -> str:
 | 
					def show_journal_entry_detail(journal_entry: JournalEntry) -> str:
 | 
				
			||||||
    """Shows the journal entry detail.
 | 
					    """Shows the journal entry detail.
 | 
				
			||||||
@@ -111,7 +111,7 @@ def show_journal_entry_detail(journal_entry: JournalEntry) -> str:
 | 
				
			|||||||
    return journal_entry_op.render_detail_template(journal_entry)
 | 
					    return journal_entry_op.render_detail_template(journal_entry)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@bp.get("/<journalEntry:journal_entry>/edit", endpoint="edit")
 | 
					@bp.get("<journalEntry:journal_entry>/edit", endpoint="edit")
 | 
				
			||||||
@has_permission(can_edit)
 | 
					@has_permission(can_edit)
 | 
				
			||||||
def show_journal_entry_edit_form(journal_entry: JournalEntry) -> str:
 | 
					def show_journal_entry_edit_form(journal_entry: JournalEntry) -> str:
 | 
				
			||||||
    """Shows the form to edit a journal entry.
 | 
					    """Shows the form to edit a journal entry.
 | 
				
			||||||
@@ -133,7 +133,7 @@ def show_journal_entry_edit_form(journal_entry: JournalEntry) -> str:
 | 
				
			|||||||
    return journal_entry_op.render_edit_template(journal_entry, form)
 | 
					    return journal_entry_op.render_edit_template(journal_entry, form)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@bp.post("/<journalEntry:journal_entry>/update", endpoint="update")
 | 
					@bp.post("<journalEntry:journal_entry>/update", endpoint="update")
 | 
				
			||||||
@has_permission(can_edit)
 | 
					@has_permission(can_edit)
 | 
				
			||||||
def update_journal_entry(journal_entry: JournalEntry) -> redirect:
 | 
					def update_journal_entry(journal_entry: JournalEntry) -> redirect:
 | 
				
			||||||
    """Updates a journal entry.
 | 
					    """Updates a journal entry.
 | 
				
			||||||
@@ -166,7 +166,7 @@ def update_journal_entry(journal_entry: JournalEntry) -> redirect:
 | 
				
			|||||||
    return redirect(inherit_next(__get_detail_uri(journal_entry)))
 | 
					    return redirect(inherit_next(__get_detail_uri(journal_entry)))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@bp.post("/<journalEntry:journal_entry>/delete", endpoint="delete")
 | 
					@bp.post("<journalEntry:journal_entry>/delete", endpoint="delete")
 | 
				
			||||||
@has_permission(can_edit)
 | 
					@has_permission(can_edit)
 | 
				
			||||||
def delete_journal_entry(journal_entry: JournalEntry) -> redirect:
 | 
					def delete_journal_entry(journal_entry: JournalEntry) -> redirect:
 | 
				
			||||||
    """Deletes a journal entry.
 | 
					    """Deletes a journal entry.
 | 
				
			||||||
@@ -186,7 +186,7 @@ def delete_journal_entry(journal_entry: JournalEntry) -> redirect:
 | 
				
			|||||||
    return redirect(or_next(__get_default_page_uri()))
 | 
					    return redirect(or_next(__get_default_page_uri()))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@bp.get("/dates/<date:journal_entry_date>", endpoint="order")
 | 
					@bp.get("dates/<date:journal_entry_date>", endpoint="order")
 | 
				
			||||||
@has_permission(can_view)
 | 
					@has_permission(can_view)
 | 
				
			||||||
def show_journal_entry_order(journal_entry_date: date) -> str:
 | 
					def show_journal_entry_order(journal_entry_date: date) -> str:
 | 
				
			||||||
    """Shows the order of the journal entries in a same date.
 | 
					    """Shows the order of the journal entries in a same date.
 | 
				
			||||||
@@ -201,7 +201,7 @@ def show_journal_entry_order(journal_entry_date: date) -> str:
 | 
				
			|||||||
                           date=journal_entry_date, list=journal_entries)
 | 
					                           date=journal_entry_date, list=journal_entries)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@bp.post("/dates/<date:journal_entry_date>", endpoint="sort")
 | 
					@bp.post("dates/<date:journal_entry_date>", endpoint="sort")
 | 
				
			||||||
@has_permission(can_edit)
 | 
					@has_permission(can_edit)
 | 
				
			||||||
def sort_journal_entries(journal_entry_date: date) -> redirect:
 | 
					def sort_journal_entries(journal_entry_date: date) -> redirect:
 | 
				
			||||||
    """Reorders the journal entries in a date.
 | 
					    """Reorders the journal entries in a date.
 | 
				
			||||||
@@ -235,4 +235,4 @@ def __get_default_page_uri() -> str:
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
    :return: The URI for the default page.
 | 
					    :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
 | 
					# Author: imacat@mail.imacat.idv.tw (imacat), 2023/1/25
 | 
				
			||||||
 | 
					
 | 
				
			||||||
#  Copyright (c) 2023 imacat.
 | 
					#  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
 | 
					# Author: imacat@mail.imacat.idv.tw (imacat), 2023/1/25
 | 
				
			||||||
 | 
					
 | 
				
			||||||
#  Copyright (c) 2023 imacat.
 | 
					#  Copyright (c) 2023 imacat.
 | 
				
			||||||
@@ -214,6 +214,25 @@ class Account(db.Model):
 | 
				
			|||||||
        """
 | 
					        """
 | 
				
			||||||
        return not self.is_real
 | 
					        return not self.is_real
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    @property
 | 
				
			||||||
 | 
					    def count(self) -> int:
 | 
				
			||||||
 | 
					        """Returns the number of items in the account.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        :return: The number of items in the account.
 | 
				
			||||||
 | 
					        """
 | 
				
			||||||
 | 
					        if not hasattr(self, "__count"):
 | 
				
			||||||
 | 
					            setattr(self, "__count", 0)
 | 
				
			||||||
 | 
					        return getattr(self, "__count")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    @count.setter
 | 
				
			||||||
 | 
					    def count(self, count: int) -> None:
 | 
				
			||||||
 | 
					        """Sets the number of items in the account.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        :param count: The number of items in the account.
 | 
				
			||||||
 | 
					        :return: None.
 | 
				
			||||||
 | 
					        """
 | 
				
			||||||
 | 
					        setattr(self, "__count", count)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    @property
 | 
					    @property
 | 
				
			||||||
    def query_values(self) -> list[str]:
 | 
					    def query_values(self) -> list[str]:
 | 
				
			||||||
        """Returns the values to be queried.
 | 
					        """Returns the values to be queried.
 | 
				
			||||||
@@ -660,12 +679,8 @@ class JournalEntryLineItem(db.Model):
 | 
				
			|||||||
                                      nullable=True)
 | 
					                                      nullable=True)
 | 
				
			||||||
    """The ID of the original line item."""
 | 
					    """The ID of the original line item."""
 | 
				
			||||||
    original_line_item = db.relationship("JournalEntryLineItem",
 | 
					    original_line_item = db.relationship("JournalEntryLineItem",
 | 
				
			||||||
                                         back_populates="offsets",
 | 
					 | 
				
			||||||
                                         remote_side=id, passive_deletes=True)
 | 
					                                         remote_side=id, passive_deletes=True)
 | 
				
			||||||
    """The original line item."""
 | 
					    """The original line item."""
 | 
				
			||||||
    offsets = db.relationship("JournalEntryLineItem",
 | 
					 | 
				
			||||||
                              back_populates="original_line_item")
 | 
					 | 
				
			||||||
    """The offset items."""
 | 
					 | 
				
			||||||
    currency_code = db.Column(db.String,
 | 
					    currency_code = db.Column(db.String,
 | 
				
			||||||
                              db.ForeignKey(Currency.code, onupdate="CASCADE"),
 | 
					                              db.ForeignKey(Currency.code, onupdate="CASCADE"),
 | 
				
			||||||
                              nullable=False)
 | 
					                              nullable=False)
 | 
				
			||||||
@@ -707,14 +722,6 @@ class JournalEntryLineItem(db.Model):
 | 
				
			|||||||
        """
 | 
					        """
 | 
				
			||||||
        return self.account.code
 | 
					        return self.account.code
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    @property
 | 
					 | 
				
			||||||
    def debit(self) -> Decimal | None:
 | 
					 | 
				
			||||||
        """Returns the debit amount.
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        :return: The debit amount, or None if this is not a debit line item.
 | 
					 | 
				
			||||||
        """
 | 
					 | 
				
			||||||
        return self.amount if self.is_debit else None
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    @property
 | 
					    @property
 | 
				
			||||||
    def is_need_offset(self) -> bool:
 | 
					    def is_need_offset(self) -> bool:
 | 
				
			||||||
        """Returns whether the line item needs offset.
 | 
					        """Returns whether the line item needs offset.
 | 
				
			||||||
@@ -729,6 +736,14 @@ class JournalEntryLineItem(db.Model):
 | 
				
			|||||||
            return False
 | 
					            return False
 | 
				
			||||||
        return True
 | 
					        return True
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    @property
 | 
				
			||||||
 | 
					    def debit(self) -> Decimal | None:
 | 
				
			||||||
 | 
					        """Returns the debit amount.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        :return: The debit amount, or None if this is not a debit line item.
 | 
				
			||||||
 | 
					        """
 | 
				
			||||||
 | 
					        return self.amount if self.is_debit else None
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    @property
 | 
					    @property
 | 
				
			||||||
    def credit(self) -> Decimal | None:
 | 
					    def credit(self) -> Decimal | None:
 | 
				
			||||||
        """Returns the credit amount.
 | 
					        """Returns the credit amount.
 | 
				
			||||||
@@ -758,6 +773,40 @@ class JournalEntryLineItem(db.Model):
 | 
				
			|||||||
        """
 | 
					        """
 | 
				
			||||||
        setattr(self, "__net_balance", net_balance)
 | 
					        setattr(self, "__net_balance", net_balance)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    @property
 | 
				
			||||||
 | 
					    def offsets(self) -> list[t.Self]:
 | 
				
			||||||
 | 
					        """Returns the offset items.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        :return: The offset items.
 | 
				
			||||||
 | 
					        """
 | 
				
			||||||
 | 
					        if not hasattr(self, "__offsets"):
 | 
				
			||||||
 | 
					            cls: t.Type[t.Self] = self.__class__
 | 
				
			||||||
 | 
					            offsets: list[t.Self] = cls.query.join(JournalEntry)\
 | 
				
			||||||
 | 
					                .filter(JournalEntryLineItem.original_line_item_id == self.id)\
 | 
				
			||||||
 | 
					                .order_by(JournalEntry.date, JournalEntry.no,
 | 
				
			||||||
 | 
					                          cls.is_debit, cls.no).all()
 | 
				
			||||||
 | 
					            setattr(self, "__offsets", offsets)
 | 
				
			||||||
 | 
					        return getattr(self, "__offsets")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    @property
 | 
				
			||||||
 | 
					    def match(self) -> t.Self | None:
 | 
				
			||||||
 | 
					        """Returns the match of the line item.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        :return: The match of the line item.
 | 
				
			||||||
 | 
					        """
 | 
				
			||||||
 | 
					        if not hasattr(self, "__match"):
 | 
				
			||||||
 | 
					            setattr(self, "__match", None)
 | 
				
			||||||
 | 
					        return getattr(self, "__match")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    @match.setter
 | 
				
			||||||
 | 
					    def match(self, match: t.Self) -> None:
 | 
				
			||||||
 | 
					        """Sets the match of the line item.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        :param match: The matcho of the line item.
 | 
				
			||||||
 | 
					        :return: None.
 | 
				
			||||||
 | 
					        """
 | 
				
			||||||
 | 
					        setattr(self, "__match", match)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    @property
 | 
					    @property
 | 
				
			||||||
    def query_values(self) -> list[str]:
 | 
					    def query_values(self) -> list[str]:
 | 
				
			||||||
        """Returns the values to be queried.
 | 
					        """Returns the values to be queried.
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1,4 +1,4 @@
 | 
				
			|||||||
# The Mia! Accounting Flask Project.
 | 
					# The Mia! Accounting Project.
 | 
				
			||||||
# Author: imacat@mail.imacat.idv.tw (imacat), 2023/3/22
 | 
					# Author: imacat@mail.imacat.idv.tw (imacat), 2023/3/22
 | 
				
			||||||
 | 
					
 | 
				
			||||||
#  Copyright (c) 2023 imacat.
 | 
					#  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
 | 
					# Author: imacat@mail.imacat.idv.tw (imacat), 2023/3/22
 | 
				
			||||||
 | 
					
 | 
				
			||||||
#  Copyright (c) 2023 imacat.
 | 
					#  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
 | 
					# Author: imacat@mail.imacat.idv.tw (imacat), 2023/3/22
 | 
				
			||||||
 | 
					
 | 
				
			||||||
#  Copyright (c) 2023 imacat.
 | 
					#  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
 | 
					# Author: imacat@mail.imacat.idv.tw (imacat), 2023/3/3
 | 
				
			||||||
 | 
					
 | 
				
			||||||
#  Copyright (c) 2023 imacat.
 | 
					#  Copyright (c) 2023 imacat.
 | 
				
			||||||
@@ -17,19 +17,21 @@
 | 
				
			|||||||
"""The report management.
 | 
					"""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.
 | 
					    """Initialize the application.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    :param app: The Flask 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.
 | 
					    :return: None.
 | 
				
			||||||
    """
 | 
					    """
 | 
				
			||||||
    from .converters import PeriodConverter, IncomeExpensesAccountConverter
 | 
					    from .converters import PeriodConverter, CurrentAccountConverter, \
 | 
				
			||||||
 | 
					        NeedOffsetAccountConverter
 | 
				
			||||||
    app.url_map.converters["period"] = PeriodConverter
 | 
					    app.url_map.converters["period"] = PeriodConverter
 | 
				
			||||||
    app.url_map.converters["ieAccount"] = IncomeExpensesAccountConverter
 | 
					    app.url_map.converters["currentAccount"] = CurrentAccountConverter
 | 
				
			||||||
 | 
					    app.url_map.converters["needOffsetAccount"] = NeedOffsetAccountConverter
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    from .views import bp as report_bp
 | 
					    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
 | 
					# Author: imacat@mail.imacat.idv.tw (imacat), 2023/3/3
 | 
				
			||||||
 | 
					
 | 
				
			||||||
#  Copyright (c) 2023 imacat.
 | 
					#  Copyright (c) 2023 imacat.
 | 
				
			||||||
@@ -28,8 +28,8 @@ from .period import Period, get_period
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
class PeriodConverter(BaseConverter):
 | 
					class PeriodConverter(BaseConverter):
 | 
				
			||||||
    """The supplier converter to convert the period specification from and to
 | 
					    """The converter to convert the period specification from and to the
 | 
				
			||||||
    the corresponding period in the routes."""
 | 
					    corresponding period in the routes."""
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def to_python(self, value: str) -> Period:
 | 
					    def to_python(self, value: str) -> Period:
 | 
				
			||||||
        """Converts a period specification to a period.
 | 
					        """Converts a period specification to a period.
 | 
				
			||||||
@@ -51,9 +51,9 @@ class PeriodConverter(BaseConverter):
 | 
				
			|||||||
        return value.spec
 | 
					        return value.spec
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
class IncomeExpensesAccountConverter(BaseConverter):
 | 
					class CurrentAccountConverter(BaseConverter):
 | 
				
			||||||
    """The supplier converter to convert the income and expenses log pseudo
 | 
					    """The converter to convert the current account code from and to the
 | 
				
			||||||
    account code from and to the corresponding pseudo account in the routes."""
 | 
					    corresponding account in the routes."""
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def to_python(self, value: str) -> CurrentAccount:
 | 
					    def to_python(self, value: str) -> CurrentAccount:
 | 
				
			||||||
        """Converts an account code to an account.
 | 
					        """Converts an account code to an account.
 | 
				
			||||||
@@ -77,3 +77,29 @@ class IncomeExpensesAccountConverter(BaseConverter):
 | 
				
			|||||||
        :return: Its code.
 | 
					        :return: Its code.
 | 
				
			||||||
        """
 | 
					        """
 | 
				
			||||||
        return value.code
 | 
					        return value.code
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					class NeedOffsetAccountConverter(BaseConverter):
 | 
				
			||||||
 | 
					    """The converter to convert the unapplied original line item account code
 | 
				
			||||||
 | 
					    from and to the corresponding account in the routes."""
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def to_python(self, value: str) -> Account:
 | 
				
			||||||
 | 
					        """Converts an account code to an account.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        :param value: The account code.
 | 
				
			||||||
 | 
					        :return: The corresponding account.
 | 
				
			||||||
 | 
					        """
 | 
				
			||||||
 | 
					        account: Account | None = Account.find_by_code(value)
 | 
				
			||||||
 | 
					        if account is None:
 | 
				
			||||||
 | 
					            abort(404)
 | 
				
			||||||
 | 
					        if not account.is_need_offset:
 | 
				
			||||||
 | 
					            abort(404)
 | 
				
			||||||
 | 
					        return account
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def to_url(self, value: Account) -> str:
 | 
				
			||||||
 | 
					        """Converts an account to account code.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        :param value: The account.
 | 
				
			||||||
 | 
					        :return: Its code.
 | 
				
			||||||
 | 
					        """
 | 
				
			||||||
 | 
					        return value.code
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1,4 +1,4 @@
 | 
				
			|||||||
# The Mia! Accounting Flask Project.
 | 
					# The Mia! Accounting Project.
 | 
				
			||||||
# Author: imacat@mail.imacat.idv.tw (imacat), 2023/3/9
 | 
					# Author: imacat@mail.imacat.idv.tw (imacat), 2023/3/9
 | 
				
			||||||
 | 
					
 | 
				
			||||||
#  Copyright (c) 2023 imacat.
 | 
					#  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
 | 
					# Author: imacat@mail.imacat.idv.tw (imacat), 2023/3/4
 | 
				
			||||||
 | 
					
 | 
				
			||||||
#  Copyright (c) 2023 imacat.
 | 
					#  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
 | 
					# Author: imacat@mail.imacat.idv.tw (imacat), 2023/3/4
 | 
				
			||||||
 | 
					
 | 
				
			||||||
#  Copyright (c) 2023 imacat.
 | 
					#  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
 | 
					# Author: imacat@mail.imacat.idv.tw (imacat), 2023/3/4
 | 
				
			||||||
 | 
					
 | 
				
			||||||
#  Copyright (c) 2023 imacat.
 | 
					#  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
 | 
					# Author: imacat@mail.imacat.idv.tw (imacat), 2023/3/4
 | 
				
			||||||
 | 
					
 | 
				
			||||||
#  Copyright (c) 2023 imacat.
 | 
					#  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
 | 
					# Author: imacat@mail.imacat.idv.tw (imacat), 2023/3/4
 | 
				
			||||||
 | 
					
 | 
				
			||||||
#  Copyright (c) 2023 imacat.
 | 
					#  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
 | 
					# Author: imacat@mail.imacat.idv.tw (imacat), 2023/3/4
 | 
				
			||||||
 | 
					
 | 
				
			||||||
#  Copyright (c) 2023 imacat.
 | 
					#  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
 | 
					# Author: imacat@mail.imacat.idv.tw (imacat), 2023/3/4
 | 
				
			||||||
 | 
					
 | 
				
			||||||
#  Copyright (c) 2023 imacat.
 | 
					#  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
 | 
					# Author: imacat@mail.imacat.idv.tw (imacat), 2023/3/7
 | 
				
			||||||
 | 
					
 | 
				
			||||||
#  Copyright (c) 2023 imacat.
 | 
					#  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
 | 
					# Author: imacat@mail.imacat.idv.tw (imacat), 2023/3/7
 | 
				
			||||||
 | 
					
 | 
				
			||||||
#  Copyright (c) 2023 imacat.
 | 
					#  Copyright (c) 2023 imacat.
 | 
				
			||||||
@@ -137,6 +137,7 @@ class AccountCollector:
 | 
				
			|||||||
            .join(JournalEntry).join(Account)\
 | 
					            .join(JournalEntry).join(Account)\
 | 
				
			||||||
            .filter(*conditions)\
 | 
					            .filter(*conditions)\
 | 
				
			||||||
            .group_by(Account.id, Account.base_code, Account.no)\
 | 
					            .group_by(Account.id, Account.base_code, Account.no)\
 | 
				
			||||||
 | 
					            .having(balance_func != 0)\
 | 
				
			||||||
            .order_by(Account.base_code, Account.no)
 | 
					            .order_by(Account.base_code, Account.no)
 | 
				
			||||||
        account_balances: list[sa.Row] \
 | 
					        account_balances: list[sa.Row] \
 | 
				
			||||||
            = db.session.execute(select_balance).all()
 | 
					            = 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
 | 
					# Author: imacat@mail.imacat.idv.tw (imacat), 2023/3/7
 | 
				
			||||||
 | 
					
 | 
				
			||||||
#  Copyright (c) 2023 imacat.
 | 
					#  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
 | 
					# Author: imacat@mail.imacat.idv.tw (imacat), 2023/3/7
 | 
				
			||||||
 | 
					
 | 
				
			||||||
#  Copyright (c) 2023 imacat.
 | 
					#  Copyright (c) 2023 imacat.
 | 
				
			||||||
@@ -269,6 +269,7 @@ class IncomeStatement(BaseReport):
 | 
				
			|||||||
            .join(JournalEntry).join(Account)\
 | 
					            .join(JournalEntry).join(Account)\
 | 
				
			||||||
            .filter(*conditions)\
 | 
					            .filter(*conditions)\
 | 
				
			||||||
            .group_by(Account.id)\
 | 
					            .group_by(Account.id)\
 | 
				
			||||||
 | 
					            .having(balance_func != 0)\
 | 
				
			||||||
            .order_by(Account.base_code, Account.no)
 | 
					            .order_by(Account.base_code, Account.no)
 | 
				
			||||||
        balances: list[sa.Row] = db.session.execute(select_balances).all()
 | 
					        balances: list[sa.Row] = db.session.execute(select_balances).all()
 | 
				
			||||||
        accounts: dict[int, Account] \
 | 
					        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
 | 
					# Author: imacat@mail.imacat.idv.tw (imacat), 2023/3/7
 | 
				
			||||||
 | 
					
 | 
				
			||||||
#  Copyright (c) 2023 imacat.
 | 
					#  Copyright (c) 2023 imacat.
 | 
				
			||||||
@@ -77,6 +77,8 @@ class CSVRow(BaseCSVRow):
 | 
				
			|||||||
        """Constructs a row in the CSV.
 | 
					        """Constructs a row in the CSV.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        :param journal_entry_date: The journal entry date.
 | 
					        :param journal_entry_date: The journal entry date.
 | 
				
			||||||
 | 
					        :param currency: The currency.
 | 
				
			||||||
 | 
					        :param account: The account.
 | 
				
			||||||
        :param description: The description.
 | 
					        :param description: The description.
 | 
				
			||||||
        :param debit: The debit amount.
 | 
					        :param debit: The debit amount.
 | 
				
			||||||
        :param credit: The credit amount.
 | 
					        :param credit: The credit amount.
 | 
				
			||||||
@@ -116,6 +118,7 @@ class PageParams(BasePageParams):
 | 
				
			|||||||
        """Constructs the HTML page parameters.
 | 
					        """Constructs the HTML page parameters.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        :param period: The period.
 | 
					        :param period: The period.
 | 
				
			||||||
 | 
					        :param pagination: The pagination.
 | 
				
			||||||
        :param line_items: The line items.
 | 
					        :param line_items: The line items.
 | 
				
			||||||
        """
 | 
					        """
 | 
				
			||||||
        self.period: Period = period
 | 
					        self.period: Period = period
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1,4 +1,4 @@
 | 
				
			|||||||
# The Mia! Accounting Flask Project.
 | 
					# The Mia! Accounting Project.
 | 
				
			||||||
# Author: imacat@mail.imacat.idv.tw (imacat), 2023/3/7
 | 
					# Author: imacat@mail.imacat.idv.tw (imacat), 2023/3/7
 | 
				
			||||||
 | 
					
 | 
				
			||||||
#  Copyright (c) 2023 imacat.
 | 
					#  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
 | 
					# Author: imacat@mail.imacat.idv.tw (imacat), 2023/3/8
 | 
				
			||||||
 | 
					
 | 
				
			||||||
#  Copyright (c) 2023 imacat.
 | 
					#  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
 | 
					# Author: imacat@mail.imacat.idv.tw (imacat), 2023/3/7
 | 
				
			||||||
 | 
					
 | 
				
			||||||
#  Copyright (c) 2023 imacat.
 | 
					#  Copyright (c) 2023 imacat.
 | 
				
			||||||
@@ -191,6 +191,7 @@ class TrialBalance(BaseReport):
 | 
				
			|||||||
            .join(JournalEntry).join(Account)\
 | 
					            .join(JournalEntry).join(Account)\
 | 
				
			||||||
            .filter(*conditions)\
 | 
					            .filter(*conditions)\
 | 
				
			||||||
            .group_by(Account.id)\
 | 
					            .group_by(Account.id)\
 | 
				
			||||||
 | 
					            .having(balance_func != 0)\
 | 
				
			||||||
            .order_by(Account.base_code, Account.no)
 | 
					            .order_by(Account.base_code, Account.no)
 | 
				
			||||||
        balances: list[sa.Row] = db.session.execute(select_balances).all()
 | 
					        balances: list[sa.Row] = db.session.execute(select_balances).all()
 | 
				
			||||||
        accounts: dict[int, Account] \
 | 
					        accounts: dict[int, Account] \
 | 
				
			||||||
 
 | 
				
			|||||||
							
								
								
									
										185
									
								
								src/accounting/report/reports/unapplied.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										185
									
								
								src/accounting/report/reports/unapplied.py
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,185 @@
 | 
				
			|||||||
 | 
					# The Mia! Accounting Project.
 | 
				
			||||||
 | 
					# Author: imacat@mail.imacat.idv.tw (imacat), 2023/4/7
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#  Copyright (c) 2023 imacat.
 | 
				
			||||||
 | 
					#
 | 
				
			||||||
 | 
					#  Licensed under the Apache License, Version 2.0 (the "License");
 | 
				
			||||||
 | 
					#  you may not use this file except in compliance with the License.
 | 
				
			||||||
 | 
					#  You may obtain a copy of the License at
 | 
				
			||||||
 | 
					#
 | 
				
			||||||
 | 
					#      http://www.apache.org/licenses/LICENSE-2.0
 | 
				
			||||||
 | 
					#
 | 
				
			||||||
 | 
					#  Unless required by applicable law or agreed to in writing, software
 | 
				
			||||||
 | 
					#  distributed under the License is distributed on an "AS IS" BASIS,
 | 
				
			||||||
 | 
					#  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 | 
				
			||||||
 | 
					#  See the License for the specific language governing permissions and
 | 
				
			||||||
 | 
					#  limitations under the License.
 | 
				
			||||||
 | 
					"""The unapplied original line items.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					"""
 | 
				
			||||||
 | 
					from datetime import date
 | 
				
			||||||
 | 
					from decimal import Decimal
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					from flask import render_template, Response
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					from accounting.locale import gettext
 | 
				
			||||||
 | 
					from accounting.models import Account, JournalEntryLineItem
 | 
				
			||||||
 | 
					from accounting.report.utils.base_page_params import BasePageParams
 | 
				
			||||||
 | 
					from accounting.report.utils.base_report import BaseReport
 | 
				
			||||||
 | 
					from accounting.report.utils.csv_export import BaseCSVRow, csv_download
 | 
				
			||||||
 | 
					from accounting.report.utils.option_link import OptionLink
 | 
				
			||||||
 | 
					from accounting.report.utils.report_chooser import ReportChooser
 | 
				
			||||||
 | 
					from accounting.report.utils.report_type import ReportType
 | 
				
			||||||
 | 
					from accounting.report.utils.unapplied import get_accounts_with_unapplied
 | 
				
			||||||
 | 
					from accounting.report.utils.urls import unapplied_url
 | 
				
			||||||
 | 
					from accounting.utils.offset_matcher import OffsetMatcher
 | 
				
			||||||
 | 
					from accounting.utils.pagination import Pagination
 | 
				
			||||||
 | 
					from accounting.utils.permission import can_edit
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					class CSVRow(BaseCSVRow):
 | 
				
			||||||
 | 
					    """A row in the CSV."""
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def __init__(self, journal_entry_date: str | date, currency: str,
 | 
				
			||||||
 | 
					                 description: str | None, amount: str | Decimal,
 | 
				
			||||||
 | 
					                 net_balance: str | Decimal):
 | 
				
			||||||
 | 
					        """Constructs a row in the CSV.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        :param journal_entry_date: The journal entry date.
 | 
				
			||||||
 | 
					        :param currency: The currency.
 | 
				
			||||||
 | 
					        :param description: The description.
 | 
				
			||||||
 | 
					        :param amount: The amount.
 | 
				
			||||||
 | 
					        :param net_balance: The net balance.
 | 
				
			||||||
 | 
					        """
 | 
				
			||||||
 | 
					        self.date: str | date = journal_entry_date
 | 
				
			||||||
 | 
					        """The date."""
 | 
				
			||||||
 | 
					        self.currency: str = currency
 | 
				
			||||||
 | 
					        """The currency."""
 | 
				
			||||||
 | 
					        self.description: str | None = description
 | 
				
			||||||
 | 
					        """The description."""
 | 
				
			||||||
 | 
					        self.amount: str | Decimal = amount
 | 
				
			||||||
 | 
					        """The amount."""
 | 
				
			||||||
 | 
					        self.net_balance: str | Decimal = net_balance
 | 
				
			||||||
 | 
					        """The net balance."""
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    @property
 | 
				
			||||||
 | 
					    def values(self) -> list[str | date | Decimal | None]:
 | 
				
			||||||
 | 
					        """Returns the values of the row.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        :return: The values of the row.
 | 
				
			||||||
 | 
					        """
 | 
				
			||||||
 | 
					        return [self.date, self.currency, self.description, self.amount,
 | 
				
			||||||
 | 
					                self.net_balance]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					class PageParams(BasePageParams):
 | 
				
			||||||
 | 
					    """The HTML page parameters."""
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def __init__(self, account: Account,
 | 
				
			||||||
 | 
					                 is_mark_matches: bool,
 | 
				
			||||||
 | 
					                 pagination: Pagination[JournalEntryLineItem],
 | 
				
			||||||
 | 
					                 line_items: list[JournalEntryLineItem]):
 | 
				
			||||||
 | 
					        """Constructs the HTML page parameters.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        :param account: The account.
 | 
				
			||||||
 | 
					        :param is_mark_matches: Whether to mark the matched offsets.
 | 
				
			||||||
 | 
					        :param pagination: The pagination.
 | 
				
			||||||
 | 
					        :param line_items: The line items.
 | 
				
			||||||
 | 
					        """
 | 
				
			||||||
 | 
					        self.account: Account = account
 | 
				
			||||||
 | 
					        """The account."""
 | 
				
			||||||
 | 
					        self.pagination: Pagination[JournalEntryLineItem] = pagination
 | 
				
			||||||
 | 
					        """The pagination."""
 | 
				
			||||||
 | 
					        self.line_items: list[JournalEntryLineItem] = line_items
 | 
				
			||||||
 | 
					        """The line items."""
 | 
				
			||||||
 | 
					        self.is_mark_matches: bool = is_mark_matches
 | 
				
			||||||
 | 
					        """Whether to mark the matched offsets."""
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    @property
 | 
				
			||||||
 | 
					    def has_data(self) -> bool:
 | 
				
			||||||
 | 
					        """Returns whether there is any data on the page.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        :return: True if there is any data, or False otherwise.
 | 
				
			||||||
 | 
					        """
 | 
				
			||||||
 | 
					        return len(self.line_items) > 0
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    @property
 | 
				
			||||||
 | 
					    def report_chooser(self) -> ReportChooser:
 | 
				
			||||||
 | 
					        """Returns the report chooser.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        :return: The report chooser.
 | 
				
			||||||
 | 
					        """
 | 
				
			||||||
 | 
					        return ReportChooser(ReportType.UNAPPLIED,
 | 
				
			||||||
 | 
					                             account=self.account)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    @property
 | 
				
			||||||
 | 
					    def account_options(self) -> list[OptionLink]:
 | 
				
			||||||
 | 
					        """Returns the account options.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        :return: The account options.
 | 
				
			||||||
 | 
					        """
 | 
				
			||||||
 | 
					        options: list[OptionLink] = [OptionLink(gettext("Accounts"),
 | 
				
			||||||
 | 
					                                                unapplied_url(None),
 | 
				
			||||||
 | 
					                                                False)]
 | 
				
			||||||
 | 
					        options.extend([OptionLink(str(x),
 | 
				
			||||||
 | 
					                                   unapplied_url(x),
 | 
				
			||||||
 | 
					                                   x.id == self.account.id)
 | 
				
			||||||
 | 
					                        for x in get_accounts_with_unapplied()])
 | 
				
			||||||
 | 
					        return options
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					def get_csv_rows(line_items: list[JournalEntryLineItem]) -> list[CSVRow]:
 | 
				
			||||||
 | 
					    """Composes and returns the CSV rows from the line items.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    :param line_items: The line items.
 | 
				
			||||||
 | 
					    :return: The CSV rows.
 | 
				
			||||||
 | 
					    """
 | 
				
			||||||
 | 
					    rows: list[CSVRow] = [CSVRow(gettext("Date"), gettext("Currency"),
 | 
				
			||||||
 | 
					                                 gettext("Description"), gettext("Amount"),
 | 
				
			||||||
 | 
					                                 gettext("Net Balance"))]
 | 
				
			||||||
 | 
					    rows.extend([CSVRow(x.journal_entry.date, x.currency.code,
 | 
				
			||||||
 | 
					                        x.description, x.amount, x.net_balance)
 | 
				
			||||||
 | 
					                 for x in line_items])
 | 
				
			||||||
 | 
					    return rows
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					class UnappliedOriginalLineItems(BaseReport):
 | 
				
			||||||
 | 
					    """The unapplied original line items."""
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def __init__(self, account: Account):
 | 
				
			||||||
 | 
					        """Constructs the unapplied original line items.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        :param account: The account.
 | 
				
			||||||
 | 
					        """
 | 
				
			||||||
 | 
					        self.__account: Account = account
 | 
				
			||||||
 | 
					        """The account."""
 | 
				
			||||||
 | 
					        offset_matcher: OffsetMatcher = OffsetMatcher(self.__account)
 | 
				
			||||||
 | 
					        self.__line_items: list[JournalEntryLineItem] \
 | 
				
			||||||
 | 
					            = offset_matcher.unapplied
 | 
				
			||||||
 | 
					        """The line items."""
 | 
				
			||||||
 | 
					        self.__is_mark_matches: bool \
 | 
				
			||||||
 | 
					            = can_edit() and len(offset_matcher.unmatched_offsets) > 0
 | 
				
			||||||
 | 
					        """Whether to mark the matched offsets."""
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def csv(self) -> Response:
 | 
				
			||||||
 | 
					        """Returns the report as CSV for download.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        :return: The response of the report for download.
 | 
				
			||||||
 | 
					        """
 | 
				
			||||||
 | 
					        filename: str = f"unapplied-{self.__account.code}.csv"
 | 
				
			||||||
 | 
					        return csv_download(filename, get_csv_rows(self.__line_items))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def html(self) -> str:
 | 
				
			||||||
 | 
					        """Composes and returns the report as HTML.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        :return: The report as HTML.
 | 
				
			||||||
 | 
					        """
 | 
				
			||||||
 | 
					        pagination: Pagination[JournalEntryLineItem] \
 | 
				
			||||||
 | 
					            = Pagination[JournalEntryLineItem](self.__line_items,
 | 
				
			||||||
 | 
					                                               is_reversed=True)
 | 
				
			||||||
 | 
					        params: PageParams = PageParams(account=self.__account,
 | 
				
			||||||
 | 
					                                        is_mark_matches=self.__is_mark_matches,
 | 
				
			||||||
 | 
					                                        pagination=pagination,
 | 
				
			||||||
 | 
					                                        line_items=pagination.list)
 | 
				
			||||||
 | 
					        return render_template("accounting/report/unapplied.html",
 | 
				
			||||||
 | 
					                               report=params)
 | 
				
			||||||
							
								
								
									
										137
									
								
								src/accounting/report/reports/unapplied_accounts.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										137
									
								
								src/accounting/report/reports/unapplied_accounts.py
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,137 @@
 | 
				
			|||||||
 | 
					# The Mia! Accounting Project.
 | 
				
			||||||
 | 
					# Author: imacat@mail.imacat.idv.tw (imacat), 2023/4/7
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#  Copyright (c) 2023 imacat.
 | 
				
			||||||
 | 
					#
 | 
				
			||||||
 | 
					#  Licensed under the Apache License, Version 2.0 (the "License");
 | 
				
			||||||
 | 
					#  you may not use this file except in compliance with the License.
 | 
				
			||||||
 | 
					#  You may obtain a copy of the License at
 | 
				
			||||||
 | 
					#
 | 
				
			||||||
 | 
					#      http://www.apache.org/licenses/LICENSE-2.0
 | 
				
			||||||
 | 
					#
 | 
				
			||||||
 | 
					#  Unless required by applicable law or agreed to in writing, software
 | 
				
			||||||
 | 
					#  distributed under the License is distributed on an "AS IS" BASIS,
 | 
				
			||||||
 | 
					#  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 | 
				
			||||||
 | 
					#  See the License for the specific language governing permissions and
 | 
				
			||||||
 | 
					#  limitations under the License.
 | 
				
			||||||
 | 
					"""The accounts with unapplied original line items.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					"""
 | 
				
			||||||
 | 
					from datetime import date
 | 
				
			||||||
 | 
					from decimal import Decimal
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					from flask import render_template, Response
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					from accounting.locale import gettext
 | 
				
			||||||
 | 
					from accounting.models import Account
 | 
				
			||||||
 | 
					from accounting.report.utils.base_page_params import BasePageParams
 | 
				
			||||||
 | 
					from accounting.report.utils.base_report import BaseReport
 | 
				
			||||||
 | 
					from accounting.report.utils.csv_export import BaseCSVRow, csv_download
 | 
				
			||||||
 | 
					from accounting.report.utils.option_link import OptionLink
 | 
				
			||||||
 | 
					from accounting.report.utils.report_chooser import ReportChooser
 | 
				
			||||||
 | 
					from accounting.report.utils.report_type import ReportType
 | 
				
			||||||
 | 
					from accounting.report.utils.unapplied import get_accounts_with_unapplied
 | 
				
			||||||
 | 
					from accounting.report.utils.urls import unapplied_url
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					class CSVRow(BaseCSVRow):
 | 
				
			||||||
 | 
					    """A row in the CSV."""
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def __init__(self, account: str, count: int | str):
 | 
				
			||||||
 | 
					        """Constructs a row in the CSV.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        :param account: The account.
 | 
				
			||||||
 | 
					        :param count: The number of unapplied original line items.
 | 
				
			||||||
 | 
					        """
 | 
				
			||||||
 | 
					        self.account: str = account
 | 
				
			||||||
 | 
					        """The currency."""
 | 
				
			||||||
 | 
					        self.count: int | str = count
 | 
				
			||||||
 | 
					        """The number of unapplied original line items."""
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    @property
 | 
				
			||||||
 | 
					    def values(self) -> list[str | date | Decimal | None]:
 | 
				
			||||||
 | 
					        """Returns the values of the row.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        :return: The values of the row.
 | 
				
			||||||
 | 
					        """
 | 
				
			||||||
 | 
					        return [self.account, self.count]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					class PageParams(BasePageParams):
 | 
				
			||||||
 | 
					    """The HTML page parameters."""
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def __init__(self, accounts: list[Account]):
 | 
				
			||||||
 | 
					        """Constructs the HTML page parameters.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        :param accounts: The accounts.
 | 
				
			||||||
 | 
					        """
 | 
				
			||||||
 | 
					        self.accounts: list[Account] = accounts
 | 
				
			||||||
 | 
					        """The accounts."""
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    @property
 | 
				
			||||||
 | 
					    def has_data(self) -> bool:
 | 
				
			||||||
 | 
					        """Returns whether there is any data on the page.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        :return: True if there is any data, or False otherwise.
 | 
				
			||||||
 | 
					        """
 | 
				
			||||||
 | 
					        return len(self.accounts) > 0
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    @property
 | 
				
			||||||
 | 
					    def report_chooser(self) -> ReportChooser:
 | 
				
			||||||
 | 
					        """Returns the report chooser.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        :return: The report chooser.
 | 
				
			||||||
 | 
					        """
 | 
				
			||||||
 | 
					        return ReportChooser(ReportType.UNAPPLIED)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    @property
 | 
				
			||||||
 | 
					    def account_options(self) -> list[OptionLink]:
 | 
				
			||||||
 | 
					        """Returns the account options.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        :return: The account options.
 | 
				
			||||||
 | 
					        """
 | 
				
			||||||
 | 
					        options: list[OptionLink] = [OptionLink(gettext("Accounts"),
 | 
				
			||||||
 | 
					                                                unapplied_url(None),
 | 
				
			||||||
 | 
					                                                True)]
 | 
				
			||||||
 | 
					        options.extend([OptionLink(str(x),
 | 
				
			||||||
 | 
					                                   unapplied_url(x),
 | 
				
			||||||
 | 
					                                   False)
 | 
				
			||||||
 | 
					                        for x in self.accounts])
 | 
				
			||||||
 | 
					        return options
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					def get_csv_rows(accounts: list[Account]) -> list[CSVRow]:
 | 
				
			||||||
 | 
					    """Composes and returns the CSV rows from the line items.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    :param accounts: The accounts.
 | 
				
			||||||
 | 
					    :return: The CSV rows.
 | 
				
			||||||
 | 
					    """
 | 
				
			||||||
 | 
					    rows: list[CSVRow] = [CSVRow(gettext("Account"), gettext("Count"))]
 | 
				
			||||||
 | 
					    rows.extend([CSVRow(str(x).title(), x.count)
 | 
				
			||||||
 | 
					                 for x in accounts])
 | 
				
			||||||
 | 
					    return rows
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					class AccountsWithUnappliedOriginalLineItems(BaseReport):
 | 
				
			||||||
 | 
					    """The accounts with unapplied original line items."""
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def __init__(self):
 | 
				
			||||||
 | 
					        """Constructs the outstanding balances."""
 | 
				
			||||||
 | 
					        self.__accounts: list[Account] = get_accounts_with_unapplied()
 | 
				
			||||||
 | 
					        """The accounts."""
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def csv(self) -> Response:
 | 
				
			||||||
 | 
					        """Returns the report as CSV for download.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        :return: The response of the report for download.
 | 
				
			||||||
 | 
					        """
 | 
				
			||||||
 | 
					        filename: str = f"unapplied-accounts.csv"
 | 
				
			||||||
 | 
					        return csv_download(filename, get_csv_rows(self.__accounts))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def html(self) -> str:
 | 
				
			||||||
 | 
					        """Composes and returns the report as HTML.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        :return: The report as HTML.
 | 
				
			||||||
 | 
					        """
 | 
				
			||||||
 | 
					        return render_template("accounting/report/unapplied-accounts.html",
 | 
				
			||||||
 | 
					                               report=PageParams(accounts=self.__accounts))
 | 
				
			||||||
@@ -1,4 +1,4 @@
 | 
				
			|||||||
# The Mia! Accounting Flask Project.
 | 
					# The Mia! Accounting Project.
 | 
				
			||||||
# Author: imacat@mail.imacat.idv.tw (imacat), 2023/3/7
 | 
					# Author: imacat@mail.imacat.idv.tw (imacat), 2023/3/7
 | 
				
			||||||
 | 
					
 | 
				
			||||||
#  Copyright (c) 2023 imacat.
 | 
					#  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
 | 
					# Author: imacat@mail.imacat.idv.tw (imacat), 2023/3/7
 | 
				
			||||||
 | 
					
 | 
				
			||||||
#  Copyright (c) 2023 imacat.
 | 
					#  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
 | 
					# Author: imacat@mail.imacat.idv.tw (imacat), 2023/3/6
 | 
				
			||||||
 | 
					
 | 
				
			||||||
#  Copyright (c) 2023 imacat.
 | 
					#  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
 | 
					# Author: imacat@mail.imacat.idv.tw (imacat), 2023/3/8
 | 
				
			||||||
 | 
					
 | 
				
			||||||
#  Copyright (c) 2023 imacat.
 | 
					#  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
 | 
					# Author: imacat@mail.imacat.idv.tw (imacat), 2023/3/7
 | 
				
			||||||
 | 
					
 | 
				
			||||||
#  Copyright (c) 2023 imacat.
 | 
					#  Copyright (c) 2023 imacat.
 | 
				
			||||||
@@ -22,6 +22,7 @@ from abc import ABC, abstractmethod
 | 
				
			|||||||
from datetime import timedelta, date
 | 
					from datetime import timedelta, date
 | 
				
			||||||
from decimal import Decimal
 | 
					from decimal import Decimal
 | 
				
			||||||
from io import StringIO
 | 
					from io import StringIO
 | 
				
			||||||
 | 
					from urllib.parse import quote
 | 
				
			||||||
 | 
					
 | 
				
			||||||
from flask import Response
 | 
					from flask import Response
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -53,7 +54,7 @@ def csv_download(filename: str, rows: list[BaseCSVRow]) -> Response:
 | 
				
			|||||||
        fp.seek(0)
 | 
					        fp.seek(0)
 | 
				
			||||||
        response: Response = Response(fp.read(), mimetype="text/csv")
 | 
					        response: Response = Response(fp.read(), mimetype="text/csv")
 | 
				
			||||||
        response.headers["Content-Disposition"] \
 | 
					        response.headers["Content-Disposition"] \
 | 
				
			||||||
            = f"attachment; filename={filename}"
 | 
					            = f"attachment; filename={quote(filename)}"
 | 
				
			||||||
        return response
 | 
					        return response
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1,4 +1,4 @@
 | 
				
			|||||||
# The Mia! Accounting Flask Project.
 | 
					# The Mia! Accounting Project.
 | 
				
			||||||
# Author: imacat@mail.imacat.idv.tw (imacat), 2023/3/5
 | 
					# Author: imacat@mail.imacat.idv.tw (imacat), 2023/3/5
 | 
				
			||||||
 | 
					
 | 
				
			||||||
#  Copyright (c) 2023 imacat.
 | 
					#  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
 | 
					# Author: imacat@mail.imacat.idv.tw (imacat), 2023/3/4
 | 
				
			||||||
 | 
					
 | 
				
			||||||
#  Copyright (c) 2023 imacat.
 | 
					#  Copyright (c) 2023 imacat.
 | 
				
			||||||
@@ -34,7 +34,7 @@ from accounting.utils.current_account import CurrentAccount
 | 
				
			|||||||
from .option_link import OptionLink
 | 
					from .option_link import OptionLink
 | 
				
			||||||
from .report_type import ReportType
 | 
					from .report_type import ReportType
 | 
				
			||||||
from .urls import journal_url, ledger_url, income_expenses_url, \
 | 
					from .urls import journal_url, ledger_url, income_expenses_url, \
 | 
				
			||||||
    trial_balance_url, income_statement_url, balance_sheet_url
 | 
					    trial_balance_url, income_statement_url, balance_sheet_url, unapplied_url
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
class ReportChooser:
 | 
					class ReportChooser:
 | 
				
			||||||
@@ -68,40 +68,19 @@ class ReportChooser:
 | 
				
			|||||||
        """The title of the current report."""
 | 
					        """The title of the current report."""
 | 
				
			||||||
        self.is_search: bool = active_report == ReportType.SEARCH
 | 
					        self.is_search: bool = active_report == ReportType.SEARCH
 | 
				
			||||||
        """Whether the current report is the search page."""
 | 
					        """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.__income_expenses)
 | 
				
			||||||
 | 
					        self.__reports.append(self.__ledger)
 | 
				
			||||||
 | 
					        self.__reports.append(self.__journal)
 | 
				
			||||||
        self.__reports.append(self.__trial_balance)
 | 
					        self.__reports.append(self.__trial_balance)
 | 
				
			||||||
        self.__reports.append(self.__income_statement)
 | 
					        self.__reports.append(self.__income_statement)
 | 
				
			||||||
        self.__reports.append(self.__balance_sheet)
 | 
					        self.__reports.append(self.__balance_sheet)
 | 
				
			||||||
 | 
					        self.__reports.append(self.__unapplied)
 | 
				
			||||||
        for report in self.__reports:
 | 
					        for report in self.__reports:
 | 
				
			||||||
            if report.is_active:
 | 
					            if report.is_active:
 | 
				
			||||||
                self.current_report = report.title
 | 
					                self.current_report = report.title
 | 
				
			||||||
        if self.is_search:
 | 
					        if self.is_search:
 | 
				
			||||||
            self.current_report = gettext("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
 | 
					    @property
 | 
				
			||||||
    def __income_expenses(self) -> OptionLink:
 | 
					    def __income_expenses(self) -> OptionLink:
 | 
				
			||||||
        """Returns the income and expenses log.
 | 
					        """Returns the income and expenses log.
 | 
				
			||||||
@@ -118,6 +97,28 @@ class ReportChooser:
 | 
				
			|||||||
                          self.__active_report == ReportType.INCOME_EXPENSES,
 | 
					                          self.__active_report == ReportType.INCOME_EXPENSES,
 | 
				
			||||||
                          fa_icon="fa-solid fa-money-bill-wave")
 | 
					                          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
 | 
					    @property
 | 
				
			||||||
    def __trial_balance(self) -> OptionLink:
 | 
					    def __trial_balance(self) -> OptionLink:
 | 
				
			||||||
        """Returns the trial balance.
 | 
					        """Returns the trial balance.
 | 
				
			||||||
@@ -151,6 +152,23 @@ class ReportChooser:
 | 
				
			|||||||
                          self.__active_report == ReportType.BALANCE_SHEET,
 | 
					                          self.__active_report == ReportType.BALANCE_SHEET,
 | 
				
			||||||
                          fa_icon="fa-solid fa-scale-balanced")
 | 
					                          fa_icon="fa-solid fa-scale-balanced")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    @property
 | 
				
			||||||
 | 
					    def __unapplied(self) -> OptionLink:
 | 
				
			||||||
 | 
					        """Returns the unapplied original line items.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        :return: The unapplied original line items.
 | 
				
			||||||
 | 
					        """
 | 
				
			||||||
 | 
					        account: Account = self.__account
 | 
				
			||||||
 | 
					        if not account.is_need_offset:
 | 
				
			||||||
 | 
					            return OptionLink(gettext("Unapplied Original Line Items"),
 | 
				
			||||||
 | 
					                              unapplied_url(None),
 | 
				
			||||||
 | 
					                              self.__active_report == ReportType.UNAPPLIED,
 | 
				
			||||||
 | 
					                              fa_icon="fa-solid fa-link-slash")
 | 
				
			||||||
 | 
					        return OptionLink(gettext("Unapplied Original Line Items"),
 | 
				
			||||||
 | 
					                          unapplied_url(account),
 | 
				
			||||||
 | 
					                          self.__active_report == ReportType.UNAPPLIED,
 | 
				
			||||||
 | 
					                          fa_icon="fa-solid fa-link-slash")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def __iter__(self) -> t.Iterator[OptionLink]:
 | 
					    def __iter__(self) -> t.Iterator[OptionLink]:
 | 
				
			||||||
        """Returns the iteration of the reports.
 | 
					        """Returns the iteration of the reports.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1,4 +1,4 @@
 | 
				
			|||||||
# The Mia! Accounting Flask Project.
 | 
					# The Mia! Accounting Project.
 | 
				
			||||||
# Author: imacat@mail.imacat.idv.tw (imacat), 2023/3/4
 | 
					# Author: imacat@mail.imacat.idv.tw (imacat), 2023/3/4
 | 
				
			||||||
 | 
					
 | 
				
			||||||
#  Copyright (c) 2023 imacat.
 | 
					#  Copyright (c) 2023 imacat.
 | 
				
			||||||
@@ -34,5 +34,7 @@ class ReportType(Enum):
 | 
				
			|||||||
    """The income statement."""
 | 
					    """The income statement."""
 | 
				
			||||||
    BALANCE_SHEET: str = "balance-sheet"
 | 
					    BALANCE_SHEET: str = "balance-sheet"
 | 
				
			||||||
    """The balance sheet."""
 | 
					    """The balance sheet."""
 | 
				
			||||||
 | 
					    UNAPPLIED: str = "unapplied"
 | 
				
			||||||
 | 
					    """The unapplied original line items."""
 | 
				
			||||||
    SEARCH: str = "search"
 | 
					    SEARCH: str = "search"
 | 
				
			||||||
    """The search."""
 | 
					    """The search."""
 | 
				
			||||||
 
 | 
				
			|||||||
							
								
								
									
										67
									
								
								src/accounting/report/utils/unapplied.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										67
									
								
								src/accounting/report/utils/unapplied.py
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,67 @@
 | 
				
			|||||||
 | 
					# The Mia! Accounting Project.
 | 
				
			||||||
 | 
					# Author: imacat@mail.imacat.idv.tw (imacat), 2023/4/7
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#  Copyright (c) 2023 imacat.
 | 
				
			||||||
 | 
					#
 | 
				
			||||||
 | 
					#  Licensed under the Apache License, Version 2.0 (the "License");
 | 
				
			||||||
 | 
					#  you may not use this file except in compliance with the License.
 | 
				
			||||||
 | 
					#  You may obtain a copy of the License at
 | 
				
			||||||
 | 
					#
 | 
				
			||||||
 | 
					#      http://www.apache.org/licenses/LICENSE-2.0
 | 
				
			||||||
 | 
					#
 | 
				
			||||||
 | 
					#  Unless required by applicable law or agreed to in writing, software
 | 
				
			||||||
 | 
					#  distributed under the License is distributed on an "AS IS" BASIS,
 | 
				
			||||||
 | 
					#  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 | 
				
			||||||
 | 
					#  See the License for the specific language governing permissions and
 | 
				
			||||||
 | 
					#  limitations under the License.
 | 
				
			||||||
 | 
					"""The unapplied original line item utilities.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					"""
 | 
				
			||||||
 | 
					import sqlalchemy as sa
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					from accounting import db
 | 
				
			||||||
 | 
					from accounting.models import Account, JournalEntryLineItem
 | 
				
			||||||
 | 
					from accounting.utils.cast import be
 | 
				
			||||||
 | 
					from accounting.utils.offset_alias import offset_alias
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					def get_accounts_with_unapplied() -> list[Account]:
 | 
				
			||||||
 | 
					    """Returns the accounts with unapplied original line items.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    :return: The accounts with unapplied original line items.
 | 
				
			||||||
 | 
					    """
 | 
				
			||||||
 | 
					    offset: sa.Alias = offset_alias()
 | 
				
			||||||
 | 
					    net_balance: sa.Label \
 | 
				
			||||||
 | 
					        = (JournalEntryLineItem.amount
 | 
				
			||||||
 | 
					           + sa.func.sum(sa.case(
 | 
				
			||||||
 | 
					                (be(offset.c.is_debit == JournalEntryLineItem.is_debit),
 | 
				
			||||||
 | 
					                 offset.c.amount),
 | 
				
			||||||
 | 
					                else_=-offset.c.amount))).label("net_balance")
 | 
				
			||||||
 | 
					    select_unapplied: sa.Select \
 | 
				
			||||||
 | 
					        = sa.select(JournalEntryLineItem.id)\
 | 
				
			||||||
 | 
					        .join(Account)\
 | 
				
			||||||
 | 
					        .join(offset, be(JournalEntryLineItem.id
 | 
				
			||||||
 | 
					                         == offset.c.original_line_item_id),
 | 
				
			||||||
 | 
					              isouter=True)\
 | 
				
			||||||
 | 
					        .filter(Account.is_need_offset,
 | 
				
			||||||
 | 
					                sa.or_(sa.and_(Account.base_code.startswith("2"),
 | 
				
			||||||
 | 
					                               sa.not_(JournalEntryLineItem.is_debit)),
 | 
				
			||||||
 | 
					                       sa.and_(Account.base_code.startswith("1"),
 | 
				
			||||||
 | 
					                               JournalEntryLineItem.is_debit)))\
 | 
				
			||||||
 | 
					        .group_by(JournalEntryLineItem.id)\
 | 
				
			||||||
 | 
					        .having(sa.or_(sa.func.count(offset.c.id) == 0, net_balance != 0))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    count_func: sa.Label \
 | 
				
			||||||
 | 
					        = sa.func.count(JournalEntryLineItem.id).label("count")
 | 
				
			||||||
 | 
					    select: sa.Select = sa.select(Account.id, count_func)\
 | 
				
			||||||
 | 
					        .join(JournalEntryLineItem, isouter=True)\
 | 
				
			||||||
 | 
					        .filter(JournalEntryLineItem.id.in_(select_unapplied))\
 | 
				
			||||||
 | 
					        .group_by(Account.id)\
 | 
				
			||||||
 | 
					        .having(count_func > 0)
 | 
				
			||||||
 | 
					    counts: dict[int, int] \
 | 
				
			||||||
 | 
					        = {x.id: x.count for x in db.session.execute(select)}
 | 
				
			||||||
 | 
					    accounts: list[Account] = Account.query.filter(Account.id.in_(counts))\
 | 
				
			||||||
 | 
					        .order_by(Account.base_code, Account.no).all()
 | 
				
			||||||
 | 
					    for account in accounts:
 | 
				
			||||||
 | 
					        account.count = counts[account.id]
 | 
				
			||||||
 | 
					    return accounts
 | 
				
			||||||
@@ -1,4 +1,4 @@
 | 
				
			|||||||
# The Mia! Accounting Flask Project.
 | 
					# The Mia! Accounting Project.
 | 
				
			||||||
# Author: imacat@mail.imacat.idv.tw (imacat), 2023/3/9
 | 
					# Author: imacat@mail.imacat.idv.tw (imacat), 2023/3/9
 | 
				
			||||||
 | 
					
 | 
				
			||||||
#  Copyright (c) 2023 imacat.
 | 
					#  Copyright (c) 2023 imacat.
 | 
				
			||||||
@@ -34,8 +34,8 @@ def journal_url(period: Period) \
 | 
				
			|||||||
    :return: The URL of the journal.
 | 
					    :return: The URL of the journal.
 | 
				
			||||||
    """
 | 
					    """
 | 
				
			||||||
    if period.is_default:
 | 
					    if period.is_default:
 | 
				
			||||||
        return url_for("accounting.report.journal-default")
 | 
					        return url_for("accounting-report.journal-default")
 | 
				
			||||||
    return url_for("accounting.report.journal", period=period)
 | 
					    return url_for("accounting-report.journal", period=period)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
def ledger_url(currency: Currency, account: Account, period: Period) \
 | 
					def ledger_url(currency: Currency, account: Account, period: Period) \
 | 
				
			||||||
@@ -47,10 +47,11 @@ def ledger_url(currency: Currency, account: Account, period: Period) \
 | 
				
			|||||||
    :param period: The period.
 | 
					    :param period: The period.
 | 
				
			||||||
    :return: The URL of the ledger.
 | 
					    :return: The URL of the ledger.
 | 
				
			||||||
    """
 | 
					    """
 | 
				
			||||||
    if period.is_default:
 | 
					    if currency.code == default_currency_code() \
 | 
				
			||||||
        return url_for("accounting.report.ledger-default",
 | 
					            and account.code == Account.CASH_CODE \
 | 
				
			||||||
                       currency=currency, account=account)
 | 
					            and period.is_default:
 | 
				
			||||||
    return url_for("accounting.report.ledger",
 | 
					        return url_for("accounting-report.ledger-default")
 | 
				
			||||||
 | 
					    return url_for("accounting-report.ledger",
 | 
				
			||||||
                   currency=currency, account=account,
 | 
					                   currency=currency, account=account,
 | 
				
			||||||
                   period=period)
 | 
					                   period=period)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -67,11 +68,8 @@ def income_expenses_url(currency: Currency, account: CurrentAccount,
 | 
				
			|||||||
    if currency.code == default_currency_code() \
 | 
					    if currency.code == default_currency_code() \
 | 
				
			||||||
            and account.code == options.default_ie_account_code \
 | 
					            and account.code == options.default_ie_account_code \
 | 
				
			||||||
            and period.is_default:
 | 
					            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",
 | 
				
			||||||
        return url_for("accounting.report.income-expenses-default",
 | 
					 | 
				
			||||||
                       currency=currency, account=account)
 | 
					 | 
				
			||||||
    return url_for("accounting.report.income-expenses",
 | 
					 | 
				
			||||||
                   currency=currency, account=account,
 | 
					                   currency=currency, account=account,
 | 
				
			||||||
                   period=period)
 | 
					                   period=period)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -83,10 +81,9 @@ def trial_balance_url(currency: Currency, period: Period) -> str:
 | 
				
			|||||||
    :param period: The period.
 | 
					    :param period: The period.
 | 
				
			||||||
    :return: The URL of the trial balance.
 | 
					    :return: The URL of the trial balance.
 | 
				
			||||||
    """
 | 
					    """
 | 
				
			||||||
    if period.is_default:
 | 
					    if currency.code == default_currency_code() and 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)
 | 
					                   currency=currency, period=period)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -97,10 +94,9 @@ def income_statement_url(currency: Currency, period: Period) -> str:
 | 
				
			|||||||
    :param period: The period.
 | 
					    :param period: The period.
 | 
				
			||||||
    :return: The URL of the income statement.
 | 
					    :return: The URL of the income statement.
 | 
				
			||||||
    """
 | 
					    """
 | 
				
			||||||
    if period.is_default:
 | 
					    if currency.code == default_currency_code() and 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)
 | 
					                   currency=currency, period=period)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -111,8 +107,19 @@ def balance_sheet_url(currency: Currency, period: Period) -> str:
 | 
				
			|||||||
    :param period: The period.
 | 
					    :param period: The period.
 | 
				
			||||||
    :return: The URL of the balance sheet.
 | 
					    :return: The URL of the balance sheet.
 | 
				
			||||||
    """
 | 
					    """
 | 
				
			||||||
    if period.is_default:
 | 
					    if currency.code == default_currency_code() and 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)
 | 
					                   currency=currency, period=period)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					def unapplied_url(account: Account | None) -> str:
 | 
				
			||||||
 | 
					    """Returns the URL of the unapplied original line items.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    :param account: The account, or None to list the accounts with unapplied
 | 
				
			||||||
 | 
					        original line items.
 | 
				
			||||||
 | 
					    :return: The URL of the unapplied original line items.
 | 
				
			||||||
 | 
					    """
 | 
				
			||||||
 | 
					    if account is None:
 | 
				
			||||||
 | 
					        return url_for("accounting-report.unapplied-default")
 | 
				
			||||||
 | 
					    return url_for("accounting-report.unapplied", account=account)
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1,4 +1,4 @@
 | 
				
			|||||||
# The Mia! Accounting Flask Project.
 | 
					# The Mia! Accounting Project.
 | 
				
			||||||
# Author: imacat@mail.imacat.idv.tw (imacat), 2023/3/3
 | 
					# Author: imacat@mail.imacat.idv.tw (imacat), 2023/3/3
 | 
				
			||||||
 | 
					
 | 
				
			||||||
#  Copyright (c) 2023 imacat.
 | 
					#  Copyright (c) 2023 imacat.
 | 
				
			||||||
@@ -28,9 +28,11 @@ from accounting.utils.options import options
 | 
				
			|||||||
from accounting.utils.permission import has_permission, can_view
 | 
					from accounting.utils.permission import has_permission, can_view
 | 
				
			||||||
from .reports import Journal, Ledger, IncomeExpenses, TrialBalance, \
 | 
					from .reports import Journal, Ledger, IncomeExpenses, TrialBalance, \
 | 
				
			||||||
    IncomeStatement, BalanceSheet, Search
 | 
					    IncomeStatement, BalanceSheet, Search
 | 
				
			||||||
 | 
					from .reports.unapplied import UnappliedOriginalLineItems
 | 
				
			||||||
 | 
					from .reports.unapplied_accounts import AccountsWithUnappliedOriginalLineItems
 | 
				
			||||||
from .template_filters import format_amount
 | 
					from .template_filters import format_amount
 | 
				
			||||||
 | 
					
 | 
				
			||||||
bp: Blueprint = Blueprint("report", __name__)
 | 
					bp: Blueprint = Blueprint("accounting-report", __name__)
 | 
				
			||||||
"""The view blueprint for the reports."""
 | 
					"""The view blueprint for the reports."""
 | 
				
			||||||
bp.add_app_template_filter(format_amount, "accounting_report_format_amount")
 | 
					bp.add_app_template_filter(format_amount, "accounting_report_format_amount")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -42,10 +44,7 @@ def get_default_report() -> str | Response:
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
    :return: The income and expenses log in the default period.
 | 
					    :return: The income and expenses log in the default period.
 | 
				
			||||||
    """
 | 
					    """
 | 
				
			||||||
    return __get_income_expenses(
 | 
					    return get_default_income_expenses()
 | 
				
			||||||
        db.session.get(Currency, default_currency_code()),
 | 
					 | 
				
			||||||
        options.default_ie_account,
 | 
					 | 
				
			||||||
        get_period())
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@bp.get("journal", endpoint="journal-default")
 | 
					@bp.get("journal", endpoint="journal-default")
 | 
				
			||||||
@@ -81,17 +80,15 @@ def __get_journal(period: Period) -> str | Response:
 | 
				
			|||||||
    return report.html()
 | 
					    return report.html()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@bp.get("ledger/<currency:currency>/<account:account>",
 | 
					@bp.get("ledger", endpoint="ledger-default")
 | 
				
			||||||
        endpoint="ledger-default")
 | 
					 | 
				
			||||||
@has_permission(can_view)
 | 
					@has_permission(can_view)
 | 
				
			||||||
def get_default_ledger(currency: Currency, account: Account) -> str | Response:
 | 
					def get_default_ledger() -> str | Response:
 | 
				
			||||||
    """Returns the ledger in the default period.
 | 
					    """Returns the ledger in the default currency, cash, and default period.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    :param currency: The currency.
 | 
					    :return: The ledger in the default currency, cash, and default period.
 | 
				
			||||||
    :param account: The account.
 | 
					 | 
				
			||||||
    :return: The ledger in the default period.
 | 
					 | 
				
			||||||
    """
 | 
					    """
 | 
				
			||||||
    return __get_ledger(currency, account, get_period())
 | 
					    return __get_ledger(db.session.get(Currency, default_currency_code()),
 | 
				
			||||||
 | 
					                        Account.cash(), get_period())
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@bp.get("ledger/<currency:currency>/<account:account>/<period:period>",
 | 
					@bp.get("ledger/<currency:currency>/<account:account>/<period:period>",
 | 
				
			||||||
@@ -124,23 +121,21 @@ def __get_ledger(currency: Currency, account: Account, period: Period) \
 | 
				
			|||||||
    return report.html()
 | 
					    return report.html()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@bp.get("income-expenses/<currency:currency>/<ieAccount:account>",
 | 
					@bp.get("income-expenses", endpoint="income-expenses-default")
 | 
				
			||||||
        endpoint="income-expenses-default")
 | 
					 | 
				
			||||||
@has_permission(can_view)
 | 
					@has_permission(can_view)
 | 
				
			||||||
def get_default_income_expenses(currency: Currency, account: CurrentAccount) \
 | 
					def get_default_income_expenses() -> str | Response:
 | 
				
			||||||
        -> str | Response:
 | 
					 | 
				
			||||||
    """Returns the income and expenses log in the default period.
 | 
					    """Returns the income and expenses log in the default period.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    :param currency: The currency.
 | 
					 | 
				
			||||||
    :param account: The account.
 | 
					 | 
				
			||||||
    :return: The income and expenses log in the default period.
 | 
					    :return: The income and expenses log in the default period.
 | 
				
			||||||
    """
 | 
					    """
 | 
				
			||||||
    return __get_income_expenses(currency, account, get_period())
 | 
					    return __get_income_expenses(
 | 
				
			||||||
 | 
					        db.session.get(Currency, default_currency_code()),
 | 
				
			||||||
 | 
					        options.default_ie_account,
 | 
				
			||||||
 | 
					        get_period())
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@bp.get(
 | 
					@bp.get("income-expenses/<currency:currency>/<currentAccount:account>/"
 | 
				
			||||||
    "income-expenses/<currency:currency>/<ieAccount:account>/<period:period>",
 | 
					        "<period:period>", endpoint="income-expenses")
 | 
				
			||||||
    endpoint="income-expenses")
 | 
					 | 
				
			||||||
@has_permission(can_view)
 | 
					@has_permission(can_view)
 | 
				
			||||||
def get_income_expenses(currency: Currency, account: CurrentAccount,
 | 
					def get_income_expenses(currency: Currency, account: CurrentAccount,
 | 
				
			||||||
                        period: Period) -> str | Response:
 | 
					                        period: Period) -> str | Response:
 | 
				
			||||||
@@ -169,16 +164,15 @@ def __get_income_expenses(currency: Currency, account: CurrentAccount,
 | 
				
			|||||||
    return report.html()
 | 
					    return report.html()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@bp.get("trial-balance/<currency:currency>",
 | 
					@bp.get("trial-balance", endpoint="trial-balance-default")
 | 
				
			||||||
        endpoint="trial-balance-default")
 | 
					 | 
				
			||||||
@has_permission(can_view)
 | 
					@has_permission(can_view)
 | 
				
			||||||
def get_default_trial_balance(currency: Currency) -> str | Response:
 | 
					def get_default_trial_balance() -> str | Response:
 | 
				
			||||||
    """Returns the trial balance in the default period.
 | 
					    """Returns the trial balance in the default period.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    :param currency: The currency.
 | 
					 | 
				
			||||||
    :return: The trial balance in the default period.
 | 
					    :return: The trial balance in the default period.
 | 
				
			||||||
    """
 | 
					    """
 | 
				
			||||||
    return __get_trial_balance(currency, get_period())
 | 
					    return __get_trial_balance(
 | 
				
			||||||
 | 
					        db.session.get(Currency, default_currency_code()), get_period())
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@bp.get("trial-balance/<currency:currency>/<period:period>",
 | 
					@bp.get("trial-balance/<currency:currency>/<period:period>",
 | 
				
			||||||
@@ -207,16 +201,15 @@ def __get_trial_balance(currency: Currency, period: Period) -> str | Response:
 | 
				
			|||||||
    return report.html()
 | 
					    return report.html()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@bp.get("income-statement/<currency:currency>",
 | 
					@bp.get("income-statement", endpoint="income-statement-default")
 | 
				
			||||||
        endpoint="income-statement-default")
 | 
					 | 
				
			||||||
@has_permission(can_view)
 | 
					@has_permission(can_view)
 | 
				
			||||||
def get_default_income_statement(currency: Currency) -> str | Response:
 | 
					def get_default_income_statement() -> str | Response:
 | 
				
			||||||
    """Returns the income statement in the default period.
 | 
					    """Returns the income statement in the default period.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    :param currency: The currency.
 | 
					 | 
				
			||||||
    :return: The income statement in the default period.
 | 
					    :return: The income statement in the default period.
 | 
				
			||||||
    """
 | 
					    """
 | 
				
			||||||
    return __get_income_statement(currency, get_period())
 | 
					    return __get_income_statement(
 | 
				
			||||||
 | 
					        db.session.get(Currency, default_currency_code()), get_period())
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@bp.get("income-statement/<currency:currency>/<period:period>",
 | 
					@bp.get("income-statement/<currency:currency>/<period:period>",
 | 
				
			||||||
@@ -246,16 +239,15 @@ def __get_income_statement(currency: Currency, period: Period) \
 | 
				
			|||||||
    return report.html()
 | 
					    return report.html()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@bp.get("balance-sheet/<currency:currency>",
 | 
					@bp.get("balance-sheet", endpoint="balance-sheet-default")
 | 
				
			||||||
        endpoint="balance-sheet-default")
 | 
					 | 
				
			||||||
@has_permission(can_view)
 | 
					@has_permission(can_view)
 | 
				
			||||||
def get_default_balance_sheet(currency: Currency) -> str | Response:
 | 
					def get_default_balance_sheet() -> str | Response:
 | 
				
			||||||
    """Returns the balance sheet in the default period.
 | 
					    """Returns the balance sheet in the default period.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    :param currency: The currency.
 | 
					 | 
				
			||||||
    :return: The balance sheet in the default period.
 | 
					    :return: The balance sheet in the default period.
 | 
				
			||||||
    """
 | 
					    """
 | 
				
			||||||
    return __get_balance_sheet(currency, get_period())
 | 
					    return __get_balance_sheet(
 | 
				
			||||||
 | 
					        db.session.get(Currency, default_currency_code()), get_period())
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@bp.get("balance-sheet/<currency:currency>/<period:period>",
 | 
					@bp.get("balance-sheet/<currency:currency>/<period:period>",
 | 
				
			||||||
@@ -286,6 +278,34 @@ def __get_balance_sheet(currency: Currency, period: Period) \
 | 
				
			|||||||
    return report.html()
 | 
					    return report.html()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					@bp.get("unapplied", endpoint="unapplied-default")
 | 
				
			||||||
 | 
					@has_permission(can_view)
 | 
				
			||||||
 | 
					def get_default_unapplied() -> str | Response:
 | 
				
			||||||
 | 
					    """Returns the accounts with unapplied original line items.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    :return: The accounts with unapplied original line items.
 | 
				
			||||||
 | 
					    """
 | 
				
			||||||
 | 
					    report: AccountsWithUnappliedOriginalLineItems \
 | 
				
			||||||
 | 
					        = AccountsWithUnappliedOriginalLineItems()
 | 
				
			||||||
 | 
					    if "as" in request.args and request.args["as"] == "csv":
 | 
				
			||||||
 | 
					        return report.csv()
 | 
				
			||||||
 | 
					    return report.html()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					@bp.get("unapplied/<needOffsetAccount:account>", endpoint="unapplied")
 | 
				
			||||||
 | 
					@has_permission(can_view)
 | 
				
			||||||
 | 
					def get_unapplied(account: Account) -> str | Response:
 | 
				
			||||||
 | 
					    """Returns the unapplied original line items.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    :param account: The Account.
 | 
				
			||||||
 | 
					    :return: The unapplied original line items.
 | 
				
			||||||
 | 
					    """
 | 
				
			||||||
 | 
					    report: UnappliedOriginalLineItems = UnappliedOriginalLineItems(account)
 | 
				
			||||||
 | 
					    if "as" in request.args and request.args["as"] == "csv":
 | 
				
			||||||
 | 
					        return report.csv()
 | 
				
			||||||
 | 
					    return report.html()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@bp.get("search", endpoint="search")
 | 
					@bp.get("search", endpoint="search")
 | 
				
			||||||
@has_permission(can_view)
 | 
					@has_permission(can_view)
 | 
				
			||||||
def search() -> str | Response:
 | 
					def search() -> str | Response:
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1,4 +1,4 @@
 | 
				
			|||||||
/* The Mia! Accounting Flask Project
 | 
					/* The Mia! Accounting Project
 | 
				
			||||||
 * style.css: The style sheet for the accounting application.
 | 
					 * style.css: The style sheet for the accounting application.
 | 
				
			||||||
 */
 | 
					 */
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -209,11 +209,23 @@ a.accounting-report-table-row {
 | 
				
			|||||||
.accounting-report-table-body .accounting-amount {
 | 
					.accounting-report-table-body .accounting-amount {
 | 
				
			||||||
    font-style: italic;
 | 
					    font-style: italic;
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					.accounting-report-table-body .accounting-report-table-row {
 | 
				
			||||||
 | 
					    background-color: #f8f9fa;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
.accounting-report-table-body .accounting-report-table-row:nth-child(2n+1) {
 | 
					.accounting-report-table-body .accounting-report-table-row:nth-child(2n+1) {
 | 
				
			||||||
    background-color: #f2f2f2;
 | 
					    background-color: #ecedee;
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
.accounting-report-table-body .accounting-report-table-row:hover {
 | 
					.accounting-report-table-body .accounting-report-table-row:hover {
 | 
				
			||||||
    background-color: rgba(0, 0, 0, 0.075);
 | 
					    background-color: #e5e6e7;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					.accounting-report-table-body .accounting-report-table-row-danger {
 | 
				
			||||||
 | 
					    background-color: #f8d7da;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					.accounting-report-table-body .accounting-report-table-row-danger:nth-child(2n+1) {
 | 
				
			||||||
 | 
					    background-color: #eccccf;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					.accounting-report-table-body .accounting-report-table-row-danger:hover {
 | 
				
			||||||
 | 
					    background-color: #e5c7ca;
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
.accounting-journal-table .accounting-report-table-row {
 | 
					.accounting-journal-table .accounting-report-table-row {
 | 
				
			||||||
    grid-template-columns: 1fr 1fr 2fr 4fr 1fr 1fr;
 | 
					    grid-template-columns: 1fr 1fr 2fr 4fr 1fr 1fr;
 | 
				
			||||||
@@ -309,6 +321,16 @@ a.accounting-report-table-row {
 | 
				
			|||||||
.accounting-balance-sheet-total .accounting-amount, .accounting-balance-sheet-subtotal, .accounting-amount {
 | 
					.accounting-balance-sheet-total .accounting-amount, .accounting-balance-sheet-subtotal, .accounting-amount {
 | 
				
			||||||
    font-style: italic;
 | 
					    font-style: italic;
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					.accounting-unapplied-table .accounting-report-table-row {
 | 
				
			||||||
 | 
					    grid-template-columns: 1fr 1fr 5fr 1fr 1fr;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					.accounting-unapplied-account-table .accounting-report-table-row {
 | 
				
			||||||
 | 
					    display: flex;
 | 
				
			||||||
 | 
					    justify-content: space-between;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					.accounting-unapplied-account-table .accounting-report-table-header .accounting-report-table-row {
 | 
				
			||||||
 | 
					    display: block;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
/* The accounting report */
 | 
					/* The accounting report */
 | 
				
			||||||
.accounting-mobile-journal-credit {
 | 
					.accounting-mobile-journal-credit {
 | 
				
			||||||
@@ -316,6 +338,10 @@ a.accounting-report-table-row {
 | 
				
			|||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
/* The description editor */
 | 
					/* The description editor */
 | 
				
			||||||
 | 
					.accounting-description-editor-buttons {
 | 
				
			||||||
 | 
					    max-height: 7rem;
 | 
				
			||||||
 | 
					    overflow-y: scroll;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
.accounting-description-editor-buttons .btn {
 | 
					.accounting-description-editor-buttons .btn {
 | 
				
			||||||
    margin-bottom: 0.3rem;
 | 
					    margin-bottom: 0.3rem;
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
@@ -339,6 +365,12 @@ a.accounting-report-table-row {
 | 
				
			|||||||
    margin: 0;
 | 
					    margin: 0;
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/* The unmatched offsets */
 | 
				
			||||||
 | 
					.accounting-unmatched-offset-pair-list {
 | 
				
			||||||
 | 
					    height: 20rem;
 | 
				
			||||||
 | 
					    overflow-y: scroll;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
/* The Material Design text field (floating form control in Bootstrap) */
 | 
					/* The Material Design text field (floating form control in Bootstrap) */
 | 
				
			||||||
.accounting-material-text-field {
 | 
					.accounting-material-text-field {
 | 
				
			||||||
    position: relative;
 | 
					    position: relative;
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1,4 +1,4 @@
 | 
				
			|||||||
/* The Mia! Accounting Flask Project
 | 
					/* The Mia! Accounting Project
 | 
				
			||||||
 * account-form.js: The JavaScript for the account form
 | 
					 * account-form.js: The JavaScript for the account form
 | 
				
			||||||
 */
 | 
					 */
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1,4 +1,4 @@
 | 
				
			|||||||
/* The Mia! Accounting Flask Project
 | 
					/* The Mia! Accounting Project
 | 
				
			||||||
 * account-order.js: The JavaScript for the account order
 | 
					 * account-order.js: The JavaScript for the account order
 | 
				
			||||||
 */
 | 
					 */
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1,4 +1,4 @@
 | 
				
			|||||||
/* The Mia! Accounting Flask Project
 | 
					/* The Mia! Accounting Project
 | 
				
			||||||
 * currency-form.js: The JavaScript for the currency form
 | 
					 * currency-form.js: The JavaScript for the currency form
 | 
				
			||||||
 */
 | 
					 */
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1,4 +1,4 @@
 | 
				
			|||||||
/* The Mia! Accounting Flask Project
 | 
					/* The Mia! Accounting Project
 | 
				
			||||||
 * description-editor.js: The JavaScript for the description editor
 | 
					 * description-editor.js: The JavaScript for the description editor
 | 
				
			||||||
 */
 | 
					 */
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -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
 | 
					 * 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
 | 
					 * journal-entry-account-selector.js: The JavaScript for the account selector of the journal entry form
 | 
				
			||||||
 */
 | 
					 */
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -123,7 +123,7 @@ class JournalEntryAccountSelector {
 | 
				
			|||||||
                option.setShown(false);
 | 
					                option.setShown(false);
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
        if (!isAnyMatched) {
 | 
					        if (!isAnyMatched && this.#isShowMore) {
 | 
				
			||||||
            this.#optionList.classList.add("d-none");
 | 
					            this.#optionList.classList.add("d-none");
 | 
				
			||||||
            this.#queryNoResult.classList.remove("d-none");
 | 
					            this.#queryNoResult.classList.remove("d-none");
 | 
				
			||||||
        } else {
 | 
					        } else {
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1,4 +1,4 @@
 | 
				
			|||||||
/* The Mia! Accounting Flask Project
 | 
					/* The Mia! Accounting Project
 | 
				
			||||||
 * journal-entry-form.js: The JavaScript for the journal entry form
 | 
					 * journal-entry-form.js: The JavaScript for the journal entry form
 | 
				
			||||||
 */
 | 
					 */
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -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
 | 
					 * journal-entry-line-item-editor.js: The JavaScript for the journal entry line item editor
 | 
				
			||||||
 */
 | 
					 */
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1,4 +1,4 @@
 | 
				
			|||||||
/* The Mia! Accounting Flask Project
 | 
					/* The Mia! Accounting Project
 | 
				
			||||||
 * journal-entry-order.js: The JavaScript for the journal entry order
 | 
					 * journal-entry-order.js: The JavaScript for the journal entry order
 | 
				
			||||||
 */
 | 
					 */
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -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
 | 
					 * 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
 | 
					 * account-form.js: The JavaScript for the account form
 | 
				
			||||||
 */
 | 
					 */
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -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
 | 
					 * original-line-item-selector.js: The JavaScript for the original line item selector
 | 
				
			||||||
 */
 | 
					 */
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1,4 +1,4 @@
 | 
				
			|||||||
/* The Mia! Accounting Flask Project
 | 
					/* The Mia! Accounting Project
 | 
				
			||||||
 * period-chooser.js: The JavaScript for the period chooser
 | 
					 * period-chooser.js: The JavaScript for the period chooser
 | 
				
			||||||
 */
 | 
					 */
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 
 | 
				
			|||||||
Some files were not shown because too many files have changed in this diff Show More
		Reference in New Issue
	
	Block a user