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