Compare commits
No commits in common. "4be1ead6b53c6068733303f6cf0f5dd7dfc0b2ca" and "1e56403b357e0d758c5bb461e0f85efa0be5eeac" have entirely different histories.
4be1ead6b5
...
1e56403b35
22
README.rst
22
README.rst
@ -47,8 +47,7 @@ Prerequisites
|
|||||||
=============
|
=============
|
||||||
|
|
||||||
You need a running Flask application with database user login.
|
You need a running Flask application with database user login.
|
||||||
The primary key of the user data model must be integer. You also
|
The primary key of the user data model must be integer.
|
||||||
need at least one user.
|
|
||||||
|
|
||||||
The following front-end JavaScript libraries must be loaded. You may
|
The following front-end JavaScript libraries must be loaded. You may
|
||||||
download it locally or use CDN_.
|
download it locally or use CDN_.
|
||||||
@ -123,13 +122,24 @@ The following is an example configuration for *Mia! Accounting*.
|
|||||||
Database Initialization
|
Database Initialization
|
||||||
=======================
|
=======================
|
||||||
|
|
||||||
After the configuration, run the ``accounting-init-db`` console
|
After the configuration, you need to run
|
||||||
command to initialize the accounting database. You need to specify
|
`flask_sqlalchemy.SQLAlchemy.create_all`_ to create the
|
||||||
the username of a user as the data creator.
|
database tables that *Mia! Accounting* uses.
|
||||||
|
|
||||||
|
*Mia! Accounting* adds three console commands:
|
||||||
|
|
||||||
|
* ``accounting-init-base``
|
||||||
|
* ``accounting-init-accounts``
|
||||||
|
* ``accounting-init-currencies``
|
||||||
|
|
||||||
|
After database tables are created, run
|
||||||
|
``accounting-init-base`` first, and then the other two commands.
|
||||||
|
|
||||||
::
|
::
|
||||||
|
|
||||||
% flask --app myapp accounting-init-db -u username
|
% flask --app myapp accounting-init-base
|
||||||
|
% flask --app myapp accounting-init-accounts
|
||||||
|
% flask --app myapp accounting-init-currencies
|
||||||
|
|
||||||
|
|
||||||
Navigation Menu
|
Navigation Menu
|
||||||
|
@ -42,8 +42,7 @@ Prerequisites
|
|||||||
-------------
|
-------------
|
||||||
|
|
||||||
You need a running Flask application with database user login.
|
You need a running Flask application with database user login.
|
||||||
The primary key of the user data model must be integer. You also
|
The primary key of the user data model must be integer.
|
||||||
need at least one user.
|
|
||||||
|
|
||||||
The following front-end JavaScript libraries must be loaded. You may
|
The following front-end JavaScript libraries must be loaded. You may
|
||||||
download it locally or use CDN_.
|
download it locally or use CDN_.
|
||||||
@ -68,13 +67,24 @@ See an example in :ref:`example-userutils`.
|
|||||||
Database Initialization
|
Database Initialization
|
||||||
-----------------------
|
-----------------------
|
||||||
|
|
||||||
After the configuration, run the ``accounting-init-db`` console
|
After the configuration, you need to run
|
||||||
command to initialize the accounting database. You need to specify
|
`flask_sqlalchemy.SQLAlchemy.create_all`_ to create the
|
||||||
the username of a user as the data creator.
|
database tables that *Mia! Accounting* uses.
|
||||||
|
|
||||||
|
*Mia! Accounting* adds three console commands:
|
||||||
|
|
||||||
|
* ``accounting-init-base``
|
||||||
|
* ``accounting-init-accounts``
|
||||||
|
* ``accounting-init-currencies``
|
||||||
|
|
||||||
|
After database tables are created, run
|
||||||
|
``accounting-init-base`` first, and then the other two commands.
|
||||||
|
|
||||||
::
|
::
|
||||||
|
|
||||||
% flask --app myapp accounting-init-db -u username
|
% flask --app myapp accounting-init-base
|
||||||
|
% flask --app myapp accounting-init-accounts
|
||||||
|
% flask --app myapp accounting-init-currencies
|
||||||
|
|
||||||
|
|
||||||
Navigation Menu
|
Navigation Menu
|
||||||
|
@ -61,9 +61,6 @@ def init_app(app: Flask, user_utils: UserUtilityInterface,
|
|||||||
bp.add_app_template_global(default_currency_code,
|
bp.add_app_template_global(default_currency_code,
|
||||||
"accounting_default_currency_code")
|
"accounting_default_currency_code")
|
||||||
|
|
||||||
from .commands import init_db_command
|
|
||||||
app.cli.add_command(init_db_command)
|
|
||||||
|
|
||||||
from . import locale
|
from . import locale
|
||||||
locale.init_app(app, bp)
|
locale.init_app(app, bp)
|
||||||
|
|
||||||
|
@ -19,8 +19,6 @@
|
|||||||
"""
|
"""
|
||||||
from flask import Flask, Blueprint
|
from flask import Flask, Blueprint
|
||||||
|
|
||||||
from .commands import init_accounts_command
|
|
||||||
|
|
||||||
|
|
||||||
def init_app(app: Flask, bp: Blueprint) -> None:
|
def init_app(app: Flask, bp: Blueprint) -> None:
|
||||||
"""Initialize the application.
|
"""Initialize the application.
|
||||||
@ -34,3 +32,6 @@ def init_app(app: Flask, bp: Blueprint) -> None:
|
|||||||
|
|
||||||
from .views import bp as account_bp
|
from .views import bp as account_bp
|
||||||
bp.register_blueprint(account_bp, url_prefix="/accounts")
|
bp.register_blueprint(account_bp, url_prefix="/accounts")
|
||||||
|
|
||||||
|
from .commands import init_accounts_command
|
||||||
|
app.cli.add_command(init_accounts_command)
|
||||||
|
@ -17,21 +17,44 @@
|
|||||||
"""The console commands for the account management.
|
"""The console commands for the account management.
|
||||||
|
|
||||||
"""
|
"""
|
||||||
import typing as t
|
import os
|
||||||
from secrets import randbelow
|
from secrets import randbelow
|
||||||
|
|
||||||
import click
|
import click
|
||||||
|
from flask.cli import with_appcontext
|
||||||
|
|
||||||
from accounting import db
|
from accounting import db
|
||||||
from accounting.models import BaseAccount, Account, AccountL10n
|
from accounting.models import BaseAccount, Account, AccountL10n
|
||||||
from accounting.utils.user import get_user_pk
|
from accounting.utils.user import has_user, get_user_pk
|
||||||
import sqlalchemy as sa
|
|
||||||
|
|
||||||
AccountData = tuple[int, str, int, str, str, str, bool]
|
AccountData = tuple[int, str, int, str, str, str, bool]
|
||||||
"""The format of the account data, as a list of (ID, base account code, number,
|
"""The format of the account data, as a list of (ID, base account code, number,
|
||||||
English, Traditional Chinese, Simplified Chinese, is-need-offset) tuples."""
|
English, Traditional Chinese, Simplified Chinese, is-need-offset) tuples."""
|
||||||
|
|
||||||
|
|
||||||
|
def __validate_username(ctx: click.core.Context, param: click.core.Option,
|
||||||
|
value: str) -> str:
|
||||||
|
"""Validates the username for the click console command.
|
||||||
|
|
||||||
|
:param ctx: The console command context.
|
||||||
|
:param param: The console command option.
|
||||||
|
:param value: The username.
|
||||||
|
:raise click.BadParameter: When validation fails.
|
||||||
|
:return: The username.
|
||||||
|
"""
|
||||||
|
value = value.strip()
|
||||||
|
if value == "":
|
||||||
|
raise click.BadParameter("Username empty.")
|
||||||
|
if not has_user(value):
|
||||||
|
raise click.BadParameter(f"User {value} does not exist.")
|
||||||
|
return value
|
||||||
|
|
||||||
|
|
||||||
|
@click.command("accounting-init-accounts")
|
||||||
|
@click.option("-u", "--username", metavar="USERNAME", prompt=True,
|
||||||
|
help="The username.", callback=__validate_username,
|
||||||
|
default=lambda: os.getlogin())
|
||||||
|
@with_appcontext
|
||||||
def init_accounts_command(username: str) -> None:
|
def init_accounts_command(username: str) -> None:
|
||||||
"""Initializes the accounts."""
|
"""Initializes the accounts."""
|
||||||
creator_pk: int = get_user_pk(username)
|
creator_pk: int = get_user_pk(username)
|
||||||
@ -40,6 +63,8 @@ def init_accounts_command(username: str) -> None:
|
|||||||
.filter(db.func.length(BaseAccount.code) == 4)\
|
.filter(db.func.length(BaseAccount.code) == 4)\
|
||||||
.order_by(BaseAccount.code).all()
|
.order_by(BaseAccount.code).all()
|
||||||
if len(bases) == 0:
|
if len(bases) == 0:
|
||||||
|
click.echo("Please initialize the base accounts with "
|
||||||
|
"\"flask accounting-init-base\" first.")
|
||||||
raise click.Abort
|
raise click.Abort
|
||||||
|
|
||||||
existing: list[Account] = Account.query.all()
|
existing: list[Account] = Account.query.all()
|
||||||
@ -48,6 +73,7 @@ def init_accounts_command(username: str) -> None:
|
|||||||
bases_to_add: list[BaseAccount] = [x for x in bases
|
bases_to_add: list[BaseAccount] = [x for x in bases
|
||||||
if x.code not in existing_base_code]
|
if x.code not in existing_base_code]
|
||||||
if len(bases_to_add) == 0:
|
if len(bases_to_add) == 0:
|
||||||
|
click.echo("No more account to import.")
|
||||||
return
|
return
|
||||||
|
|
||||||
existing_id: set[int] = {x.id for x in existing}
|
existing_id: set[int] = {x.id for x in existing}
|
||||||
@ -63,24 +89,14 @@ def init_accounts_command(username: str) -> None:
|
|||||||
existing_id.add(new_id)
|
existing_id.add(new_id)
|
||||||
return new_id
|
return new_id
|
||||||
|
|
||||||
data: list[dict[str, t.Any]] = []
|
data: list[AccountData] = []
|
||||||
l10n_data: list[dict[str, t.Any]] = []
|
|
||||||
for base in bases_to_add:
|
for base in bases_to_add:
|
||||||
l10n: dict[str, str] = {x.locale: x.title for x in base.l10n}
|
l10n: dict[str, str] = {x.locale: x.title for x in base.l10n}
|
||||||
account_id: int = get_new_id()
|
is_need_offset: bool = __is_need_offset(base.code)
|
||||||
data.append({"id": account_id,
|
data.append((get_new_id(), base.code, 1, base.title_l10n,
|
||||||
"base_code": base.code,
|
l10n["zh_Hant"], l10n["zh_Hans"], is_need_offset))
|
||||||
"no": 1,
|
__add_accounting_accounts(data, creator_pk)
|
||||||
"title_l10n": base.title_l10n,
|
click.echo(F"{len(data)} added. Accounting accounts initialized.")
|
||||||
"is_need_offset": __is_need_offset(base.code),
|
|
||||||
"created_by_id": creator_pk,
|
|
||||||
"updated_by_id": creator_pk})
|
|
||||||
for locale in {"zh_Hant", "zh_Hans"}:
|
|
||||||
l10n_data.append({"account_id": account_id,
|
|
||||||
"locale": locale,
|
|
||||||
"title": l10n[locale]})
|
|
||||||
db.session.execute(sa.insert(Account), data)
|
|
||||||
db.session.execute(sa.insert(AccountL10n), l10n_data)
|
|
||||||
|
|
||||||
|
|
||||||
def __is_need_offset(base_code: str) -> bool:
|
def __is_need_offset(base_code: str) -> bool:
|
||||||
@ -105,3 +121,29 @@ def __is_need_offset(base_code: str) -> bool:
|
|||||||
return True
|
return True
|
||||||
# Only assets and liabilities need offset
|
# Only assets and liabilities need offset
|
||||||
return False
|
return False
|
||||||
|
|
||||||
|
|
||||||
|
def __add_accounting_accounts(data: list[AccountData], creator_pk: int)\
|
||||||
|
-> None:
|
||||||
|
"""Adds the accounts.
|
||||||
|
|
||||||
|
:param data: A list of (base code, number, title) tuples.
|
||||||
|
:param creator_pk: The primary key of the creator.
|
||||||
|
:return: None.
|
||||||
|
"""
|
||||||
|
accounts: list[Account] = [Account(id=x[0],
|
||||||
|
base_code=x[1],
|
||||||
|
no=x[2],
|
||||||
|
title_l10n=x[3],
|
||||||
|
is_need_offset=x[6],
|
||||||
|
created_by_id=creator_pk,
|
||||||
|
updated_by_id=creator_pk)
|
||||||
|
for x in data]
|
||||||
|
l10n: list[AccountL10n] = [AccountL10n(account_id=x[0],
|
||||||
|
locale=y[0],
|
||||||
|
title=y[1])
|
||||||
|
for x in data
|
||||||
|
for y in (("zh_Hant", x[4]), ("zh_Hans", x[5]))]
|
||||||
|
db.session.bulk_save_objects(accounts)
|
||||||
|
db.session.bulk_save_objects(l10n)
|
||||||
|
db.session.commit()
|
||||||
|
@ -19,8 +19,6 @@
|
|||||||
"""
|
"""
|
||||||
from flask import Flask, Blueprint
|
from flask import Flask, Blueprint
|
||||||
|
|
||||||
from .commands import init_base_accounts_command
|
|
||||||
|
|
||||||
|
|
||||||
def init_app(app: Flask, bp: Blueprint) -> None:
|
def init_app(app: Flask, bp: Blueprint) -> None:
|
||||||
"""Initialize the application.
|
"""Initialize the application.
|
||||||
@ -34,3 +32,6 @@ def init_app(app: Flask, bp: Blueprint) -> None:
|
|||||||
|
|
||||||
from .views import bp as base_account_bp
|
from .views import bp as base_account_bp
|
||||||
bp.register_blueprint(base_account_bp, url_prefix="/base-accounts")
|
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)
|
||||||
|
@ -19,17 +19,21 @@
|
|||||||
"""
|
"""
|
||||||
import csv
|
import csv
|
||||||
|
|
||||||
import sqlalchemy as sa
|
import click
|
||||||
|
from flask.cli import with_appcontext
|
||||||
|
|
||||||
from accounting import data_dir
|
from accounting import data_dir
|
||||||
from accounting import db
|
from accounting import db
|
||||||
from accounting.models import BaseAccount, BaseAccountL10n
|
from accounting.models import BaseAccount, BaseAccountL10n
|
||||||
|
|
||||||
|
|
||||||
|
@click.command("accounting-init-base")
|
||||||
|
@with_appcontext
|
||||||
def init_base_accounts_command() -> None:
|
def init_base_accounts_command() -> None:
|
||||||
"""Initializes the base accounts."""
|
"""Initializes the base accounts."""
|
||||||
if BaseAccount.query.first() is not None:
|
if BaseAccount.query.first() is not None:
|
||||||
return
|
click.echo("Base accounts already exist.")
|
||||||
|
raise click.Abort
|
||||||
|
|
||||||
with open(data_dir / "base_accounts.csv") as fp:
|
with open(data_dir / "base_accounts.csv") as fp:
|
||||||
data: list[dict[str, str]] = [x for x in csv.DictReader(fp)]
|
data: list[dict[str, str]] = [x for x in csv.DictReader(fp)]
|
||||||
@ -41,5 +45,7 @@ def init_base_accounts_command() -> None:
|
|||||||
"locale": y,
|
"locale": y,
|
||||||
"title": x[f"l10n-{y}"]}
|
"title": x[f"l10n-{y}"]}
|
||||||
for x in data for y in locales]
|
for x in data for y in locales]
|
||||||
db.session.execute(sa.insert(BaseAccount), account_data)
|
db.session.bulk_insert_mappings(BaseAccount, account_data)
|
||||||
db.session.execute(sa.insert(BaseAccountL10n), l10n_data)
|
db.session.bulk_insert_mappings(BaseAccountL10n, l10n_data)
|
||||||
|
db.session.commit()
|
||||||
|
click.echo("Base accounts initialized.")
|
||||||
|
@ -1,62 +0,0 @@
|
|||||||
# The Mia! Accounting Project.
|
|
||||||
# Author: imacat@mail.imacat.idv.tw (imacat), 2023/4/10
|
|
||||||
|
|
||||||
# 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.
|
|
||||||
|
|
||||||
"""
|
|
||||||
import os
|
|
||||||
|
|
||||||
import click
|
|
||||||
from flask.cli import with_appcontext
|
|
||||||
|
|
||||||
from accounting import db
|
|
||||||
from accounting.account import init_accounts_command
|
|
||||||
from accounting.base_account import init_base_accounts_command
|
|
||||||
from accounting.currency import init_currencies_command
|
|
||||||
from accounting.utils.user import has_user
|
|
||||||
|
|
||||||
|
|
||||||
def __validate_username(ctx: click.core.Context, param: click.core.Option,
|
|
||||||
value: str) -> str:
|
|
||||||
"""Validates the username for the click console command.
|
|
||||||
|
|
||||||
:param ctx: The console command context.
|
|
||||||
:param param: The console command option.
|
|
||||||
:param value: The username.
|
|
||||||
:raise click.BadParameter: When validation fails.
|
|
||||||
:return: The username.
|
|
||||||
"""
|
|
||||||
value = value.strip()
|
|
||||||
if value == "":
|
|
||||||
raise click.BadParameter("Username empty.")
|
|
||||||
if not has_user(value):
|
|
||||||
raise click.BadParameter(f"User {value} does not exist.")
|
|
||||||
return value
|
|
||||||
|
|
||||||
|
|
||||||
@click.command("accounting-init-db")
|
|
||||||
@click.option("-u", "--username", metavar="USERNAME", prompt=True,
|
|
||||||
help="The username.", callback=__validate_username,
|
|
||||||
default=lambda: os.getlogin())
|
|
||||||
@with_appcontext
|
|
||||||
def init_db_command(username: str) -> None:
|
|
||||||
"""Initializes the accounting database."""
|
|
||||||
db.create_all()
|
|
||||||
init_base_accounts_command()
|
|
||||||
init_accounts_command(username)
|
|
||||||
init_currencies_command(username)
|
|
||||||
db.session.commit()
|
|
||||||
click.echo("Accounting database initialized.")
|
|
@ -19,8 +19,6 @@
|
|||||||
"""
|
"""
|
||||||
from flask import Flask, Blueprint
|
from flask import Flask, Blueprint
|
||||||
|
|
||||||
from .commands import init_currencies_command
|
|
||||||
|
|
||||||
|
|
||||||
def init_app(app: Flask, bp: Blueprint) -> None:
|
def init_app(app: Flask, bp: Blueprint) -> None:
|
||||||
"""Initialize the application.
|
"""Initialize the application.
|
||||||
@ -35,3 +33,6 @@ def init_app(app: Flask, bp: Blueprint) -> None:
|
|||||||
from .views import bp as currency_bp, api_bp as currency_api_bp
|
from .views import bp as currency_bp, api_bp as currency_api_bp
|
||||||
bp.register_blueprint(currency_bp, url_prefix="/currencies")
|
bp.register_blueprint(currency_bp, url_prefix="/currencies")
|
||||||
bp.register_blueprint(currency_api_bp, url_prefix="/api/currencies")
|
bp.register_blueprint(currency_api_bp, url_prefix="/api/currencies")
|
||||||
|
|
||||||
|
from .commands import init_currencies_command
|
||||||
|
app.cli.add_command(init_currencies_command)
|
||||||
|
@ -18,15 +18,42 @@
|
|||||||
|
|
||||||
"""
|
"""
|
||||||
import csv
|
import csv
|
||||||
|
import os
|
||||||
import typing as t
|
import typing as t
|
||||||
|
|
||||||
import sqlalchemy as sa
|
import click
|
||||||
|
from flask.cli import with_appcontext
|
||||||
|
|
||||||
from accounting import db, data_dir
|
from accounting import db, data_dir
|
||||||
from accounting.models import Currency, CurrencyL10n
|
from accounting.models import Currency, CurrencyL10n
|
||||||
from accounting.utils.user import get_user_pk
|
from accounting.utils.user import has_user, get_user_pk
|
||||||
|
|
||||||
|
CurrencyData = tuple[str, str, str, str]
|
||||||
|
|
||||||
|
|
||||||
|
def __validate_username(ctx: click.core.Context, param: click.core.Option,
|
||||||
|
value: str) -> str:
|
||||||
|
"""Validates the username for the click console command.
|
||||||
|
|
||||||
|
:param ctx: The console command context.
|
||||||
|
:param param: The console command option.
|
||||||
|
:param value: The username.
|
||||||
|
:raise click.BadParameter: When validation fails.
|
||||||
|
:return: The username.
|
||||||
|
"""
|
||||||
|
value = value.strip()
|
||||||
|
if value == "":
|
||||||
|
raise click.BadParameter("Username empty.")
|
||||||
|
if not has_user(value):
|
||||||
|
raise click.BadParameter(f"User {value} does not exist.")
|
||||||
|
return value
|
||||||
|
|
||||||
|
|
||||||
|
@click.command("accounting-init-currencies")
|
||||||
|
@click.option("-u", "--username", metavar="USERNAME", prompt=True,
|
||||||
|
help="The username.", callback=__validate_username,
|
||||||
|
default=lambda: os.getlogin())
|
||||||
|
@with_appcontext
|
||||||
def init_currencies_command(username: str) -> None:
|
def init_currencies_command(username: str) -> None:
|
||||||
"""Initializes the currencies."""
|
"""Initializes the currencies."""
|
||||||
existing_codes: set[str] = {x.code for x in Currency.query.all()}
|
existing_codes: set[str] = {x.code for x in Currency.query.all()}
|
||||||
@ -36,6 +63,7 @@ def init_currencies_command(username: str) -> None:
|
|||||||
to_add: list[dict[str, str]] = [x for x in data
|
to_add: list[dict[str, str]] = [x for x in data
|
||||||
if x["code"] not in existing_codes]
|
if x["code"] not in existing_codes]
|
||||||
if len(to_add) == 0:
|
if len(to_add) == 0:
|
||||||
|
click.echo("No more currency to add.")
|
||||||
return
|
return
|
||||||
|
|
||||||
creator_pk: int = get_user_pk(username)
|
creator_pk: int = get_user_pk(username)
|
||||||
@ -49,5 +77,8 @@ def init_currencies_command(username: str) -> None:
|
|||||||
"locale": y,
|
"locale": y,
|
||||||
"name": x[f"l10n-{y}"]}
|
"name": x[f"l10n-{y}"]}
|
||||||
for x in to_add for y in locales]
|
for x in to_add for y in locales]
|
||||||
db.session.execute(sa.insert(Currency), currency_data)
|
db.session.bulk_insert_mappings(Currency, currency_data)
|
||||||
db.session.execute(sa.insert(CurrencyL10n), l10n_data)
|
db.session.bulk_insert_mappings(CurrencyL10n, l10n_data)
|
||||||
|
db.session.commit()
|
||||||
|
|
||||||
|
click.echo(F"{len(to_add)} added. Currencies initialized.")
|
||||||
|
@ -21,7 +21,10 @@ import unittest
|
|||||||
from datetime import timedelta, date
|
from datetime import timedelta, date
|
||||||
|
|
||||||
import httpx
|
import httpx
|
||||||
|
import sqlalchemy as sa
|
||||||
|
from click.testing import Result
|
||||||
from flask import Flask
|
from flask import Flask
|
||||||
|
from flask.testing import FlaskCliRunner
|
||||||
|
|
||||||
from test_site import db
|
from test_site import db
|
||||||
from testlib import NEXT_URI, create_test_app, get_client, set_locale, \
|
from testlib import NEXT_URI, create_test_app, get_client, set_locale, \
|
||||||
@ -62,6 +65,59 @@ PREFIX: str = "/accounting/accounts"
|
|||||||
"""The URL prefix for the account management."""
|
"""The URL prefix for the account management."""
|
||||||
|
|
||||||
|
|
||||||
|
class AccountCommandTestCase(unittest.TestCase):
|
||||||
|
"""The account console command test case."""
|
||||||
|
|
||||||
|
def setUp(self) -> None:
|
||||||
|
"""Sets up the test.
|
||||||
|
This is run once per test.
|
||||||
|
|
||||||
|
:return: None.
|
||||||
|
"""
|
||||||
|
self.app: Flask = create_test_app()
|
||||||
|
|
||||||
|
runner: FlaskCliRunner = self.app.test_cli_runner()
|
||||||
|
with self.app.app_context():
|
||||||
|
from accounting.models import BaseAccount, Account, AccountL10n
|
||||||
|
result: Result
|
||||||
|
result = runner.invoke(args="init-db")
|
||||||
|
self.assertEqual(result.exit_code, 0)
|
||||||
|
if BaseAccount.query.first() is None:
|
||||||
|
result = runner.invoke(args="accounting-init-base")
|
||||||
|
self.assertEqual(result.exit_code, 0)
|
||||||
|
AccountL10n.query.delete()
|
||||||
|
Account.query.delete()
|
||||||
|
db.session.commit()
|
||||||
|
|
||||||
|
def test_init(self) -> None:
|
||||||
|
"""Tests the "accounting-init-account" console command.
|
||||||
|
|
||||||
|
:return: None.
|
||||||
|
"""
|
||||||
|
from accounting.models import BaseAccount, Account, AccountL10n
|
||||||
|
runner: FlaskCliRunner = self.app.test_cli_runner()
|
||||||
|
with self.app.app_context():
|
||||||
|
result: Result = runner.invoke(args=["accounting-init-accounts",
|
||||||
|
"-u", "editor"])
|
||||||
|
self.assertEqual(result.exit_code, 0)
|
||||||
|
with self.app.app_context():
|
||||||
|
bases: list[BaseAccount] = BaseAccount.query\
|
||||||
|
.filter(sa.func.char_length(BaseAccount.code) == 4).all()
|
||||||
|
accounts: list[Account] = Account.query.all()
|
||||||
|
l10n: list[AccountL10n] = AccountL10n.query.all()
|
||||||
|
self.assertEqual({x.code for x in bases},
|
||||||
|
{x.base_code for x in accounts})
|
||||||
|
self.assertEqual(len(accounts), len(bases))
|
||||||
|
self.assertEqual(len(l10n), len(bases) * 2)
|
||||||
|
base_dict: dict[str, BaseAccount] = {x.code: x for x in bases}
|
||||||
|
for account in accounts:
|
||||||
|
base: BaseAccount = base_dict[account.base_code]
|
||||||
|
self.assertEqual(account.no, 1)
|
||||||
|
self.assertEqual(account.title_l10n, base.title_l10n)
|
||||||
|
self.assertEqual({x.locale: x.title for x in account.l10n},
|
||||||
|
{x.locale: x.title for x in base.l10n})
|
||||||
|
|
||||||
|
|
||||||
class AccountTestCase(unittest.TestCase):
|
class AccountTestCase(unittest.TestCase):
|
||||||
"""The account test case."""
|
"""The account test case."""
|
||||||
|
|
||||||
@ -73,8 +129,15 @@ class AccountTestCase(unittest.TestCase):
|
|||||||
"""
|
"""
|
||||||
self.app: Flask = create_test_app()
|
self.app: Flask = create_test_app()
|
||||||
|
|
||||||
|
runner: FlaskCliRunner = self.app.test_cli_runner()
|
||||||
with self.app.app_context():
|
with self.app.app_context():
|
||||||
from accounting.models import Account, AccountL10n
|
from accounting.models import BaseAccount, Account, AccountL10n
|
||||||
|
result: Result
|
||||||
|
result = runner.invoke(args="init-db")
|
||||||
|
self.assertEqual(result.exit_code, 0)
|
||||||
|
if BaseAccount.query.first() is None:
|
||||||
|
result = runner.invoke(args="accounting-init-base")
|
||||||
|
self.assertEqual(result.exit_code, 0)
|
||||||
AccountL10n.query.delete()
|
AccountL10n.query.delete()
|
||||||
Account.query.delete()
|
Account.query.delete()
|
||||||
db.session.commit()
|
db.session.commit()
|
||||||
@ -589,6 +652,14 @@ class AccountTestCase(unittest.TestCase):
|
|||||||
self.assertEqual(response.status_code, 302)
|
self.assertEqual(response.status_code, 302)
|
||||||
self.assertEqual(response.headers["Location"], detail_uri)
|
self.assertEqual(response.headers["Location"], detail_uri)
|
||||||
|
|
||||||
|
response = self.client.post("/accounting/currencies/store",
|
||||||
|
data={"csrf_token": self.csrf_token,
|
||||||
|
"code": "USD",
|
||||||
|
"name": "US Dollars"})
|
||||||
|
self.assertEqual(response.status_code, 302)
|
||||||
|
self.assertEqual(response.headers["Location"],
|
||||||
|
"/accounting/currencies/USD")
|
||||||
|
|
||||||
add_journal_entry(self.client,
|
add_journal_entry(self.client,
|
||||||
form={"csrf_token": self.csrf_token,
|
form={"csrf_token": self.csrf_token,
|
||||||
"next": NEXT_URI,
|
"next": NEXT_URI,
|
||||||
|
@ -17,10 +17,14 @@
|
|||||||
"""The test for the base account management.
|
"""The test for the base account management.
|
||||||
|
|
||||||
"""
|
"""
|
||||||
|
import csv
|
||||||
|
import typing as t
|
||||||
import unittest
|
import unittest
|
||||||
|
|
||||||
import httpx
|
import httpx
|
||||||
|
from click.testing import Result
|
||||||
from flask import Flask
|
from flask import Flask
|
||||||
|
from flask.testing import FlaskCliRunner
|
||||||
|
|
||||||
from testlib import create_test_app, get_client
|
from testlib import create_test_app, get_client
|
||||||
|
|
||||||
@ -30,6 +34,59 @@ DETAIL_URI: str = "/accounting/base-accounts/1111"
|
|||||||
"""The detail URI."""
|
"""The detail URI."""
|
||||||
|
|
||||||
|
|
||||||
|
class BaseAccountCommandTestCase(unittest.TestCase):
|
||||||
|
"""The base account console command test case."""
|
||||||
|
|
||||||
|
def setUp(self) -> None:
|
||||||
|
"""Sets up the test.
|
||||||
|
This is run once per test.
|
||||||
|
|
||||||
|
:return: None.
|
||||||
|
"""
|
||||||
|
from accounting.models import BaseAccount, BaseAccountL10n
|
||||||
|
self.app: Flask = create_test_app()
|
||||||
|
|
||||||
|
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)
|
||||||
|
BaseAccountL10n.query.delete()
|
||||||
|
BaseAccount.query.delete()
|
||||||
|
|
||||||
|
def test_init(self) -> None:
|
||||||
|
"""Tests the "accounting-init-base" console command.
|
||||||
|
|
||||||
|
:return: None.
|
||||||
|
"""
|
||||||
|
from accounting import data_dir
|
||||||
|
from accounting.models import BaseAccount
|
||||||
|
|
||||||
|
with open(data_dir / "base_accounts.csv") as fp:
|
||||||
|
data: dict[dict[str, t.Any]] \
|
||||||
|
= {x["code"]: {"code": x["code"],
|
||||||
|
"title": x["title"],
|
||||||
|
"l10n": {y[5:]: x[y]
|
||||||
|
for y in x if y.startswith("l10n-")}}
|
||||||
|
for x in csv.DictReader(fp)}
|
||||||
|
|
||||||
|
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()
|
||||||
|
|
||||||
|
self.assertEqual(len(accounts), len(data))
|
||||||
|
for account in accounts:
|
||||||
|
self.assertIn(account.code, data)
|
||||||
|
self.assertEqual(account.title_l10n, data[account.code]["title"])
|
||||||
|
l10n: dict[str, str] = {x.locale: x.title for x in account.l10n}
|
||||||
|
self.assertEqual(len(l10n), len(data[account.code]["l10n"]))
|
||||||
|
for locale in l10n:
|
||||||
|
self.assertIn(locale, data[account.code]["l10n"])
|
||||||
|
self.assertEqual(l10n[locale],
|
||||||
|
data[account.code]["l10n"][locale])
|
||||||
|
|
||||||
|
|
||||||
class BaseAccountTestCase(unittest.TestCase):
|
class BaseAccountTestCase(unittest.TestCase):
|
||||||
"""The base account test case."""
|
"""The base account test case."""
|
||||||
|
|
||||||
@ -39,8 +96,17 @@ class BaseAccountTestCase(unittest.TestCase):
|
|||||||
|
|
||||||
:return: None.
|
:return: None.
|
||||||
"""
|
"""
|
||||||
|
from accounting.models import BaseAccount
|
||||||
self.app: Flask = create_test_app()
|
self.app: Flask = create_test_app()
|
||||||
|
|
||||||
|
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)
|
||||||
|
if BaseAccount.query.first() is None:
|
||||||
|
result = runner.invoke(args="accounting-init-base")
|
||||||
|
self.assertEqual(result.exit_code, 0)
|
||||||
|
|
||||||
def test_nobody(self) -> None:
|
def test_nobody(self) -> None:
|
||||||
"""Test the permission as nobody.
|
"""Test the permission as nobody.
|
||||||
|
|
||||||
|
@ -1,157 +0,0 @@
|
|||||||
# The Mia! Accounting Project.
|
|
||||||
# Author: imacat@mail.imacat.idv.tw (imacat), 2023/4/10
|
|
||||||
|
|
||||||
# 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 console commands.
|
|
||||||
|
|
||||||
"""
|
|
||||||
import csv
|
|
||||||
import typing as t
|
|
||||||
import unittest
|
|
||||||
|
|
||||||
import sqlalchemy as sa
|
|
||||||
from click.testing import Result
|
|
||||||
from flask import Flask
|
|
||||||
from flask.testing import FlaskCliRunner
|
|
||||||
from sqlalchemy.sql.ddl import DropTable
|
|
||||||
|
|
||||||
from test_site import db
|
|
||||||
from testlib import create_test_app
|
|
||||||
|
|
||||||
|
|
||||||
class ConsoleCommandTestCase(unittest.TestCase):
|
|
||||||
"""The console command test case."""
|
|
||||||
|
|
||||||
def setUp(self) -> None:
|
|
||||||
"""Sets up the test.
|
|
||||||
This is run once per test.
|
|
||||||
|
|
||||||
:return: None.
|
|
||||||
"""
|
|
||||||
self.app: Flask = create_test_app()
|
|
||||||
|
|
||||||
with self.app.app_context():
|
|
||||||
# Drop every accounting table, to see if accounting-init recreates
|
|
||||||
# them correctly.
|
|
||||||
tables: list[sa.Table] \
|
|
||||||
= [db.metadata.tables[x] for x in db.metadata.tables
|
|
||||||
if x.startswith("accounting_")]
|
|
||||||
for table in tables:
|
|
||||||
db.session.execute(DropTable(table))
|
|
||||||
db.session.commit()
|
|
||||||
inspector: sa.Inspector = sa.inspect(db.session.connection())
|
|
||||||
self.assertEqual(len({x for x in inspector.get_table_names()
|
|
||||||
if x.startswith("accounting_")}),
|
|
||||||
0)
|
|
||||||
|
|
||||||
def test_init(self) -> None:
|
|
||||||
"""Tests the "accounting-init" console command.
|
|
||||||
|
|
||||||
:return: None.
|
|
||||||
"""
|
|
||||||
runner: FlaskCliRunner = self.app.test_cli_runner()
|
|
||||||
with self.app.app_context():
|
|
||||||
result: Result = runner.invoke(
|
|
||||||
args=["accounting-init-db", "-u", "editor"])
|
|
||||||
self.assertEqual(result.exit_code, 0,
|
|
||||||
result.output + str(result.exception))
|
|
||||||
self.__test_base_account_data()
|
|
||||||
self.__test_account_data()
|
|
||||||
self.__test_currency_data()
|
|
||||||
|
|
||||||
def __test_base_account_data(self) -> None:
|
|
||||||
"""Tests the base account data.
|
|
||||||
|
|
||||||
:return: None.
|
|
||||||
"""
|
|
||||||
from accounting import data_dir
|
|
||||||
from accounting.models import BaseAccount
|
|
||||||
|
|
||||||
with open(data_dir / "base_accounts.csv") as fp:
|
|
||||||
data: dict[dict[str, t.Any]] \
|
|
||||||
= {x["code"]: {"code": x["code"],
|
|
||||||
"title": x["title"],
|
|
||||||
"l10n": {y[5:]: x[y]
|
|
||||||
for y in x if y.startswith("l10n-")}}
|
|
||||||
for x in csv.DictReader(fp)}
|
|
||||||
|
|
||||||
with self.app.app_context():
|
|
||||||
accounts: list[BaseAccount] = BaseAccount.query.all()
|
|
||||||
|
|
||||||
self.assertEqual(len(accounts), len(data))
|
|
||||||
for account in accounts:
|
|
||||||
self.assertIn(account.code, data)
|
|
||||||
self.assertEqual(account.title_l10n, data[account.code]["title"])
|
|
||||||
l10n: dict[str, str] = {x.locale: x.title for x in account.l10n}
|
|
||||||
self.assertEqual(len(l10n), len(data[account.code]["l10n"]))
|
|
||||||
for locale in l10n:
|
|
||||||
self.assertIn(locale, data[account.code]["l10n"])
|
|
||||||
self.assertEqual(l10n[locale],
|
|
||||||
data[account.code]["l10n"][locale])
|
|
||||||
|
|
||||||
def __test_account_data(self) -> None:
|
|
||||||
"""Tests the account data.
|
|
||||||
|
|
||||||
:return: None.
|
|
||||||
"""
|
|
||||||
from accounting.models import BaseAccount, Account, AccountL10n
|
|
||||||
|
|
||||||
with self.app.app_context():
|
|
||||||
bases: list[BaseAccount] = BaseAccount.query\
|
|
||||||
.filter(sa.func.char_length(BaseAccount.code) == 4).all()
|
|
||||||
accounts: list[Account] = Account.query.all()
|
|
||||||
l10n: list[AccountL10n] = AccountL10n.query.all()
|
|
||||||
|
|
||||||
self.assertEqual({x.code for x in bases},
|
|
||||||
{x.base_code for x in accounts})
|
|
||||||
self.assertEqual(len(accounts), len(bases))
|
|
||||||
self.assertEqual(len(l10n), len(bases) * 2)
|
|
||||||
base_dict: dict[str, BaseAccount] = {x.code: x for x in bases}
|
|
||||||
for account in accounts:
|
|
||||||
base: BaseAccount = base_dict[account.base_code]
|
|
||||||
self.assertEqual(account.no, 1)
|
|
||||||
self.assertEqual(account.title_l10n, base.title_l10n)
|
|
||||||
self.assertEqual({x.locale: x.title for x in account.l10n},
|
|
||||||
{x.locale: x.title for x in base.l10n})
|
|
||||||
|
|
||||||
def __test_currency_data(self) -> None:
|
|
||||||
"""Tests the currency data.
|
|
||||||
|
|
||||||
:return: None.
|
|
||||||
"""
|
|
||||||
from accounting import data_dir
|
|
||||||
from accounting.models import Currency
|
|
||||||
|
|
||||||
with open(data_dir / "currencies.csv") as fp:
|
|
||||||
data: dict[dict[str, t.Any]] \
|
|
||||||
= {x["code"]: {"code": x["code"],
|
|
||||||
"name": x["name"],
|
|
||||||
"l10n": {y[5:]: x[y]
|
|
||||||
for y in x if y.startswith("l10n-")}}
|
|
||||||
for x in csv.DictReader(fp)}
|
|
||||||
|
|
||||||
with self.app.app_context():
|
|
||||||
currencies: list[Currency] = Currency.query.all()
|
|
||||||
|
|
||||||
self.assertEqual(len(currencies), len(data))
|
|
||||||
for currency in currencies:
|
|
||||||
self.assertIn(currency.code, data)
|
|
||||||
self.assertEqual(currency.name_l10n, data[currency.code]["name"])
|
|
||||||
l10n: dict[str, str] = {x.locale: x.name for x in currency.l10n}
|
|
||||||
self.assertEqual(len(l10n), len(data[currency.code]["l10n"]))
|
|
||||||
for locale in l10n:
|
|
||||||
self.assertIn(locale, data[currency.code]["l10n"])
|
|
||||||
self.assertEqual(l10n[locale],
|
|
||||||
data[currency.code]["l10n"][locale])
|
|
@ -17,11 +17,15 @@
|
|||||||
"""The test for the currency management.
|
"""The test for the currency management.
|
||||||
|
|
||||||
"""
|
"""
|
||||||
|
import csv
|
||||||
|
import typing as t
|
||||||
import unittest
|
import unittest
|
||||||
from datetime import timedelta, date
|
from datetime import timedelta, date
|
||||||
|
|
||||||
import httpx
|
import httpx
|
||||||
|
from click.testing import Result
|
||||||
from flask import Flask
|
from flask import Flask
|
||||||
|
from flask.testing import FlaskCliRunner
|
||||||
|
|
||||||
from test_site import db
|
from test_site import db
|
||||||
from testlib import NEXT_URI, create_test_app, get_client, set_locale, \
|
from testlib import NEXT_URI, create_test_app, get_client, set_locale, \
|
||||||
@ -55,6 +59,62 @@ PREFIX: str = "/accounting/currencies"
|
|||||||
"""The URL prefix for the currency management."""
|
"""The URL prefix for the currency management."""
|
||||||
|
|
||||||
|
|
||||||
|
class CurrencyCommandTestCase(unittest.TestCase):
|
||||||
|
"""The account console command test case."""
|
||||||
|
|
||||||
|
def setUp(self) -> None:
|
||||||
|
"""Sets up the test.
|
||||||
|
This is run once per test.
|
||||||
|
|
||||||
|
:return: None.
|
||||||
|
"""
|
||||||
|
self.app: Flask = create_test_app()
|
||||||
|
|
||||||
|
runner: FlaskCliRunner = self.app.test_cli_runner()
|
||||||
|
with self.app.app_context():
|
||||||
|
from accounting.models import Currency, CurrencyL10n
|
||||||
|
result: Result
|
||||||
|
result = runner.invoke(args="init-db")
|
||||||
|
self.assertEqual(result.exit_code, 0)
|
||||||
|
CurrencyL10n.query.delete()
|
||||||
|
Currency.query.delete()
|
||||||
|
db.session.commit()
|
||||||
|
|
||||||
|
def test_init(self) -> None:
|
||||||
|
"""Tests the "accounting-init-currencies" console command.
|
||||||
|
|
||||||
|
:return: None.
|
||||||
|
"""
|
||||||
|
from accounting import data_dir
|
||||||
|
from accounting.models import Currency
|
||||||
|
|
||||||
|
with open(data_dir / "currencies.csv") as fp:
|
||||||
|
data: dict[dict[str, t.Any]] \
|
||||||
|
= {x["code"]: {"code": x["code"],
|
||||||
|
"name": x["name"],
|
||||||
|
"l10n": {y[5:]: x[y]
|
||||||
|
for y in x if y.startswith("l10n-")}}
|
||||||
|
for x in csv.DictReader(fp)}
|
||||||
|
|
||||||
|
runner: FlaskCliRunner = self.app.test_cli_runner()
|
||||||
|
with self.app.app_context():
|
||||||
|
result: Result = runner.invoke(
|
||||||
|
args=["accounting-init-currencies", "-u", "editor"])
|
||||||
|
self.assertEqual(result.exit_code, 0)
|
||||||
|
currencies: list[Currency] = Currency.query.all()
|
||||||
|
|
||||||
|
self.assertEqual(len(currencies), len(data))
|
||||||
|
for currency in currencies:
|
||||||
|
self.assertIn(currency.code, data)
|
||||||
|
self.assertEqual(currency.name_l10n, data[currency.code]["name"])
|
||||||
|
l10n: dict[str, str] = {x.locale: x.name for x in currency.l10n}
|
||||||
|
self.assertEqual(len(l10n), len(data[currency.code]["l10n"]))
|
||||||
|
for locale in l10n:
|
||||||
|
self.assertIn(locale, data[currency.code]["l10n"])
|
||||||
|
self.assertEqual(l10n[locale],
|
||||||
|
data[currency.code]["l10n"][locale])
|
||||||
|
|
||||||
|
|
||||||
class CurrencyTestCase(unittest.TestCase):
|
class CurrencyTestCase(unittest.TestCase):
|
||||||
"""The currency test case."""
|
"""The currency test case."""
|
||||||
|
|
||||||
@ -66,8 +126,12 @@ class CurrencyTestCase(unittest.TestCase):
|
|||||||
"""
|
"""
|
||||||
self.app: Flask = create_test_app()
|
self.app: Flask = create_test_app()
|
||||||
|
|
||||||
|
runner: FlaskCliRunner = self.app.test_cli_runner()
|
||||||
with self.app.app_context():
|
with self.app.app_context():
|
||||||
from accounting.models import Currency, CurrencyL10n
|
from accounting.models import Currency, CurrencyL10n
|
||||||
|
result: Result
|
||||||
|
result = runner.invoke(args="init-db")
|
||||||
|
self.assertEqual(result.exit_code, 0)
|
||||||
CurrencyL10n.query.delete()
|
CurrencyL10n.query.delete()
|
||||||
Currency.query.delete()
|
Currency.query.delete()
|
||||||
db.session.commit()
|
db.session.commit()
|
||||||
@ -524,6 +588,21 @@ class CurrencyTestCase(unittest.TestCase):
|
|||||||
list_uri: str = PREFIX
|
list_uri: str = PREFIX
|
||||||
response: httpx.Response
|
response: httpx.Response
|
||||||
|
|
||||||
|
runner: FlaskCliRunner = self.app.test_cli_runner()
|
||||||
|
with self.app.app_context():
|
||||||
|
from accounting.models import BaseAccount
|
||||||
|
if BaseAccount.query.first() is None:
|
||||||
|
result = runner.invoke(args="accounting-init-base")
|
||||||
|
self.assertEqual(result.exit_code, 0)
|
||||||
|
|
||||||
|
response = self.client.post("/accounting/accounts/store",
|
||||||
|
data={"csrf_token": self.csrf_token,
|
||||||
|
"base_code": "1111",
|
||||||
|
"title": "Cash"})
|
||||||
|
self.assertEqual(response.status_code, 302)
|
||||||
|
self.assertEqual(response.headers["Location"],
|
||||||
|
"/accounting/accounts/1111-001")
|
||||||
|
|
||||||
response = self.client.post(f"{PREFIX}/store",
|
response = self.client.post(f"{PREFIX}/store",
|
||||||
data={"csrf_token": self.csrf_token,
|
data={"csrf_token": self.csrf_token,
|
||||||
"code": JPY.code,
|
"code": JPY.code,
|
||||||
|
@ -20,7 +20,9 @@
|
|||||||
import unittest
|
import unittest
|
||||||
from datetime import date
|
from datetime import date
|
||||||
|
|
||||||
|
from click.testing import Result
|
||||||
from flask import Flask
|
from flask import Flask
|
||||||
|
from flask.testing import FlaskCliRunner
|
||||||
|
|
||||||
from testlib import NEXT_URI, Accounts, create_test_app, get_client, \
|
from testlib import NEXT_URI, Accounts, create_test_app, get_client, \
|
||||||
add_journal_entry
|
add_journal_entry
|
||||||
@ -37,8 +39,22 @@ class DescriptionEditorTestCase(unittest.TestCase):
|
|||||||
"""
|
"""
|
||||||
self.app: Flask = create_test_app()
|
self.app: Flask = create_test_app()
|
||||||
|
|
||||||
|
runner: FlaskCliRunner = self.app.test_cli_runner()
|
||||||
with self.app.app_context():
|
with self.app.app_context():
|
||||||
from accounting.models import JournalEntry, JournalEntryLineItem
|
from accounting.models import BaseAccount, JournalEntry, \
|
||||||
|
JournalEntryLineItem
|
||||||
|
result: Result
|
||||||
|
result = runner.invoke(args="init-db")
|
||||||
|
self.assertEqual(result.exit_code, 0)
|
||||||
|
if BaseAccount.query.first() is None:
|
||||||
|
result = runner.invoke(args="accounting-init-base")
|
||||||
|
self.assertEqual(result.exit_code, 0)
|
||||||
|
result = runner.invoke(args=["accounting-init-currencies",
|
||||||
|
"-u", "editor"])
|
||||||
|
self.assertEqual(result.exit_code, 0)
|
||||||
|
result = runner.invoke(args=["accounting-init-accounts",
|
||||||
|
"-u", "editor"])
|
||||||
|
self.assertEqual(result.exit_code, 0)
|
||||||
JournalEntry.query.delete()
|
JournalEntry.query.delete()
|
||||||
JournalEntryLineItem.query.delete()
|
JournalEntryLineItem.query.delete()
|
||||||
|
|
||||||
|
@ -22,7 +22,9 @@ from datetime import date, timedelta
|
|||||||
from decimal import Decimal
|
from decimal import Decimal
|
||||||
|
|
||||||
import httpx
|
import httpx
|
||||||
|
from click.testing import Result
|
||||||
from flask import Flask
|
from flask import Flask
|
||||||
|
from flask.testing import FlaskCliRunner
|
||||||
|
|
||||||
from test_site import db
|
from test_site import db
|
||||||
from testlib import NEXT_URI, Accounts, create_test_app, get_client, \
|
from testlib import NEXT_URI, Accounts, create_test_app, get_client, \
|
||||||
@ -49,8 +51,22 @@ class CashReceiptJournalEntryTestCase(unittest.TestCase):
|
|||||||
"""
|
"""
|
||||||
self.app: Flask = create_test_app()
|
self.app: Flask = create_test_app()
|
||||||
|
|
||||||
|
runner: FlaskCliRunner = self.app.test_cli_runner()
|
||||||
with self.app.app_context():
|
with self.app.app_context():
|
||||||
from accounting.models import JournalEntry, JournalEntryLineItem
|
from accounting.models import BaseAccount, JournalEntry, \
|
||||||
|
JournalEntryLineItem
|
||||||
|
result: Result
|
||||||
|
result = runner.invoke(args="init-db")
|
||||||
|
self.assertEqual(result.exit_code, 0)
|
||||||
|
if BaseAccount.query.first() is None:
|
||||||
|
result = runner.invoke(args="accounting-init-base")
|
||||||
|
self.assertEqual(result.exit_code, 0)
|
||||||
|
result = runner.invoke(args=["accounting-init-currencies",
|
||||||
|
"-u", "editor"])
|
||||||
|
self.assertEqual(result.exit_code, 0)
|
||||||
|
result = runner.invoke(args=["accounting-init-accounts",
|
||||||
|
"-u", "editor"])
|
||||||
|
self.assertEqual(result.exit_code, 0)
|
||||||
JournalEntry.query.delete()
|
JournalEntry.query.delete()
|
||||||
JournalEntryLineItem.query.delete()
|
JournalEntryLineItem.query.delete()
|
||||||
|
|
||||||
@ -654,8 +670,22 @@ class CashDisbursementJournalEntryTestCase(unittest.TestCase):
|
|||||||
"""
|
"""
|
||||||
self.app: Flask = create_test_app()
|
self.app: Flask = create_test_app()
|
||||||
|
|
||||||
|
runner: FlaskCliRunner = self.app.test_cli_runner()
|
||||||
with self.app.app_context():
|
with self.app.app_context():
|
||||||
from accounting.models import JournalEntry, JournalEntryLineItem
|
from accounting.models import BaseAccount, JournalEntry, \
|
||||||
|
JournalEntryLineItem
|
||||||
|
result: Result
|
||||||
|
result = runner.invoke(args="init-db")
|
||||||
|
self.assertEqual(result.exit_code, 0)
|
||||||
|
if BaseAccount.query.first() is None:
|
||||||
|
result = runner.invoke(args="accounting-init-base")
|
||||||
|
self.assertEqual(result.exit_code, 0)
|
||||||
|
result = runner.invoke(args=["accounting-init-currencies",
|
||||||
|
"-u", "editor"])
|
||||||
|
self.assertEqual(result.exit_code, 0)
|
||||||
|
result = runner.invoke(args=["accounting-init-accounts",
|
||||||
|
"-u", "editor"])
|
||||||
|
self.assertEqual(result.exit_code, 0)
|
||||||
JournalEntry.query.delete()
|
JournalEntry.query.delete()
|
||||||
JournalEntryLineItem.query.delete()
|
JournalEntryLineItem.query.delete()
|
||||||
|
|
||||||
@ -1235,9 +1265,22 @@ class TransferJournalEntryTestCase(unittest.TestCase):
|
|||||||
"""
|
"""
|
||||||
self.app: Flask = create_test_app()
|
self.app: Flask = create_test_app()
|
||||||
|
|
||||||
|
runner: FlaskCliRunner = self.app.test_cli_runner()
|
||||||
with self.app.app_context():
|
with self.app.app_context():
|
||||||
from accounting.models import JournalEntry, \
|
from accounting.models import BaseAccount, JournalEntry, \
|
||||||
JournalEntryLineItem
|
JournalEntryLineItem
|
||||||
|
result: Result
|
||||||
|
result = runner.invoke(args="init-db")
|
||||||
|
self.assertEqual(result.exit_code, 0)
|
||||||
|
if BaseAccount.query.first() is None:
|
||||||
|
result = runner.invoke(args="accounting-init-base")
|
||||||
|
self.assertEqual(result.exit_code, 0)
|
||||||
|
result = runner.invoke(args=["accounting-init-currencies",
|
||||||
|
"-u", "editor"])
|
||||||
|
self.assertEqual(result.exit_code, 0)
|
||||||
|
result = runner.invoke(args=["accounting-init-accounts",
|
||||||
|
"-u", "editor"])
|
||||||
|
self.assertEqual(result.exit_code, 0)
|
||||||
JournalEntry.query.delete()
|
JournalEntry.query.delete()
|
||||||
JournalEntryLineItem.query.delete()
|
JournalEntryLineItem.query.delete()
|
||||||
|
|
||||||
@ -2096,8 +2139,22 @@ class JournalEntryReorderTestCase(unittest.TestCase):
|
|||||||
"""
|
"""
|
||||||
self.app: Flask = create_test_app()
|
self.app: Flask = create_test_app()
|
||||||
|
|
||||||
|
runner: FlaskCliRunner = self.app.test_cli_runner()
|
||||||
with self.app.app_context():
|
with self.app.app_context():
|
||||||
from accounting.models import JournalEntry, JournalEntryLineItem
|
from accounting.models import BaseAccount, JournalEntry, \
|
||||||
|
JournalEntryLineItem
|
||||||
|
result: Result
|
||||||
|
result = runner.invoke(args="init-db")
|
||||||
|
self.assertEqual(result.exit_code, 0)
|
||||||
|
if BaseAccount.query.first() is None:
|
||||||
|
result = runner.invoke(args="accounting-init-base")
|
||||||
|
self.assertEqual(result.exit_code, 0)
|
||||||
|
result = runner.invoke(args=["accounting-init-currencies",
|
||||||
|
"-u", "editor"])
|
||||||
|
self.assertEqual(result.exit_code, 0)
|
||||||
|
result = runner.invoke(args=["accounting-init-accounts",
|
||||||
|
"-u", "editor"])
|
||||||
|
self.assertEqual(result.exit_code, 0)
|
||||||
JournalEntry.query.delete()
|
JournalEntry.query.delete()
|
||||||
JournalEntryLineItem.query.delete()
|
JournalEntryLineItem.query.delete()
|
||||||
|
|
||||||
|
@ -23,7 +23,9 @@ import unittest
|
|||||||
from decimal import Decimal
|
from decimal import Decimal
|
||||||
|
|
||||||
import httpx
|
import httpx
|
||||||
|
from click.testing import Result
|
||||||
from flask import Flask
|
from flask import Flask
|
||||||
|
from flask.testing import FlaskCliRunner
|
||||||
|
|
||||||
from test_site import db
|
from test_site import db
|
||||||
from testlib import Accounts, create_test_app, get_client, \
|
from testlib import Accounts, create_test_app, get_client, \
|
||||||
@ -45,8 +47,22 @@ class OffsetTestCase(unittest.TestCase):
|
|||||||
"""
|
"""
|
||||||
self.app: Flask = create_test_app()
|
self.app: Flask = create_test_app()
|
||||||
|
|
||||||
|
runner: FlaskCliRunner = self.app.test_cli_runner()
|
||||||
with self.app.app_context():
|
with self.app.app_context():
|
||||||
from accounting.models import JournalEntry, JournalEntryLineItem
|
from accounting.models import BaseAccount, JournalEntry, \
|
||||||
|
JournalEntryLineItem
|
||||||
|
result: Result
|
||||||
|
result = runner.invoke(args="init-db")
|
||||||
|
self.assertEqual(result.exit_code, 0)
|
||||||
|
if BaseAccount.query.first() is None:
|
||||||
|
result = runner.invoke(args="accounting-init-base")
|
||||||
|
self.assertEqual(result.exit_code, 0)
|
||||||
|
result = runner.invoke(args=["accounting-init-currencies",
|
||||||
|
"-u", "editor"])
|
||||||
|
self.assertEqual(result.exit_code, 0)
|
||||||
|
result = runner.invoke(args=["accounting-init-accounts",
|
||||||
|
"-u", "editor"])
|
||||||
|
self.assertEqual(result.exit_code, 0)
|
||||||
JournalEntry.query.delete()
|
JournalEntry.query.delete()
|
||||||
JournalEntryLineItem.query.delete()
|
JournalEntryLineItem.query.delete()
|
||||||
|
|
||||||
|
@ -21,7 +21,9 @@ import unittest
|
|||||||
from datetime import datetime, timedelta
|
from datetime import datetime, timedelta
|
||||||
|
|
||||||
import httpx
|
import httpx
|
||||||
|
from click.testing import Result
|
||||||
from flask import Flask
|
from flask import Flask
|
||||||
|
from flask.testing import FlaskCliRunner
|
||||||
|
|
||||||
from test_site import db
|
from test_site import db
|
||||||
from testlib import NEXT_URI, Accounts, create_test_app, get_client
|
from testlib import NEXT_URI, Accounts, create_test_app, get_client
|
||||||
@ -47,8 +49,21 @@ class OptionTestCase(unittest.TestCase):
|
|||||||
"""
|
"""
|
||||||
self.app: Flask = create_test_app()
|
self.app: Flask = create_test_app()
|
||||||
|
|
||||||
|
runner: FlaskCliRunner = self.app.test_cli_runner()
|
||||||
with self.app.app_context():
|
with self.app.app_context():
|
||||||
from accounting.models import Option
|
from accounting.models import BaseAccount, Option
|
||||||
|
result: Result
|
||||||
|
result = runner.invoke(args="init-db")
|
||||||
|
self.assertEqual(result.exit_code, 0)
|
||||||
|
if BaseAccount.query.first() is None:
|
||||||
|
result = runner.invoke(args="accounting-init-base")
|
||||||
|
self.assertEqual(result.exit_code, 0)
|
||||||
|
result = runner.invoke(args=["accounting-init-currencies",
|
||||||
|
"-u", "editor"])
|
||||||
|
self.assertEqual(result.exit_code, 0)
|
||||||
|
result = runner.invoke(args=["accounting-init-accounts",
|
||||||
|
"-u", "editor"])
|
||||||
|
self.assertEqual(result.exit_code, 0)
|
||||||
Option.query.delete()
|
Option.query.delete()
|
||||||
|
|
||||||
self.client, self.csrf_token = get_client(self.app, "admin")
|
self.client, self.csrf_token = get_client(self.app, "admin")
|
||||||
|
@ -21,7 +21,9 @@ import unittest
|
|||||||
from datetime import date
|
from datetime import date
|
||||||
|
|
||||||
import httpx
|
import httpx
|
||||||
|
from click.testing import Result
|
||||||
from flask import Flask
|
from flask import Flask
|
||||||
|
from flask.testing import FlaskCliRunner
|
||||||
|
|
||||||
from testlib import create_test_app, get_client, Accounts, BaseTestData
|
from testlib import create_test_app, get_client, Accounts, BaseTestData
|
||||||
|
|
||||||
@ -42,8 +44,22 @@ class ReportTestCase(unittest.TestCase):
|
|||||||
"""
|
"""
|
||||||
self.app: Flask = create_test_app()
|
self.app: Flask = create_test_app()
|
||||||
|
|
||||||
|
runner: FlaskCliRunner = self.app.test_cli_runner()
|
||||||
with self.app.app_context():
|
with self.app.app_context():
|
||||||
from accounting.models import JournalEntry, JournalEntryLineItem
|
from accounting.models import BaseAccount, JournalEntry, \
|
||||||
|
JournalEntryLineItem
|
||||||
|
result: Result
|
||||||
|
result = runner.invoke(args="init-db")
|
||||||
|
self.assertEqual(result.exit_code, 0)
|
||||||
|
if BaseAccount.query.first() is None:
|
||||||
|
result = runner.invoke(args="accounting-init-base")
|
||||||
|
self.assertEqual(result.exit_code, 0)
|
||||||
|
result = runner.invoke(args=["accounting-init-currencies",
|
||||||
|
"-u", "editor"])
|
||||||
|
self.assertEqual(result.exit_code, 0)
|
||||||
|
result = runner.invoke(args=["accounting-init-accounts",
|
||||||
|
"-u", "editor"])
|
||||||
|
self.assertEqual(result.exit_code, 0)
|
||||||
JournalEntry.query.delete()
|
JournalEntry.query.delete()
|
||||||
JournalEntryLineItem.query.delete()
|
JournalEntryLineItem.query.delete()
|
||||||
|
|
||||||
|
@ -21,9 +21,9 @@ import os
|
|||||||
import typing as t
|
import typing as t
|
||||||
from secrets import token_urlsafe
|
from secrets import token_urlsafe
|
||||||
|
|
||||||
from click.testing import Result
|
import click
|
||||||
from flask import Flask, Blueprint, render_template, redirect, Response
|
from flask import Flask, Blueprint, render_template, redirect, Response
|
||||||
from flask.testing import FlaskCliRunner
|
from flask.cli import with_appcontext
|
||||||
from flask_babel_js import BabelJS
|
from flask_babel_js import BabelJS
|
||||||
from flask_sqlalchemy import SQLAlchemy
|
from flask_sqlalchemy import SQLAlchemy
|
||||||
from flask_wtf import CSRFProtect
|
from flask_wtf import CSRFProtect
|
||||||
@ -63,6 +63,7 @@ def create_app(is_testing: bool = False) -> Flask:
|
|||||||
db.init_app(app)
|
db.init_app(app)
|
||||||
|
|
||||||
app.register_blueprint(bp, url_prefix="/")
|
app.register_blueprint(bp, url_prefix="/")
|
||||||
|
app.cli.add_command(init_db_command)
|
||||||
|
|
||||||
from . import locale
|
from . import locale
|
||||||
locale.init_app(app)
|
locale.init_app(app)
|
||||||
@ -109,27 +110,20 @@ def create_app(is_testing: bool = False) -> Flask:
|
|||||||
|
|
||||||
accounting.init_app(app, user_utils=UserUtilities())
|
accounting.init_app(app, user_utils=UserUtilities())
|
||||||
|
|
||||||
with app.app_context():
|
|
||||||
init_db(app)
|
|
||||||
|
|
||||||
return app
|
return app
|
||||||
|
|
||||||
|
|
||||||
def init_db(app: Flask) -> None:
|
@click.command("init-db")
|
||||||
"""Initializes the database.
|
@with_appcontext
|
||||||
|
def init_db_command() -> None:
|
||||||
:param app: The Flask application.
|
"""Initializes the database."""
|
||||||
:return: None.
|
|
||||||
"""
|
|
||||||
db.create_all()
|
db.create_all()
|
||||||
from .auth import User
|
from .auth import User
|
||||||
for username in ["viewer", "editor", "admin", "nobody"]:
|
for username in ["viewer", "editor", "admin", "nobody"]:
|
||||||
if User.query.filter(User.username == username).first() is None:
|
if User.query.filter(User.username == username).first() is None:
|
||||||
db.session.add(User(username=username))
|
db.session.add(User(username=username))
|
||||||
db.session.commit()
|
db.session.commit()
|
||||||
runner: FlaskCliRunner = app.test_cli_runner()
|
click.echo("Database initialized successfully.")
|
||||||
result: Result = runner.invoke(args=["accounting-init-db", "-u", "editor"])
|
|
||||||
assert result.exit_code == 0, result.output + str(result.exception)
|
|
||||||
|
|
||||||
|
|
||||||
@bp.get("/", endpoint="home")
|
@bp.get("/", endpoint="home")
|
||||||
|
@ -20,7 +20,9 @@
|
|||||||
import unittest
|
import unittest
|
||||||
|
|
||||||
import httpx
|
import httpx
|
||||||
|
from click.testing import Result
|
||||||
from flask import Flask
|
from flask import Flask
|
||||||
|
from flask.testing import FlaskCliRunner
|
||||||
|
|
||||||
from test_site import db
|
from test_site import db
|
||||||
from testlib import create_test_app, get_client, Accounts, \
|
from testlib import create_test_app, get_client, Accounts, \
|
||||||
@ -41,8 +43,22 @@ class UnmatchedOffsetTestCase(unittest.TestCase):
|
|||||||
"""
|
"""
|
||||||
self.app: Flask = create_test_app()
|
self.app: Flask = create_test_app()
|
||||||
|
|
||||||
|
runner: FlaskCliRunner = self.app.test_cli_runner()
|
||||||
with self.app.app_context():
|
with self.app.app_context():
|
||||||
from accounting.models import JournalEntry, JournalEntryLineItem
|
from accounting.models import BaseAccount, JournalEntry, \
|
||||||
|
JournalEntryLineItem
|
||||||
|
result: Result
|
||||||
|
result = runner.invoke(args="init-db")
|
||||||
|
self.assertEqual(result.exit_code, 0)
|
||||||
|
if BaseAccount.query.first() is None:
|
||||||
|
result = runner.invoke(args="accounting-init-base")
|
||||||
|
self.assertEqual(result.exit_code, 0)
|
||||||
|
result = runner.invoke(args=["accounting-init-currencies",
|
||||||
|
"-u", "editor"])
|
||||||
|
self.assertEqual(result.exit_code, 0)
|
||||||
|
result = runner.invoke(args=["accounting-init-accounts",
|
||||||
|
"-u", "editor"])
|
||||||
|
self.assertEqual(result.exit_code, 0)
|
||||||
JournalEntry.query.delete()
|
JournalEntry.query.delete()
|
||||||
JournalEntryLineItem.query.delete()
|
JournalEntryLineItem.query.delete()
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user