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:
2023-01-29 22:28:27 +08:00
parent 9c83ad97c1
commit 14638f574e
45 changed files with 3302 additions and 0 deletions

View 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)

View 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)

View 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."""

View 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."""

View 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()

View 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)

View 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
View 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

View 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 %}

View 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 %}

View 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 %}

View 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 %}

View File

@ -0,0 +1,3 @@
[python: **.py]
[jinja2: **/templates/**.html]
[javascript: **/static/js/**.js]

View File

@ -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 "下一頁"

View 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.
"""

View 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)

View 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

View 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