Added the initial application with the main account list, the pagination, the query, the permission, the localization, the documentation, the test case, and a test demonstration site.
This commit is contained in:
		
							
								
								
									
										202
									
								
								LICENSE
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										202
									
								
								LICENSE
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,202 @@ | ||||
|  | ||||
|                                  Apache License | ||||
|                            Version 2.0, January 2004 | ||||
|                         http://www.apache.org/licenses/ | ||||
|  | ||||
|    TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION | ||||
|  | ||||
|    1. Definitions. | ||||
|  | ||||
|       "License" shall mean the terms and conditions for use, reproduction, | ||||
|       and distribution as defined by Sections 1 through 9 of this document. | ||||
|  | ||||
|       "Licensor" shall mean the copyright owner or entity authorized by | ||||
|       the copyright owner that is granting the License. | ||||
|  | ||||
|       "Legal Entity" shall mean the union of the acting entity and all | ||||
|       other entities that control, are controlled by, or are under common | ||||
|       control with that entity. For the purposes of this definition, | ||||
|       "control" means (i) the power, direct or indirect, to cause the | ||||
|       direction or management of such entity, whether by contract or | ||||
|       otherwise, or (ii) ownership of fifty percent (50%) or more of the | ||||
|       outstanding shares, or (iii) beneficial ownership of such entity. | ||||
|  | ||||
|       "You" (or "Your") shall mean an individual or Legal Entity | ||||
|       exercising permissions granted by this License. | ||||
|  | ||||
|       "Source" form shall mean the preferred form for making modifications, | ||||
|       including but not limited to software source code, documentation | ||||
|       source, and configuration files. | ||||
|  | ||||
|       "Object" form shall mean any form resulting from mechanical | ||||
|       transformation or translation of a Source form, including but | ||||
|       not limited to compiled object code, generated documentation, | ||||
|       and conversions to other media types. | ||||
|  | ||||
|       "Work" shall mean the work of authorship, whether in Source or | ||||
|       Object form, made available under the License, as indicated by a | ||||
|       copyright notice that is included in or attached to the work | ||||
|       (an example is provided in the Appendix below). | ||||
|  | ||||
|       "Derivative Works" shall mean any work, whether in Source or Object | ||||
|       form, that is based on (or derived from) the Work and for which the | ||||
|       editorial revisions, annotations, elaborations, or other modifications | ||||
|       represent, as a whole, an original work of authorship. For the purposes | ||||
|       of this License, Derivative Works shall not include works that remain | ||||
|       separable from, or merely link (or bind by name) to the interfaces of, | ||||
|       the Work and Derivative Works thereof. | ||||
|  | ||||
|       "Contribution" shall mean any work of authorship, including | ||||
|       the original version of the Work and any modifications or additions | ||||
|       to that Work or Derivative Works thereof, that is intentionally | ||||
|       submitted to Licensor for inclusion in the Work by the copyright owner | ||||
|       or by an individual or Legal Entity authorized to submit on behalf of | ||||
|       the copyright owner. For the purposes of this definition, "submitted" | ||||
|       means any form of electronic, verbal, or written communication sent | ||||
|       to the Licensor or its representatives, including but not limited to | ||||
|       communication on electronic mailing lists, source code control systems, | ||||
|       and issue tracking systems that are managed by, or on behalf of, the | ||||
|       Licensor for the purpose of discussing and improving the Work, but | ||||
|       excluding communication that is conspicuously marked or otherwise | ||||
|       designated in writing by the copyright owner as "Not a Contribution." | ||||
|  | ||||
|       "Contributor" shall mean Licensor and any individual or Legal Entity | ||||
|       on behalf of whom a Contribution has been received by Licensor and | ||||
|       subsequently incorporated within the Work. | ||||
|  | ||||
|    2. Grant of Copyright License. Subject to the terms and conditions of | ||||
|       this License, each Contributor hereby grants to You a perpetual, | ||||
|       worldwide, non-exclusive, no-charge, royalty-free, irrevocable | ||||
|       copyright license to reproduce, prepare Derivative Works of, | ||||
|       publicly display, publicly perform, sublicense, and distribute the | ||||
|       Work and such Derivative Works in Source or Object form. | ||||
|  | ||||
|    3. Grant of Patent License. Subject to the terms and conditions of | ||||
|       this License, each Contributor hereby grants to You a perpetual, | ||||
|       worldwide, non-exclusive, no-charge, royalty-free, irrevocable | ||||
|       (except as stated in this section) patent license to make, have made, | ||||
|       use, offer to sell, sell, import, and otherwise transfer the Work, | ||||
|       where such license applies only to those patent claims licensable | ||||
|       by such Contributor that are necessarily infringed by their | ||||
|       Contribution(s) alone or by combination of their Contribution(s) | ||||
|       with the Work to which such Contribution(s) was submitted. If You | ||||
|       institute patent litigation against any entity (including a | ||||
|       cross-claim or counterclaim in a lawsuit) alleging that the Work | ||||
|       or a Contribution incorporated within the Work constitutes direct | ||||
|       or contributory patent infringement, then any patent licenses | ||||
|       granted to You under this License for that Work shall terminate | ||||
|       as of the date such litigation is filed. | ||||
|  | ||||
|    4. Redistribution. You may reproduce and distribute copies of the | ||||
|       Work or Derivative Works thereof in any medium, with or without | ||||
|       modifications, and in Source or Object form, provided that You | ||||
|       meet the following conditions: | ||||
|  | ||||
|       (a) You must give any other recipients of the Work or | ||||
|           Derivative Works a copy of this License; and | ||||
|  | ||||
|       (b) You must cause any modified files to carry prominent notices | ||||
|           stating that You changed the files; and | ||||
|  | ||||
|       (c) You must retain, in the Source form of any Derivative Works | ||||
|           that You distribute, all copyright, patent, trademark, and | ||||
|           attribution notices from the Source form of the Work, | ||||
|           excluding those notices that do not pertain to any part of | ||||
|           the Derivative Works; and | ||||
|  | ||||
|       (d) If the Work includes a "NOTICE" text file as part of its | ||||
|           distribution, then any Derivative Works that You distribute must | ||||
|           include a readable copy of the attribution notices contained | ||||
|           within such NOTICE file, excluding those notices that do not | ||||
|           pertain to any part of the Derivative Works, in at least one | ||||
|           of the following places: within a NOTICE text file distributed | ||||
|           as part of the Derivative Works; within the Source form or | ||||
|           documentation, if provided along with the Derivative Works; or, | ||||
|           within a display generated by the Derivative Works, if and | ||||
|           wherever such third-party notices normally appear. The contents | ||||
|           of the NOTICE file are for informational purposes only and | ||||
|           do not modify the License. You may add Your own attribution | ||||
|           notices within Derivative Works that You distribute, alongside | ||||
|           or as an addendum to the NOTICE text from the Work, provided | ||||
|           that such additional attribution notices cannot be construed | ||||
|           as modifying the License. | ||||
|  | ||||
|       You may add Your own copyright statement to Your modifications and | ||||
|       may provide additional or different license terms and conditions | ||||
|       for use, reproduction, or distribution of Your modifications, or | ||||
|       for any such Derivative Works as a whole, provided Your use, | ||||
|       reproduction, and distribution of the Work otherwise complies with | ||||
|       the conditions stated in this License. | ||||
|  | ||||
|    5. Submission of Contributions. Unless You explicitly state otherwise, | ||||
|       any Contribution intentionally submitted for inclusion in the Work | ||||
|       by You to the Licensor shall be under the terms and conditions of | ||||
|       this License, without any additional terms or conditions. | ||||
|       Notwithstanding the above, nothing herein shall supersede or modify | ||||
|       the terms of any separate license agreement you may have executed | ||||
|       with Licensor regarding such Contributions. | ||||
|  | ||||
|    6. Trademarks. This License does not grant permission to use the trade | ||||
|       names, trademarks, service marks, or product names of the Licensor, | ||||
|       except as required for reasonable and customary use in describing the | ||||
|       origin of the Work and reproducing the content of the NOTICE file. | ||||
|  | ||||
|    7. Disclaimer of Warranty. Unless required by applicable law or | ||||
|       agreed to in writing, Licensor provides the Work (and each | ||||
|       Contributor provides its Contributions) on an "AS IS" BASIS, | ||||
|       WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or | ||||
|       implied, including, without limitation, any warranties or conditions | ||||
|       of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A | ||||
|       PARTICULAR PURPOSE. You are solely responsible for determining the | ||||
|       appropriateness of using or redistributing the Work and assume any | ||||
|       risks associated with Your exercise of permissions under this License. | ||||
|  | ||||
|    8. Limitation of Liability. In no event and under no legal theory, | ||||
|       whether in tort (including negligence), contract, or otherwise, | ||||
|       unless required by applicable law (such as deliberate and grossly | ||||
|       negligent acts) or agreed to in writing, shall any Contributor be | ||||
|       liable to You for damages, including any direct, indirect, special, | ||||
|       incidental, or consequential damages of any character arising as a | ||||
|       result of this License or out of the use or inability to use the | ||||
|       Work (including but not limited to damages for loss of goodwill, | ||||
|       work stoppage, computer failure or malfunction, or any and all | ||||
|       other commercial damages or losses), even if such Contributor | ||||
|       has been advised of the possibility of such damages. | ||||
|  | ||||
|    9. Accepting Warranty or Additional Liability. While redistributing | ||||
|       the Work or Derivative Works thereof, You may choose to offer, | ||||
|       and charge a fee for, acceptance of support, warranty, indemnity, | ||||
|       or other liability obligations and/or rights consistent with this | ||||
|       License. However, in accepting such obligations, You may act only | ||||
|       on Your own behalf and on Your sole responsibility, not on behalf | ||||
|       of any other Contributor, and only if You agree to indemnify, | ||||
|       defend, and hold each Contributor harmless for any liability | ||||
|       incurred by, or claims asserted against, such Contributor by reason | ||||
|       of your accepting any such warranty or additional liability. | ||||
|  | ||||
|    END OF TERMS AND CONDITIONS | ||||
|  | ||||
|    APPENDIX: How to apply the Apache License to your work. | ||||
|  | ||||
|       To apply the Apache License to your work, attach the following | ||||
|       boilerplate notice, with the fields enclosed by brackets "[]" | ||||
|       replaced with your own identifying information. (Don't include | ||||
|       the brackets!)  The text should be enclosed in the appropriate | ||||
|       comment syntax for the file format. We also recommend that a | ||||
|       file or class name and description of purpose be included on the | ||||
|       same "printed page" as the copyright notice for easier | ||||
|       identification within third-party archives. | ||||
|  | ||||
|    Copyright [yyyy] [name of copyright owner] | ||||
|  | ||||
|    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. | ||||
							
								
								
									
										28
									
								
								MANIFEST.in
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										28
									
								
								MANIFEST.in
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,28 @@ | ||||
| # 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. | ||||
|  | ||||
| include src/accounting/translations/* | ||||
| include src/accounting/translations/*/LC_MESSAGES/* | ||||
| include docs/* | ||||
| include docs/source/* | ||||
| include docs/source/_static/* | ||||
| include docs/source/_templates/* | ||||
| include tests/* | ||||
| include tests/testsite/* | ||||
| include tests/testsite/templates/* | ||||
| include tests/testsite/translations/* | ||||
| include tests/testsite/translations/*/LC_MESSAGES/* | ||||
							
								
								
									
										50
									
								
								README.rst
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										50
									
								
								README.rst
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,50 @@ | ||||
| ===================== | ||||
| Mia! Accounting Flask | ||||
| ===================== | ||||
|  | ||||
|  | ||||
| Description | ||||
| =========== | ||||
|  | ||||
| This is the Mia! Accounting Flask project.  It is an accounting | ||||
| module for the Flask_ applications. | ||||
|  | ||||
|  | ||||
| Install | ||||
| ======= | ||||
|  | ||||
| Install the latest source from the | ||||
| `Mia! Accounting Flask repository`_. | ||||
|  | ||||
| :: | ||||
|  | ||||
|     pip install git+https://gitea.imacat.idv.tw/imacat/mia-accounting-flask.git | ||||
|  | ||||
|  | ||||
| Copyright | ||||
| ========= | ||||
|  | ||||
|  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. | ||||
|  | ||||
|  | ||||
| Authors | ||||
| ======= | ||||
|  | ||||
| | imacat | ||||
| | imacat@mail.imacat.idv.tw | ||||
| | 2023/1/27 | ||||
|  | ||||
| .. _Flask: https://flask.palletsprojects.com | ||||
| .. _Mia! Accounting Flask repository: https://gitea.imacat.idv.tw/imacat/mia-accounting-flask | ||||
							
								
								
									
										20
									
								
								docs/Makefile
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										20
									
								
								docs/Makefile
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,20 @@ | ||||
| # Minimal makefile for Sphinx documentation | ||||
| # | ||||
|  | ||||
| # You can set these variables from the command line, and also | ||||
| # from the environment for the first two. | ||||
| SPHINXOPTS    ?= | ||||
| SPHINXBUILD   ?= sphinx-build | ||||
| SOURCEDIR     = source | ||||
| BUILDDIR      = build | ||||
|  | ||||
| # Put it first so that "make" without argument is like "make help". | ||||
| help: | ||||
| 	@$(SPHINXBUILD) -M help "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O) | ||||
|  | ||||
| .PHONY: help Makefile | ||||
|  | ||||
| # Catch-all target: route all unknown targets to Sphinx using the new | ||||
| # "make mode" option.  $(O) is meant as a shortcut for $(SPHINXOPTS). | ||||
| %: Makefile | ||||
| 	@$(SPHINXBUILD) -M $@ "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O) | ||||
							
								
								
									
										35
									
								
								docs/make.bat
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										35
									
								
								docs/make.bat
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,35 @@ | ||||
| @ECHO OFF | ||||
|  | ||||
| pushd %~dp0 | ||||
|  | ||||
| REM Command file for Sphinx documentation | ||||
|  | ||||
| if "%SPHINXBUILD%" == "" ( | ||||
| 	set SPHINXBUILD=sphinx-build | ||||
| ) | ||||
| set SOURCEDIR=source | ||||
| set BUILDDIR=build | ||||
|  | ||||
| %SPHINXBUILD% >NUL 2>NUL | ||||
| if errorlevel 9009 ( | ||||
| 	echo. | ||||
| 	echo.The 'sphinx-build' command was not found. Make sure you have Sphinx | ||||
| 	echo.installed, then set the SPHINXBUILD environment variable to point | ||||
| 	echo.to the full path of the 'sphinx-build' executable. Alternatively you | ||||
| 	echo.may add the Sphinx directory to PATH. | ||||
| 	echo. | ||||
| 	echo.If you don't have Sphinx installed, grab it from | ||||
| 	echo.https://www.sphinx-doc.org/ | ||||
| 	exit /b 1 | ||||
| ) | ||||
|  | ||||
| if "%1" == "" goto help | ||||
|  | ||||
| %SPHINXBUILD% -M %1 %SOURCEDIR% %BUILDDIR% %SPHINXOPTS% %O% | ||||
| goto end | ||||
|  | ||||
| :help | ||||
| %SPHINXBUILD% -M help %SOURCEDIR% %BUILDDIR% %SPHINXOPTS% %O% | ||||
|  | ||||
| :end | ||||
| popd | ||||
							
								
								
									
										0
									
								
								docs/source/_static/.keep
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										0
									
								
								docs/source/_static/.keep
									
									
									
									
									
										Normal file
									
								
							
							
								
								
									
										0
									
								
								docs/source/_templates/.keep
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										0
									
								
								docs/source/_templates/.keep
									
									
									
									
									
										Normal file
									
								
							
							
								
								
									
										53
									
								
								docs/source/accounting.base_account.rst
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										53
									
								
								docs/source/accounting.base_account.rst
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,53 @@ | ||||
| accounting.base\_account package | ||||
| ================================ | ||||
|  | ||||
| Submodules | ||||
| ---------- | ||||
|  | ||||
| accounting.base\_account.commands module | ||||
| ---------------------------------------- | ||||
|  | ||||
| .. automodule:: accounting.base_account.commands | ||||
|    :members: | ||||
|    :undoc-members: | ||||
|    :show-inheritance: | ||||
|  | ||||
| accounting.base\_account.database module | ||||
| ---------------------------------------- | ||||
|  | ||||
| .. automodule:: accounting.base_account.database | ||||
|    :members: | ||||
|    :undoc-members: | ||||
|    :show-inheritance: | ||||
|  | ||||
| accounting.base\_account.models module | ||||
| -------------------------------------- | ||||
|  | ||||
| .. automodule:: accounting.base_account.models | ||||
|    :members: | ||||
|    :undoc-members: | ||||
|    :show-inheritance: | ||||
|  | ||||
| accounting.base\_account.query module | ||||
| ------------------------------------- | ||||
|  | ||||
| .. automodule:: accounting.base_account.query | ||||
|    :members: | ||||
|    :undoc-members: | ||||
|    :show-inheritance: | ||||
|  | ||||
| accounting.base\_account.views module | ||||
| ------------------------------------- | ||||
|  | ||||
| .. automodule:: accounting.base_account.views | ||||
|    :members: | ||||
|    :undoc-members: | ||||
|    :show-inheritance: | ||||
|  | ||||
| Module contents | ||||
| --------------- | ||||
|  | ||||
| .. automodule:: accounting.base_account | ||||
|    :members: | ||||
|    :undoc-members: | ||||
|    :show-inheritance: | ||||
							
								
								
									
										30
									
								
								docs/source/accounting.rst
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										30
									
								
								docs/source/accounting.rst
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,30 @@ | ||||
| accounting package | ||||
| ================== | ||||
|  | ||||
| Subpackages | ||||
| ----------- | ||||
|  | ||||
| .. toctree:: | ||||
|    :maxdepth: 4 | ||||
|  | ||||
|    accounting.base_account | ||||
|    accounting.utils | ||||
|  | ||||
| Submodules | ||||
| ---------- | ||||
|  | ||||
| accounting.locale module | ||||
| ------------------------ | ||||
|  | ||||
| .. automodule:: accounting.locale | ||||
|    :members: | ||||
|    :undoc-members: | ||||
|    :show-inheritance: | ||||
|  | ||||
| Module contents | ||||
| --------------- | ||||
|  | ||||
| .. automodule:: accounting | ||||
|    :members: | ||||
|    :undoc-members: | ||||
|    :show-inheritance: | ||||
							
								
								
									
										37
									
								
								docs/source/accounting.utils.rst
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										37
									
								
								docs/source/accounting.utils.rst
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,37 @@ | ||||
| accounting.utils package | ||||
| ======================== | ||||
|  | ||||
| Submodules | ||||
| ---------- | ||||
|  | ||||
| accounting.utils.pagination module | ||||
| ---------------------------------- | ||||
|  | ||||
| .. automodule:: accounting.utils.pagination | ||||
|    :members: | ||||
|    :undoc-members: | ||||
|    :show-inheritance: | ||||
|  | ||||
| accounting.utils.permission module | ||||
| ---------------------------------- | ||||
|  | ||||
| .. automodule:: accounting.utils.permission | ||||
|    :members: | ||||
|    :undoc-members: | ||||
|    :show-inheritance: | ||||
|  | ||||
| accounting.utils.query module | ||||
| ----------------------------- | ||||
|  | ||||
| .. automodule:: accounting.utils.query | ||||
|    :members: | ||||
|    :undoc-members: | ||||
|    :show-inheritance: | ||||
|  | ||||
| Module contents | ||||
| --------------- | ||||
|  | ||||
| .. automodule:: accounting.utils | ||||
|    :members: | ||||
|    :undoc-members: | ||||
|    :show-inheritance: | ||||
							
								
								
									
										32
									
								
								docs/source/conf.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										32
									
								
								docs/source/conf.py
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,32 @@ | ||||
| # Configuration file for the Sphinx documentation builder. | ||||
| # | ||||
| # For the full list of built-in configuration values, see the documentation: | ||||
| # https://www.sphinx-doc.org/en/master/usage/configuration.html | ||||
| import os | ||||
| import sys | ||||
|  | ||||
| sys.path.insert(0, os.path.abspath('../../src/')) | ||||
|  | ||||
| # -- Project information ----------------------------------------------------- | ||||
| # https://www.sphinx-doc.org/en/master/usage/configuration.html#project-information | ||||
|  | ||||
| project = 'Mia! Accounting Flask' | ||||
| copyright = '2023, imacat' | ||||
| author = 'imacat' | ||||
| release = '0.0.0' | ||||
|  | ||||
| # -- General configuration --------------------------------------------------- | ||||
| # https://www.sphinx-doc.org/en/master/usage/configuration.html#general-configuration | ||||
|  | ||||
| extensions = ['sphinx.ext.autodoc'] | ||||
|  | ||||
| templates_path = ['_templates'] | ||||
| exclude_patterns = [] | ||||
|  | ||||
|  | ||||
|  | ||||
| # -- Options for HTML output ------------------------------------------------- | ||||
| # https://www.sphinx-doc.org/en/master/usage/configuration.html#options-for-html-output | ||||
|  | ||||
| html_theme = 'nature' | ||||
| html_static_path = ['_static'] | ||||
							
								
								
									
										20
									
								
								docs/source/index.rst
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										20
									
								
								docs/source/index.rst
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,20 @@ | ||||
| .. Mia! Accounting Flask documentation master file, created by | ||||
|    sphinx-quickstart on Fri Jan 27 12:20:04 2023. | ||||
|    You can adapt this file completely to your liking, but it should at least | ||||
|    contain the root `toctree` directive. | ||||
|  | ||||
| Welcome to Mia! Accounting Flask's documentation! | ||||
| ================================================= | ||||
|  | ||||
| .. toctree:: | ||||
|    :maxdepth: 2 | ||||
|    :caption: Contents: | ||||
|  | ||||
|  | ||||
|  | ||||
| Indices and tables | ||||
| ================== | ||||
|  | ||||
| * :ref:`genindex` | ||||
| * :ref:`modindex` | ||||
| * :ref:`search` | ||||
							
								
								
									
										7
									
								
								docs/source/modules.rst
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										7
									
								
								docs/source/modules.rst
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,7 @@ | ||||
| src | ||||
| === | ||||
|  | ||||
| .. toctree:: | ||||
|    :maxdepth: 4 | ||||
|  | ||||
|    accounting | ||||
							
								
								
									
										20
									
								
								pyproject.toml
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										20
									
								
								pyproject.toml
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,20 @@ | ||||
| # The Mia! Flask Project. | ||||
| # Author: imacat@mail.imacat.idv.tw (imacat), 2022/8/21 | ||||
|  | ||||
| #  Copyright (c) 2022 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. | ||||
|  | ||||
| [build-system] | ||||
| requires = ["setuptools>=42"] | ||||
| build-backend = "setuptools.build_meta" | ||||
							
								
								
									
										56
									
								
								setup.cfg
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										56
									
								
								setup.cfg
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,56 @@ | ||||
| # 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.0.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.10 | ||||
| install_requires = | ||||
|   flask | ||||
|   Flask-SQLAlchemy | ||||
|   Flask-WTF | ||||
|   Flask-Babel >= 3 | ||||
|   Flask-Babel-JS | ||||
| tests_require = | ||||
|   unittest | ||||
|   httpx | ||||
|   OpenCC | ||||
|  | ||||
| [options.package_data] | ||||
| accounting = | ||||
|   templates/** | ||||
|   translations/*/LC_MESSAGES/*.mo | ||||
| accounting.base_account = | ||||
|   templates/** | ||||
							
								
								
									
										57
									
								
								src/accounting/__init__.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										57
									
								
								src/accounting/__init__.py
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,57 @@ | ||||
| # The Mia! Accounting Flask Project. | ||||
| # Author: imacat@mail.imacat.idv.tw (imacat), 2023/1/25 | ||||
|  | ||||
| #  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 accounting application. | ||||
|  | ||||
| """ | ||||
| import typing as t | ||||
|  | ||||
| from flask import Flask, Blueprint | ||||
|  | ||||
|  | ||||
| def init_app(app: Flask, url_prefix: str = "/accounting", | ||||
|              can_view_func: t.Callable[[], bool] | None = None, | ||||
|              can_edit_func: t.Callable[[], bool] | None = None) -> None: | ||||
|     """Initialize the application. | ||||
|  | ||||
|     :param app: The Flask application. | ||||
|     :param url_prefix: The URL prefix of the accounting application. | ||||
|     :param can_view_func: A callback that returns whether the current user can | ||||
|         view the accounting data. | ||||
|     :param can_edit_func: A callback that returns whether the current user can | ||||
|         edit the accounting data. | ||||
|     :return: None. | ||||
|     """ | ||||
|     # The database instance must be set before loading everything | ||||
|     # in the application. | ||||
|     from .database import set_db | ||||
|     set_db(app.extensions["sqlalchemy"]) | ||||
|  | ||||
|     bp: Blueprint = Blueprint("accounting", __name__, | ||||
|                               url_prefix=url_prefix, | ||||
|                               template_folder="templates", | ||||
|                               static_folder="static") | ||||
|  | ||||
|     from . import locale | ||||
|     locale.init_app(app, bp) | ||||
|  | ||||
|     from .utils import permission | ||||
|     permission.init_app(app, can_view_func, can_edit_func) | ||||
|  | ||||
|     from . import base_account | ||||
|     base_account.init_app(app, bp) | ||||
|  | ||||
|     app.register_blueprint(bp) | ||||
							
								
								
									
										34
									
								
								src/accounting/base_account/__init__.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										34
									
								
								src/accounting/base_account/__init__.py
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,34 @@ | ||||
| # The Mia! Accounting Flask Project. | ||||
| # Author: imacat@mail.imacat.idv.tw (imacat), 2023/1/25 | ||||
|  | ||||
| #  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 base account management. | ||||
|  | ||||
| """ | ||||
| from flask import Flask, Blueprint | ||||
|  | ||||
|  | ||||
| def init_app(app: Flask, bp: Blueprint) -> None: | ||||
|     """Initialize the application. | ||||
|  | ||||
|     :param bp: The blueprint of the accounting application. | ||||
|     :param app: The Flask application. | ||||
|     :return: None. | ||||
|     """ | ||||
|     from .views import bp as base_account_bp | ||||
|     bp.register_blueprint(base_account_bp, url_prefix="/base-accounts") | ||||
|  | ||||
|     from .commands import init_base_accounts_command | ||||
|     app.cli.add_command(init_base_accounts_command) | ||||
							
								
								
									
										709
									
								
								src/accounting/base_account/commands.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										709
									
								
								src/accounting/base_account/commands.py
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,709 @@ | ||||
| # The Mia! Accounting Flask Project. | ||||
| # Author: imacat@mail.imacat.idv.tw (imacat), 2023/1/25 | ||||
|  | ||||
| #  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 console commands for the base account management. | ||||
|  | ||||
| """ | ||||
| import click | ||||
| from flask.cli import with_appcontext | ||||
|  | ||||
| from accounting.database import db | ||||
| from .models import BaseAccount, BaseAccountL10n | ||||
|  | ||||
| BaseAccountData = tuple[int, str, str, str] | ||||
| """The format of the base account data, as a list of (code, English, | ||||
| Traditional Chinese, Simplified Chinese) tuples.""" | ||||
|  | ||||
|  | ||||
| @click.command("accounting-init-base") | ||||
| @with_appcontext | ||||
| def init_base_accounts_command() -> None: | ||||
|     """Initializes the base accounts.""" | ||||
|     if BaseAccount.query.first() is not None: | ||||
|         click.echo("Base accounts already exist.") | ||||
|         raise click.Abort | ||||
|  | ||||
|     db.session.bulk_save_objects( | ||||
|         [BaseAccount(code=str(x[0]), title_l10n=x[1]) for x in DATA]) | ||||
|     db.session.bulk_save_objects( | ||||
|         [BaseAccountL10n(account_code=x[0], locale=y[0], title=y[1]) | ||||
|          for x in DATA for y in (("zh_Hant", x[2]), ("zh_Hans", x[3]))]) | ||||
|     db.session.commit() | ||||
|     click.echo("Base accounts initialized.") | ||||
|  | ||||
|  | ||||
| DATA: list[BaseAccountData] = [ | ||||
|     (1, "assets", "資產", "资产"), | ||||
|     (2, "liabilities", "負債", "负债"), | ||||
|     (3, "owners’ equity", "業主權益", "业主权益"), | ||||
|     (4, "operating revenue", "營業收入", "营业收入"), | ||||
|     (5, "operating costs", "營業成本", "营业成本"), | ||||
|     (6, "operating expenses", "營業費用", "营业费用"), | ||||
|     (7, "non-operating revenue and expenses, other income (expense)", | ||||
|      "營業外收入及費用", "营业外收入及费用"), | ||||
|     (8, "income tax expense (or benefit)", "所得稅費用(或利益)", | ||||
|      "所得税费用(或利益)"), | ||||
|     (9, "nonrecurring gain or loss", "非經常營業損益", "非经常营业损益"), | ||||
|     (11, "current assets", "流動資產", "流动资产"), | ||||
|     (12, "current assets", "流動資產", "流动资产"), | ||||
|     (13, "funds and long-term investments", "基金及長期投資", "基金及长期投资"), | ||||
|     (14, "property , plant, and equipment", "固定資產", "固定资产"), | ||||
|     (15, "property , plant, and equipment", "固定資產", "固定资产"), | ||||
|     (16, "depletable assets", "遞耗資產", "递耗资产"), | ||||
|     (17, "intangible assets", "無形資產", "无形资产"), | ||||
|     (18, "other assets", "其他資產", "其他资产"), | ||||
|     (21, "current liabilities", "流動負債", "流动负债"), | ||||
|     (22, "current liabilities", "流動負債", "流动负债"), | ||||
|     (23, "long-term liabilities", "長期負債", "长期负债"), | ||||
|     (28, "other liabilities", "其他負債", "其他负债"), | ||||
|     (31, "capital", "資本", "资本"), | ||||
|     (32, "additional paid-in capital", "資本公積", "资本公积"), | ||||
|     (33, "retained earnings (accumulated deficit)", "保留盈餘(或累積虧損)", | ||||
|      "保留盈余(或累积亏损)"), | ||||
|     (34, "equity adjustments", "權益調整", "权益调整"), | ||||
|     (35, "treasury stock", "庫藏股", "库藏股"), | ||||
|     (36, "minority interest", "少數股權", "少数股权"), | ||||
|     (41, "sales revenue", "銷貨收入", "销货收入"), | ||||
|     (46, "service revenue", "勞務收入", "劳务收入"), | ||||
|     (47, "agency revenue", "業務收入", "业务收入"), | ||||
|     (48, "other operating revenue", "其他營業收入", "其他营业收入"), | ||||
|     (51, "cost of goods sold", "銷貨成本", "销货成本"), | ||||
|     (56, "service costs", "勞務成本", "劳务成本"), | ||||
|     (57, "agency costs", "業務成本", "业务成本"), | ||||
|     (58, "other operating costs", "其他營業成本", "其他营业成本"), | ||||
|     (61, "selling expenses", "推銷費用", "推销费用"), | ||||
|     (62, "general & administrative expenses", "管理及總務費用", "管理及总务费用"), | ||||
|     (63, "research and development expenses", "研究發展費用", "研究发展费用"), | ||||
|     (71, "non-operating revenue", "營業外收入", "营业外收入"), | ||||
|     (72, "non-operating revenue", "營業外收入", "营业外收入"), | ||||
|     (73, "non-operating revenue", "營業外收入", "营业外收入"), | ||||
|     (74, "non-operating revenue", "營業外收入", "营业外收入"), | ||||
|     (75, "non-operating expenses", "營業外費用", "营业外费用"), | ||||
|     (76, "non-operating expenses", "營業外費用", "营业外费用"), | ||||
|     (77, "non-operating expenses", "營業外費用", "营业外费用"), | ||||
|     (78, "non-operating expenses", "營業外費用", "营业外费用"), | ||||
|     (81, "income tax expense (or benefit)", "所得稅費用(或利益)", | ||||
|      "所得税费用(或利益)"), | ||||
|     (91, "gain (loss) from discontinued operations", "停業部門損益", | ||||
|      "停业部门损益"), | ||||
|     (92, "extraordinary gain or loss", "非常損益", "非常损益"), | ||||
|     (93, "cumulative effect of changes in accounting principles", | ||||
|      "會計原則變動累積影響數", "会计原则变动累积影响数"), | ||||
|     (94, "minority interest income", "少數股權淨利", "少数股权净利"), | ||||
|     (111, "cash and cash equivalents", "現金及約當現金", "现金及约当现金"), | ||||
|     (112, "short-term investments", "短期投資", "短期投资"), | ||||
|     (113, "notes receivable", "應收票據", "应收票据"), | ||||
|     (114, "accounts receivable", "應收帳款", "应收帐款"), | ||||
|     (118, "other receivables", "其他應收款", "其他应收款"), | ||||
|     (121, "inventories", "存貨", "存货"), | ||||
|     (122, "inventories", "存貨", "存货"), | ||||
|     (125, "prepaid expenses", "預付費用", "预付费用"), | ||||
|     (126, "prepayments", "預付款項", "预付款项"), | ||||
|     (128, "other current assets", "其他流動資產", "其他流动资产"), | ||||
|     (129, "other current assets", "其他流動資產", "其他流动资产"), | ||||
|     (131, "funds", "基金", "基金"), | ||||
|     (132, "long-term investments", "長期投資", "长期投资"), | ||||
|     (141, "land", "土地", "土地"), | ||||
|     (142, "land improvements", "土地改良物", "土地改良物"), | ||||
|     (143, "buildings", "房屋及建物", "房屋及建物"), | ||||
|     (144, "machinery and equipment", "機(器)具及設備", "机(器)具及设备"), | ||||
|     (145, "machinery and equipment", "機(器)具及設備", "机(器)具及设备"), | ||||
|     (146, "machinery and equipment", "機(器)具及設備", "机(器)具及设备"), | ||||
|     (151, "leased assets", "租賃資產", "租赁资产"), | ||||
|     (152, "leasehold improvements", "租賃權益改良", "租赁权益改良"), | ||||
|     (156, "construction in progress and prepayments for equipment", | ||||
|      "未完工程及預付購置設備款", "未完工程及预付购置设备款"), | ||||
|     (158, "miscellaneous property, plant, and equipment", "雜項固定資產", | ||||
|      "杂项固定资产"), | ||||
|     (161, "depletable assets", "遞耗資產", "递耗资产"), | ||||
|     (171, "trademarks", "商標權", "商标权"), | ||||
|     (172, "patents", "專利權", "专利权"), | ||||
|     (173, "franchise", "特許權", "特许权"), | ||||
|     (174, "copyright", "著作權", "著作权"), | ||||
|     (175, "computer software", "電腦軟體", "电脑软体"), | ||||
|     (176, "goodwill", "商譽", "商誉"), | ||||
|     (177, "organization costs", "開辦費", "开办费"), | ||||
|     (178, "other intangibles", "其他無形資產", "其他无形资产"), | ||||
|     (181, "deferred assets", "遞延資產", "递延资产"), | ||||
|     (182, "idle assets", "閒置資產", "闲置资产"), | ||||
|     (184, "long-term notes , accounts and overdue receivables", | ||||
|      "長期應收票據及款項與催收帳款", "长期应收票据及款项与催收帐款"), | ||||
|     (185, "assets leased to others", "出租資產", "出租资产"), | ||||
|     (186, "refundable deposit", "存出保證金", "存出保证金"), | ||||
|     (188, "miscellaneous assets", "雜項資產", "杂项资产"), | ||||
|     (211, "short-term borrowings (debt)", "短期借款", "短期借款"), | ||||
|     (212, "short-term notes and bills payable", "應付短期票券", "应付短期票券"), | ||||
|     (213, "notes payable", "應付票據", "应付票据"), | ||||
|     (214, "accounts pay able", "應付帳款", "应付帐款"), | ||||
|     (216, "income taxes payable", "應付所得稅", "应付所得税"), | ||||
|     (217, "accrued expenses", "應付費用", "应付费用"), | ||||
|     (218, "other payables", "其他應付款", "其他应付款"), | ||||
|     (219, "other payables", "其他應付款", "其他应付款"), | ||||
|     (226, "advance receipts", "預收款項", "预收款项"), | ||||
|     (227, "long-term liabilities -current portion", | ||||
|      "一年或一營業週期內到期長期負債", "一年或一营业周期内到期长期负债"), | ||||
|     (228, "other current liabilities", "其他流動負債", | ||||
|      "其他流动负债"), | ||||
|     (229, "other current liabilities", "其他流動負債", | ||||
|      "其他流动负债"), | ||||
|     (231, "corporate bonds payable", "應付公司債", "应付公司债"), | ||||
|     (232, "long-term loans payable", "長期借款", "长期借款"), | ||||
|     (233, "long-term notes and accounts payable", "長期應付票據及款項", | ||||
|      "长期应付票据及款项"), | ||||
|     (234, "accrued liabilities for land value increment tax", | ||||
|      "估計應付土地增值稅", "估计应付土地增值税"), | ||||
|     (235, "accrued pension liabilities", "應計退休金負債", "应计退休金负债"), | ||||
|     (238, "other long-term liabilities", "其他長期負債", "其他长期负债"), | ||||
|     (281, "deferred liabilities", "遞延負債", "递延负债"), | ||||
|     (286, "deposits received", "存入保證金", "存入保证金"), | ||||
|     (288, "miscellaneous liabilities", "雜項負債", "杂项负债"), | ||||
|     (311, "capital", "資本(或股本)", "资本(或股本)"), | ||||
|     (321, "paid-in capital in excess of par", "股票溢價", "股票溢价"), | ||||
|     (323, "capital surplus from assets revaluation", "資產重估增值準備", | ||||
|      "资产重估增值准备"), | ||||
|     (324, "capital surplus from gain on disposal of assets", "處分資產溢價公積", | ||||
|      "处分资产溢价公积"), | ||||
|     (325, "capital surplus from business combination", "合併公積", "合并公积"), | ||||
|     (326, "donated surplus", "受贈公積", "受赠公积"), | ||||
|     (328, "other additional paid-in capital", "其他資本公積", "其他资本公积"), | ||||
|     (331, "legal reserve", "法定盈餘公積", "法定盈余公积"), | ||||
|     (332, "special reserve", "特別盈餘公積", "特别盈余公积"), | ||||
|     (335, "retained earnings-unappropriated (or accumulated deficit)", | ||||
|      "未分配盈餘(或累積虧損)", "未分配盈余(或累积亏损)"), | ||||
|     (341, | ||||
|      "unrealized loss on market value decline of long-term equity investments", | ||||
|      "長期股權投資未實現跌價損失", "长期股权投资未实现跌价损失"), | ||||
|     (342, "cumulative translation adjustment", "累積換算調整數", "累积换算调整数"), | ||||
|     (343, "net loss not recognized as pension cost", "未認列為退休金成本之淨損失", | ||||
|      "未认列为退休金成本之净损失"), | ||||
|     (351, "treasury stock", "庫藏股", "库藏股"), | ||||
|     (361, "minority interest", "少數股權", "少数股权"), | ||||
|     (411, "sales revenue", "銷貨收入", "销货收入"), | ||||
|     (417, "sales return", "銷貨退回", "销货退回"), | ||||
|     (419, "sales allowances", "銷貨折讓", "销货折让"), | ||||
|     (461, "service revenue", "勞務收入", "劳务收入"), | ||||
|     (471, "agency revenue", "業務收入", "业务收入"), | ||||
|     (488, "other operating revenue", "其他營業收入—其他", "其他营业收入—其他"), | ||||
|     (511, "cost of goods sold", "銷貨成本", "销货成本"), | ||||
|     (512, "purchases", "進貨", "进货"), | ||||
|     (513, "materials purchased", "進料", "进料"), | ||||
|     (514, "direct labor", "直接人工", "直接人工"), | ||||
|     (515, "manufacturing overhead", "製造費用", "制造费用"), | ||||
|     (516, "manufacturing overhead", "製造費用", "制造费用"), | ||||
|     (517, "manufacturing overhead", "製造費用", "制造费用"), | ||||
|     (518, "manufacturing overhead", "製造費用", "制造费用"), | ||||
|     (561, "service costs", "勞務成本", "劳务成本"), | ||||
|     (571, "agency costs", "業務成本", "业务成本"), | ||||
|     (588, "other operating costs-other", "其他營業成本—其他", "其他营业成本—其他"), | ||||
|     (615, "selling expenses", "推銷費用", "推销费用"), | ||||
|     (616, "selling expenses", "推銷費用", "推销费用"), | ||||
|     (617, "selling expenses", "推銷費用", "推销费用"), | ||||
|     (618, "selling expenses", "推銷費用", "推销费用"), | ||||
|     (625, "general & administrative expenses", "管理及總務費用", "管理及总务费用"), | ||||
|     (626, "general & administrative expenses", "管理及總務費用", "管理及总务费用"), | ||||
|     (627, "general & administrative expenses", "管理及總務費用", "管理及总务费用"), | ||||
|     (628, "general & administrative expenses", "管理及總務費用", "管理及总务费用"), | ||||
|     (635, "research and development expenses", "研究發展費用", "研究发展费用"), | ||||
|     (636, "research and development expenses", "研究發展費用", "研究发展费用"), | ||||
|     (637, "research and development expenses", "研究發展費用", "研究发展费用"), | ||||
|     (638, "research and development expenses", "研究發展費用", "研究发展费用"), | ||||
|     (711, "interest revenue", "利息收入", "利息收入"), | ||||
|     (712, "investment income", "投資收益", "投资收益"), | ||||
|     (713, "foreign exchange gain", "兌換利益", "兑换利益"), | ||||
|     (714, "gain on disposal of investments", "處分投資收益", "处分投资收益"), | ||||
|     (715, "gain on disposal of assets", "處分資產溢價收入", "处分资产溢价收入"), | ||||
|     (748, "other non-operating revenue", "其他營業外收入", "其他营业外收入"), | ||||
|     (751, "interest expense", "利息費用", "利息费用"), | ||||
|     (752, "investment loss", "投資損失", "投资损失"), | ||||
|     (753, "foreign exchange loss", "兌換損失", "兑换损失"), | ||||
|     (754, "loss on disposal of investments", "處分投資損失", "处分投资损失"), | ||||
|     (755, "loss on disposal of assets", "處分資產損失", "处分资产损失"), | ||||
|     (788, "other non-operating expenses", "其他營業外費用", "其他营业外费用"), | ||||
|     (811, "income tax expense (or benefit)", "所得稅費用(或利益)", | ||||
|      "所得税费用(或利益)"), | ||||
|     (911, "income (loss) from operations of discontinued segments", | ||||
|      "停業部門損益—停業前營業損益", "停业部门损益—停业前营业损益"), | ||||
|     (912, "gain (loss) from disposal of discontinued segments", | ||||
|      "停業部門損益—處分損益", "停业部门损益—处分损益"), | ||||
|     (921, "extraordinary gain or loss", "非常損益", "非常损益"), | ||||
|     (931, "cumulative effect of changes in accounting principles", | ||||
|      "會計原則變動累積影響數", "会计原则变动累积影响数"), | ||||
|     (941, "minority interest income", "少數股權淨利", "少数股权净利"), | ||||
|     (1111, "cash on hand", "庫存現金", "库存现金"), | ||||
|     (1112, "petty cash/revolving funds", "零用金/週轉金", "零用金/周转金"), | ||||
|     (1113, "cash in banks", "銀行存款", "银行存款"), | ||||
|     (1116, "cash in transit", "在途現金", "在途现金"), | ||||
|     (1117, "cash equivalents", "約當現金", "约当现金"), | ||||
|     (1118, "other cash and cash equivalents", "其他現金及約當現金", | ||||
|      "其他现金及约当现金"), | ||||
|     (1121, "short-term investments – stock", "短期投資—股票", "短期投资—股票"), | ||||
|     (1122, "short-term investments – short-term notes and bills", | ||||
|      "短期投資—短期票券", "短期投资—短期票券"), | ||||
|     (1123, "short-term investments – government bonds", "短期投資—政府債券", | ||||
|      "短期投资—政府债券"), | ||||
|     (1124, "short-term investments – beneficiary certificates", | ||||
|      "短期投資—受益憑證", "短期投资—受益凭证"), | ||||
|     (1125, "short-term investments – corporate bonds", "短期投資—公司債", | ||||
|      "短期投资—公司债"), | ||||
|     (1128, "short-term investments – other", "短期投資—其他", "短期投资—其他"), | ||||
|     (1129, "allowance for reduction of short-term investment to market", | ||||
|      "備抵短期投資跌價損失", "备抵短期投资跌价损失"), | ||||
|     (1131, "notes receivable", "應收票據", "应收票据"), | ||||
|     (1132, "discounted notes receivable", "應收票據貼現", "应收票据贴现"), | ||||
|     (1137, "notes receivable – related parties", "應收票據—關係人", | ||||
|      "应收票据—关系人"), | ||||
|     (1138, "other notes receivable", "其他應收票據", "其他应收票据"), | ||||
|     (1139, "allowance for uncollectible accounts – notes receivable", | ||||
|      "備抵呆帳-應收票據", "备抵呆帐-应收票据"), | ||||
|     (1141, "accounts receivable", "應收帳款", "应收帐款"), | ||||
|     (1142, "installment accounts receivable", "應收分期帳款", | ||||
|      "应收分期帐款"), | ||||
|     (1147, "accounts receivable – related parties", "應收帳款—關係人", | ||||
|      "应收帐款—关系人"), | ||||
|     (1149, "allowance for uncollectible accounts – accounts receivable", | ||||
|      "備抵呆帳-應收帳款", "备抵呆帐-应收帐款"), | ||||
|     (1181, "forward exchange contract receivable", "應收出售遠匯款", | ||||
|      "应收出售远汇款"), | ||||
|     (1182, "forward exchange contract receivable – foreign currencies", | ||||
|      "應收遠匯款—外幣", "应收远汇款—外币"), | ||||
|     (1183, "discount on forward ex-change contract", "買賣遠匯折價", | ||||
|      "买卖远汇折价"), | ||||
|     (1184, "earned revenue receivable", "應收收益", "应收收益"), | ||||
|     (1185, "income tax refund receivable", "應收退稅款", "应收退税款"), | ||||
|     (1187, "other receivables – related parties", "其他應收款—關係人", | ||||
|      "其他应收款—关系人"), | ||||
|     (1188, "other receivables – other", "其他應收款—其他", "其他应收款—其他"), | ||||
|     (1189, "allowance for uncollectible accounts – other receivables", | ||||
|      "備抵呆帳—其他應收款", "备抵呆帐—其他应收款"), | ||||
|     (1211, "merchandise inventory", "商品存貨", "商品存货"), | ||||
|     (1212, "consigned goods", "寄銷商品", "寄销商品"), | ||||
|     (1213, "goods in transit", "在途商品", "在途商品"), | ||||
|     (1219, "allowance for reduction of inventory to market", "備抵存貨跌價損失", | ||||
|      "备抵存货跌价损失"), | ||||
|     (1221, "finished goods", "製成品", "制成品"), | ||||
|     (1222, "consigned finished goods", "寄銷製成品", "寄销制成品"), | ||||
|     (1223, "by-products", "副產品", "副产品"), | ||||
|     (1224, "work in process", "在製品", "在制品"), | ||||
|     (1225, "work in process – outsourced", "委外加工", "委外加工"), | ||||
|     (1226, "raw materials", "原料", "原料"), | ||||
|     (1227, "supplies", "物料", "物料"), | ||||
|     (1228, "materials and supplies in transit", "在途原物料", "在途原物料"), | ||||
|     (1229, "allowance for reduction of inventory to market", "備抵存貨跌價損失", | ||||
|      "备抵存货跌价损失"), | ||||
|     (1251, "prepaid payroll", "預付薪資", "预付薪资"), | ||||
|     (1252, "prepaid rents", "預付租金", "预付租金"), | ||||
|     (1253, "prepaid insurance", "預付保險費", "预付保险费"), | ||||
|     (1254, "office supplies", "用品盤存", "用品盘存"), | ||||
|     (1255, "prepaid income tax", "預付所得稅", "预付所得税"), | ||||
|     (1258, "other prepaid expenses", "其他預付費用", "其他预付费用"), | ||||
|     (1261, "prepayment for purchases", "預付貨款", "预付货款"), | ||||
|     (1268, "other prepayments", "其他預付款項", "其他预付款项"), | ||||
|     (1281, "VAT paid ( or input tax)", "進項稅額", "进项税额"), | ||||
|     (1282, "excess VAT paid (or overpaid VAT)", "留抵稅額", "留抵税额"), | ||||
|     (1283, "temporary payments", "暫付款", "暂付款"), | ||||
|     (1284, "payment on behalf of others", "代付款", "代付款"), | ||||
|     (1285, "advances to employees", "員工借支", "员工借支"), | ||||
|     (1286, "refundable deposits", "存出保證金", "存出保证金"), | ||||
|     (1287, "certificate of deposit-restricted", "受限制存款", "受限制存款"), | ||||
|     (1291, "deferred income tax assets", "遞延所得稅資產", "递延所得税资产"), | ||||
|     (1292, "deferred foreign exchange losses", "遞延兌換損失", "递延兑换损失"), | ||||
|     (1293, "owners’ (stockholders’) current account", "業主(股東)往來", | ||||
|      "业主(股东)往来"), | ||||
|     (1294, "current account with others", "同業往來", "同业往来"), | ||||
|     (1298, "other current assets – other", "其他流動資產—其他", | ||||
|      "其他流动资产—其他"), | ||||
|     (1311, "redemption fund (or sinking fund)", "償債基金", "偿债基金"), | ||||
|     (1312, "fund for improvement and expansion", "改良及擴充基金", | ||||
|      "改良及扩充基金"), | ||||
|     (1313, "contingency fund", "意外損失準備基金", "意外损失准备基金"), | ||||
|     (1314, "pension fund", "退休基金", "退休基金"), | ||||
|     (1318, "other funds", "其他基金", "其他基金"), | ||||
|     (1321, "long-term equity investments", "長期股權投資", "长期股权投资"), | ||||
|     (1322, "long-term bond investments", "長期債券投資", "长期债券投资"), | ||||
|     (1323, "long-term real estate in-vestments", "長期不動產投資", | ||||
|      "长期不动产投资"), | ||||
|     (1324, "cash surrender value of life insurance", "人壽保險現金解約價值", | ||||
|      "人寿保险现金解约价值"), | ||||
|     (1328, "other long-term investments", "其他長期投資", "其他长期投资"), | ||||
|     (1329, | ||||
|      "allowance for excess of cost over market value of long-term investments", | ||||
|      "備抵長期投資跌價損失", "备抵长期投资跌价损失"), | ||||
|     (1411, "land", "土地", "土地"), | ||||
|     (1418, "land – revaluation increments", "土地—重估增值", "土地—重估增值"), | ||||
|     (1421, "land improvements", "土地改良物", "土地改良物"), | ||||
|     (1428, "land improvements – revaluation increments", "土地改良物—重估增值", | ||||
|      "土地改良物—重估增值"), | ||||
|     (1429, "accumulated depreciation – land improvements", "累積折舊—土地改良物", | ||||
|      "累积折旧—土地改良物"), | ||||
|     (1431, "buildings", "房屋及建物", "房屋及建物"), | ||||
|     (1438, "buildings –revaluation increments", "房屋及建物—重估增值", | ||||
|      "房屋及建物—重估增值"), | ||||
|     (1439, "accumulated depreciation – buildings", "累積折舊—房屋及建物", | ||||
|      "累积折旧—房屋及建物"), | ||||
|     (1441, "machinery", "機(器)具", "机(器)具"), | ||||
|     (1448, "machinery – revaluation increments", "機(器)具—重估增值", | ||||
|      "机(器)具—重估增值"), | ||||
|     (1449, "accumulated depreciation – machinery", "累積折舊—機(器)具", | ||||
|      "累积折旧—机(器)具"), | ||||
|     (1511, "leased assets", "租賃資產", "租赁资产"), | ||||
|     (1519, "accumulated depreciation – leased assets", "累積折舊—租賃資產", | ||||
|      "累积折旧—租赁资产"), | ||||
|     (1521, "leasehold improvements", "租賃權益改良", "租赁权益改良"), | ||||
|     (1529, "accumulated depreciation – leasehold improvements", | ||||
|      "累積折舊—租賃權益改良", "累积折旧—租赁权益改良"), | ||||
|     (1561, "construction in progress", "未完工程", "未完工程"), | ||||
|     (1562, "prepayment for equipment", "預付購置設備款", "预付购置设备款"), | ||||
|     (1581, "miscellaneous property, plant, and equipment", "雜項固定資產", | ||||
|      "杂项固定资产"), | ||||
|     (1588, | ||||
|      "miscellaneous property, plant, and equipment – revaluation increments", | ||||
|      "雜項固定資產—重估增值", "杂项固定资产—重估增值"), | ||||
|     (1589, | ||||
|      "accumulated depreciation – miscellaneous property, plant, and equipment", | ||||
|      "累積折舊—雜項固定資產", "累积折旧—杂项固定资产"), | ||||
|     (1611, "natural resources", "天然資源", "天然资源"), | ||||
|     (1618, "natural resources –revaluation increments", "天然資源—重估增值", | ||||
|      "天然资源—重估增值"), | ||||
|     (1619, "accumulated depletion – natural resources", "累積折耗—天然資源", | ||||
|      "累积折耗—天然资源"), | ||||
|     (1711, "trademarks", "商標權", "商标权"), | ||||
|     (1721, "patents", "專利權", "专利权"), | ||||
|     (1731, "franchise", "特許權", "特许权"), | ||||
|     (1741, "copyright", "著作權", "著作权"), | ||||
|     (1751, "computer software cost", "電腦軟體", "电脑软体"), | ||||
|     (1761, "goodwill", "商譽", "商誉"), | ||||
|     (1771, "organization costs", "開辦費", "开办费"), | ||||
|     (1781, "deferred pension costs", "遞延退休金成本", "递延退休金成本"), | ||||
|     (1782, "leasehold improvements", "租賃權益改良", "租赁权益改良"), | ||||
|     (1788, "other intangible assets – other", "其他無形資產—其他", | ||||
|      "其他无形资产—其他"), | ||||
|     (1811, "deferred bond issuance costs", "債券發行成本", "债券发行成本"), | ||||
|     (1812, "long-term prepaid rent", "長期預付租金", "长期预付租金"), | ||||
|     (1813, "long-term prepaid insurance", "長期預付保險費", "长期预付保险费"), | ||||
|     (1814, "deferred income tax assets", "遞延所得稅資產", "递延所得税资产"), | ||||
|     (1815, "prepaid pension cost", "預付退休金", "预付退休金"), | ||||
|     (1818, "other deferred assets", "其他遞延資產", "其他递延资产"), | ||||
|     (1821, "idle assets", "閒置資產", "闲置资产"), | ||||
|     (1841, "long-term notes receivable", "長期應收票據", "长期应收票据"), | ||||
|     (1842, "long-term accounts receivable", "長期應收帳款", "长期应收帐款"), | ||||
|     (1843, "overdue receivables", "催收帳款", "催收帐款"), | ||||
|     (1847, | ||||
|      "long-term notes, accounts and overdue receivables – related parties", | ||||
|      "長期應收票據及款項與催收帳款—關係人", "长期应收票据及款项与催收帐款—关系人"), | ||||
|     (1848, "other long-term receivables", "其他長期應收款項", "其他长期应收款项"), | ||||
|     (1849, | ||||
|      "allowance for uncollectible accounts – long-term notes, accounts and" | ||||
|      " overdue receivables", | ||||
|      "備抵呆帳—長期應收票據及款項與催收帳款", "备抵呆帐—长期应收票据及款项与催收帐款"), | ||||
|     (1851, "assets leased to others", "出租資產", "出租资产"), | ||||
|     (1858, "assets leased to others – incremental value from revaluation", | ||||
|      "出租資產—重估增值", "出租资产—重估增值"), | ||||
|     (1859, "accumulated depreciation – assets leased to others", | ||||
|      "累積折舊—出租資產", "累积折旧—出租资产"), | ||||
|     (1861, "refundable deposits", "存出保證金", "存出保证金"), | ||||
|     (1881, "certificate of deposit – restricted", "受限制存款", "受限制存款"), | ||||
|     (1888, "miscellaneous assets – other", "雜項資產—其他", "杂项资产—其他"), | ||||
|     (2111, "bank overdraft", "銀行透支", "银行透支"), | ||||
|     (2112, "bank loan", "銀行借款", "银行借款"), | ||||
|     (2114, "short-term borrowings – owners", "短期借款—業主", "短期借款—业主"), | ||||
|     (2115, "short-term borrowings – employees", "短期借款—員工", "短期借款—员工"), | ||||
|     (2117, "short-term borrowings – related parties", "短期借款—關係人", | ||||
|      "短期借款—关系人"), | ||||
|     (2118, "short-term borrowings – other", "短期借款—其他", "短期借款—其他"), | ||||
|     (2121, "commercial paper payable", "應付商業本票", "应付商业本票"), | ||||
|     (2122, "bank acceptance", "銀行承兌匯票", "银行承兑汇票"), | ||||
|     (2128, "other short-term notes and bills payable", "其他應付短期票券", | ||||
|      "其他应付短期票券"), | ||||
|     (2129, "discount on short-term notes and bills payable", "應付短期票券折價", | ||||
|      "应付短期票券折价"), | ||||
|     (2131, "notes payable", "應付票據", "应付票据"), | ||||
|     (2137, "notes payable – related parties", "應付票據—關係人", | ||||
|      "应付票据—关系人"), | ||||
|     (2138, "other notes payable", "其他應付票據", "其他应付票据"), | ||||
|     (2141, "accounts payable", "應付帳款", "应付帐款"), | ||||
|     (2147, "accounts payable – related parties", "應付帳款—關係人", | ||||
|      "应付帐款—关系人"), | ||||
|     (2161, "income tax payable", "應付所得稅", "应付所得税"), | ||||
|     (2171, "accrued payroll", "應付薪工", "应付薪工"), | ||||
|     (2172, "accrued rent payable", "應付租金", "应付租金"), | ||||
|     (2173, "accrued interest payable", "應付利息", "应付利息"), | ||||
|     (2174, "accrued VAT payable", "應付營業稅", "应付营业税"), | ||||
|     (2175, "accrued taxes payable – other", "應付稅捐—其他", "应付税捐—其他"), | ||||
|     (2178, "other accrued expenses payable", "其他應付費用", "其他应付费用"), | ||||
|     (2181, "forward exchange contract payable", "應付購入遠匯款", "应付购入远汇款"), | ||||
|     (2182, "forward exchange contract payable – foreign currencies", | ||||
|      "應付遠匯款—外幣", "应付远汇款—外币"), | ||||
|     (2183, "premium on forward exchange contract", "買賣遠匯溢價", "买卖远汇溢价"), | ||||
|     (2184, "payables on land and building purchased", "應付土地房屋款", | ||||
|      "应付土地房屋款"), | ||||
|     (2185, "Payables on equipment", "應付設備款", "应付设备款"), | ||||
|     (2187, "other payables – related parties", "其他應付款—關係人", | ||||
|      "其他应付款—关系人"), | ||||
|     (2191, "dividend payable", "應付股利", "应付股利"), | ||||
|     (2192, "bonus payable", "應付紅利", "应付红利"), | ||||
|     (2193, "compensation payable to directors and supervisors", "應付董監事酬勞", | ||||
|      "应付董监事酬劳"), | ||||
|     (2198, "other payables – other", "其他應付款—其他", "其他应付款—其他"), | ||||
|     (2261, "sales revenue received in advance", "預收貨款", "预收货款"), | ||||
|     (2262, "revenue received in advance", "預收收入", "预收收入"), | ||||
|     (2268, "other advance receipts", "其他預收款", "其他预收款"), | ||||
|     (2271, "corporate bonds payable – current portion", | ||||
|      "一年或一營業週期內到期公司債", "一年或一营业周期内到期公司债"), | ||||
|     (2272, "long-term loans payable – current portion", | ||||
|      "一年或一營業週期內到期長期借款", "一年或一营业周期内到期长期借款"), | ||||
|     (2273, | ||||
|      "long-term notes and accounts payable due within one year or one" | ||||
|      " operating cycle", | ||||
|      "一年或一營業週期內到期長期應付票據及款項", | ||||
|      "一年或一营业周期内到期长期应付票据及款项"), | ||||
|     (2277, | ||||
|      "long-term notes and accounts payables to related parties – current" | ||||
|      " portion", | ||||
|      "一年或一營業週期內到期長期應付票據及款項—關係人", | ||||
|      "一年或一营业周期内到期长期应付票据及款项—关系人"), | ||||
|     (2278, "other long-term liabilities – current portion", | ||||
|      "其他一年或一營業週期內到期長期負債", "其他一年或一营业周期内到期长期负债"), | ||||
|     (2281, "VAT received (or output tax)", "銷項稅額", "销项税额"), | ||||
|     (2283, "temporary receipts", "暫收款", "暂收款"), | ||||
|     (2284, "receipts under custody", "代收款", "代收款"), | ||||
|     (2285, "estimated warranty liabilities", "估計售後服務/保固負債", | ||||
|      "估计售后服务/保固负债"), | ||||
|     (2291, "deferred income tax liabilities", "遞延所得稅負債", "递延所得税负债"), | ||||
|     (2292, "deferred foreign exchange gain", "遞延兌換利益", "递延兑换利益"), | ||||
|     (2293, "owners’ current account", "業主(股東)往來", "业主(股东)往来"), | ||||
|     (2294, "current account with others", "同業往來", "同业往来"), | ||||
|     (2298, "other current liabilities – others", "其他流動負債—其他", | ||||
|      "其他流动负债—其他"), | ||||
|     (2311, "corporate bonds payable", "應付公司債", "应付公司债"), | ||||
|     (2319, "premium (discount) on corporate bonds payable", | ||||
|      "應付公司債溢(折)價", "应付公司债溢(折)价"), | ||||
|     (2321, "long-term loans payable – bank", "長期銀行借款", "长期银行借款"), | ||||
|     (2324, "long-term loans payable – owners", "長期借款—業主", "长期借款—业主"), | ||||
|     (2325, "long-term loans payable – employees", "長期借款—員工", | ||||
|      "长期借款—员工"), | ||||
|     (2327, "long-term loans payable – related parties", "長期借款—關係人", | ||||
|      "长期借款—关系人"), | ||||
|     (2328, "long-term loans payable – other", "長期借款—其他", "长期借款—其他"), | ||||
|     (2331, "long-term notes payable", "長期應付票據", "长期应付票据"), | ||||
|     (2332, "long-term accounts pay-able", "長期應付帳款", "长期应付帐款"), | ||||
|     (2333, "long-term capital lease liabilities", "長期應付租賃負債", | ||||
|      "长期应付租赁负债"), | ||||
|     (2337, "Long-term notes and accounts payable – related parties", | ||||
|      "長期應付票據及款項—關係人", "长期应付票据及款项—关系人"), | ||||
|     (2338, "other long-term payables", "其他長期應付款項", "其他长期应付款项"), | ||||
|     (2341, "estimated accrued land value incremental tax pay-able", | ||||
|      "估計應付土地增值稅", "估计应付土地增值税"), | ||||
|     (2351, "accrued pension liabilities", "應計退休金負債", "应计退休金负债"), | ||||
|     (2388, "other long-term liabilities – other", "其他長期負債—其他", | ||||
|      "其他长期负债—其他"), | ||||
|     (2811, "deferred revenue", "遞延收入", "递延收入"), | ||||
|     (2814, "deferred income tax liabilities", "遞延所得稅負債", "递延所得税负债"), | ||||
|     (2818, "other deferred liabilities", "其他遞延負債", "其他递延负债"), | ||||
|     (2861, "guarantee deposit received", "存入保證金", "存入保证金"), | ||||
|     (2888, "miscellaneous liabilities – other", "雜項負債—其他", "杂项负债—其他"), | ||||
|     (3111, "capital – common stock", "普通股股本", "普通股股本"), | ||||
|     (3112, "capital – preferred stock", "特別股股本", "特别股股本"), | ||||
|     (3113, "capital collected in advance", "預收股本", "预收股本"), | ||||
|     (3114, "stock dividends to be distributed", "待分配股票股利", | ||||
|      "待分配股票股利"), | ||||
|     (3115, "capital", "資本", "资本"), | ||||
|     (3211, "paid-in capital in excess of par- common stock", "普通股股票溢價", | ||||
|      "普通股股票溢价"), | ||||
|     (3212, "paid-in capital in excess of par- preferred stock", "特別股股票溢價", | ||||
|      "特别股股票溢价"), | ||||
|     (3231, "capital surplus from assets revaluation", "資產重估增值準備", | ||||
|      "资产重估增值准备"), | ||||
|     (3241, "capital surplus from gain on disposal of assets", "處分資產溢價公積", | ||||
|      "处分资产溢价公积"), | ||||
|     (3251, "capital surplus from business combination", "合併公積", "合并公积"), | ||||
|     (3261, "donated surplus", "受贈公積", "受赠公积"), | ||||
|     (3281, "additional paid-in capital from investee under equity method", | ||||
|      "權益法長期股權投資資本公積", "权益法长期股权投资资本公积"), | ||||
|     (3282, "additional paid-in capital – treasury stock trans-actions", | ||||
|      "資本公積—庫藏股票交易", "资本公积—库藏股票交易"), | ||||
|     (3311, "legal reserve", "法定盈餘公積", "法定盈余公积"), | ||||
|     (3321, "contingency reserve", "意外損失準備", "意外损失准备"), | ||||
|     (3322, "improvement and expansion reserve", "改良擴充準備", "改良扩充准备"), | ||||
|     (3323, "special reserve for redemption of liabilities", "償債準備", | ||||
|      "偿债准备"), | ||||
|     (3328, "other special reserve", "其他特別盈餘公積", "其他特别盈余公积"), | ||||
|     (3351, "accumulated profit or loss", "累積盈虧", "累积盈亏"), | ||||
|     (3352, "prior period adjustments", "前期損益調整", "前期损益调整"), | ||||
|     (3353, "net income or loss for current period", "本期損益", "本期损益"), | ||||
|     (3411, | ||||
|      "unrealized loss on market value decline of long-term equity investments", | ||||
|      "長期股權投資未實現跌價損失", "长期股权投资未实现跌价损失"), | ||||
|     (3421, "cumulative translation adjustments", "累積換算調整數", | ||||
|      "累积换算调整数"), | ||||
|     (3431, "net loss not recognized as pension costs", | ||||
|      "未認列為退休金成本之淨損失", "未认列为退休金成本之净损失"), | ||||
|     (3511, "treasury stock", "庫藏股", "库藏股"), | ||||
|     (3611, "minority interest", "少數股權", "少数股权"), | ||||
|     (4111, "sales revenue", "銷貨收入", "销货收入"), | ||||
|     (4112, "installment sales revenue", "分期付款銷貨收入", "分期付款销货收入"), | ||||
|     (4171, "sales return", "銷貨退回", "销货退回"), | ||||
|     (4191, "sales discounts and allowances", "銷貨折讓", "销货折让"), | ||||
|     (4611, "service revenue", "勞務收入", "劳务收入"), | ||||
|     (4711, "agency revenue", "業務收入", "业务收入"), | ||||
|     (4888, "other operating revenue – other", "其他營業收入—其他", | ||||
|      "其他营业收入—其他"), | ||||
|     (5111, "cost of goods sold", "銷貨成本", "销货成本"), | ||||
|     (5112, "installment cost of goods sold", "分期付款銷貨成本", | ||||
|      "分期付款销货成本"), | ||||
|     (5121, "purchases", "進貨", "进货"), | ||||
|     (5122, "purchase expenses", "進貨費用", "进货费用"), | ||||
|     (5123, "purchase returns", "進貨退出", "进货退出"), | ||||
|     (5124, "charges on purchased merchandise", "進貨折讓", "进货折让"), | ||||
|     (5131, "material purchased", "進料", "进料"), | ||||
|     (5132, "charges on purchased material", "進料費用", "进料费用"), | ||||
|     (5133, "material purchase returns", "進料退出", "进料退出"), | ||||
|     (5134, "material purchase allowances", "進料折讓", "进料折让"), | ||||
|     (5141, "direct labor", "直接人工", "直接人工"), | ||||
|     (5151, "indirect labor", "間接人工", "间接人工"), | ||||
|     (5152, "rent expense, rent", "租金支出", "租金支出"), | ||||
|     (5153, "office supplies (expense)", "文具用品", "文具用品"), | ||||
|     (5154, "travelling expense, travel", "旅費", "旅费"), | ||||
|     (5155, "shipping expenses, freight", "運費", "运费"), | ||||
|     (5156, "postage (expenses)", "郵電費", "邮电费"), | ||||
|     (5157, "repair (s) and maintenance (expense )", "修繕費", "修缮费"), | ||||
|     (5158, "packing expenses", "包裝費", "包装费"), | ||||
|     (5161, "utilities (expense)", "水電瓦斯費", "水电瓦斯费"), | ||||
|     (5162, "insurance (expense)", "保險費", "保险费"), | ||||
|     (5163, "manufacturing overhead – outsourced", "加工費", "加工费"), | ||||
|     (5166, "taxes", "稅捐", "税捐"), | ||||
|     (5168, "depreciation expense", "折舊", "折旧"), | ||||
|     (5169, "various amortization", "各項耗竭及攤提", "各项耗竭及摊提"), | ||||
|     (5172, "meal (expenses)", "伙食費", "伙食费"), | ||||
|     (5173, "employee benefits/welfare", "職工福利", "职工福利"), | ||||
|     (5176, "training (expense)", "訓練費", "训练费"), | ||||
|     (5177, "indirect materials", "間接材料", "间接材料"), | ||||
|     (5188, "other manufacturing expenses", "其他製造費用", "其他制造费用"), | ||||
|     (5611, "service costs", "勞務成本", "劳务成本"), | ||||
|     (5711, "agency costs", "業務成本", "业务成本"), | ||||
|     (5888, "other operating costs – other", "其他營業成本—其他", | ||||
|      "其他营业成本—其他"), | ||||
|     (6151, "payroll expense", "薪資支出", "薪资支出"), | ||||
|     (6152, "rent expense, rent", "租金支出", "租金支出"), | ||||
|     (6153, "office supplies (expense)", "文具用品", "文具用品"), | ||||
|     (6154, "travelling expense, travel", "旅費", "旅费"), | ||||
|     (6155, "shipping expenses, freight", "運費", "运费"), | ||||
|     (6156, "postage (expenses)", "郵電費", "邮电费"), | ||||
|     (6157, "repair (s) and maintenance (expense)", "修繕費", "修缮费"), | ||||
|     (6159, "advertisement expense, advertisement", "廣告費", "广告费"), | ||||
|     (6161, "utilities (expense)", "水電瓦斯費", "水电瓦斯费"), | ||||
|     (6162, "insurance (expense)", "保險費", "保险费"), | ||||
|     (6164, "entertainment (expense)", "交際費", "交际费"), | ||||
|     (6165, "donation (expense)", "捐贈", "捐赠"), | ||||
|     (6166, "taxes", "稅捐", "税捐"), | ||||
|     (6167, "loss on uncollectible accounts", "呆帳損失", "呆帐损失"), | ||||
|     (6168, "depreciation expense", "折舊", "折旧"), | ||||
|     (6169, "various amortization", "各項耗竭及攤提", "各项耗竭及摊提"), | ||||
|     (6172, "meal (expenses)", "伙食費", "伙食费"), | ||||
|     (6173, "employee benefits/welfare", "職工福利", "职工福利"), | ||||
|     (6175, "commission (expense)", "佣金支出", "佣金支出"), | ||||
|     (6176, "training (expense)", "訓練費", "训练费"), | ||||
|     (6188, "other selling expenses", "其他推銷費用", "其他推销费用"), | ||||
|     (6251, "payroll expense", "薪資支出", "薪资支出"), | ||||
|     (6252, "rent expense, rent", "租金支出", "租金支出"), | ||||
|     (6253, "office supplies", "文具用品", "文具用品"), | ||||
|     (6254, "travelling expense, travel", "旅費", "旅费"), | ||||
|     (6255, "shipping expenses,freight", "運費", "运费"), | ||||
|     (6256, "postage (expenses)", "郵電費", "邮电费"), | ||||
|     (6257, "repair (s) and maintenance (expense)", "修繕費", "修缮费"), | ||||
|     (6259, "advertisement expense, advertisement", "廣告費", "广告费"), | ||||
|     (6261, "utilities (expense)", "水電瓦斯費", "水电瓦斯费"), | ||||
|     (6262, "insurance (expense)", "保險費", "保险费"), | ||||
|     (6264, "entertainment (expense)", "交際費", "交际费"), | ||||
|     (6265, "donation (expense)", "捐贈", "捐赠"), | ||||
|     (6266, "taxes", "稅捐", "税捐"), | ||||
|     (6267, "loss on uncollectible accounts", "呆帳損失", "呆帐损失"), | ||||
|     (6268, "depreciation expense", "折舊", "折旧"), | ||||
|     (6269, "various amortization", "各項耗竭及攤提", "各项耗竭及摊提"), | ||||
|     (6271, "loss on export sales", "外銷損失", "外销损失"), | ||||
|     (6272, "meal (expenses)", "伙食費", "伙食费"), | ||||
|     (6273, "employee benefits/welfare", "職工福利", "职工福利"), | ||||
|     (6274, "research and development expense", "研究發展費用", "研究发展费用"), | ||||
|     (6275, "commission (expense)", "佣金支出", "佣金支出"), | ||||
|     (6276, "training (expense)", "訓練費", "训练费"), | ||||
|     (6278, "professional service fees", "勞務費", "劳务费"), | ||||
|     (6288, "other general and administrative expenses", "其他管理及總務費用", | ||||
|      "其他管理及总务费用"), | ||||
|     (6351, "payroll expense", "薪資支出", "薪资支出"), | ||||
|     (6352, "rent expense, rent", "租金支出", "租金支出"), | ||||
|     (6353, "office supplies", "文具用品", "文具用品"), | ||||
|     (6354, "travelling expense, travel", "旅費", "旅费"), | ||||
|     (6355, "shipping expenses, freight", "運費", "运费"), | ||||
|     (6356, "postage (expenses)", "郵電費", "邮电费"), | ||||
|     (6357, "repair (s) and maintenance (expense)", "修繕費", "修缮费"), | ||||
|     (6361, "utilities (expense)", "水電瓦斯費", "水电瓦斯费"), | ||||
|     (6362, "insurance (expense)", "保險費", "保险费"), | ||||
|     (6364, "entertainment (expense)", "交際費", "交际费"), | ||||
|     (6366, "taxes", "稅捐", "税捐"), | ||||
|     (6368, "depreciation expense", "折舊", "折旧"), | ||||
|     (6369, "various amortization", "各項耗竭及攤提", "各项耗竭及摊提"), | ||||
|     (6372, "meal (expenses)", "伙食費", "伙食费"), | ||||
|     (6373, "employee benefits/welfare", "職工福利", "职工福利"), | ||||
|     (6376, "training (expense)", "訓練費", "训练费"), | ||||
|     (6378, "other research and development expenses", "其他研究發展費用", | ||||
|      "其他研究发展费用"), | ||||
|     (7111, "interest revenue/income", "利息收入", "利息收入"), | ||||
|     (7121, "investment income recognized under equity method", | ||||
|      "權益法認列之投資收益", "权益法认列之投资收益"), | ||||
|     (7122, "dividends income", "股利收入", "股利收入"), | ||||
|     (7123, "gain on market price recovery of short-term investment", | ||||
|      "短期投資市價回升利益", "短期投资市价回升利益"), | ||||
|     (7131, "foreign exchange gain", "兌換利益", "兑换利益"), | ||||
|     (7141, "gain on disposal of investments", "處分投資收益", "处分投资收益"), | ||||
|     (7151, "gain on disposal of assets", "處分資產溢價收入", "处分资产溢价收入"), | ||||
|     (7481, "donation income", "捐贈收入", "捐赠收入"), | ||||
|     (7482, "rent revenue/income", "租金收入", "租金收入"), | ||||
|     (7483, "commission revenue/income", "佣金收入", "佣金收入"), | ||||
|     (7484, "revenue from sale of scraps", "出售下腳及廢料收入", | ||||
|      "出售下脚及废料收入"), | ||||
|     (7485, "gain on physical inventory", "存貨盤盈", "存货盘盈"), | ||||
|     (7486, "gain from price recovery of inventory", "存貨跌價回升利益", | ||||
|      "存货跌价回升利益"), | ||||
|     (7487, "gain on reversal of bad debts", "壞帳轉回利益", "坏帐转回利益"), | ||||
|     (7488, "other non-operating revenue – other items", "其他營業外收入—其他", | ||||
|      "其他营业外收入—其他"), | ||||
|     (7511, "interest expense", "利息費用", "利息费用"), | ||||
|     (7521, "investment loss recognized under equity method", | ||||
|      "權益法認列之投資損失", "权益法认列之投资损失"), | ||||
|     (7523, "unrealized loss on reduction of short-term investments to market", | ||||
|      "短期投資未實現跌價損失", "短期投资未实现跌价损失"), | ||||
|     (7531, "foreign exchange loss", "兌換損失", "兑换损失"), | ||||
|     (7541, "loss on disposal of investments", "處分投資損失", "处分投资损失"), | ||||
|     (7551, "loss on disposal of assets", "處分資產損失", "处分资产损失"), | ||||
|     (7881, "loss on work stoppages", "停工損失", "停工损失"), | ||||
|     (7882, "casualty loss", "災害損失", "灾害损失"), | ||||
|     (7885, "loss on physical inventory", "存貨盤損", "存货盘损"), | ||||
|     (7886, | ||||
|      "loss for market price decline and obsolete and slow-moving inventories", | ||||
|      "存貨跌價及呆滯損失", "存货跌价及呆滞损失"), | ||||
|     (7888, "other non-operating expenses – other", "其他營業外費用—其他", | ||||
|      "其他营业外费用—其他"), | ||||
|     (8111, "income tax expense ( or benefit)", "所得稅費用(或利益)", | ||||
|      "所得税费用(或利益)"), | ||||
|     (9111, "income (loss) from operations of discontinued segment", | ||||
|      "停業部門損益—停業前營業損益", "停业部门损益—停业前营业损益"), | ||||
|     (9121, "gain (loss) from disposal of discontinued segment", | ||||
|      "停業部門損益—處分損益", "停业部门损益—处分损益"), | ||||
|     (9211, "extraordinary gain or loss", "非常損益", "非常损益"), | ||||
|     (9311, "cumulative effect of changes in accounting principles", | ||||
|      "會計原則變動累積影響數", "会计原则变动累积影响数"), | ||||
|     (9411, "minority interest income", "少數股權淨利", "少数股权净利"), | ||||
| ] | ||||
| """The base account data.""" | ||||
							
								
								
									
										73
									
								
								src/accounting/base_account/models.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										73
									
								
								src/accounting/base_account/models.py
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,73 @@ | ||||
| # The Mia! Accounting Flask Project. | ||||
| # Author: imacat@mail.imacat.idv.tw (imacat), 2023/1/25 | ||||
|  | ||||
| #  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 data models for the base account management. | ||||
|  | ||||
| """ | ||||
| from flask import current_app | ||||
| from flask_babel import get_locale | ||||
|  | ||||
| from accounting.database import db | ||||
|  | ||||
|  | ||||
| class BaseAccount(db.Model): | ||||
|     """A base account.""" | ||||
|     __tablename__ = "accounting_base_accounts" | ||||
|     """The table name.""" | ||||
|     code = db.Column(db.String, nullable=False, primary_key=True) | ||||
|     """The code.""" | ||||
|     title_l10n = db.Column("title", db.String, nullable=False) | ||||
|     """The title.""" | ||||
|     l10n = db.relationship("BaseAccountL10n", back_populates="account", | ||||
|                            lazy=False) | ||||
|     """The localized titles.""" | ||||
|  | ||||
|     def __str__(self) -> str: | ||||
|         """Returns the string representation of the base account. | ||||
|  | ||||
|         :return: The string representation of the base account. | ||||
|         """ | ||||
|         return F"{self.code} {self.title}" | ||||
|  | ||||
|     @property | ||||
|     def title(self) -> str: | ||||
|         """Returns the title in the current locale. | ||||
|  | ||||
|         :return: The title in the current locale. | ||||
|         """ | ||||
|         current_locale = str(get_locale()) | ||||
|         if current_locale == current_app.config["BABEL_DEFAULT_LOCALE"]: | ||||
|             return self.title_l10n | ||||
|         for l10n in self.l10n: | ||||
|             if l10n.locale == current_locale: | ||||
|                 return l10n.title | ||||
|         return self.title_l10n | ||||
|  | ||||
|  | ||||
| class BaseAccountL10n(db.Model): | ||||
|     """A localized base account title.""" | ||||
|     __tablename__ = "accounting_base_accounts_l10n" | ||||
|     """The table name.""" | ||||
|     account_code = db.Column(db.String, db.ForeignKey(BaseAccount.code, | ||||
|                                                       ondelete="CASCADE"), | ||||
|                              nullable=False, primary_key=True) | ||||
|     """The code of the account.""" | ||||
|     account = db.relationship(BaseAccount, back_populates="l10n") | ||||
|     """The account.""" | ||||
|     locale = db.Column(db.String, nullable=False, primary_key=True) | ||||
|     """The locale.""" | ||||
|     title = db.Column(db.String, nullable=False) | ||||
|     """The localized title.""" | ||||
							
								
								
									
										44
									
								
								src/accounting/base_account/query.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										44
									
								
								src/accounting/base_account/query.py
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,44 @@ | ||||
| # The Mia! Accounting Flask Project. | ||||
| # Author: imacat@mail.imacat.idv.tw (imacat), 2023/1/26 | ||||
|  | ||||
| #  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 base account query. | ||||
|  | ||||
| """ | ||||
| import sqlalchemy as sa | ||||
| from flask import request | ||||
|  | ||||
| from accounting.utils.query import parse_query_keywords | ||||
| from .models import BaseAccount, BaseAccountL10n | ||||
|  | ||||
|  | ||||
| def get_base_account_query() -> list[BaseAccount]: | ||||
|     """Returns the base accounts, optionally filtered by the query. | ||||
|  | ||||
|     :return: The base accounts. | ||||
|     """ | ||||
|     keywords: list[str] = parse_query_keywords(request.args.get("q")) | ||||
|     if len(keywords) == 0: | ||||
|         return BaseAccount.query.order_by(BaseAccount.code).all() | ||||
|     conditions: list[sa.BinaryExpression] = [] | ||||
|     for k in keywords: | ||||
|         l10n: list[BaseAccountL10n] = BaseAccountL10n.query\ | ||||
|             .filter(BaseAccountL10n.title.contains(k)).all() | ||||
|         l10n_matches: set[str] = {x.account_code for x in l10n} | ||||
|         conditions.append(sa.or_(BaseAccount.code.contains(k), | ||||
|                                  BaseAccount.title_l10n.contains(k), | ||||
|                                  BaseAccount.code.in_(l10n_matches))) | ||||
|     return BaseAccount.query.filter(*conditions)\ | ||||
|         .order_by(BaseAccount.code).all() | ||||
							
								
								
									
										41
									
								
								src/accounting/base_account/views.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										41
									
								
								src/accounting/base_account/views.py
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,41 @@ | ||||
| # The Mia! Accounting Flask Project. | ||||
| # Author: imacat@mail.imacat.idv.tw (imacat), 2023/1/26 | ||||
|  | ||||
| #  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 views for the base account management. | ||||
|  | ||||
| """ | ||||
| from flask import Blueprint, render_template | ||||
|  | ||||
| from accounting.utils.pagination import Pagination | ||||
| from accounting.utils.permission import has_permission, can_view | ||||
|  | ||||
| bp: Blueprint = Blueprint("base-account", __name__) | ||||
| """The view blueprint for the base account management.""" | ||||
|  | ||||
|  | ||||
| @bp.get("", endpoint="list") | ||||
| @has_permission(can_view) | ||||
| def list_accounts() -> str: | ||||
|     """Lists the base accounts. | ||||
|  | ||||
|     :return: The account list. | ||||
|     """ | ||||
|     from .models import BaseAccount | ||||
|     from .query import get_base_account_query | ||||
|     accounts: list[BaseAccount] = get_base_account_query() | ||||
|     pagination: Pagination = Pagination[BaseAccount](accounts) | ||||
|     return render_template("accounting/base-account/list.html", | ||||
|                            list=pagination.list, pagination=pagination) | ||||
							
								
								
									
										38
									
								
								src/accounting/database.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										38
									
								
								src/accounting/database.py
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,38 @@ | ||||
| # The Mia! Accounting Flask Project. | ||||
| # Author: imacat@mail.imacat.idv.tw (imacat), 2023/1/25 | ||||
|  | ||||
| #  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 database instance factory for the base account management. | ||||
|  | ||||
| This is to overcome the problem that the database instance needs to be | ||||
| initialized at compile time, but as a submodule it is only available at run | ||||
| time. | ||||
|  | ||||
| """ | ||||
| from flask_sqlalchemy import SQLAlchemy | ||||
|  | ||||
| db: SQLAlchemy | ||||
| """The database instance.""" | ||||
|  | ||||
|  | ||||
| def set_db(new_db: SQLAlchemy) -> None: | ||||
|     """Sets the database instance. | ||||
|  | ||||
|     :param new_db: The database instance. | ||||
|     :return: None. | ||||
|     """ | ||||
|     global db | ||||
|     db = new_db | ||||
							
								
								
									
										114
									
								
								src/accounting/locale.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										114
									
								
								src/accounting/locale.py
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,114 @@ | ||||
| # The Mia! Accounting Flask Project. | ||||
| # Author: imacat@mail.imacat.idv.tw (imacat), 2023/1/25 | ||||
|  | ||||
| #  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 localization for the accounting application. | ||||
|  | ||||
| """ | ||||
| import json | ||||
| from pathlib import Path | ||||
|  | ||||
| from flask import Flask, Response, Blueprint | ||||
| from flask_babel import LazyString, Domain | ||||
| from flask_babel_js import JAVASCRIPT, c2js | ||||
|  | ||||
| translation_dir: Path = Path(__file__).parent / "translations" | ||||
| domain: Domain = Domain(translation_directories=[translation_dir], | ||||
|                         domain="accounting") | ||||
|  | ||||
|  | ||||
| def gettext(string, **variables) -> str: | ||||
|     """A replacement of the Babel gettext() function.. | ||||
|  | ||||
|     :param string: The message to translate. | ||||
|     :param variables: The variable substitution. | ||||
|     :return: The translated message. | ||||
|     """ | ||||
|     return domain.gettext(string, **variables) | ||||
|  | ||||
|  | ||||
| def lazy_gettext(string, **variables) -> LazyString: | ||||
|     """A replacement of the Babel lazy_gettext() function.. | ||||
|  | ||||
|     :param string: The message to translate. | ||||
|     :param variables: The variable substitution. | ||||
|     :return: The translated message. | ||||
|     """ | ||||
|     return domain.lazy_gettext(string, **variables) | ||||
|  | ||||
|  | ||||
| def __babel_js_catalog_view() -> Response: | ||||
|     """A tweaked view taken from Flask-Babel-JS that returns the messages and | ||||
|     with the A_() function instead of _(). | ||||
|  | ||||
|     :return: The response. | ||||
|     """ | ||||
|     js = [ | ||||
|             """"use strict"; | ||||
|  | ||||
| (function() { | ||||
|     var babel = {}; | ||||
|     babel.catalog = """ | ||||
|     ] | ||||
|  | ||||
|     translations = domain.get_translations() | ||||
|     # Here used to be an isinstance check for NullTranslations, but the | ||||
|     # translation object that is "merged" by flask-babel is seen as an | ||||
|     # instance of NullTranslations. | ||||
|     catalog = translations._catalog.copy() | ||||
|  | ||||
|     # copy()ing the catalog here because we're modifying the original copy. | ||||
|     for key, value in catalog.copy().items(): | ||||
|         if isinstance(key, tuple): | ||||
|             text, plural = key | ||||
|             if text not in catalog: | ||||
|                 catalog[text] = {} | ||||
|  | ||||
|             catalog[text][plural] = value | ||||
|             del catalog[key] | ||||
|  | ||||
|     js.append(json.dumps(catalog, indent=4)) | ||||
|  | ||||
|     js.append(";\n") | ||||
|     js.append(JAVASCRIPT) | ||||
|  | ||||
|     metadata = translations.gettext("") | ||||
|     if metadata: | ||||
|         for m in metadata.splitlines(): | ||||
|             if m.lower().startswith("plural-forms:"): | ||||
|                 js.append("    babel.plural = ") | ||||
|                 js.append(c2js(m.lower().split("plural=")[1].rstrip(';'))) | ||||
|  | ||||
|     js.append(""" | ||||
|  | ||||
|     window.A_ = babel.gettext; | ||||
| })(); | ||||
| """) | ||||
|  | ||||
|     resp = Response("".join(js)) | ||||
|     resp.headers["Content-Type"] = "text/javascript" | ||||
|     return resp | ||||
|  | ||||
|  | ||||
| def init_app(app: Flask, bp: Blueprint) -> None: | ||||
|     """Initializes the application. | ||||
|  | ||||
|     :param bp: The blueprint of the accounting application. | ||||
|     :param app: The Flask application. | ||||
|     :return: None. | ||||
|     """ | ||||
|     bp.add_url_rule("/_jstrans.js", "babel_catalog", | ||||
|                     __babel_js_catalog_view) | ||||
|     app.jinja_env.globals["A_"] = domain.gettext | ||||
							
								
								
									
										58
									
								
								src/accounting/templates/accounting/base-account/list.html
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										58
									
								
								src/accounting/templates/accounting/base-account/list.html
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,58 @@ | ||||
| {# | ||||
| The Mia! Accounting Flask Project | ||||
| list.html: The base account list | ||||
|  | ||||
|  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. | ||||
|  | ||||
| Author: imacat@mail.imacat.idv.tw (imacat) | ||||
| First written: 2023/1/26 | ||||
| #} | ||||
| {% extends "accounting/base.html" %} | ||||
|  | ||||
| {% block header %}{% block title %}{{ A_("Base Accounts") }}{% endblock %}{% endblock %} | ||||
|  | ||||
| {% block content %} | ||||
|  | ||||
| <form action="{{ url_for("accounting.base-account.list") }}" method="get" role="search"> | ||||
|   <div class="row"> | ||||
|     <div class="col-sm-3"> | ||||
|       <div class="input-group mb-2"> | ||||
|         <input id="query" class="form-control form-control-sm" type="search" name="q" value="{{ request.args["q"] if "q" in request.args else "" }}" placeholder=" " required="required" aria-label="Search"> | ||||
|         <button class="input-group-text" type="submit"> | ||||
|           <label for="query"> | ||||
|             <i class="fa-solid fa-magnifying-glass"></i> | ||||
|             {{ A_("Search") }} | ||||
|           </label> | ||||
|         </button> | ||||
|       </div> | ||||
|     </div> | ||||
|   </div> | ||||
| </form> | ||||
|  | ||||
| {% if list %} | ||||
|   {% include "accounting/include/pagination.html" %} | ||||
|  | ||||
|   <ul class="list-group"> | ||||
|   {% for item in list %} | ||||
|     <li class="list-group-item list-group-item-action"> | ||||
|       {{ item }} | ||||
|     </li> | ||||
|   {% endfor %} | ||||
|   </ul> | ||||
| {% else %} | ||||
|   <p>{{ A_("There is no data.") }}</p> | ||||
| {% endif %} | ||||
|  | ||||
| {% endblock %} | ||||
							
								
								
									
										27
									
								
								src/accounting/templates/accounting/base.html
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										27
									
								
								src/accounting/templates/accounting/base.html
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,27 @@ | ||||
| {# | ||||
| The Mia! Accounting Flask Project | ||||
| base.html: The application-wide base template. | ||||
|  | ||||
|  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. | ||||
|  | ||||
| Author: imacat@mail.imacat.idv.tw (imacat) | ||||
| First written: 2023/1/27 | ||||
| #} | ||||
| {% extends "base.html" %} | ||||
|  | ||||
| {% block scripts %} | ||||
|   <script src="{{ url_for("accounting.babel_catalog") }}"></script> | ||||
|   {% block accounting_scripts %}{% endblock %} | ||||
| {% endblock %} | ||||
							
								
								
									
										37
									
								
								src/accounting/templates/accounting/include/nav.html
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										37
									
								
								src/accounting/templates/accounting/include/nav.html
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,37 @@ | ||||
| {# | ||||
| The Mia! Accounting Flask Project | ||||
| nav.html: The navigation menu for the accounting application. | ||||
|  | ||||
|  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. | ||||
|  | ||||
| Author: imacat@mail.imacat.idv.tw (imacat) | ||||
| First written: 2023/1/26 | ||||
| #} | ||||
| {% if can_view_accounting() %} | ||||
|   <li class="nav-item dropdown"> | ||||
|     <span class="nav-link dropdown-toggle" data-bs-toggle="dropdown"> | ||||
|       <i class="fa-solid fa-gear"></i> | ||||
|       {{ A_("Accounting") }} | ||||
|     </span> | ||||
|     <ul class="dropdown-menu"> | ||||
|       <li> | ||||
|         <a class="dropdown-item {% if request.endpoint.startswith("accounting.base-account.") %} active {% endif %}" href="{{ url_for("accounting.base-account.list") }}"> | ||||
|           <i class="fa-solid fa-list"></i> | ||||
|           {{ A_("Base Accounts") }} | ||||
|         </a> | ||||
|       </li> | ||||
|     </ul> | ||||
|   </li> | ||||
| {% endif %} | ||||
							
								
								
									
										56
									
								
								src/accounting/templates/accounting/include/pagination.html
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										56
									
								
								src/accounting/templates/accounting/include/pagination.html
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,56 @@ | ||||
| {# | ||||
| The Mia! Accounting Flask Project | ||||
| pagination.html: The pagination navigation bar. | ||||
|  | ||||
|  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. | ||||
|  | ||||
| Author: imacat@mail.imacat.idv.tw (imacat) | ||||
| First written: 2023/1/26 | ||||
| #} | ||||
| {% if pagination.is_needed %} | ||||
|   <nav aria-label="Page navigation"> | ||||
|     <ul class="pagination"> | ||||
|       {% for link in pagination.page_links %} | ||||
|         {% if link.uri is none %} | ||||
|           <li class="page-item disabled {% if not link.is_for_mobile %} d-none d-md-inline {% endif %}"> | ||||
|             <span class="page-link"> | ||||
|               {{ link.text }} | ||||
|             </span> | ||||
|           </li> | ||||
|         {% else %} | ||||
|         <li class="page-item {% if not link.is_for_mobile %} d-none d-md-inline {% endif %} {% if link.is_current %} active {% endif %}"> | ||||
|           <a class="page-link" href="{{ link.uri }}"> | ||||
|             {{ link.text }} | ||||
|           </a> | ||||
|         </li> | ||||
|         {% endif %} | ||||
|       {% endfor %} | ||||
|       <li class="page-item d-none d-md-inline active dropdown"> | ||||
|         <div class="page-link dropdown-toggle" data-bs-toggle="dropdown" aria-expanded="false"> | ||||
|           {{ pagination.page_size }} | ||||
|         </div> | ||||
|         <ul class="dropdown-menu"> | ||||
|           {% for link in pagination.page_sizes %} | ||||
|             <li> | ||||
|               <a class="dropdown-item {% if link.is_current %} active {% endif %}" href="{{ link.uri }}"> | ||||
|                 {{ link.text }} | ||||
|               </a> | ||||
|             </li> | ||||
|           {% endfor %} | ||||
|         </ul> | ||||
|       </li> | ||||
|     </ul> | ||||
|   </nav> | ||||
| {% endif %} | ||||
							
								
								
									
										3
									
								
								src/accounting/translations/babel.cfg
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										3
									
								
								src/accounting/translations/babel.cfg
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,3 @@ | ||||
| [python: **.py] | ||||
| [jinja2: **/templates/**.html] | ||||
| [javascript: **/static/js/**.js] | ||||
| @@ -0,0 +1,46 @@ | ||||
| # Chinese (Traditional) translations for the Mia! Accounting Flask project. | ||||
| # Copyright (C) 2023 imacat | ||||
| # This file is distributed under the same license as the Mia! Accounting | ||||
| # Flask project. | ||||
| # imacat <imacat@mail.imacat.idv.tw>, 2023. | ||||
| # | ||||
| msgid "" | ||||
| msgstr "" | ||||
| "Project-Id-Version: Mia! Accounting Flask 0.0.0\n" | ||||
| "Report-Msgid-Bugs-To: imacat@mail.imacat.idv.tw\n" | ||||
| "POT-Creation-Date: 2023-01-28 13:37+0800\n" | ||||
| "PO-Revision-Date: 2023-01-28 13:37+0800\n" | ||||
| "Last-Translator: imacat <imacat@mail.imacat.idv.tw>\n" | ||||
| "Language: zh_Hant\n" | ||||
| "Language-Team: zh_Hant <imacat@mail.imacat.idv.tw>\n" | ||||
| "Plural-Forms: nplurals=1; plural=0;\n" | ||||
| "MIME-Version: 1.0\n" | ||||
| "Content-Type: text/plain; charset=utf-8\n" | ||||
| "Content-Transfer-Encoding: 8bit\n" | ||||
| "Generated-By: Babel 2.11.0\n" | ||||
|  | ||||
| #: src/accounting/base_account/templates/accounting/base-account/list.html:24 | ||||
| #: src/accounting/templates/accounting/include/nav.html:32 | ||||
| msgid "Base Accounts" | ||||
| msgstr "基本科目" | ||||
|  | ||||
| #: src/accounting/base_account/templates/accounting/base-account/list.html:35 | ||||
| msgid "Search" | ||||
| msgstr "搜尋" | ||||
|  | ||||
| #: src/accounting/base_account/templates/accounting/base-account/list.html:53 | ||||
| msgid "There is no data." | ||||
| msgstr "沒有資料。" | ||||
|  | ||||
| #: src/accounting/templates/accounting/include/nav.html:26 | ||||
| msgid "Accounting" | ||||
| msgstr "記帳" | ||||
|  | ||||
| #: src/accounting/utils/pagination.py:146 | ||||
| msgid "Previous" | ||||
| msgstr "前一頁" | ||||
|  | ||||
| #: src/accounting/utils/pagination.py:194 | ||||
| msgid "Next" | ||||
| msgstr "下一頁" | ||||
|  | ||||
							
								
								
									
										21
									
								
								src/accounting/utils/__init__.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										21
									
								
								src/accounting/utils/__init__.py
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,21 @@ | ||||
| # The Mia! Accounting Flask Project. | ||||
| # Author: imacat@mail.imacat.idv.tw (imacat), 2023/1/25 | ||||
|  | ||||
| #  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 independent utilities. | ||||
|  | ||||
| This module should not import any other module from the application. | ||||
|  | ||||
| """ | ||||
							
								
								
									
										240
									
								
								src/accounting/utils/pagination.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										240
									
								
								src/accounting/utils/pagination.py
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,240 @@ | ||||
| # The Mia! Accounting Flask Project. | ||||
| # Author: imacat@mail.imacat.idv.tw (imacat), 2023/1/25 | ||||
|  | ||||
| #  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 pagination utilities. | ||||
|  | ||||
| This module should not import any other module from the application. | ||||
|  | ||||
| """ | ||||
| import typing as t | ||||
| from urllib.parse import urlparse, parse_qsl, urlencode, urlunparse, \ | ||||
|     ParseResult | ||||
|  | ||||
| from flask import request | ||||
|  | ||||
| from accounting.locale import gettext | ||||
|  | ||||
|  | ||||
| class PageLink: | ||||
|     """A link in the pagination.""" | ||||
|  | ||||
|     def __init__(self, text: str, uri: str | None = None, | ||||
|                  is_current: bool = False, is_for_mobile: bool = False): | ||||
|         """Constructs the link. | ||||
|  | ||||
|         :param text: The link text. | ||||
|         :param uri: The link URI, or None if there is no link. | ||||
|         :param is_current: True if the page is the current page, or False | ||||
|             otherwise. | ||||
|         :param is_for_mobile: True if the page should be shown on small | ||||
|             screens, or False otherwise. | ||||
|         """ | ||||
|         self.text: str = text | ||||
|         """The link text""" | ||||
|         self.uri: str | None = uri | ||||
|         """The link URI, or None if there is no link.""" | ||||
|         self.is_current: bool = is_current | ||||
|         """Whether the link is the current page.""" | ||||
|         self.is_for_mobile: bool = is_for_mobile | ||||
|         """Whether the link should be shown on mobile screens.""" | ||||
|  | ||||
|  | ||||
| T = t.TypeVar("T") | ||||
|  | ||||
|  | ||||
| class Pagination(t.Generic[T]): | ||||
|     """The pagination utilities""" | ||||
|     AVAILABLE_PAGE_SIZES: list[int] = [10, 100, 200] | ||||
|     """The available page sizes.""" | ||||
|     DEFAULT_PAGE_SIZE: int = 10 | ||||
|     """The default page size.""" | ||||
|  | ||||
|     def __init__(self, items: list[T], is_reversed: bool = False): | ||||
|         """Constructs the pagination. | ||||
|  | ||||
|         :param items: The items. | ||||
|         :param is_reversed: True if the default page is the last page, or False | ||||
|             otherwise. | ||||
|         """ | ||||
|         self.__items: list[T] = items | ||||
|         """All the items.""" | ||||
|         self.__is_reversed: bool = is_reversed | ||||
|         """Whether the default page is the last page.""" | ||||
|         self.page_size: int = int(request.args.get("page-size", | ||||
|                                                    self.DEFAULT_PAGE_SIZE)) | ||||
|         """The number of items in a page.""" | ||||
|         self.__total_pages: int = 0 if len(items) == 0 \ | ||||
|             else int((len(items) - 1) / self.page_size) + 1 | ||||
|         """The total number of pages.""" | ||||
|         self.is_needed: bool = self.__total_pages > 1 | ||||
|         """Whether there should be pagination.""" | ||||
|         self.__default_page_no: int = 0 | ||||
|         """The default page number.""" | ||||
|         self.page_no: int = 0 | ||||
|         """The current page number.""" | ||||
|         self.list: list[T] = [] | ||||
|         """The items shown in the list""" | ||||
|         if self.__total_pages > 0: | ||||
|             self.__set_list() | ||||
|         self.__current_uri: str = request.full_path if request.query_string \ | ||||
|             else request.path | ||||
|         """The current URI.""" | ||||
|         self.__base_uri_params: tuple[list[str], list[tuple[str, str]]] \ | ||||
|             = self.__get_base_uri_params() | ||||
|         """The base URI parameters.""" | ||||
|         self.page_links: list[PageLink] = self.__get_page_links() | ||||
|         """The pagination links.""" | ||||
|         self.page_sizes: list[PageLink] = self.__get_page_sizes() | ||||
|         """The links to switch the number of items in a page.""" | ||||
|  | ||||
|     def __set_list(self) -> None: | ||||
|         """Sets the items to show in the list. | ||||
|  | ||||
|         :return: None. | ||||
|         """ | ||||
|         self.__default_page_no = self.__total_pages if self.__is_reversed \ | ||||
|             else 1 | ||||
|         self.page_no = int(request.args.get("page-no", | ||||
|                                             self.__default_page_no)) | ||||
|         if self.page_no < 1: | ||||
|             self.page_no = 1 | ||||
|         if self.page_no > self.__total_pages: | ||||
|             self.page_no = self.__total_pages | ||||
|         lower_bound: int = (self.page_no - 1) * self.page_size | ||||
|         upper_bound: int = lower_bound + self.page_size | ||||
|         if upper_bound > len(self.__items): | ||||
|             upper_bound = len(self.__items) | ||||
|         self.list = self.__items[lower_bound:upper_bound] | ||||
|  | ||||
|     def __get_base_uri_params(self) -> tuple[list[str], list[tuple[str, str]]]: | ||||
|         """Returns the base URI and its parameters, with the "page-no" and | ||||
|         "page-size" parameters removed. | ||||
|  | ||||
|         :return: The URI parts and the cleaned-up query parameters. | ||||
|         """ | ||||
|         uri_p: ParseResult = urlparse(self.__current_uri) | ||||
|         params: list[tuple[str, str]] = parse_qsl(uri_p.query) | ||||
|         params = [x for x in params if x[0] not in ["page-no", "page-size"]] | ||||
|         parts: list[str] = list(uri_p) | ||||
|         return parts, params | ||||
|  | ||||
|     def __get_page_links(self) -> list[PageLink]: | ||||
|         """Returns the page links in the pagination navigation. | ||||
|  | ||||
|         :return: The page links in the pagination navigation. | ||||
|         """ | ||||
|         if self.__total_pages < 2: | ||||
|             return [] | ||||
|         uri: str | None | ||||
|         links: list[PageLink] = [] | ||||
|  | ||||
|         # The previous page. | ||||
|         uri = None if self.page_no == 1 else self.__uri_page(self.page_no - 1) | ||||
|         links.append(PageLink(gettext("Previous"), uri, is_for_mobile=True)) | ||||
|  | ||||
|         # The first page. | ||||
|         if self.page_no > 1: | ||||
|             links.append(PageLink("1", self.__uri_page(1))) | ||||
|  | ||||
|         # The eclipse of the previous pages. | ||||
|         if self.page_no - 3 == 2: | ||||
|             links.append(PageLink(str(self.page_no - 3), | ||||
|                                   self.__uri_page(self.page_no - 3))) | ||||
|         elif self.page_no - 3 > 2: | ||||
|             links.append(PageLink("…")) | ||||
|  | ||||
|         # The previous two pages. | ||||
|         if self.page_no - 2 > 1: | ||||
|             links.append(PageLink(str(self.page_no - 2), | ||||
|                                   self.__uri_page(self.page_no - 2))) | ||||
|         if self.page_no - 1 > 1: | ||||
|             links.append(PageLink(str(self.page_no - 1), | ||||
|                                   self.__uri_page(self.page_no - 1))) | ||||
|  | ||||
|         # The current page. | ||||
|         links.append(PageLink(str(self.page_no), self.__uri_page(self.page_no), | ||||
|                               is_current=True)) | ||||
|  | ||||
|         # The next two pages. | ||||
|         if self.page_no + 1 < self.__total_pages: | ||||
|             links.append(PageLink(str(self.page_no + 1), | ||||
|                                   self.__uri_page(self.page_no + 1))) | ||||
|         if self.page_no + 2 < self.__total_pages: | ||||
|             links.append(PageLink(str(self.page_no + 2), | ||||
|                                   self.__uri_page(self.page_no + 2))) | ||||
|  | ||||
|         # The eclipse of the next pages. | ||||
|         if self.page_no + 3 == self.__total_pages - 1: | ||||
|             links.append(PageLink(str(self.page_no + 3), | ||||
|                                   self.__uri_page(self.page_no + 3))) | ||||
|         elif self.page_no + 3 < self.__total_pages - 1: | ||||
|             links.append(PageLink("…")) | ||||
|  | ||||
|         # The last page. | ||||
|         if self.page_no < self.__total_pages: | ||||
|             links.append(PageLink(str(self.__total_pages), | ||||
|                                   self.__uri_page(self.__total_pages))) | ||||
|  | ||||
|         # The next page. | ||||
|         uri = None if self.page_no == self.__total_pages \ | ||||
|             else self.__uri_page(self.page_no + 1) | ||||
|         links.append(PageLink(gettext("Next"), uri, is_for_mobile=True)) | ||||
|  | ||||
|         return links | ||||
|  | ||||
|     def __uri_page(self, page_no: int) -> str: | ||||
|         """Returns the URI of a page. | ||||
|  | ||||
|         :param page_no: The page number. | ||||
|         :return: The URI of the page. | ||||
|         """ | ||||
|         params: list[tuple[str, str]] = [] | ||||
|         if page_no != self.__default_page_no: | ||||
|             params.append(("page-no", str(page_no))) | ||||
|         if self.page_size != self.DEFAULT_PAGE_SIZE: | ||||
|             params.append(("page-size", str(self.page_size))) | ||||
|         return self.__uri_set_params(params) | ||||
|  | ||||
|     def __get_page_sizes(self) -> list[PageLink]: | ||||
|         """Returns the available page sizes. | ||||
|  | ||||
|         :return: The available page sizes. | ||||
|         """ | ||||
|         return [PageLink(str(x), self.__uri_size(x), | ||||
|                          is_current=x == self.page_size) | ||||
|                 for x in self.AVAILABLE_PAGE_SIZES] | ||||
|  | ||||
|     def __uri_size(self, page_size: int) -> str: | ||||
|         """Returns the URI of a page size. | ||||
|  | ||||
|         :param page_size: The page size. | ||||
|         :return: The URI of the page size. | ||||
|         """ | ||||
|         if page_size == self.page_size: | ||||
|             return self.__current_uri | ||||
|         return self.__uri_set_params([("page-size", str(page_size))]) | ||||
|  | ||||
|     def __uri_set_params(self, params: list[tuple[str, str]]) -> str: | ||||
|         """Returns the URI with the query parameters set. | ||||
|  | ||||
|         :param params: The query parameters. | ||||
|         :return: The URI with the query parameters set. | ||||
|         """ | ||||
|         cur_params: list[tuple[str, str]] = self.__base_uri_params[1].copy() | ||||
|         cur_params.extend(params) | ||||
|         parts: list[str] = self.__base_uri_params[0].copy() | ||||
|         parts[4] = urlencode(cur_params) | ||||
|         return urlunparse(parts) | ||||
							
								
								
									
										100
									
								
								src/accounting/utils/permission.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										100
									
								
								src/accounting/utils/permission.py
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,100 @@ | ||||
| # The Mia! Accounting Flask Project. | ||||
| # Author: imacat@mail.imacat.idv.tw (imacat), 2023/1/25 | ||||
|  | ||||
| #  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 permissions. | ||||
|  | ||||
| This module should not import any other module from the application. | ||||
|  | ||||
| """ | ||||
| import typing as t | ||||
|  | ||||
| from flask import Flask, abort | ||||
|  | ||||
|  | ||||
| def has_permission(rule: t.Callable[[], bool]) -> t.Callable: | ||||
|     """The permission decorator to check whether the current user is allowed. | ||||
|  | ||||
|     :param rule: The permission rule. | ||||
|     :return: The view decorator. | ||||
|     """ | ||||
|  | ||||
|     def decorator(view: t.Callable) -> t.Callable: | ||||
|         """The view decorator to decorate a view with permission tests. | ||||
|  | ||||
|         :param view: The view. | ||||
|         :return: The decorated view. | ||||
|         """ | ||||
|  | ||||
|         def decorated_view(*args, **kwargs): | ||||
|             """The decorated view that tests against a permission rule. | ||||
|  | ||||
|             :param args: The arguments of the view. | ||||
|             :param kwargs: The keyword arguments of the view. | ||||
|             :return: The response of the view. | ||||
|             :raise Forbidden: When the user is denied. | ||||
|             """ | ||||
|             if not rule(): | ||||
|                 abort(403) | ||||
|             return view(*args, **kwargs) | ||||
|  | ||||
|         return decorated_view | ||||
|  | ||||
|     return decorator | ||||
|  | ||||
|  | ||||
| __can_view_func: t.Callable[[], bool] = lambda: True | ||||
| """The callback that returns whether the current user can view the accounting | ||||
| data.""" | ||||
| __can_edit_func: t.Callable[[], bool] = lambda: True | ||||
| """The callback that returns whether the current user can edit the accounting | ||||
| data.""" | ||||
|  | ||||
|  | ||||
| def can_view() -> bool: | ||||
|     """Returns whether the current user can view the account data. | ||||
|  | ||||
|     :return: True if the current user can view the accounting data, or False | ||||
|         otherwise. | ||||
|     """ | ||||
|     return __can_view_func() | ||||
|  | ||||
|  | ||||
| def can_edit() -> bool: | ||||
|     """Returns whether the current user can edit the account data. | ||||
|  | ||||
|     :return: True if the current user can edit the accounting data, or False | ||||
|         otherwise. | ||||
|     """ | ||||
|     return __can_edit_func() | ||||
|  | ||||
|  | ||||
| def init_app(app: Flask, can_view_func: t.Callable[[], bool] | None = None, | ||||
|              can_edit_func: t.Callable[[], bool] | None = None) -> None: | ||||
|     """Initializes the application. | ||||
|  | ||||
|     :param app: The Flask application. | ||||
|     :param can_view_func: A callback that returns whether the current user can | ||||
|         view the accounting data. | ||||
|     :param can_edit_func: A callback that returns whether the current user can | ||||
|         edit the accounting data. | ||||
|     :return: None. | ||||
|     """ | ||||
|     global __can_view_func, __can_edit_func | ||||
|     if can_view_func is not None: | ||||
|         __can_view_func = can_view_func | ||||
|     if can_edit_func is not None: | ||||
|         __can_edit_func = can_edit_func | ||||
|     app.jinja_env.globals["can_view_accounting"] = __can_view_func | ||||
							
								
								
									
										44
									
								
								src/accounting/utils/query.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										44
									
								
								src/accounting/utils/query.py
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,44 @@ | ||||
| # The Mia! Accounting Flask Project. | ||||
| # Author: imacat@mail.imacat.idv.tw (imacat), 2023/1/25 | ||||
|  | ||||
| #  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 query keyword parser. | ||||
|  | ||||
| This module should not import any other module from the application. | ||||
|  | ||||
| """ | ||||
| import re | ||||
|  | ||||
|  | ||||
| def parse_query_keywords(q: str | None) -> list[str]: | ||||
|     """Returns the query keywords by the query parameter. | ||||
|  | ||||
|     :param q: The query parameter. | ||||
|     :return: The query keywords. | ||||
|     """ | ||||
|     if q is None: | ||||
|         return [] | ||||
|     q = q.strip() | ||||
|     if q == "": | ||||
|         return [] | ||||
|     keywords: list[str] = [] | ||||
|     while q is not None: | ||||
|         m: re.Match = re.match(r"(?:\"([^\"]+)\"|(\S+))(?:\s+(.+)|)$", q) | ||||
|         if m.group(1) is not None: | ||||
|             keywords.append(m.group(1)) | ||||
|         else: | ||||
|             keywords.append(m.group(2)) | ||||
|         q = m.group(3) | ||||
|     return keywords | ||||
							
								
								
									
										133
									
								
								tests/babel-utils-testsite.py
									
									
									
									
									
										Executable file
									
								
							
							
						
						
									
										133
									
								
								tests/babel-utils-testsite.py
									
									
									
									
									
										Executable file
									
								
							| @@ -0,0 +1,133 @@ | ||||
| #! env python3 | ||||
| # The Mia! Accounting Flask Project. | ||||
| # Author: imacat@mail.imacat.idv.tw (imacat), 2023/1/28 | ||||
|  | ||||
| #  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 translation management utilities for the test site. | ||||
|  | ||||
| """ | ||||
| import os | ||||
| import re | ||||
| from pathlib import Path | ||||
| from time import strftime | ||||
|  | ||||
| import click | ||||
| from babel.messages.frontend import CommandLineInterface | ||||
| from opencc import OpenCC | ||||
|  | ||||
| root_dir: Path = Path(__file__).parent.parent | ||||
| translation_dir: Path = root_dir / "tests" / "testsite" / "translations" | ||||
| domain: str = "messages" | ||||
|  | ||||
|  | ||||
| @click.group() | ||||
| def main() -> None: | ||||
|     """Manages the message translation.""" | ||||
|  | ||||
|  | ||||
| @click.command("extract") | ||||
| def babel_extract() -> None: | ||||
|     """Extracts the messages for translation.""" | ||||
|     os.chdir(root_dir) | ||||
|     cfg: Path = translation_dir / "babel.cfg" | ||||
|     pot: Path = translation_dir / f"{domain}.pot" | ||||
|     zh_hant: Path = translation_dir / "zh_Hant" / "LC_MESSAGES"\ | ||||
|         / f"{domain}.po" | ||||
|     zh_hans: Path = translation_dir / "zh_Hans" / "LC_MESSAGES"\ | ||||
|         / f"{domain}.po" | ||||
|     CommandLineInterface().run([ | ||||
|         "pybabel", "extract", "-F", str(cfg), "-k", "lazy_gettext", "-k", "A_", | ||||
|         "-o", str(pot), str(Path("tests") / "testsite")]) | ||||
|     if not zh_hant.exists(): | ||||
|         zh_hant.touch() | ||||
|     if not zh_hans.exists(): | ||||
|         zh_hans.touch() | ||||
|     CommandLineInterface().run([ | ||||
|         "pybabel", "update", "-i", str(pot), "-D", domain, | ||||
|         "-d", translation_dir]) | ||||
|  | ||||
|  | ||||
| @click.command("compile") | ||||
| def babel_compile() -> None: | ||||
|     """Compiles the translated messages.""" | ||||
|     __convert_chinese() | ||||
|     __update_rev_date() | ||||
|     CommandLineInterface().run([ | ||||
|         "pybabel", "compile", "-D", domain, "-d", translation_dir]) | ||||
|  | ||||
|  | ||||
| def __convert_chinese() -> None: | ||||
|     """Updates the Simplified Chinese translation according to the Traditional | ||||
|     Chinese translation. | ||||
|  | ||||
|     :return: None. | ||||
|     """ | ||||
|     cc: OpenCC = OpenCC("tw2sp") | ||||
|     zh_hant: Path = translation_dir / "zh_Hant" / "LC_MESSAGES"\ | ||||
|         / f"{domain}.po" | ||||
|     zh_hans: Path = translation_dir / "zh_Hans" / "LC_MESSAGES"\ | ||||
|         / f"{domain}.po" | ||||
|     now: str = strftime("%Y-%m-%d %H:%M%z") | ||||
|     with open(zh_hant, "r") as f: | ||||
|         content: str = f.read() | ||||
|     content = cc.convert(content) | ||||
|     content = re.sub(r"^# Chinese \\(Traditional\\) translations ", | ||||
|                      "# Chinese (Simplified) translations ", content) | ||||
|     content = re.sub(r"\n\"PO-Revision-Date: [^\n]*\"\n", | ||||
|                      f"\n\"PO-Revision-Date: {now}\\\\n\"\n", | ||||
|                      content) | ||||
|     content = content.replace("\n\"Language-Team: zh_Hant", | ||||
|                               "\n\"Language-Team: zh_Hans") | ||||
|     content = content.replace("\n\"Language: zh_Hant\\n\"\n", | ||||
|                               "\n\"Language: zh_Hans\\n\"\n") | ||||
|     content = content.replace("\nmsgstr \"zh-Hant\"\n", | ||||
|                               "\nmsgstr \"zh-Hans\"\n") | ||||
|     zh_hans.parent.mkdir(exist_ok=True) | ||||
|     with open(zh_hans, "w") as f: | ||||
|         f.write(content) | ||||
|  | ||||
|  | ||||
| def __update_rev_date() -> None: | ||||
|     """Updates the revision dates in the PO files. | ||||
|  | ||||
|     :return: None. | ||||
|     """ | ||||
|     for language_dir in translation_dir.glob("*"): | ||||
|         po_file: Path = language_dir / "LC_MESSAGES" / f"{domain}.po" | ||||
|         if po_file.is_file(): | ||||
|             __update_file_rev_date(po_file) | ||||
|  | ||||
|  | ||||
| def __update_file_rev_date(file: Path) -> None: | ||||
|     """Updates the revision date of a PO file | ||||
|  | ||||
|     :param file: The PO file. | ||||
|     :return: None. | ||||
|     """ | ||||
|     now = strftime("%Y-%m-%d %H:%M%z") | ||||
|     with open(file, "r+") as f: | ||||
|         content = f.read() | ||||
|         content = re.sub(r"\n\"PO-Revision-Date: [^\n]*\"\n", | ||||
|                          f"\n\"PO-Revision-Date: {now}\\\\n\"\n", | ||||
|                          content) | ||||
|         f.seek(0) | ||||
|         f.write(content) | ||||
|  | ||||
|  | ||||
| main.add_command(babel_extract) | ||||
| main.add_command(babel_compile) | ||||
|  | ||||
| if __name__ == '__main__': | ||||
|     main() | ||||
							
								
								
									
										133
									
								
								tests/babel-utils.py
									
									
									
									
									
										Executable file
									
								
							
							
						
						
									
										133
									
								
								tests/babel-utils.py
									
									
									
									
									
										Executable file
									
								
							| @@ -0,0 +1,133 @@ | ||||
| #! env python3 | ||||
| # The Mia! Accounting Flask Project. | ||||
| # Author: imacat@mail.imacat.idv.tw (imacat), 2023/1/28 | ||||
|  | ||||
| #  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 translation management utilities. | ||||
|  | ||||
| """ | ||||
| import os | ||||
| import re | ||||
| from pathlib import Path | ||||
| from time import strftime | ||||
|  | ||||
| import click | ||||
| from babel.messages.frontend import CommandLineInterface | ||||
| from opencc import OpenCC | ||||
|  | ||||
| root_dir: Path = Path(__file__).parent.parent | ||||
| translation_dir: Path = root_dir / "src" / "accounting" / "translations" | ||||
| domain: str = "accounting" | ||||
|  | ||||
|  | ||||
| @click.group() | ||||
| def main() -> None: | ||||
|     """Manages the message translation.""" | ||||
|  | ||||
|  | ||||
| @click.command("extract") | ||||
| def babel_extract() -> None: | ||||
|     """Extracts the messages for translation.""" | ||||
|     os.chdir(root_dir) | ||||
|     cfg: Path = translation_dir / "babel.cfg" | ||||
|     pot: Path = translation_dir / f"{domain}.pot" | ||||
|     zh_hant: Path = translation_dir / "zh_Hant" / "LC_MESSAGES"\ | ||||
|         / f"{domain}.po" | ||||
|     zh_hans: Path = translation_dir / "zh_Hans" / "LC_MESSAGES"\ | ||||
|         / f"{domain}.po" | ||||
|     CommandLineInterface().run([ | ||||
|         "pybabel", "extract", "-F", str(cfg), "-k", "lazy_gettext", "-k", "A_", | ||||
|         "-o", str(pot), "src"]) | ||||
|     if not zh_hant.exists(): | ||||
|         zh_hant.touch() | ||||
|     if not zh_hans.exists(): | ||||
|         zh_hans.touch() | ||||
|     CommandLineInterface().run([ | ||||
|         "pybabel", "update", "-i", str(pot), "-D", domain, | ||||
|         "-d", translation_dir]) | ||||
|  | ||||
|  | ||||
| @click.command("compile") | ||||
| def babel_compile() -> None: | ||||
|     """Compiles the translated messages.""" | ||||
|     __convert_chinese() | ||||
|     __update_rev_date() | ||||
|     CommandLineInterface().run([ | ||||
|         "pybabel", "compile", "-D", domain, "-d", translation_dir]) | ||||
|  | ||||
|  | ||||
| def __convert_chinese() -> None: | ||||
|     """Updates the Simplified Chinese translation according to the Traditional | ||||
|     Chinese translation. | ||||
|  | ||||
|     :return: None. | ||||
|     """ | ||||
|     cc: OpenCC = OpenCC("tw2sp") | ||||
|     zh_hant: Path = translation_dir / "zh_Hant" / "LC_MESSAGES"\ | ||||
|         / f"{domain}.po" | ||||
|     zh_hans: Path = translation_dir / "zh_Hans" / "LC_MESSAGES"\ | ||||
|         / f"{domain}.po" | ||||
|     now: str = strftime("%Y-%m-%d %H:%M%z") | ||||
|     with open(zh_hant, "r") as f: | ||||
|         content: str = f.read() | ||||
|     content = cc.convert(content) | ||||
|     content = re.sub(r"^# Chinese \\(Traditional\\) translations ", | ||||
|                      "# Chinese (Simplified) translations ", content) | ||||
|     content = re.sub(r"\n\"PO-Revision-Date: [^\n]*\"\n", | ||||
|                      f"\n\"PO-Revision-Date: {now}\\\\n\"\n", | ||||
|                      content) | ||||
|     content = content.replace("\n\"Language-Team: zh_Hant", | ||||
|                               "\n\"Language-Team: zh_Hans") | ||||
|     content = content.replace("\n\"Language: zh_Hant\\n\"\n", | ||||
|                               "\n\"Language: zh_Hans\\n\"\n") | ||||
|     content = content.replace("\nmsgstr \"zh-Hant\"\n", | ||||
|                               "\nmsgstr \"zh-Hans\"\n") | ||||
|     zh_hans.parent.mkdir(exist_ok=True) | ||||
|     with open(zh_hans, "w") as f: | ||||
|         f.write(content) | ||||
|  | ||||
|  | ||||
| def __update_rev_date() -> None: | ||||
|     """Updates the revision dates in the PO files. | ||||
|  | ||||
|     :return: None. | ||||
|     """ | ||||
|     for language_dir in translation_dir.glob("*"): | ||||
|         po_file: Path = language_dir / "LC_MESSAGES" / f"{domain}.po" | ||||
|         if po_file.is_file(): | ||||
|             __update_file_rev_date(po_file) | ||||
|  | ||||
|  | ||||
| def __update_file_rev_date(file: Path) -> None: | ||||
|     """Updates the revision date of a PO file | ||||
|  | ||||
|     :param file: The PO file. | ||||
|     :return: None. | ||||
|     """ | ||||
|     now = strftime("%Y-%m-%d %H:%M%z") | ||||
|     with open(file, "r+") as f: | ||||
|         content = f.read() | ||||
|         content = re.sub(r"\n\"PO-Revision-Date: [^\n]*\"\n", | ||||
|                          f"\n\"PO-Revision-Date: {now}\\\\n\"\n", | ||||
|                          content) | ||||
|         f.seek(0) | ||||
|         f.write(content) | ||||
|  | ||||
|  | ||||
| main.add_command(babel_extract) | ||||
| main.add_command(babel_compile) | ||||
|  | ||||
| if __name__ == '__main__': | ||||
|     main() | ||||
							
								
								
									
										113
									
								
								tests/test_base_account.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										113
									
								
								tests/test_base_account.py
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,113 @@ | ||||
| # The Mia! Accounting Flask Project. | ||||
| # Author: imacat@mail.imacat.idv.tw (imacat), 2023/1/26 | ||||
|  | ||||
| #  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 test for the base account management. | ||||
|  | ||||
| """ | ||||
| import unittest | ||||
|  | ||||
| import httpx | ||||
| from click.testing import Result | ||||
| from flask import Flask | ||||
| from flask.testing import FlaskCliRunner | ||||
|  | ||||
| from testlib import get_csrf_token | ||||
| from testsite import create_app | ||||
|  | ||||
|  | ||||
| class BaseAccountTestCase(unittest.TestCase): | ||||
|     """The base account test case.""" | ||||
|  | ||||
|     def setUp(self) -> None: | ||||
|         """Sets up the test. | ||||
|         This is run once per test. | ||||
|  | ||||
|         :return: None. | ||||
|         """ | ||||
|         self.app: Flask = create_app(is_testing=True) | ||||
|  | ||||
|         runner: FlaskCliRunner = self.app.test_cli_runner() | ||||
|         with self.app.app_context(): | ||||
|             result: Result = runner.invoke(args="init-db") | ||||
|             self.assertEqual(result.exit_code, 0) | ||||
|         self.client: httpx.Client = httpx.Client(app=self.app, | ||||
|                                                  base_url="https://testserver") | ||||
|         self.client.headers["Referer"] = "https://testserver" | ||||
|         self.csrf_token: str = get_csrf_token(self, self.client, "/login") | ||||
|  | ||||
|     def test_init(self) -> None: | ||||
|         """Tests the "accounting-init-base" console command. | ||||
|  | ||||
|         :return: None. | ||||
|         """ | ||||
|         from accounting.base_account.models import BaseAccount, BaseAccountL10n | ||||
|         runner: FlaskCliRunner = self.app.test_cli_runner() | ||||
|         result: Result = runner.invoke(args="accounting-init-base") | ||||
|         self.assertEqual(result.exit_code, 0) | ||||
|         with self.app.app_context(): | ||||
|             accounts: list[BaseAccount] = BaseAccount.query.all() | ||||
|             l10n: list[BaseAccountL10n] = BaseAccountL10n.query.all() | ||||
|         self.assertEqual(len(accounts), 527) | ||||
|         self.assertEqual(len(l10n), 527 * 2) | ||||
|         l10n_keys: set[str] = {f"{x.account_code}-{x.locale}" for x in l10n} | ||||
|         for account in accounts: | ||||
|             self.assertIn(f"{account.code}-zh_Hant", l10n_keys) | ||||
|             self.assertIn(f"{account.code}-zh_Hant", l10n_keys) | ||||
|  | ||||
|         list_uri: str = "/accounting/base-accounts" | ||||
|         response: httpx.Response | ||||
|  | ||||
|         self.__logout() | ||||
|         response = self.client.get(list_uri) | ||||
|         self.assertEqual(response.status_code, 403) | ||||
|  | ||||
|         self.__logout() | ||||
|         self.__login_as("viewer") | ||||
|         response = self.client.get(list_uri) | ||||
|         self.assertEqual(response.status_code, 200) | ||||
|  | ||||
|         self.__logout() | ||||
|         self.__login_as("editor") | ||||
|         response = self.client.get(list_uri) | ||||
|         self.assertEqual(response.status_code, 200) | ||||
|  | ||||
|         self.__logout() | ||||
|         self.__login_as("nobody") | ||||
|         response = self.client.get(list_uri) | ||||
|         self.assertEqual(response.status_code, 403) | ||||
|  | ||||
|     def __logout(self) -> None: | ||||
|         """Logs out the currently logged-in user. | ||||
|  | ||||
|         :return: None. | ||||
|         """ | ||||
|         response: httpx.Response = self.client.post( | ||||
|             "/logout", data={"csrf_token": self.csrf_token}) | ||||
|         self.assertEqual(response.status_code, 302) | ||||
|         self.assertEqual(response.headers["Location"], "/") | ||||
|  | ||||
|     def __login_as(self, username: str) -> None: | ||||
|         """Logs in as a specific user. | ||||
|  | ||||
|         :param username: The username. | ||||
|         :return: None. | ||||
|         """ | ||||
|         response: httpx.Response = self.client.post( | ||||
|             "/login", data={"csrf_token": self.csrf_token, | ||||
|                             "username": username}) | ||||
|         self.assertEqual(response.status_code, 302) | ||||
|         self.assertEqual(response.headers["Location"], "/") | ||||
							
								
								
									
										56
									
								
								tests/testlib.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										56
									
								
								tests/testlib.py
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,56 @@ | ||||
| # The Mia! Accounting Flask Project. | ||||
| # Author: imacat@mail.imacat.idv.tw (imacat), 2023/1/27 | ||||
|  | ||||
| #  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 common test libraries. | ||||
|  | ||||
| """ | ||||
| from html.parser import HTMLParser | ||||
| from unittest import TestCase | ||||
|  | ||||
| import httpx | ||||
|  | ||||
|  | ||||
| def get_csrf_token(test_case: TestCase, client: httpx.Client, uri: str) -> str: | ||||
|     """Returns the CSRF token from a form in a URI. | ||||
|  | ||||
|     :param test_case: The test case. | ||||
|     :param client: The httpx client. | ||||
|     :param uri: The URI. | ||||
|     :return: The CSRF token. | ||||
|     """ | ||||
|  | ||||
|     class CsrfParser(HTMLParser): | ||||
|         """The CSRF token parser.""" | ||||
|  | ||||
|         def __init__(self): | ||||
|             """Constructs the CSRF token parser.""" | ||||
|             super().__init__() | ||||
|             self.csrf_token: str | None = None | ||||
|             """The CSRF token.""" | ||||
|  | ||||
|         def handle_starttag(self, tag: str, | ||||
|                             attrs: list[tuple[str, str | None]]) -> None: | ||||
|             """Handles when a start tag is found.""" | ||||
|             attrs_dict: dict[str, str] = dict(attrs) | ||||
|             if attrs_dict.get("name") == "csrf_token": | ||||
|                 self.csrf_token = attrs_dict["value"] | ||||
|  | ||||
|     response: httpx.Response = client.get(uri) | ||||
|     test_case.assertEqual(response.status_code, 200) | ||||
|     parser: CsrfParser = CsrfParser() | ||||
|     parser.feed(response.text) | ||||
|     test_case.assertIsNotNone(parser.csrf_token) | ||||
|     return parser.csrf_token | ||||
							
								
								
									
										96
									
								
								tests/testsite/__init__.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										96
									
								
								tests/testsite/__init__.py
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,96 @@ | ||||
| # The Mia! Accounting Flask Demonstration Website. | ||||
| # Author: imacat@mail.imacat.idv.tw (imacat), 2023/1/27 | ||||
|  | ||||
| #  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 Mia! Accounting Flask demonstration website. | ||||
|  | ||||
| """ | ||||
| import typing as t | ||||
| from secrets import token_urlsafe | ||||
|  | ||||
| import click | ||||
| from flask import Flask, Blueprint, render_template | ||||
| from flask.cli import with_appcontext | ||||
| from flask_babel_js import BabelJS | ||||
| from flask_sqlalchemy import SQLAlchemy | ||||
| from flask_wtf import CSRFProtect | ||||
|  | ||||
| bp: Blueprint = Blueprint("home", __name__) | ||||
| babel_js: BabelJS = BabelJS() | ||||
| csrf: CSRFProtect = CSRFProtect() | ||||
| db: SQLAlchemy = SQLAlchemy() | ||||
|  | ||||
|  | ||||
| def create_app(is_testing: bool = False) -> Flask: | ||||
|     """Create and configure the application. | ||||
|  | ||||
|     :param is_testing: True if we are running for testing, or False otherwise. | ||||
|     :return: The application. | ||||
|     """ | ||||
|     import accounting | ||||
|  | ||||
|     app: Flask = Flask(__name__) | ||||
|     db_uri: str = "sqlite:///" if is_testing else "sqlite:///local.sqlite" | ||||
|     app.config.from_mapping({ | ||||
|         "SECRET_KEY": token_urlsafe(32), | ||||
|         "SQLALCHEMY_DATABASE_URI": db_uri, | ||||
|         "BABEL_DEFAULT_LOCALE": "en", | ||||
|         "ALL_LINGUAS": "zh_Hant|正體中文,en|English,zh_Hans|简体中文", | ||||
|     }) | ||||
|     if is_testing: | ||||
|         app.config["TESTING"] = True | ||||
|  | ||||
|     babel_js.init_app(app) | ||||
|     csrf.init_app(app) | ||||
|     db.init_app(app) | ||||
|  | ||||
|     app.register_blueprint(bp, url_prefix="/") | ||||
|     app.cli.add_command(init_db_command) | ||||
|  | ||||
|     from . import locale | ||||
|     locale.init_app(app) | ||||
|  | ||||
|     from . import auth | ||||
|     auth.init_app(app) | ||||
|  | ||||
|     can_view: t.Callable[[], bool] = lambda: auth.current_user() is not None \ | ||||
|         and auth.current_user().username in ["viewer", "editor"] | ||||
|     can_edit: t.Callable[[], bool] = lambda: auth.current_user() is not None \ | ||||
|         and auth.current_user().username == "editor" | ||||
|     accounting.init_app(app, can_view_func=can_view, can_edit_func=can_edit) | ||||
|  | ||||
|     return app | ||||
|  | ||||
|  | ||||
| @click.command("init-db") | ||||
| @with_appcontext | ||||
| def init_db_command() -> None: | ||||
|     """Initializes the database.""" | ||||
|     db.create_all() | ||||
|     from .auth import User | ||||
|     for username in ["viewer", "editor", "nobody"]: | ||||
|         if User.query.filter(User.username == username).first() is None: | ||||
|             db.session.add(User(username=username)) | ||||
|     db.session.commit() | ||||
|     click.echo("Database initialized successfully.") | ||||
|  | ||||
|  | ||||
| @bp.get("/", endpoint="home") | ||||
| def get_home() -> str: | ||||
|     """Returns the home page. | ||||
|  | ||||
|     :return: The home page. | ||||
|     """ | ||||
|     return render_template("home.html") | ||||
							
								
								
									
										92
									
								
								tests/testsite/auth.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										92
									
								
								tests/testsite/auth.py
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,92 @@ | ||||
| # The Mia! Accounting Flask Demonstration Website. | ||||
| # Author: imacat@mail.imacat.idv.tw (imacat), 2023/1/27 | ||||
|  | ||||
| #  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 authentication for the Mia! Accounting Flask demonstration website. | ||||
|  | ||||
| """ | ||||
| from flask import Blueprint, render_template, Flask, redirect, url_for, \ | ||||
|     session, request, g | ||||
|  | ||||
| from . import db | ||||
|  | ||||
| bp: Blueprint = Blueprint("auth", __name__, url_prefix="/") | ||||
|  | ||||
|  | ||||
| class User(db.Model): | ||||
|     """A user.""" | ||||
|     __tablename__ = "users" | ||||
|     """The table name.""" | ||||
|     id = db.Column(db.Integer, nullable=False, primary_key=True, | ||||
|                    autoincrement=True) | ||||
|     """The ID""" | ||||
|     username = db.Column(db.String, nullable=False, unique=True) | ||||
|     """The username.""" | ||||
|  | ||||
|  | ||||
| @bp.get("login", endpoint="login-form") | ||||
| def show_login_form() -> str: | ||||
|     """Shows the login form. | ||||
|  | ||||
|     :return: The login form. | ||||
|     """ | ||||
|     return render_template("login.html") | ||||
|  | ||||
|  | ||||
| @bp.post("login", endpoint="login") | ||||
| def login() -> redirect: | ||||
|     """Logs in the user. | ||||
|  | ||||
|     :return: The redirection to the home page. | ||||
|     """ | ||||
|     if request.form.get("username") not in ["viewer", "editor", "nobody"]: | ||||
|         return redirect(url_for("auth.login")) | ||||
|     session["user"] = request.form.get("username") | ||||
|     return redirect(url_for("home.home")) | ||||
|  | ||||
|  | ||||
| @bp.post("logout", endpoint="logout") | ||||
| def logout() -> redirect: | ||||
|     """Logs out the user. | ||||
|  | ||||
|     :return: The redirection to the home page. | ||||
|     """ | ||||
|     if "user" in session: | ||||
|         del session["user"] | ||||
|     return redirect(url_for("home.home")) | ||||
|  | ||||
|  | ||||
| def current_user() -> User | None: | ||||
|     """Returns the current user. | ||||
|  | ||||
|     :return: The current user, or None if the user did not log in. | ||||
|     """ | ||||
|     if not hasattr(g, "user"): | ||||
|         if "user" not in session: | ||||
|             g.user = None | ||||
|         else: | ||||
|             g.user = User.query.filter( | ||||
|                 User.username == session["user"]).first() | ||||
|     return g.user | ||||
|  | ||||
|  | ||||
| def init_app(app: Flask) -> None: | ||||
|     """Initialize the localization. | ||||
|  | ||||
|     :param app: The Flask application. | ||||
|     :return: None. | ||||
|     """ | ||||
|     app.register_blueprint(bp) | ||||
|     app.jinja_env.globals["current_user"] = current_user | ||||
							
								
								
									
										97
									
								
								tests/testsite/locale.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										97
									
								
								tests/testsite/locale.py
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,97 @@ | ||||
| # The Mia! Accounting Flask Demonstration Website. | ||||
| # Author: imacat@mail.imacat.idv.tw (imacat), 2023/1/2 | ||||
|  | ||||
| #  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 localization for the Mia! Accounting Flask demonstration website. | ||||
|  | ||||
| """ | ||||
| from babel import Locale | ||||
| from flask import request, session, current_app, Blueprint, Response, \ | ||||
|     redirect, url_for, Flask | ||||
| from flask_babel import Babel | ||||
| from werkzeug.datastructures import LanguageAccept | ||||
|  | ||||
| bp: Blueprint = Blueprint("locale", __name__, url_prefix="/") | ||||
|  | ||||
|  | ||||
| def get_locale(): | ||||
|     """Returns the locale of the user | ||||
|  | ||||
|     :return: The locale of the user. | ||||
|     """ | ||||
|     all_linguas: dict[str, str] = get_all_linguas() | ||||
|     if "locale" in session and session["locale"] in all_linguas: | ||||
|         return session["locale"] | ||||
|     return __fix_accept_language(request.accept_languages)\ | ||||
|         .best_match(all_linguas.keys()) | ||||
|  | ||||
|  | ||||
| def __fix_accept_language(accept: LanguageAccept) -> LanguageAccept: | ||||
|     """Fixes the accept-language so that territory variants may be matched to | ||||
|     script variants.  For example, zh_TW, zh_HK to zh_Hant, and zh_CN, zh_SG to | ||||
|     zh_Hans.  This is to solve the issue that Flask only recognizes the script | ||||
|     variants, like zh_Hant and zh_Hans. | ||||
|  | ||||
|     :param accept: The original HTTP accept languages. | ||||
|     :return: The fixed HTTP accept languages | ||||
|     """ | ||||
|     accept_list: list[tuple[str, float]] = list(accept) | ||||
|     to_add: list[tuple[str, float]] = [] | ||||
|     for pair in accept_list: | ||||
|         locale: Locale = Locale.parse(pair[0].replace("-", "_")) | ||||
|         if locale.script is not None: | ||||
|             tag: str = f"{locale.language}-{locale.script}" | ||||
|             if tag not in accept: | ||||
|                 to_add.append((tag, pair[1])) | ||||
|     accept_list.extend(to_add) | ||||
|     return LanguageAccept(accept_list) | ||||
|  | ||||
|  | ||||
| @bp.post("/locale", endpoint="set-locale") | ||||
| def set_locale() -> Response: | ||||
|     """Sets the locale for the user. | ||||
|  | ||||
|     :return: The response. | ||||
|     """ | ||||
|     all_linguas: dict[str, str] = get_all_linguas() | ||||
|     if "locale" in request.form and request.form["locale"] in all_linguas: | ||||
|         session["locale"] = request.form["locale"] | ||||
|     if "next" in request.form: | ||||
|         return redirect(request.form["next"]) | ||||
|     return redirect(url_for("home.home")) | ||||
|  | ||||
|  | ||||
| def get_all_linguas() -> dict[str, str]: | ||||
|     """Returns all the available languages. | ||||
|  | ||||
|     :return: All the available languages, as a dictionary of the language code | ||||
|         and their local names. | ||||
|     """ | ||||
|     return {y[0]: y[1] for y in | ||||
|             [x.split("|") for x in | ||||
|              current_app.config["ALL_LINGUAS"].split(",")]} | ||||
|  | ||||
|  | ||||
| def init_app(app: Flask) -> None: | ||||
|     """Initialize the localization. | ||||
|  | ||||
|     :param app: The Flask application. | ||||
|     :return: None. | ||||
|     """ | ||||
|     babel = Babel() | ||||
|     babel.init_app(app, locale_selector=get_locale) | ||||
|     app.register_blueprint(bp) | ||||
|     app.jinja_env.globals["get_locale"] = get_locale | ||||
|     app.jinja_env.globals["get_all_linguas"] = get_all_linguas | ||||
							
								
								
									
										134
									
								
								tests/testsite/templates/base.html
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										134
									
								
								tests/testsite/templates/base.html
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,134 @@ | ||||
| {# | ||||
| The Mia! Accounting Flask Demonstration Website | ||||
| base.html: The side-wide layout template | ||||
|  | ||||
|  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. | ||||
|  | ||||
| Author: imacat@mail.imacat.idv.tw (imacat) | ||||
| First written: 2023/1/27 | ||||
| #} | ||||
| <!doctype html> | ||||
| <html xmlns="http://www.w3.org/1999/xhtml" lang="{{ _("en") }}"> | ||||
| <head> | ||||
|   <meta charset="UTF-8"> | ||||
|   <meta name="viewport" content="width=device-width, initial-scale=1"> | ||||
|   <meta name="author" content="{{ "imacat" }}" /> | ||||
|   <link rel="stylesheet" type="text/css" href="https://cdn.jsdelivr.net/npm/bootstrap@5.2.3/dist/css/bootstrap.min.css"> | ||||
|   <link rel="stylesheet" type="text/css" href="https://cdn.jsdelivr.net/npm/@fortawesome/fontawesome-free@6.2.1/css/all.min.css"> | ||||
|   {% block styles %}{% endblock %} | ||||
|   <script src="{{ url_for("babel_catalog") }}"></script> | ||||
|   <script src="https://cdn.jsdelivr.net/npm/bootstrap@5.2.3/dist/js/bootstrap.bundle.min.js"></script> | ||||
|   <script src="https://cdnjs.cloudflare.com/ajax/libs/decimal.js/9.0.0/decimal.min.js"></script> | ||||
|   {% block scripts %}{% endblock %} | ||||
|   <title>{% block title %}{% endblock %}</title> | ||||
| </head> | ||||
| <body> | ||||
|  | ||||
| <nav class="navbar navbar-expand-lg bg-body-tertiary bg-dark navbar-dark"> | ||||
|   <div class="container-fluid"> | ||||
|     <a class="navbar-brand" href="{{ url_for("home.home") }}"> | ||||
|       <i class="fa-solid fa-house"></i> | ||||
|       {{ _("Home") }} | ||||
|     </a> | ||||
|     <button class="navbar-toggler" type="button" data-bs-toggle="collapse" data-bs-target="#collapsible-navbar" aria-controls="collapsible-navbar" aria-expanded="false" aria-label="Toggle navigation"> | ||||
|       <span class="navbar-toggler-icon"></span> | ||||
|     </button> | ||||
|  | ||||
|     <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> | ||||
|  | ||||
|       <!-- The right side --> | ||||
|       <ul class="navbar-nav d-flex"> | ||||
|         {% if current_user() is not none %} | ||||
|           <li class="nav-item dropdown"> | ||||
|             <span class="nav-link dropdown-toggle" data-bs-toggle="dropdown"> | ||||
|               <i class="fa-solid fa-user"></i> | ||||
|               {{ current_user().username }} | ||||
|             </span> | ||||
|             <ul class="dropdown-menu dropdown-menu-end"> | ||||
|               <li> | ||||
|                 <form action="{{ url_for("auth.logout") }}" method="post"> | ||||
|                   <input type="hidden" name="csrf_token" value="{{ csrf_token() }}"> | ||||
|                   <button class="btn dropdown-item" type="submit"> | ||||
|                     <i class="fa-solid fa-right-from-bracket"></i> | ||||
|                     {{ _("Log Out") }} | ||||
|                   </button> | ||||
|                 </form> | ||||
|               </li> | ||||
|             </ul> | ||||
|           </li> | ||||
|         {% else %} | ||||
|           <li class="nav-item"> | ||||
|             <a class="nav-link" href="{{ url_for("auth.login") }}"> | ||||
|               <i class="fa-solid fa-right-to-bracket"></i> | ||||
|               {{ _("Log In") }} | ||||
|             </a> | ||||
|           </li> | ||||
|         {% endif %} | ||||
|         <li class="nav-item dropdown"> | ||||
|           <span class="nav-link dropdown-toggle" data-bs-toggle="dropdown"> | ||||
|             <i class="fa-solid fa-language"></i> | ||||
|           </span> | ||||
|           <form action="{{ url_for("locale.set-locale") }}" method="post"> | ||||
|             <input type="hidden" name="csrf_token" value="{{ csrf_token() }}"> | ||||
|             <input type="hidden" name="next" value="{{ request.full_path if request.query_string else request.path }}"> | ||||
|             <ul class="dropdown-menu dropdown-menu-end"> | ||||
|               {% for locale_code, locale_name in get_all_linguas().items() %} | ||||
|                 <li> | ||||
|                   <button class="dropdown-item {% if locale_code == get_locale() %} active {% endif %}" type="submit" name="locale" value="{{ locale_code }}"> | ||||
|                     {{ locale_name }} | ||||
|                   </button> | ||||
|                 </li> | ||||
|               {% endfor %} | ||||
|             </ul> | ||||
|           </form> | ||||
|         </li> | ||||
|       </ul> | ||||
|     </div> | ||||
|   </div> | ||||
| </nav> | ||||
|  | ||||
| <div class="container"> | ||||
|  | ||||
| <h1>{% block header %}{% endblock %}</h1> | ||||
|  | ||||
| {% with messages = get_flashed_messages(with_categories=true) %} | ||||
|   {% if messages %} | ||||
|     {% for category, message in messages %} | ||||
|       {% if category == "success" %} | ||||
|           <div class="alert alert-success alert-dismissible fade show" role="alert"> | ||||
|             {{ message }} | ||||
|             <button type="button" class="btn-close" data-bs-dismiss="alert" aria-label="Close"></button> | ||||
|           </div> | ||||
|       {% elif category == "error" %} | ||||
|         <div class="alert alert-danger alert-dismissible fade show" role="alert"> | ||||
|           <strong>{{ _("Error:") }}</strong> {{ message }} | ||||
|           <button type="button" class="btn-close" data-bs-dismiss="alert" aria-label="Close"></button> | ||||
|         </div> | ||||
|       {% endif %} | ||||
|     {% endfor %} | ||||
|   {% endif %} | ||||
| {% endwith %} | ||||
|  | ||||
| <main class="pb-5"> | ||||
|   {% block content %}{% endblock %} | ||||
| </main> | ||||
|  | ||||
| </div> | ||||
|  | ||||
| </body> | ||||
| </html> | ||||
							
								
								
									
										24
									
								
								tests/testsite/templates/home.html
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										24
									
								
								tests/testsite/templates/home.html
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,24 @@ | ||||
| {# | ||||
| The Mia! Accounting Flask Demonstration Website | ||||
| home.html: The home page. | ||||
|  | ||||
|  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. | ||||
|  | ||||
| Author: imacat@mail.imacat.idv.tw (imacat) | ||||
| First written: 2023/1/27 | ||||
| #} | ||||
| {% extends "base.html" %} | ||||
|  | ||||
| {% block header %}{% block title %}{{ _("Home") }}{% endblock %}{% endblock %} | ||||
							
								
								
									
										35
									
								
								tests/testsite/templates/login.html
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										35
									
								
								tests/testsite/templates/login.html
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,35 @@ | ||||
| {# | ||||
| The Mia! Accounting Flask Demonstration Website | ||||
| login.html: The login page. | ||||
|  | ||||
|  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. | ||||
|  | ||||
| Author: imacat@mail.imacat.idv.tw (imacat) | ||||
| First written: 2023/1/27 | ||||
| #} | ||||
| {% extends "base.html" %} | ||||
|  | ||||
| {% block header %}{% block title %}{{ _("Log In") }}{% endblock %}{% endblock %} | ||||
|  | ||||
| {% block content %} | ||||
|  | ||||
| <form action="{{ url_for("auth.login") }}" method="post"> | ||||
|   <input type="hidden" name="csrf_token" value="{{ csrf_token() }}"> | ||||
|   <button class="btn btn-primary" type="submit" name="username" value="viewer">{{ _("Viewer") }}</button> | ||||
|   <button class="btn btn-primary" type="submit" name="username" value="editor">{{ _("Editor") }}</button> | ||||
|   <button class="btn btn-primary" type="submit" name="username" value="nobody">{{ _("Nobody") }}</button> | ||||
| </form> | ||||
|  | ||||
| {% endblock %} | ||||
							
								
								
									
										3
									
								
								tests/testsite/translations/babel.cfg
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										3
									
								
								tests/testsite/translations/babel.cfg
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,3 @@ | ||||
| [python: **.py] | ||||
| [jinja2: **/templates/**.html] | ||||
| [javascript: **/static/js/**.js] | ||||
							
								
								
									
										54
									
								
								tests/testsite/translations/zh_Hant/LC_MESSAGES/messages.po
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										54
									
								
								tests/testsite/translations/zh_Hant/LC_MESSAGES/messages.po
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,54 @@ | ||||
| # Chinese (Traditional) translations for the Mia! Accounting Flask | ||||
| # Demonstration website. | ||||
| # Copyright (C) 2023 imacat | ||||
| # This file is distributed under the same license as the Mia! Accounting | ||||
| # Flask Demonstration project. | ||||
| # imacat <imacat@mail.imacat.idv.tw>, 2023. | ||||
| # | ||||
| msgid "" | ||||
| msgstr "" | ||||
| "Project-Id-Version: Mia! Accounting Flask Demonstration 0.0.0\n" | ||||
| "Report-Msgid-Bugs-To: imacat@mail.imacat.idv.tw\n" | ||||
| "POT-Creation-Date: 2023-01-28 13:42+0800\n" | ||||
| "PO-Revision-Date: 2023-01-28 13:42+0800\n" | ||||
| "Last-Translator: imacat <imacat@mail.imacat.idv.tw>\n" | ||||
| "Language: zh_Hant\n" | ||||
| "Language-Team: zh_Hant <imacat@mail.imacat.idv.tw>\n" | ||||
| "Plural-Forms: nplurals=1; plural=0;\n" | ||||
| "MIME-Version: 1.0\n" | ||||
| "Content-Type: text/plain; charset=utf-8\n" | ||||
| "Content-Transfer-Encoding: 8bit\n" | ||||
| "Generated-By: Babel 2.11.0\n" | ||||
|  | ||||
| #: tests/testsite/templates/base.html:23 | ||||
| msgid "en" | ||||
| msgstr "zh-Hant" | ||||
|  | ||||
| #: tests/testsite/templates/base.html:43 tests/testsite/templates/home.html:24 | ||||
| msgid "Home" | ||||
| msgstr "首頁" | ||||
|  | ||||
| #: tests/testsite/templates/base.html:68 | ||||
| msgid "Log Out" | ||||
| msgstr "" | ||||
|  | ||||
| #: tests/testsite/templates/base.html:78 tests/testsite/templates/login.html:24 | ||||
| msgid "Log In" | ||||
| msgstr "登入" | ||||
|  | ||||
| #: tests/testsite/templates/base.html:119 | ||||
| msgid "Error:" | ||||
| msgstr "錯誤:" | ||||
|  | ||||
| #: tests/testsite/templates/login.html:30 | ||||
| msgid "Viewer" | ||||
| msgstr "讀報表者" | ||||
|  | ||||
| #: tests/testsite/templates/login.html:31 | ||||
| msgid "Editor" | ||||
| msgstr "記帳者" | ||||
|  | ||||
| #: tests/testsite/templates/login.html:32 | ||||
| msgid "Nobody" | ||||
| msgstr "沒有權限者" | ||||
|  | ||||
		Reference in New Issue
	
	Block a user