Compare commits

...

17 Commits
v1.5.8 ... main

Author SHA1 Message Date
f20c462685 Advanced to version 1.6.0. 2024-06-04 08:29:26 +08:00
80ae4bd91c Revised the calculation of "today" to use the client's timezone instead of the server's timezone. 2024-06-04 08:28:59 +08:00
6ee3ee76ea Updated optional dependencies in pyproject.toml. 2024-06-04 08:28:58 +08:00
2bfcc8b889 Updated the dependencies in pyproject.toml. 2024-06-04 08:28:15 +08:00
99564c02d0 Updated the Bootstrap, FontAwesome, and Tempus-Dominus versions in the test site. 2024-04-21 22:41:46 +02:00
25d9904180 Applied the new type parameter syntax to the generic classes for Python 3.12. 2024-03-03 07:39:37 +08:00
1cf83adf87 Applied the "type" statement to type aliases for Python 3.12. 2024-03-03 07:39:20 +08:00
8e3d1f11b5 Updated Python version to 3.12. 2024-03-03 07:38:59 +08:00
0ab14aa34d Updated the copyright year in README.rst. 2024-03-03 07:38:32 +08:00
e0ed81ad1f Advanced to version 1.5.11. 2023-12-16 21:52:15 +08:00
ece7481e9e Refined to enable the selection of the 3351-001 Accumulated Profit or Loss account. 2023-12-16 21:51:14 +08:00
50d4526e0b Advanced to version 1.5.10. 2023-11-28 08:27:31 +08:00
3f0a0b4227 Fixed the form validator to enable the selection of Accumulated Profit or Loss accounts other than 3351-001. 2023-11-28 08:26:37 +08:00
dcc9626b23 Fixed the release date of version 1.5.9 in the change log. 2023-11-28 08:17:25 +08:00
79eb077129 Advanced to version 1.5.9. 2023-11-28 08:10:00 +08:00
d5719ad223 Refined to enable the selection of Accumulated Profit or Loss accounts other than 3351-001, facilitating the consolidation of existing balances. 2023-11-28 08:09:35 +08:00
eb3fa8f414 Added docs/requirements.txt and the "sphinx_rtd_theme" theme to the readthedocs configuration, as Read the Docs does not install sphinx_rtd_theme by default
after August 7, 2023.
2023-11-28 08:04:11 +08:00
22 changed files with 188 additions and 66 deletions

View File

@ -38,3 +38,4 @@ python:
install: install:
- method: pip - method: pip
path: . path: .
- requirements: docs/requirements.txt

View File

@ -59,7 +59,7 @@ Refer to the `change log`_.
Copyright Copyright
========= =========
Copyright (c) 2023 imacat. Copyright (c) 2023-2024 imacat.
Licensed under the Apache License, Version 2.0 (the "License"); Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License. you may not use this file except in compliance with the License.

1
docs/requirements.txt Normal file
View File

@ -0,0 +1 @@
sphinx_rtd_theme

View File

@ -2,6 +2,51 @@ Change Log
========== ==========
Version 1.6.0
--------------
Released 2024/6/4
* Updated Python version to 3.12.
* Revised the calculation of "today" to use the client's timezone instead of
the server's timezone.
* Updated the Bootstrap, FontAwesome, and Tempus-Dominus versions in the test
site.
Version 1.5.11
--------------
Released 2023/12/26
Bug fix.
* Refined to enable the selection of the 3351-001 Accumulated Profit or Loss
account.
Version 1.5.10
--------------
Released 2023/11/28
Bug fix.
* Fixed the form validator to enable the selection of Accumulated Profit or
Loss accounts other than 3351-001.
Version 1.5.9
-------------
Released 2023/11/28
Bug fix.
* Refined to enable the selection of Accumulated Profit or Loss accounts other
than 3351-001, facilitating the consolidation of existing balances.
Version 1.5.8 Version 1.5.8
------------- -------------

View File

@ -1,7 +1,7 @@
# The Mia! Accounting Project. # The Mia! Accounting Project.
# Author: imacat@mail.imacat.idv.tw (imacat), 2022/8/21 # Author: imacat@mail.imacat.idv.tw (imacat), 2022/8/21
# Copyright (c) 2022-2023 imacat. # Copyright (c) 2022-2024 imacat.
# #
# Licensed under the Apache License, Version 2.0 (the "License"); # Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License. # you may not use this file except in compliance with the License.
@ -20,7 +20,7 @@ name = "mia-accounting"
dynamic = ["version"] dynamic = ["version"]
description = "A Flask accounting module." description = "A Flask accounting module."
readme = "README.rst" readme = "README.rst"
requires-python = ">=3.11" requires-python = ">=3.12"
authors = [ authors = [
{name = "imacat", email = "imacat@mail.imacat.idv.tw"}, {name = "imacat", email = "imacat@mail.imacat.idv.tw"},
] ]
@ -33,7 +33,7 @@ classifiers = [
"Topic :: Office/Business :: Financial :: Accounting", "Topic :: Office/Business :: Financial :: Accounting",
] ]
dependencies = [ dependencies = [
"flask", "Flask",
"SQLAlchemy >= 2", "SQLAlchemy >= 2",
"Flask-SQLAlchemy", "Flask-SQLAlchemy",
"Flask-WTF", "Flask-WTF",
@ -42,8 +42,7 @@ dependencies = [
] ]
[project.optional-dependencies] [project.optional-dependencies]
test = [ devel = [
"unittest",
"httpx", "httpx",
"OpenCC", "OpenCC",
] ]

View File

@ -1,7 +1,7 @@
# The Mia! Accounting Project. # The Mia! Accounting Project.
# Author: imacat@mail.imacat.idv.tw (imacat), 2023/1/25 # Author: imacat@mail.imacat.idv.tw (imacat), 2023/1/25
# Copyright (c) 2023 imacat. # Copyright (c) 2023-2024 imacat.
# #
# Licensed under the Apache License, Version 2.0 (the "License"); # Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License. # you may not use this file except in compliance with the License.
@ -24,7 +24,7 @@ from flask_sqlalchemy import SQLAlchemy
from accounting.utils.user import UserUtilityInterface from accounting.utils.user import UserUtilityInterface
VERSION: str = "1.5.8" VERSION: str = "1.6.0"
"""The package version.""" """The package version."""
db: SQLAlchemy = SQLAlchemy() db: SQLAlchemy = SQLAlchemy()
"""The database instance.""" """The database instance."""

View File

@ -1,7 +1,7 @@
# The Mia! Accounting Project. # The Mia! Accounting Project.
# Author: imacat@mail.imacat.idv.tw (imacat), 2023/1/30 # Author: imacat@mail.imacat.idv.tw (imacat), 2023/1/30
# Copyright (c) 2023 imacat. # Copyright (c) 2023-2024 imacat.
# #
# Licensed under the Apache License, Version 2.0 (the "License"); # Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License. # you may not use this file except in compliance with the License.
@ -27,7 +27,7 @@ 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 get_user_pk
AccountData = tuple[int, str, int, str, str, str, bool] type 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."""

View File

@ -71,7 +71,6 @@ class IsDebitAccount:
if field.data is None: if field.data is None:
return return
if re.match(r"^(?:[1235689]|7[5678])", field.data) \ if re.match(r"^(?:[1235689]|7[5678])", field.data) \
and not field.data.startswith("3351-") \
and not field.data.startswith("3353-"): and not field.data.startswith("3353-"):
return return
raise ValidationError(self.__message) raise ValidationError(self.__message)
@ -92,7 +91,6 @@ class IsCreditAccount:
if field.data is None: if field.data is None:
return return
if re.match(r"^(?:[123489]|7[1234])", field.data) \ if re.match(r"^(?:[123489]|7[1234])", field.data) \
and not field.data.startswith("3351-") \
and not field.data.startswith("3353-"): and not field.data.startswith("3353-"):
return return
raise ValidationError(self.__message) raise ValidationError(self.__message)

View File

@ -1,7 +1,7 @@
# The Mia! Accounting Project. # The Mia! Accounting Project.
# Author: imacat@mail.imacat.idv.tw (imacat), 2023/2/18 # Author: imacat@mail.imacat.idv.tw (imacat), 2023/2/18
# Copyright (c) 2023 imacat. # Copyright (c) 2023-2024 imacat.
# #
# Licensed under the Apache License, Version 2.0 (the "License"); # Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License. # you may not use this file except in compliance with the License.
@ -19,7 +19,7 @@
""" """
import datetime as dt import datetime as dt
from abc import ABC, abstractmethod from abc import ABC, abstractmethod
from typing import TypeVar, Generic, Type from typing import Type
import sqlalchemy as sa import sqlalchemy as sa
from flask_babel import LazyString from flask_babel import LazyString
@ -308,11 +308,7 @@ class JournalEntryForm(FlaskForm):
return db.session.scalar(select) return db.session.scalar(select)
T = TypeVar("T", bound=JournalEntryForm) class LineItemCollector[T: JournalEntryForm](ABC):
"""A journal entry form variant."""
class LineItemCollector(Generic[T], ABC):
"""The line item collector.""" """The line item collector."""
def __init__(self, form: T, obj: JournalEntry): def __init__(self, form: T, obj: JournalEntry):

View File

@ -1,7 +1,7 @@
# The Mia! Accounting Project. # The Mia! Accounting Project.
# Author: imacat@mail.imacat.idv.tw (imacat), 2023/2/18 # Author: imacat@mail.imacat.idv.tw (imacat), 2023/2/18
# Copyright (c) 2023 imacat. # Copyright (c) 2023-2024 imacat.
# #
# Licensed under the Apache License, Version 2.0 (the "License"); # Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License. # you may not use this file except in compliance with the License.
@ -33,6 +33,7 @@ from accounting.utils.flash_errors import flash_form_errors
from accounting.utils.journal_entry_types import JournalEntryType from accounting.utils.journal_entry_types import JournalEntryType
from accounting.utils.next_uri import inherit_next, or_next from accounting.utils.next_uri import inherit_next, or_next
from accounting.utils.permission import has_permission, can_view, can_edit from accounting.utils.permission import has_permission, can_view, can_edit
from accounting.utils.timezone import get_tz_today
from accounting.utils.user import get_current_user_pk from accounting.utils.user import get_current_user_pk
from .forms import sort_journal_entries_in, JournalEntryReorderForm from .forms import sort_journal_entries_in, JournalEntryReorderForm
from .template_filters import with_type, to_transfer, format_amount_input, \ from .template_filters import with_type, to_transfer, format_amount_input, \
@ -67,7 +68,7 @@ def show_add_journal_entry_form(journal_entry_type: JournalEntryType) -> str:
form.validate() form.validate()
else: else:
form = journal_entry_op.form() form = journal_entry_op.form()
form.date.data = dt.date.today() form.date.data = get_tz_today()
return journal_entry_op.render_create_template(form) return journal_entry_op.render_create_template(form)

View File

@ -304,7 +304,6 @@ class Account(db.Model):
cls.base_code.startswith("78"), cls.base_code.startswith("78"),
cls.base_code.startswith("8"), cls.base_code.startswith("8"),
cls.base_code.startswith("9")), cls.base_code.startswith("9")),
cls.base_code != "3351",
cls.base_code != "3353")\ cls.base_code != "3353")\
.order_by(cls.base_code, cls.no).all() .order_by(cls.base_code, cls.no).all()
@ -326,7 +325,6 @@ class Account(db.Model):
cls.base_code.startswith("74"), cls.base_code.startswith("74"),
cls.base_code.startswith("8"), cls.base_code.startswith("8"),
cls.base_code.startswith("9")), cls.base_code.startswith("9")),
cls.base_code != "3351",
cls.base_code != "3353")\ cls.base_code != "3353")\
.order_by(cls.base_code, cls.no).all() .order_by(cls.base_code, cls.no).all()

View File

@ -1,7 +1,7 @@
# The Mia! Accounting Project. # The Mia! Accounting Project.
# Author: imacat@mail.imacat.idv.tw (imacat), 2023/3/4 # Author: imacat@mail.imacat.idv.tw (imacat), 2023/3/4
# Copyright (c) 2023 imacat. # Copyright (c) 2023-2024 imacat.
# #
# Licensed under the Apache License, Version 2.0 (the "License"); # Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License. # you may not use this file except in compliance with the License.
@ -24,6 +24,7 @@ import datetime as dt
from collections.abc import Callable from collections.abc import Callable
from accounting.models import JournalEntry from accounting.models import JournalEntry
from accounting.utils.timezone import get_tz_today
from .period import Period from .period import Period
from .shortcuts import ThisMonth, LastMonth, SinceLastMonth, ThisYear, \ from .shortcuts import ThisMonth, LastMonth, SinceLastMonth, ThisYear, \
LastYear, Today, Yesterday, AllTime, TemplatePeriod, YearPeriod LastYear, Today, Yesterday, AllTime, TemplatePeriod, YearPeriod
@ -80,7 +81,7 @@ class PeriodChooser:
"""The available years.""" """The available years."""
if self.has_data: if self.has_data:
today: dt.date = dt.date.today() today: dt.date = get_tz_today()
self.has_last_month = start < dt.date(today.year, today.month, 1) self.has_last_month = start < dt.date(today.year, today.month, 1)
self.has_last_year = start.year < today.year self.has_last_year = start.year < today.year
self.has_yesterday = start < today self.has_yesterday = start < today

View File

@ -1,7 +1,7 @@
# The Mia! Accounting Project. # The Mia! Accounting Project.
# Author: imacat@mail.imacat.idv.tw (imacat), 2023/3/4 # Author: imacat@mail.imacat.idv.tw (imacat), 2023/3/4
# Copyright (c) 2023 imacat. # Copyright (c) 2023-2024 imacat.
# #
# Licensed under the Apache License, Version 2.0 (the "License"); # Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License. # you may not use this file except in compliance with the License.
@ -20,6 +20,7 @@
import datetime as dt import datetime as dt
from accounting.locale import gettext from accounting.locale import gettext
from accounting.utils.timezone import get_tz_today
from .month_end import month_end from .month_end import month_end
from .period import Period from .period import Period
@ -27,7 +28,7 @@ from .period import Period
class ThisMonth(Period): class ThisMonth(Period):
"""The period of this month.""" """The period of this month."""
def __init__(self): def __init__(self):
today: dt.date = dt.date.today() today: dt.date = get_tz_today()
this_month_start: dt.date = dt.date(today.year, today.month, 1) this_month_start: dt.date = dt.date(today.year, today.month, 1)
super().__init__(this_month_start, month_end(today)) super().__init__(this_month_start, month_end(today))
self.is_default = True self.is_default = True
@ -43,7 +44,7 @@ class ThisMonth(Period):
class LastMonth(Period): class LastMonth(Period):
"""The period of this month.""" """The period of this month."""
def __init__(self): def __init__(self):
today: dt.date = dt.date.today() today: dt.date = get_tz_today()
year: int = today.year year: int = today.year
month: int = today.month - 1 month: int = today.month - 1
if month < 1: if month < 1:
@ -63,7 +64,7 @@ class LastMonth(Period):
class SinceLastMonth(Period): class SinceLastMonth(Period):
"""The period of this month.""" """The period of this month."""
def __init__(self): def __init__(self):
today: dt.date = dt.date.today() today: dt.date = get_tz_today()
year: int = today.year year: int = today.year
month: int = today.month - 1 month: int = today.month - 1
if month < 1: if month < 1:
@ -82,7 +83,7 @@ class SinceLastMonth(Period):
class ThisYear(Period): class ThisYear(Period):
"""The period of this year.""" """The period of this year."""
def __init__(self): def __init__(self):
year: int = dt.date.today().year year: int = get_tz_today().year
start: dt.date = dt.date(year, 1, 1) start: dt.date = dt.date(year, 1, 1)
end: dt.date = dt.date(year, 12, 31) end: dt.date = dt.date(year, 12, 31)
super().__init__(start, end) super().__init__(start, end)
@ -97,7 +98,7 @@ class ThisYear(Period):
class LastYear(Period): class LastYear(Period):
"""The period of last year.""" """The period of last year."""
def __init__(self): def __init__(self):
year: int = dt.date.today().year year: int = get_tz_today().year
start: dt.date = dt.date(year - 1, 1, 1) start: dt.date = dt.date(year - 1, 1, 1)
end: dt.date = dt.date(year - 1, 12, 31) end: dt.date = dt.date(year - 1, 12, 31)
super().__init__(start, end) super().__init__(start, end)
@ -112,7 +113,7 @@ class LastYear(Period):
class Today(Period): class Today(Period):
"""The period of today.""" """The period of today."""
def __init__(self): def __init__(self):
today: dt.date = dt.date.today() today: dt.date = get_tz_today()
super().__init__(today, today) super().__init__(today, today)
self.is_today = True self.is_today = True
@ -125,7 +126,7 @@ class Today(Period):
class Yesterday(Period): class Yesterday(Period):
"""The period of yesterday.""" """The period of yesterday."""
def __init__(self): def __init__(self):
yesterday: dt.date = dt.date.today() - dt.timedelta(days=1) yesterday: dt.date = get_tz_today() - dt.timedelta(days=1)
super().__init__(yesterday, yesterday) super().__init__(yesterday, yesterday)
self.is_yesterday = True self.is_yesterday = True

View File

@ -0,0 +1,37 @@
/* The Mia! Accounting Project
* timezone.js: The JavaScript for the timezone
*/
/* Copyright (c) 2024 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: 2024/6/4
*/
"use strict";
// Initializes the page JavaScript.
document.addEventListener("DOMContentLoaded", () => {
setTimeZone();
});
/**
* Sets the time zone.
*
* @private
*/
function setTimeZone() {
document.cookie = `accounting-tz=${Intl.DateTimeFormat().resolvedOptions().timeZone}; SameSite=Strict`;
}

View File

@ -1,7 +1,7 @@
# The Mia! Accounting Project. # The Mia! Accounting Project.
# Author: imacat@mail.imacat.idv.tw (imacat), 2023/2/25 # Author: imacat@mail.imacat.idv.tw (imacat), 2023/2/25
# Copyright (c) 2023 imacat. # Copyright (c) 2023-2024 imacat.
# #
# Licensed under the Apache License, Version 2.0 (the "License"); # Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License. # you may not use this file except in compliance with the License.
@ -24,6 +24,7 @@ from typing import Any
from flask_babel import get_locale from flask_babel import get_locale
from accounting.locale import gettext from accounting.locale import gettext
from accounting.utils.timezone import get_tz_today
def format_amount(value: Decimal | None) -> str | None: def format_amount(value: Decimal | None) -> str | None:
@ -47,7 +48,7 @@ def format_date(value: dt.date) -> str:
:param value: The date. :param value: The date.
:return: The human-friendly date text. :return: The human-friendly date text.
""" """
today: dt.date = dt.date.today() today: dt.date = get_tz_today()
if value == today: if value == today:
return gettext("Today") return gettext("Today")
if value == today - dt.timedelta(days=1): if value == today - dt.timedelta(days=1):

View File

@ -2,7 +2,7 @@
The Mia! Accounting Project The Mia! Accounting Project
base.html: The application-wide base template. base.html: The application-wide base template.
Copyright (c) 2023 imacat. Copyright (c) 2023-2024 imacat.
Licensed under the Apache License, Version 2.0 (the "License"); Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License. you may not use this file except in compliance with the License.
@ -27,5 +27,6 @@ First written: 2023/1/27
{% block scripts %} {% block scripts %}
<script src="{{ url_for("accounting.babel_catalog") }}"></script> <script src="{{ url_for("accounting.babel_catalog") }}"></script>
<script src="{{ url_for("accounting.static", filename="js/timezone.js") }}"></script>
{% block accounting_scripts %}{% endblock %} {% block accounting_scripts %}{% endblock %}
{% endblock %} {% endblock %}

View File

@ -1,7 +1,7 @@
# The Mia! Accounting Project. # The Mia! Accounting Project.
# Author: imacat@mail.imacat.idv.tw (imacat), 2023/1/25 # Author: imacat@mail.imacat.idv.tw (imacat), 2023/1/25
# Copyright (c) 2023 imacat. # Copyright (c) 2023-2024 imacat.
# #
# Licensed under the Apache License, Version 2.0 (the "License"); # Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License. # you may not use this file except in compliance with the License.
@ -19,7 +19,6 @@
This module should not import any other module from the application. This module should not import any other module from the application.
""" """
from typing import TypeVar, Generic
from urllib.parse import urlparse, parse_qsl, urlencode, urlunparse, \ from urllib.parse import urlparse, parse_qsl, urlencode, urlunparse, \
ParseResult ParseResult
@ -62,11 +61,8 @@ class Redirection(RequestRedirect):
DEFAULT_PAGE_SIZE: int = 10 DEFAULT_PAGE_SIZE: int = 10
"""The default page size.""" """The default page size."""
T = TypeVar("T")
"""The pagination item type."""
class Pagination[T]:
class Pagination(Generic[T]):
"""The pagination utility.""" """The pagination utility."""
def __init__(self, items: list[T], is_reversed: bool = False): def __init__(self, items: list[T], is_reversed: bool = False):
@ -92,7 +88,7 @@ class Pagination(Generic[T]):
"""The options to the number of items in a page.""" """The options to the number of items in a page."""
class AbstractPagination(Generic[T]): class AbstractPagination[T]:
"""An abstract pagination.""" """An abstract pagination."""
def __init__(self): def __init__(self):
@ -109,12 +105,12 @@ class AbstractPagination(Generic[T]):
"""The options to the number of items in a page.""" """The options to the number of items in a page."""
class EmptyPagination(AbstractPagination[T]): class EmptyPagination[T](AbstractPagination[T]):
"""The pagination from empty data.""" """The pagination from empty data."""
pass pass
class NonEmptyPagination(AbstractPagination[T]): class NonEmptyPagination[T](AbstractPagination[T]):
"""The pagination with real data.""" """The pagination with real data."""
PAGE_SIZE_OPTION_VALUES: list[int] = [10, 100, 200] PAGE_SIZE_OPTION_VALUES: list[int] = [10, 100, 200]
"""The page size options.""" """The page size options."""

View File

@ -0,0 +1,37 @@
# The Mia! Accounting Project.
# Author: imacat@mail.imacat.idv.tw (imacat), 2024/6/4
# Copyright (c) 2024 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 timezone utility.
This module should not import any other module from the application.
"""
import datetime as dt
import pytz
from flask import request
def get_tz_today() -> dt.date:
"""Returns today in the client timezone.
:return: today in the client timezone.
"""
tz_name: str | None = request.cookies.get("accounting-tz")
if tz_name is None:
return dt.date.today()
return dt.datetime.now(tz=pytz.timezone(tz_name)).date()

View File

@ -1,7 +1,7 @@
# The Mia! Accounting Project. # The Mia! Accounting Project.
# Author: imacat@mail.imacat.idv.tw (imacat), 2023/2/1 # Author: imacat@mail.imacat.idv.tw (imacat), 2023/2/1
# Copyright (c) 2023 imacat. # Copyright (c) 2023-2024 imacat.
# #
# Licensed under the Apache License, Version 2.0 (the "License"); # Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License. # you may not use this file except in compliance with the License.
@ -20,17 +20,14 @@ This module should not import any other module from the application.
""" """
from abc import ABC, abstractmethod from abc import ABC, abstractmethod
from typing import TypeVar, Generic, Type from typing import Type
import sqlalchemy as sa import sqlalchemy as sa
from flask import g, Response from flask import g, Response
from flask_sqlalchemy.model import Model from flask_sqlalchemy.model import Model
T = TypeVar("T", bound=Model)
"""The user data model data type."""
class UserUtilityInterface[T: Model](ABC):
class UserUtilityInterface(Generic[T], ABC):
"""The interface for the user utilities.""" """The interface for the user utilities."""
@abstractmethod @abstractmethod
@ -113,7 +110,7 @@ class UserUtilityInterface(Generic[T], ABC):
__user_utils: UserUtilityInterface __user_utils: UserUtilityInterface
"""The user utilities.""" """The user utilities."""
user_cls: Type[Model] = Model type user_cls = Model
"""The user class.""" """The user class."""
user_pk_column: sa.Column = sa.Column(sa.Integer) user_pk_column: sa.Column = sa.Column(sa.Integer)
"""The primary key column of the user class.""" """The primary key column of the user class."""

View File

@ -1,7 +1,7 @@
# The Mia! Accounting Demonstration Website. # The Mia! Accounting Demonstration Website.
# Author: imacat@mail.imacat.idv.tw (imacat), 2023/4/13 # Author: imacat@mail.imacat.idv.tw (imacat), 2023/4/13
# Copyright (c) 2023 imacat. # Copyright (c) 2023-2024 imacat.
# #
# Licensed under the Apache License, Version 2.0 (the "License"); # Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License. # you may not use this file except in compliance with the License.
@ -28,6 +28,7 @@ from typing import Any
import sqlalchemy as sa import sqlalchemy as sa
from flask import Flask from flask import Flask
from accounting.utils.timezone import get_tz_today
from . import db from . import db
from .auth import User from .auth import User
@ -44,6 +45,17 @@ class Accounts:
MEAL: str = "6272-001" MEAL: str = "6272-001"
def get_today() -> dt.date:
"""Returns today, based on the context.
:return: Today.
"""
try:
return get_tz_today()
except RuntimeError:
return dt.date.today()
class JournalEntryLineItemData: class JournalEntryLineItemData:
"""The journal entry line item data.""" """The journal entry line item data."""
@ -183,7 +195,7 @@ class JournalEntryData:
:param is_update: True for an update operation, or False otherwise :param is_update: True for an update operation, or False otherwise
:return: The journal entry as a form. :return: The journal entry as a form.
""" """
date: dt.date = dt.date.today() - dt.timedelta(days=self.days) date: dt.date = get_today() - dt.timedelta(days=self.days)
form: dict[str, str] = {"csrf_token": csrf_token, form: dict[str, str] = {"csrf_token": csrf_token,
"next": encoded_next_uri, "next": encoded_next_uri,
"date": date.isoformat()} "date": date.isoformat()}
@ -260,8 +272,7 @@ class BaseTestData(ABC):
existing_j_id: set[int] = {x["id"] for x in self.__journal_entries} existing_j_id: set[int] = {x["id"] for x in self.__journal_entries}
existing_l_id: set[int] = {x["id"] for x in self.__line_items} existing_l_id: set[int] = {x["id"] for x in self.__line_items}
journal_entry_data.id = self.__new_id(existing_j_id) journal_entry_data.id = self.__new_id(existing_j_id)
date: dt.date \ date: dt.date = get_today() - dt.timedelta(days=journal_entry_data.days)
= dt.date.today() - dt.timedelta(days=journal_entry_data.days)
self.__journal_entries.append( self.__journal_entries.append(
{"id": journal_entry_data.id, {"id": journal_entry_data.id,
"date": date, "date": date,

View File

@ -1,7 +1,7 @@
# The Mia! Accounting Demonstration Website. # The Mia! Accounting Demonstration Website.
# Author: imacat@mail.imacat.idv.tw (imacat), 2023/4/12 # Author: imacat@mail.imacat.idv.tw (imacat), 2023/4/12
# Copyright (c) 2023 imacat. # Copyright (c) 2023-2024 imacat.
# #
# Licensed under the Apache License, Version 2.0 (the "License"); # Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License. # you may not use this file except in compliance with the License.
@ -23,6 +23,7 @@ from flask import Flask, Blueprint, url_for, flash, redirect, session, \
render_template, current_app render_template, current_app
from flask_babel import lazy_gettext from flask_babel import lazy_gettext
from accounting.utils.timezone import get_tz_today
from . import db from . import db
from .auth import admin_required from .auth import admin_required
from .lib import Accounts, JournalEntryLineItemData, JournalEntryData, \ from .lib import Accounts, JournalEntryLineItemData, JournalEntryData, \
@ -117,7 +118,7 @@ class SampleData(BaseTestData):
:return: None. :return: None.
""" """
today: dt.date = dt.date.today() today: dt.date = get_tz_today()
days: int days: int
year: int year: int
month: int month: int
@ -160,7 +161,7 @@ class SampleData(BaseTestData):
:return: None. :return: None.
""" """
today: dt.date = dt.date.today() today: dt.date = get_tz_today()
year: int = today.year - 5 year: int = today.year - 5
month: int = today.month month: int = today.month

View File

@ -2,7 +2,7 @@
The Mia! Accounting Demonstration Website The Mia! Accounting Demonstration Website
base.html: The side-wide layout template base.html: The side-wide layout template
Copyright (c) 2023 imacat. Copyright (c) 2023-2024 imacat.
Licensed under the Apache License, Version 2.0 (the "License"); Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License. you may not use this file except in compliance with the License.
@ -25,21 +25,21 @@ First written: 2023/1/27
<meta charset="UTF-8"> <meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1"> <meta name="viewport" content="width=device-width, initial-scale=1">
<meta name="author" content="{{ "imacat" }}" /> <meta name="author" content="{{ "imacat" }}" />
<link rel="stylesheet" type="text/css" href="https://cdn.jsdelivr.net/npm/bootstrap@5.2.3/dist/css/bootstrap.min.css" integrity="sha384-rbsA2VBKQhggwzxH7pPCaAqO46MgnOM80zW1RWuH61DGLwZJEdK2Kadq2F9CUG65" crossorigin="anonymous"> <link rel="stylesheet" type="text/css" href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/css/bootstrap.min.css" integrity="sha384-QWTKZyjpPEjISv5WaRU9OFeRpok6YctnYmDr5pNlyT2bRjXh0JMhjY6hW+ALEwIH" crossorigin="anonymous">
<link rel="stylesheet" type="text/css" href="https://cdn.jsdelivr.net/npm/@fortawesome/fontawesome-free@6.4.0/css/all.min.css" integrity="sha384-iw3OoTErCYJJB9mCa8LNS2hbsQ7M3C0EpIsO/H5+EGAkPGc6rk+V8i04oW/K5xq0" crossorigin="anonymous"> <link rel="stylesheet" type="text/css" href="https://cdn.jsdelivr.net/npm/@fortawesome/fontawesome-free@6.5.1/css/all.min.css" integrity="sha384-t1nt8BQoYMLFN5p42tRAtuAAFQaCQODekUVeKKZrEnEyp4H2R0RHFz0KWpmj7i8g" crossorigin="anonymous">
<link rel="stylesheet" type="text/css" href="https://cdn.jsdelivr.net/npm/@eonasdan/tempus-dominus@6.7.7/dist/css/tempus-dominus.min.css" integrity="sha384-l66rSL7gUubrdJxFRbXUo/tO7eNPAcCiZXFs/Xl147146xNqQ1qt4oPW6jlVezsS" crossorigin="anonymous"> <link rel="stylesheet" type="text/css" href="https://cdn.jsdelivr.net/npm/@eonasdan/tempus-dominus@6.9.6/dist/css/tempus-dominus.min.css" integrity="sha384-NzVf7b26bC2au5J9EqNceWlrs7iIkBa0bA46tRpK5C3J08J7MRTPmSdpRKhWNgDL" crossorigin="anonymous">
{% block styles %}{% endblock %} {% block styles %}{% endblock %}
<script src="{{ url_for("babel_catalog") }}"></script> <script src="{{ url_for("babel_catalog") }}"></script>
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.2.3/dist/js/bootstrap.bundle.min.js" integrity="sha384-kenU1KFdBIe4zVF0s0G1M5b4hcpxyD9F7jL+jjXkk+Q2h455rYXK/7HAuoJl+0I4" crossorigin="anonymous"></script> <script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/js/bootstrap.bundle.min.js" integrity="sha384-YvpcrYf0tY3lHB60NNkmXc5s9fDVZLESaAA55NDzOxhy9GkcIdslK1eN7N6jIeHz" crossorigin="anonymous"></script>
<script src="https://cdn.jsdelivr.net/npm/decimal.js-light@2.5.1/decimal.min.js" integrity="sha384-QdsxGEq4Y0erX8WUIsZJDtfoSSyBF6dmNCnzRNYCa2AOM/xzNsyhHu0RbdFBAm+l" crossorigin="anonymous"></script> <script src="https://cdn.jsdelivr.net/npm/decimal.js-light@2.5.1/decimal.min.js" integrity="sha384-QdsxGEq4Y0erX8WUIsZJDtfoSSyBF6dmNCnzRNYCa2AOM/xzNsyhHu0RbdFBAm+l" crossorigin="anonymous"></script>
<script src="https://cdn.jsdelivr.net/npm/@eonasdan/tempus-dominus@6.7.7/dist/js/tempus-dominus.min.js" integrity="sha384-MxHp+/TqTjbku1jSTIe1e/4l6CZTLhACLDbWyxYaFRgD3AM4oh99AY8bxsGhIoRc" crossorigin="anonymous"></script> <script src="https://cdn.jsdelivr.net/npm/@eonasdan/tempus-dominus@6.9.6/dist/js/tempus-dominus.min.js" integrity="sha384-GRg4jmBEA/AnwmpV7MhpXUTim20ncyZTm9/1fbna86CRqMcdrou46etX8scQ9dPe" crossorigin="anonymous"></script>
{% block scripts %}{% endblock %} {% block scripts %}{% endblock %}
<link rel="shortcut icon" href="{{ url_for("static", filename="favicon.svg") }}"> <link rel="shortcut icon" href="{{ url_for("static", filename="favicon.svg") }}">
<title>{% block title %}{% endblock %}</title> <title>{% block title %}{% endblock %}</title>
</head> </head>
<body> <body>
<nav class="navbar navbar-expand-lg bg-body-tertiary bg-dark navbar-dark"> <nav class="navbar navbar-expand-lg bg-body-tertiary bg-dark" data-bs-theme="dark">
<div class="container-fluid"> <div class="container-fluid">
<a class="navbar-brand" href="{{ url_for("home.home") }}"> <a class="navbar-brand" href="{{ url_for("home.home") }}">
<i class="fa-solid fa-house"></i> <i class="fa-solid fa-house"></i>