15 Commits

Author SHA1 Message Date
86637267d3 Advanced to version 1.5.0. 2023-04-23 20:13:34 +08:00
71e97721aa Removed the documentation link from the documentation in intro.rst. It does not make sense for a circular link to itself. 2023-04-23 20:13:33 +08:00
5815608288 Added the VERSION constant to the accounting module for the package version, and revised pyproject.toml and conf.py to read the version from it. 2023-04-23 20:13:10 +08:00
5f75d93c6a Simplified README.rst. 2023-04-23 18:42:54 +08:00
118c4b458e Added the change log. 2023-04-23 18:42:42 +08:00
3f7e4c0dda Rewrote the data model declaration with the mapped type hint and the mapped columns in SQLAlchemy 2.0. Added "SQLAlchemy >= 2" to the dependencies. 2023-04-23 13:21:54 +08:00
eed4c923f6 Removed the "be" cast function to cast data type for the binary expressions. It is to be replaced by the mapped type hints. 2023-04-23 13:21:48 +08:00
09dd5ae541 Revised the long line in the JournalEntryConverter converter. 2023-04-23 09:52:21 +08:00
172a12b134 Fixed the type hint of the "currency_options" function. 2023-04-23 09:44:25 +08:00
f3c558f48a Advanced to version 1.4.1. 2023-04-22 18:22:47 +08:00
988757d30e Revised the JavaScript journal entry line item editor to only override the description with the description of the original line item when there is no existing description. 2023-04-20 00:28:28 +08:00
50cea90d1b Revised the JavaScript journal entry line item editor to allow editing the description for offsets and partially-offset original items. 2023-04-20 00:26:58 +08:00
71dfb6f003 Advanced to version 1.4.0. 2023-04-18 09:33:35 +08:00
be628b4aa1 Updated the Sphinx documentation. 2023-04-18 09:33:00 +08:00
5d444adec4 Updated the translation. 2023-04-18 09:32:38 +08:00
26 changed files with 747 additions and 551 deletions

View File

@ -44,126 +44,18 @@ You may also download from the `PyPI project page`_ or the
`release page`_ on the `Git repository`_.
Prerequisites
=============
You need a running Flask application with database user login.
The primary key of the user data model must be integer. You also
need at least one user.
The following front-end JavaScript libraries must be loaded. You may
download it locally or use CDN_.
* Bootstrap_ 5.2.3 or above
* FontAwesome_ 6.2.1 or above
* `Decimal.js`_ 6.4.3 or above
* `Tempus-Dominus`_ 6.4.3 or above
Configuration
=============
You need to pass the Flask *app* and an implementation of
`UserUtilityInterface`_ to the `init_app`_ function.
``UserUtilityInterface`` contains everything *Mia! Accounting* needs.
The following is an example configuration for *Mia! Accounting*.
::
from flask import Response, redirect
from .auth import current_user()
from .modules import User
def create_app(test_config=None) -> Flask:
app: Flask = Flask(__name__)
... (Configuration of SQLAlchemy, CSRF, Babel_JS, ... etc) ...
import accounting
class UserUtils(accounting.UserUtilityInterface[User]):
def can_view(self) -> bool:
return True
def can_edit(self) -> bool:
return "editor" in current_user().roles
def can_admin(self) -> bool:
return current_user().is_admin
def unauthorized(self) -> Response:
return redirect("/login")
@property
def cls(self) -> t.Type[User]:
return User
@property
def pk_column(self) -> Column:
return User.id
@property
def current_user(self) -> User | None:
return current_user()
def get_by_username(self, username: str) -> User | None:
return User.query.filter(User.username == username).first()
def get_pk(self, user: User) -> int:
return user.id
accounting.init_app(app, UserUtils())
... (Any other configuration) ...
return app
Database Initialization
=======================
After the configuration, run the ``accounting-init-db`` console
command to initialize the accounting database. You need to specify
the username of a user as the data creator.
::
% flask --app myapp accounting-init-db -u username
Navigation Menu
===============
Include the navigation menu in the `Bootstrap navigation bar`_ in your
base template:
::
<nav class="navbar navbar-expand-lg bg-body-tertiary bg-dark navbar-dark">
<div class="container-fluid">
...
<div id="collapsible-navbar" class="collapse navbar-collapse">
<ul class="navbar-nav me-auto mb-2 mb-lg-0">
...
{% include "accounting/include/nav.html" %}
...
</ul>
...
</div>
</div>
</nav>
Check your Flask application and see how it works.
Documentation
=============
Refer to the `documentation on Read the Docs`_.
Change Log
==========
Refer to the `change log`_.
Copyright
=========
@ -198,12 +90,5 @@ Authors
.. _PyPI project page: https://pypi.org/project/mia-accounting
.. _release page: https://github.com/imacat/mia-accounting/releases
.. _Git repository: https://github.com/imacat/mia-accounting
.. _CDN: https://en.wikipedia.org/wiki/Content_delivery_network
.. _Bootstrap: https://getbootstrap.com
.. _FontAwesome: https://fontawesome.com
.. _Decimal.js: https://mikemcl.github.io/decimal.js
.. _Tempus-Dominus: https://getdatepicker.com
.. _UserUtilityInterface: https://mia-accounting.readthedocs.io/en/latest/accounting.utils.html#accounting.utils.user.UserUtilityInterface
.. _init_app: https://mia-accounting.readthedocs.io/en/latest/accounting.html#accounting.init_app
.. _Bootstrap navigation bar: https://getbootstrap.com/docs/5.3/components/navbar/
.. _documentation on Read the Docs: https://mia-accounting.readthedocs.io
.. _change log: https://mia-accounting.readthedocs.io/en/latest/changelog.html

View File

@ -76,6 +76,22 @@ accounting.report.reports.unapplied\_accounts module
:undoc-members:
:show-inheritance:
accounting.report.reports.unmatched module
------------------------------------------
.. automodule:: accounting.report.reports.unmatched
:members:
:undoc-members:
:show-inheritance:
accounting.report.reports.unmatched\_accounts module
----------------------------------------------------
.. automodule:: accounting.report.reports.unmatched_accounts
:members:
:undoc-members:
:show-inheritance:
Module contents
---------------

View File

@ -28,6 +28,14 @@ accounting.report.utils.csv\_export module
:undoc-members:
:show-inheritance:
accounting.report.utils.offset\_matcher module
----------------------------------------------
.. automodule:: accounting.report.utils.offset_matcher
:members:
:undoc-members:
:show-inheritance:
accounting.report.utils.option\_link module
-------------------------------------------
@ -60,6 +68,14 @@ accounting.report.utils.unapplied module
:undoc-members:
:show-inheritance:
accounting.report.utils.unmatched module
----------------------------------------
.. automodule:: accounting.report.utils.unmatched
:members:
:undoc-members:
:show-inheritance:
accounting.report.utils.urls module
-----------------------------------

View File

@ -13,7 +13,6 @@ Subpackages
accounting.journal_entry
accounting.option
accounting.report
accounting.unmatched_offset
accounting.utils
Submodules

View File

@ -1,29 +0,0 @@
accounting.unmatched\_offset package
====================================
Submodules
----------
accounting.unmatched\_offset.queries module
-------------------------------------------
.. automodule:: accounting.unmatched_offset.queries
:members:
:undoc-members:
:show-inheritance:
accounting.unmatched\_offset.views module
-----------------------------------------
.. automodule:: accounting.unmatched_offset.views
:members:
:undoc-members:
:show-inheritance:
Module contents
---------------
.. automodule:: accounting.unmatched_offset
:members:
:undoc-members:
:show-inheritance:

View File

@ -52,14 +52,6 @@ accounting.utils.offset\_alias module
:undoc-members:
:show-inheritance:
accounting.utils.offset\_matcher module
---------------------------------------
.. automodule:: accounting.utils.offset_matcher
:members:
:undoc-members:
:show-inheritance:
accounting.utils.options module
-------------------------------
@ -108,14 +100,6 @@ accounting.utils.strip\_text module
:undoc-members:
:show-inheritance:
accounting.utils.unapplied module
---------------------------------
.. automodule:: accounting.utils.unapplied
:members:
:undoc-members:
:show-inheritance:
accounting.utils.user module
----------------------------

310
docs/source/changelog.rst Normal file
View File

@ -0,0 +1,310 @@
Changes
=======
Version 1.5.0
-------------
Released 2023/4/23
* Updated to require ``SQLAlchemy >= 2``.
* Added the change log.
* Added the ``VERSION`` constant to the ``accounting`` module for
the package version, and revised ``pyproject.toml`` and ``conf.py``
to read the version from it.
Version 1.4.1
-------------
Released 2023/4/22
* Updated to allow editing the description of the journal entry line
item with offsets or are offsetting to original line items.
* Updated not to override the existing description of a journal entry
line item after choosing the original line item to offset to.
Version 1.4.0
-------------
Released 2023/4/18
* Rewrote the unapplied original line items and unmatched offsets.
* The unapplied original line items and unmatched offsets are both
in the report submodule. They can be filtered with currency and
period now.
* Show the unapplied original line items and unmatched offsets
together, and added the accumulated balance in the unmatched
offset list, for ease of reference.
* Removed the account code from the journal entry detail and journal
entry form for mobile devices.
* Made the account options in the reports to be scrollable.
Version 1.3.3
-------------
Released 2023/4/13
Changed the sample data generation in the test site live demonstration
from pre-recorded data to real-time generation, to avoid the problem
with the start of months and weeks changed with the date of the
import.
Version 1.3.2
-------------
Released 2023/4/12
Added the sample data generation and database reset on the test site
for live demonstration.
Version 1.3.1
-------------
Released 2023/4/11
* Fixed the permission of the navigation menu of the unmatched offsets.
* Revised the test site to be more accessible as the live demonstration.
Version 1.3.0
-------------
Released 2023/4/11
Added the ``accounting-init-db`` console command to replace all the
other console commands to initialize the accounting database. The
test site does not work with previous versions (<1.3.0).
Version 1.2.1
-------------
Released 2023/4/9
Fixed the search result to allow full ``year/month/day``
specification.
Version 1.2.0
-------------
Released 2023/4/9
* Simplified the URL of the default reports.
* Fixed the crash with malformed Chinese translation.
* Fixed the crash when downloading CSV data with non-US-ASCII
filenames.
Version 1.1.0
-------------
Released 2023/4/9
* Added the unapplied original line item list, to track unpaid
payables, unreceived receivables, assets, prepaids, refundable
deposits, etc.
* Added the offset matcher to match unapplied original line items
with unmatched offsets.
Version 1.0.1
-------------
Released 2023/4/6
Documentation fixes.
Version 1.0.0
-------------
Released 2023/4/6
The first formal release in Flask.
Added the documentation.
Version 0.11.1 (Pre-release)
----------------------------
Released 2023/4/5
Removed the zero balances from the trial balance, the income
statement, and the balance sheet.
Version 0.11.0 (Pre-release)
----------------------------
Released 2023/4/5
* Renamed the project from ``mia-accounting-flask`` to
``mia-accounting``.
* Updated the URL of the reports, as the default views of the
accounting application.
* Updated ``README``.
* Various fixes.
Version 0.10.0 (Pre-release)
----------------------------
Released 2023/4/3
* Added the unauthorized method to the ``UserUtilityInterface``
interface to allow fine control to how to handle the case when the
user has not logged in.
* Revised the JavaScript description editor to respect the account
that the user has confirmed or specifically selected.
* Various fixes.
Version 0.9.1 (Pre-release)
---------------------------
Released 2023/3/24
* A distinguishable look in the option detail than the option form.
* A better look in the new journal entry forms when there is no line
item yet.
* Fixed the search in the original entry selector in the journal
entry form to always do a partial match, to fix the problem that
there is no match when typing is not finished yet.
* Fixed the search in the original entry selector to search the net
balance correctly.
* Replaced the ``editor`` and ``editor2`` accounts with the ``admin``
and ``editor`` accounts.
* Various fixes.
Version 0.9.0 (Pre-release)
---------------------------
Released 2023/3/23
Moved the settings from the ``.env`` file to the option table in the
database that can be set and updated on the web interface. Added the
settings page to show and update the settings.
Version 0.8.0 (Pre-release)
---------------------------
Released 2023/3/22
* Added the recurring transactions to the description editor.
* Added prevention to delete database objects that are essential or
referenced by others with foreign keys.
* Various fixes on the visual layout.
Version 0.7.0 (Pre-release)
---------------------------
Released 2023/3/21
* Renamed "transaction" to "journal entry", and "journal entry" to
"journal entry line item".
* Renamed ``summary`` to ``description``.
* Updated tempus-dominus from version 6.2.10 to 6.4.3.
* Fixed titles and capitalization.
* Fixed to search case-insensitively.
* Added favicon to the test site.
* Fixed the navigation menu when there is no matching endpoint.
* Various fixes.
Version 0.6.0 (Pre-release)
---------------------------
Released 2023/3/18
* Added offset tracking to the journal entries in the payable and
receivable accounts.
* Renamed the ``is_offset_needed`` column to ``is_need_offset`` in
the ``Account`` data model.
Version 0.5.0 (Pre-release)
---------------------------
Released 2023/3/10
Added the accounting reports.
Version 0.4.0 (Pre-release)
---------------------------
Released 2023/3/1
Added the transaction summary helper.
Version 0.3.1 (Pre-release)
---------------------------
Released 2023/2/28
* Fixed the error that cannot select any account when adding new
transactions.
* Fixed the database error when adding new transactions.
* Added the button to convert a cash income or cash expense
transaction to a transfer transaction.
Version 0.3.0 (Pre-release)
---------------------------
Released 2023/2/27
Added the transaction management.
Version 0.2.0 (Pre-release)
---------------------------
Released 2023/2/7
* Added the currency management.
* Changed the ``can_edit`` permission to at least require the user to
log in first.
* Changed the type hint of the ``current_user`` pseudo property of
the ``AbstractUserUtils`` class to return ``None`` when the user
has not logged in.
Version 0.1.1 (Pre-release)
---------------------------
Released 2023/2/3
Finalized the account management, with tests and reordering.
Version 0.1.0 (Pre-release)
---------------------------
Released 2023/2/3
Added the account management, and updated the API to initialize the
accounting application.
Version 0.0.0 (Pre-release)
---------------------------
Released 2023/2/3
Initial release with main account list, localization, pagination,
query, permission, Sphinx documentation, and a test case based on a
test demonstration site.

View File

@ -6,6 +6,7 @@ import os
import sys
sys.path.insert(0, os.path.abspath('../../src/'))
import accounting
# -- Project information -----------------------------------------------------
# https://www.sphinx-doc.org/en/master/usage/configuration.html#project-information
@ -13,7 +14,7 @@ sys.path.insert(0, os.path.abspath('../../src/'))
project = 'Mia! Accounting'
copyright = '2023, imacat'
author = 'imacat'
release = '1.3.3'
release = accounting.VERSION
# -- General configuration ---------------------------------------------------
# https://www.sphinx-doc.org/en/master/usage/configuration.html#general-configuration

View File

@ -14,6 +14,7 @@ Welcome to Mia! Accounting's documentation!
accounting
examples
history
changelog

View File

@ -103,12 +103,6 @@ base template:
Check your Flask application and see how it works.
Documentation
-------------
Refer to the `documentation on Read the Docs`_.
.. _Flask: https://flask.palletsprojects.com
.. _double-entry bookkeeping: https://en.wikipedia.org/wiki/Double-entry_bookkeeping
.. _live demonstration: https://accounting.imacat.idv.tw
@ -123,4 +117,3 @@ Refer to the `documentation on Read the Docs`_.
.. _Decimal.js: https://mikemcl.github.io/decimal.js
.. _Tempus-Dominus: https://getdatepicker.com
.. _Bootstrap navigation bar: https://getbootstrap.com/docs/5.3/components/navbar/
.. _documentation on Read the Docs: https://mia-accounting.readthedocs.io

View File

@ -17,7 +17,7 @@
[project]
name = "mia-accounting"
version = "1.3.3"
dynamic = ["version"]
description = "A Flask accounting module."
readme = "README.rst"
requires-python = ">=3.11"
@ -34,6 +34,7 @@ classifiers = [
]
dependencies = [
"flask",
"SQLAlchemy >= 2",
"Flask-SQLAlchemy",
"Flask-WTF",
"Flask-Babel >= 3",
@ -49,6 +50,7 @@ test = [
[project.urls]
"Documentation" = "https://mia-accounting.readthedocs.io"
"Change Log" = "https://mia-accounting.readthedocs.io/en/latest/changelog.html"
"Repository" = "https://github.com/imacat/mia-accounting"
"Bug Tracker" = "https://github.com/imacat/mia-accounting/issues"
"Demonstration" = "https://accounting.imacat.idv.tw"
@ -57,6 +59,9 @@ test = [
requires = ["setuptools>=42"]
build-backend = "setuptools.build_meta"
[tool.setuptools.dynamic]
version = {attr = "accounting.VERSION"}
[tool.setuptools.exclude-package-data]
"*" = [
"babel.cfg",

View File

@ -24,6 +24,8 @@ from flask_sqlalchemy import SQLAlchemy
from accounting.utils.user import UserUtilityInterface
VERSION: str = "1.5.0"
"""The package version."""
db: SQLAlchemy = SQLAlchemy()
"""The database instance."""
data_dir: Path = Path(__file__).parent / "data"

View File

@ -37,7 +37,8 @@ class JournalEntryConverter(BaseConverter):
:param value: The journal entry ID.
:return: The corresponding journal entry.
"""
journal_entry: JournalEntry | None = db.session.get(JournalEntry, value)
journal_entry: JournalEntry | None \
= db.session.get(JournalEntry, value)
if journal_entry is None:
abort(404)
return journal_entry

View File

@ -30,7 +30,6 @@ from accounting import db
from accounting.forms import CurrencyExists
from accounting.locale import lazy_gettext
from accounting.models import JournalEntryLineItem
from accounting.utils.cast import be
from accounting.utils.offset_alias import offset_alias
from accounting.utils.strip_text import strip_text
from .line_item import LineItemForm, CreditLineItemForm, DebitLineItemForm
@ -75,8 +74,8 @@ class KeepCurrencyWhenHavingOffset:
offset: sa.Alias = offset_alias()
original_line_items: list[JournalEntryLineItem]\
= JournalEntryLineItem.query\
.join(offset, be(JournalEntryLineItem.id
== offset.c.original_line_item_id),
.join(offset,
JournalEntryLineItem.id == offset.c.original_line_item_id,
isouter=True)\
.filter(JournalEntryLineItem.id
.in_({x.id.data for x in form.line_items

View File

@ -33,7 +33,6 @@ from accounting.forms import ACCOUNT_REQUIRED, AccountExists, IsDebitAccount, \
from accounting.locale import lazy_gettext
from accounting.models import Account, JournalEntry, JournalEntryLineItem
from accounting.template_filters import format_amount
from accounting.utils.cast import be
from accounting.utils.random_id import new_id
from accounting.utils.strip_text import strip_text
from accounting.utils.user import get_current_user_pk
@ -198,13 +197,13 @@ class NotExceedingOriginalLineItemNetBalance:
existing_line_item_id \
= {x.id for x in form.journal_entry_form.obj.line_items}
offset_total_func: sa.Function = sa.func.sum(sa.case(
(be(JournalEntryLineItem.is_debit == is_debit),
(JournalEntryLineItem.is_debit == is_debit,
JournalEntryLineItem.amount),
else_=-JournalEntryLineItem.amount))
offset_total_but_form: Decimal | None = db.session.scalar(
sa.select(offset_total_func)
.filter(be(JournalEntryLineItem.original_line_item_id
== original_line_item.id),
.filter(JournalEntryLineItem.original_line_item_id
== original_line_item.id,
JournalEntryLineItem.id.not_in(existing_line_item_id)))
if offset_total_but_form is None:
offset_total_but_form = Decimal("0")
@ -232,8 +231,7 @@ class NotLessThanOffsetTotal:
(JournalEntryLineItem.is_debit != is_debit,
JournalEntryLineItem.amount),
else_=-JournalEntryLineItem.amount)))\
.filter(be(JournalEntryLineItem.original_line_item_id
== form.id.data))
.filter(JournalEntryLineItem.original_line_item_id == form.id.data)
offset_total: Decimal | None = db.session.scalar(select_offset_total)
if offset_total is not None and field.data < offset_total:
raise ValidationError(lazy_gettext(

View File

@ -24,7 +24,6 @@ from sqlalchemy.orm import selectinload
from accounting import db
from accounting.models import Account, JournalEntry, JournalEntryLineItem
from accounting.utils.cast import be
from accounting.utils.offset_alias import offset_alias
@ -45,8 +44,7 @@ def get_selectable_original_line_items(
offset: sa.Alias = offset_alias()
net_balance: sa.Label = (JournalEntryLineItem.amount + sa.func.sum(sa.case(
(offset.c.id.in_(line_item_id_on_form), 0),
(be(offset.c.is_debit == JournalEntryLineItem.is_debit),
offset.c.amount),
(offset.c.is_debit == JournalEntryLineItem.is_debit, offset.c.amount),
else_=-offset.c.amount))).label("net_balance")
conditions: list[sa.BinaryExpression] = [Account.is_need_offset]
sub_conditions: list[sa.BinaryExpression] = []
@ -60,8 +58,8 @@ def get_selectable_original_line_items(
select_net_balances: sa.Select \
= sa.select(JournalEntryLineItem.id, net_balance)\
.join(Account)\
.join(offset, be(JournalEntryLineItem.id
== offset.c.original_line_item_id),
.join(offset,
JournalEntryLineItem.id == offset.c.original_line_item_id,
isouter=True)\
.filter(*conditions)\
.group_by(JournalEntryLineItem.id)\

View File

@ -19,6 +19,7 @@
"""
from __future__ import annotations
import datetime as dt
import re
import typing as t
from decimal import Decimal
@ -27,6 +28,7 @@ import sqlalchemy as sa
from babel import Locale
from flask_babel import get_locale, get_babel
from sqlalchemy import text
from sqlalchemy.orm import Mapped, mapped_column
from accounting import db
from accounting.locale import gettext
@ -37,14 +39,14 @@ class BaseAccount(db.Model):
"""A base account."""
__tablename__ = "accounting_base_accounts"
"""The table name."""
code = db.Column(db.String, nullable=False, primary_key=True)
code: Mapped[str] = mapped_column(primary_key=True)
"""The code."""
title_l10n = db.Column("title", db.String, nullable=False)
title_l10n: Mapped[str] = mapped_column("title")
"""The title."""
l10n = db.relationship("BaseAccountL10n", back_populates="account",
lazy=False)
l10n: Mapped[list[BaseAccountL10n]] \
= db.relationship(back_populates="account", lazy=False)
"""The localized titles."""
accounts = db.relationship("Account", back_populates="base")
accounts: Mapped[list[Account]] = db.relationship(back_populates="base")
"""The descendant accounts under the base account."""
def __str__(self) -> str:
@ -81,17 +83,16 @@ class BaseAccountL10n(db.Model):
"""A localized base account title."""
__tablename__ = "accounting_base_accounts_l10n"
"""The table name."""
account_code = db.Column(db.String,
db.ForeignKey(BaseAccount.code,
onupdate="CASCADE",
ondelete="CASCADE"),
nullable=False, primary_key=True)
account_code: Mapped[str] \
= mapped_column(db.ForeignKey(BaseAccount.code, onupdate="CASCADE",
ondelete="CASCADE"),
primary_key=True)
"""The code of the account."""
account = db.relationship(BaseAccount, back_populates="l10n")
account: Mapped[BaseAccount] = db.relationship(back_populates="l10n")
"""The account."""
locale = db.Column(db.String, nullable=False, primary_key=True)
locale: Mapped[str] = mapped_column(primary_key=True)
"""The locale."""
title = db.Column(db.String, nullable=False)
title: Mapped[str]
"""The localized title."""
@ -99,47 +100,43 @@ class Account(db.Model):
"""An account."""
__tablename__ = "accounting_accounts"
"""The table name."""
id = db.Column(db.Integer, nullable=False, primary_key=True,
autoincrement=False)
id: Mapped[int] = mapped_column(primary_key=True, autoincrement=False)
"""The account ID."""
base_code = db.Column(db.String,
db.ForeignKey(BaseAccount.code, onupdate="CASCADE",
ondelete="CASCADE"),
nullable=False)
base_code: Mapped[str] \
= mapped_column(db.ForeignKey(BaseAccount.code, onupdate="CASCADE",
ondelete="CASCADE"))
"""The code of the base account."""
base = db.relationship(BaseAccount, back_populates="accounts")
base: Mapped[BaseAccount] = db.relationship(back_populates="accounts")
"""The base account."""
no = db.Column(db.Integer, nullable=False, default=text("1"))
no: Mapped[int] = mapped_column(default=text("1"))
"""The account number under the base account."""
title_l10n = db.Column("title", db.String, nullable=False)
title_l10n: Mapped[str] = mapped_column("title")
"""The title."""
is_need_offset = db.Column(db.Boolean, nullable=False, default=False)
is_need_offset: Mapped[bool] = mapped_column(default=False)
"""Whether the journal entry line items of this account need offset."""
created_at = db.Column(db.DateTime(timezone=True), nullable=False,
server_default=db.func.now())
created_at: Mapped[dt.datetime] \
= mapped_column(db.DateTime(timezone=True),
server_default=db.func.now())
"""The time of creation."""
created_by_id = db.Column(db.Integer,
db.ForeignKey(user_pk_column,
onupdate="CASCADE"),
nullable=False)
created_by_id: Mapped[int] \
= mapped_column(db.ForeignKey(user_pk_column, onupdate="CASCADE"))
"""The ID of the creator."""
created_by = db.relationship(user_cls, foreign_keys=created_by_id)
created_by: Mapped[user_cls] = db.relationship(foreign_keys=created_by_id)
"""The creator."""
updated_at = db.Column(db.DateTime(timezone=True), nullable=False,
server_default=db.func.now())
updated_at: Mapped[dt.datetime] \
= mapped_column(db.DateTime(timezone=True),
server_default=db.func.now())
"""The time of last update."""
updated_by_id = db.Column(db.Integer,
db.ForeignKey(user_pk_column,
onupdate="CASCADE"),
nullable=False)
updated_by_id: Mapped[int] \
= mapped_column(db.ForeignKey(user_pk_column, onupdate="CASCADE"))
"""The ID of the updator."""
updated_by = db.relationship(user_cls, foreign_keys=updated_by_id)
updated_by: Mapped[user_cls] = db.relationship(foreign_keys=updated_by_id)
"""The updator."""
l10n = db.relationship("AccountL10n", back_populates="account",
lazy=False)
l10n: Mapped[list[AccountL10n]] \
= db.relationship(back_populates="account", lazy=False)
"""The localized titles."""
line_items = db.relationship("JournalEntryLineItem",
back_populates="account")
line_items: Mapped[list[JournalEntryLineItem]] \
= db.relationship(back_populates="account")
"""The journal entry line items."""
CASH_CODE: str = "1111-001"
@ -352,16 +349,16 @@ class AccountL10n(db.Model):
"""A localized account title."""
__tablename__ = "accounting_accounts_l10n"
"""The table name."""
account_id = db.Column(db.Integer,
db.ForeignKey(Account.id, onupdate="CASCADE",
ondelete="CASCADE"),
nullable=False, primary_key=True)
account_id: Mapped[int] \
= mapped_column(db.ForeignKey(Account.id, onupdate="CASCADE",
ondelete="CASCADE"),
primary_key=True)
"""The account ID."""
account = db.relationship(Account, back_populates="l10n")
account: Mapped[Account] = db.relationship(back_populates="l10n")
"""The account."""
locale = db.Column(db.String, nullable=False, primary_key=True)
locale: Mapped[str] = mapped_column(primary_key=True)
"""The locale."""
title = db.Column(db.String, nullable=False)
title: Mapped[str]
"""The localized title."""
@ -369,35 +366,34 @@ class Currency(db.Model):
"""A currency."""
__tablename__ = "accounting_currencies"
"""The table name."""
code = db.Column(db.String, nullable=False, primary_key=True)
code: Mapped[str] = mapped_column(primary_key=True)
"""The code."""
name_l10n = db.Column("name", db.String, nullable=False)
name_l10n: Mapped[str] = mapped_column("name")
"""The name."""
created_at = db.Column(db.DateTime(timezone=True), nullable=False,
server_default=db.func.now())
created_at: Mapped[dt.datetime] \
= mapped_column(db.DateTime(timezone=True),
server_default=db.func.now())
"""The time of creation."""
created_by_id = db.Column(db.Integer,
db.ForeignKey(user_pk_column,
onupdate="CASCADE"),
nullable=False)
created_by_id: Mapped[int] \
= mapped_column(db.ForeignKey(user_pk_column, onupdate="CASCADE"))
"""The ID of the creator."""
created_by = db.relationship(user_cls, foreign_keys=created_by_id)
created_by: Mapped[user_cls] = db.relationship(foreign_keys=created_by_id)
"""The creator."""
updated_at = db.Column(db.DateTime(timezone=True), nullable=False,
server_default=db.func.now())
updated_at: Mapped[dt.datetime] \
= mapped_column(db.DateTime(timezone=True),
server_default=db.func.now())
"""The time of last update."""
updated_by_id = db.Column(db.Integer,
db.ForeignKey(user_pk_column,
onupdate="CASCADE"),
nullable=False)
updated_by_id: Mapped[int] \
= mapped_column(db.ForeignKey(user_pk_column, onupdate="CASCADE"))
"""The ID of the updator."""
updated_by = db.relationship(user_cls, foreign_keys=updated_by_id)
updated_by: Mapped[user_cls] \
= db.relationship(foreign_keys=updated_by_id)
"""The updator."""
l10n = db.relationship("CurrencyL10n", back_populates="currency",
lazy=False)
l10n: Mapped[list[CurrencyL10n]] \
= db.relationship(back_populates="currency", lazy=False)
"""The localized names."""
line_items = db.relationship("JournalEntryLineItem",
back_populates="currency")
line_items: Mapped[list[JournalEntryLineItem]] \
= db.relationship(back_populates="currency")
"""The journal entry line items."""
def __str__(self) -> str:
@ -479,16 +475,16 @@ class CurrencyL10n(db.Model):
"""A localized currency name."""
__tablename__ = "accounting_currencies_l10n"
"""The table name."""
currency_code = db.Column(db.String,
db.ForeignKey(Currency.code, onupdate="CASCADE",
ondelete="CASCADE"),
nullable=False, primary_key=True)
currency_code: Mapped[str] \
= mapped_column(db.ForeignKey(Currency.code, onupdate="CASCADE",
ondelete="CASCADE"),
primary_key=True)
"""The currency code."""
currency = db.relationship(Currency, back_populates="l10n")
currency: Mapped[Currency] = db.relationship(back_populates="l10n")
"""The currency."""
locale = db.Column(db.String, nullable=False, primary_key=True)
locale: Mapped[str] = mapped_column(primary_key=True)
"""The locale."""
name = db.Column(db.String, nullable=False)
name: Mapped[str]
"""The localized name."""
@ -539,37 +535,34 @@ class JournalEntry(db.Model):
"""A journal entry."""
__tablename__ = "accounting_journal_entries"
"""The table name."""
id = db.Column(db.Integer, nullable=False, primary_key=True,
autoincrement=False)
id: Mapped[int] = mapped_column(primary_key=True, autoincrement=False)
"""The journal entry ID."""
date = db.Column(db.Date, nullable=False)
date: Mapped[dt.date]
"""The date."""
no = db.Column(db.Integer, nullable=False, default=text("1"))
no: Mapped[int] = mapped_column(default=text("1"))
"""The account number under the date."""
note = db.Column(db.String)
note: Mapped[str | None]
"""The note."""
created_at = db.Column(db.DateTime(timezone=True), nullable=False,
server_default=db.func.now())
created_at: Mapped[dt.datetime] \
= mapped_column(db.DateTime(timezone=True),
server_default=db.func.now())
"""The time of creation."""
created_by_id = db.Column(db.Integer,
db.ForeignKey(user_pk_column,
onupdate="CASCADE"),
nullable=False)
created_by_id: Mapped[int] \
= mapped_column(db.ForeignKey(user_pk_column, onupdate="CASCADE"))
"""The ID of the creator."""
created_by = db.relationship(user_cls, foreign_keys=created_by_id)
created_by: Mapped[user_cls] = db.relationship(foreign_keys=created_by_id)
"""The creator."""
updated_at = db.Column(db.DateTime(timezone=True), nullable=False,
server_default=db.func.now())
updated_at: Mapped[dt.datetime] \
= mapped_column(db.DateTime(timezone=True),
server_default=db.func.now())
"""The time of last update."""
updated_by_id = db.Column(db.Integer,
db.ForeignKey(user_pk_column,
onupdate="CASCADE"),
nullable=False)
updated_by_id: Mapped[int] \
= mapped_column(db.ForeignKey(user_pk_column, onupdate="CASCADE"))
"""The ID of the updator."""
updated_by = db.relationship(user_cls, foreign_keys=updated_by_id)
updated_by: Mapped[user_cls] = db.relationship(foreign_keys=updated_by_id)
"""The updator."""
line_items = db.relationship("JournalEntryLineItem",
back_populates="journal_entry")
line_items: Mapped[list[JournalEntryLineItem]] \
= db.relationship(back_populates="journal_entry")
"""The line items."""
def __str__(self) -> str:
@ -659,44 +652,39 @@ class JournalEntryLineItem(db.Model):
"""A line item in the journal entry."""
__tablename__ = "accounting_journal_entry_line_items"
"""The table name."""
id = db.Column(db.Integer, nullable=False, primary_key=True,
autoincrement=False)
id: Mapped[int] = mapped_column(primary_key=True, autoincrement=False)
"""The line item ID."""
journal_entry_id = db.Column(db.Integer,
db.ForeignKey(JournalEntry.id,
onupdate="CASCADE",
ondelete="CASCADE"),
nullable=False)
journal_entry_id: Mapped[int] \
= mapped_column(db.ForeignKey(JournalEntry.id, onupdate="CASCADE",
ondelete="CASCADE"))
"""The journal entry ID."""
journal_entry = db.relationship(JournalEntry, back_populates="line_items")
journal_entry: Mapped[JournalEntry] \
= db.relationship(back_populates="line_items")
"""The journal entry."""
is_debit = db.Column(db.Boolean, nullable=False)
is_debit: Mapped[bool]
"""True for a debit line item, or False for a credit line item."""
no = db.Column(db.Integer, nullable=False)
no: Mapped[int]
"""The line item number under the journal entry and debit or credit."""
original_line_item_id = db.Column(db.Integer,
db.ForeignKey(id, onupdate="CASCADE"),
nullable=True)
original_line_item_id: Mapped[int | None] \
= mapped_column(db.ForeignKey(id, onupdate="CASCADE"))
"""The ID of the original line item."""
original_line_item = db.relationship("JournalEntryLineItem",
remote_side=id, passive_deletes=True)
original_line_item: Mapped[JournalEntryLineItem | None] \
= db.relationship(remote_side=id, passive_deletes=True)
"""The original line item."""
currency_code = db.Column(db.String,
db.ForeignKey(Currency.code, onupdate="CASCADE"),
nullable=False)
currency_code: Mapped[str] \
= mapped_column(db.ForeignKey(Currency.code, onupdate="CASCADE"))
"""The currency code."""
currency = db.relationship(Currency, back_populates="line_items")
currency: Mapped[Currency] = db.relationship(back_populates="line_items")
"""The currency."""
account_id = db.Column(db.Integer,
db.ForeignKey(Account.id,
onupdate="CASCADE"),
nullable=False)
account_id: Mapped[int] \
= mapped_column(db.ForeignKey(Account.id, onupdate="CASCADE"))
"""The account ID."""
account = db.relationship(Account, back_populates="line_items", lazy=False)
account: Mapped[Account] \
= db.relationship(back_populates="line_items", lazy=False)
"""The account."""
description = db.Column(db.String, nullable=True)
description: Mapped[str | None]
"""The description."""
amount = db.Column(db.Numeric(14, 2), nullable=False)
amount: Mapped[Decimal] = mapped_column(db.Numeric(14, 2))
"""The amount."""
def __str__(self) -> str:
@ -891,27 +879,25 @@ class Option(db.Model):
"""An option."""
__tablename__ = "accounting_options"
"""The table name."""
name = db.Column(db.String, nullable=False, primary_key=True)
name: Mapped[str] = mapped_column(primary_key=True)
"""The name."""
value = db.Column(db.Text, nullable=False)
value: Mapped[str] = mapped_column(db.Text)
"""The option value."""
created_at = db.Column(db.DateTime(timezone=True), nullable=False,
server_default=db.func.now())
created_at: Mapped[dt.datetime] \
= mapped_column(db.DateTime(timezone=True),
server_default=db.func.now())
"""The time of creation."""
created_by_id = db.Column(db.Integer,
db.ForeignKey(user_pk_column,
onupdate="CASCADE"),
nullable=False)
created_by_id: Mapped[int] \
= mapped_column(db.ForeignKey(user_pk_column, onupdate="CASCADE"))
"""The ID of the creator."""
created_by = db.relationship(user_cls, foreign_keys=created_by_id)
created_by: Mapped[user_cls] = db.relationship(foreign_keys=created_by_id)
"""The creator."""
updated_at = db.Column(db.DateTime(timezone=True), nullable=False,
server_default=db.func.now())
updated_at: Mapped[dt.datetime] \
= mapped_column(db.DateTime(timezone=True),
server_default=db.func.now())
"""The time of last update."""
updated_by_id = db.Column(db.Integer,
db.ForeignKey(user_pk_column,
onupdate="CASCADE"),
nullable=False)
updated_by_id: Mapped[int] \
= mapped_column(db.ForeignKey(user_pk_column, onupdate="CASCADE"))
"""The ID of the updator."""
updated_by = db.relationship(user_cls, foreign_keys=updated_by_id)
updated_by: Mapped[user_cls] = db.relationship(foreign_keys=updated_by_id)
"""The updator."""

View File

@ -37,7 +37,6 @@ from accounting.report.utils.option_link import OptionLink
from accounting.report.utils.report_chooser import ReportChooser
from accounting.report.utils.report_type import ReportType
from accounting.report.utils.urls import income_expenses_url
from accounting.utils.cast import be
from accounting.utils.current_account import CurrentAccount
from accounting.utils.pagination import Pagination
@ -122,8 +121,7 @@ class LineItemCollector:
else_=-JournalEntryLineItem.amount))
select: sa.Select = sa.Select(balance_func)\
.join(JournalEntry).join(Account)\
.filter(be(JournalEntryLineItem.currency_code
== self.__currency.code),
.filter(JournalEntryLineItem.currency_code == self.__currency.code,
self.__account_condition,
JournalEntry.date < self.__period.start)
balance: int | None = db.session.scalar(select)
@ -347,8 +345,7 @@ class PageParams(BasePageParams):
self.account.id == 0)]
in_use: sa.Select = sa.Select(JournalEntryLineItem.account_id)\
.join(Account)\
.filter(be(JournalEntryLineItem.currency_code
== self.currency.code),
.filter(JournalEntryLineItem.currency_code == self.currency.code,
CurrentAccount.sql_condition())\
.group_by(JournalEntryLineItem.account_id)
options.extend([OptionLink(str(x),

View File

@ -37,7 +37,6 @@ from accounting.report.utils.option_link import OptionLink
from accounting.report.utils.report_chooser import ReportChooser
from accounting.report.utils.report_type import ReportType
from accounting.report.utils.urls import ledger_url
from accounting.utils.cast import be
from accounting.utils.pagination import Pagination
@ -118,10 +117,8 @@ class LineItemCollector:
(JournalEntryLineItem.is_debit, JournalEntryLineItem.amount),
else_=-JournalEntryLineItem.amount))
select: sa.Select = sa.Select(balance_func).join(JournalEntry)\
.filter(be(JournalEntryLineItem.currency_code
== self.__currency.code),
be(JournalEntryLineItem.account_id
== self.__account.id),
.filter(JournalEntryLineItem.currency_code == self.__currency.code,
JournalEntryLineItem.account_id == self.__account.id,
JournalEntry.date < self.__period.start)
balance: int | None = db.session.scalar(select)
if balance is None:
@ -313,8 +310,7 @@ class PageParams(BasePageParams):
:return: The account options.
"""
in_use: sa.Select = sa.Select(JournalEntryLineItem.account_id)\
.filter(be(JournalEntryLineItem.currency_code
== self.currency.code))\
.filter(JournalEntryLineItem.currency_code == self.currency.code)\
.group_by(JournalEntryLineItem.account_id)
return [OptionLink(str(x), ledger_url(self.currency, x, self.period),
x.id == self.account.id)

View File

@ -32,7 +32,6 @@ from accounting.report.utils.base_report import BaseReport
from accounting.report.utils.csv_export import csv_download
from accounting.report.utils.report_chooser import ReportChooser
from accounting.report.utils.report_type import ReportType
from accounting.utils.cast import be
from accounting.utils.pagination import Pagination
from accounting.utils.query import parse_query_keywords
from .journal import get_csv_rows
@ -128,9 +127,8 @@ class LineItemCollector:
journal_entry_date: datetime
try:
journal_entry_date = datetime.strptime(k, "%Y")
conditions.append(
be(sa.extract("year", JournalEntry.date)
== journal_entry_date.year))
conditions.append(sa.extract("year", JournalEntry.date)
== journal_entry_date.year)
except ValueError:
pass
try:

View File

@ -24,7 +24,6 @@ import sqlalchemy as sa
from accounting import db
from accounting.models import Currency, Account, JournalEntry, \
JournalEntryLineItem
from accounting.utils.cast import be
from accounting.utils.offset_alias import offset_alias
@ -38,17 +37,17 @@ def get_accounts_with_unapplied(currency: Currency) -> list[Account]:
net_balance: sa.Label \
= (JournalEntryLineItem.amount
+ sa.func.sum(sa.case(
(be(offset.c.is_debit == JournalEntryLineItem.is_debit),
(offset.c.is_debit == JournalEntryLineItem.is_debit,
offset.c.amount),
else_=-offset.c.amount))).label("net_balance")
select_unapplied: sa.Select \
= sa.select(JournalEntryLineItem.id)\
.join(JournalEntry).join(Account)\
.join(offset, be(JournalEntryLineItem.id
== offset.c.original_line_item_id),
.join(offset,
JournalEntryLineItem.id == offset.c.original_line_item_id,
isouter=True)\
.filter(Account.is_need_offset,
be(JournalEntryLineItem.currency_code == currency.code),
JournalEntryLineItem.currency_code == currency.code,
sa.or_(sa.and_(Account.base_code.startswith("2"),
sa.not_(JournalEntryLineItem.is_debit)),
sa.and_(Account.base_code.startswith("1"),
@ -84,17 +83,17 @@ def get_net_balances(currency: Currency, account: Account) \
net_balance: sa.Label \
= (JournalEntryLineItem.amount
+ sa.func.sum(sa.case(
(be(offset.c.is_debit == JournalEntryLineItem.is_debit),
(offset.c.is_debit == JournalEntryLineItem.is_debit,
offset.c.amount),
else_=-offset.c.amount))).label("net_balance")
select_net_balances: sa.Select \
= sa.select(JournalEntryLineItem.id, net_balance) \
.join(JournalEntry).join(Account) \
.join(offset, be(JournalEntryLineItem.id
== offset.c.original_line_item_id),
.join(offset,
JournalEntryLineItem.id == offset.c.original_line_item_id,
isouter=True) \
.filter(be(Account.id == account.id),
be(JournalEntryLineItem.currency_code == currency.code),
.filter(Account.id == account.id,
JournalEntryLineItem.currency_code == currency.code,
sa.or_(sa.and_(Account.base_code.startswith("2"),
sa.not_(JournalEntryLineItem.is_debit)),
sa.and_(Account.base_code.startswith("1"),

View File

@ -22,7 +22,6 @@ import sqlalchemy as sa
from accounting import db
from accounting.models import Currency, Account, JournalEntry, \
JournalEntryLineItem
from accounting.utils.cast import be
def get_accounts_with_unmatched(currency: Currency) -> list[Account]:
@ -38,7 +37,7 @@ def get_accounts_with_unmatched(currency: Currency) -> list[Account]:
.select_from(Account)\
.join(JournalEntryLineItem, isouter=True).join(JournalEntry)\
.filter(Account.is_need_offset,
be(JournalEntryLineItem.currency_code == currency.code),
JournalEntryLineItem.currency_code == currency.code,
JournalEntryLineItem.original_line_item_id.is_(None),
sa.or_(sa.and_(Account.base_code.startswith("2"),
JournalEntryLineItem.is_debit),

View File

@ -277,13 +277,16 @@ class JournalEntryLineItemEditor {
this.originalLineItemText = originalLineItem.text;
this.#originalLineItemText.innerText = originalLineItem.text;
this.#setEnableDescriptionAccount(false);
if (originalLineItem.description === "") {
this.#descriptionControl.classList.remove("accounting-not-empty");
} else {
this.#descriptionControl.classList.add("accounting-not-empty");
if (this.description === null) {
if (originalLineItem.description === "") {
this.#descriptionControl.classList.remove("accounting-not-empty");
} else {
this.#descriptionControl.classList.add("accounting-not-empty");
}
this.description = originalLineItem.description === ""? null: originalLineItem.description;
this.#descriptionText.innerText = originalLineItem.description;
}
this.description = originalLineItem.description === ""? null: originalLineItem.description;
this.#descriptionText.innerText = originalLineItem.description;
this.#setEnableAccount(false);
this.#accountControl.classList.add("accounting-not-empty");
this.account = originalLineItem.account.copy();
this.isAccountConfirmed = false;
@ -305,7 +308,7 @@ class JournalEntryLineItemEditor {
this.originalLineItemDate = null;
this.originalLineItemText = null;
this.#originalLineItemText.innerText = "";
this.#setEnableDescriptionAccount(true);
this.#setEnableAccount(true);
this.#accountControl.classList.remove("accounting-not-empty");
this.account = null;
this.isAccountConfirmed = false;
@ -472,12 +475,13 @@ class JournalEntryLineItemEditor {
this.originalLineItemDate = null;
this.originalLineItemText = null;
this.#originalLineItemText.innerText = "";
this.#setEnableDescriptionAccount(true);
this.#descriptionControl.dataset.bsTarget = `#accounting-description-editor-${this.#debitCreditSubForm.debitCredit}-modal`;
this.#descriptionControl.classList.remove("accounting-not-empty");
this.#descriptionControl.classList.remove("is-invalid");
this.description = null;
this.#descriptionText.innerText = ""
this.#descriptionError.innerText = ""
this.#setEnableAccount(true);
this.#accountControl.classList.remove("accounting-not-empty");
this.#accountControl.classList.remove("is-invalid");
this.account = null;
@ -511,7 +515,7 @@ class JournalEntryLineItemEditor {
this.#originalLineItemContainer.classList.remove("d-none");
this.#originalLineItemControl.classList.add("accounting-not-empty");
}
this.#setEnableDescriptionAccount(!lineItem.isMatched && this.originalLineItemId === null);
this.#descriptionControl.dataset.bsTarget = `#accounting-description-editor-${this.#debitCreditSubForm.debitCredit}-modal`;
this.description = lineItem.description;
if (this.description === null) {
this.#descriptionControl.classList.remove("accounting-not-empty");
@ -519,6 +523,7 @@ class JournalEntryLineItemEditor {
this.#descriptionControl.classList.add("accounting-not-empty");
}
this.#descriptionText.innerText = this.description === null? "": this.description;
this.#setEnableAccount(!lineItem.isMatched && this.originalLineItemId === null);
this.account = lineItem.account;
this.isAccountConfirmed = true;
if (this.account === null) {
@ -547,25 +552,17 @@ class JournalEntryLineItemEditor {
}
/**
* Sets the enable status of the description and account.
* Sets the enable status of the account.
*
* @param isEnabled {boolean} true to enable, or false otherwise
*/
#setEnableDescriptionAccount(isEnabled) {
#setEnableAccount(isEnabled) {
if (isEnabled) {
this.#descriptionControl.dataset.bsToggle = "modal";
this.#descriptionControl.dataset.bsTarget = `#accounting-description-editor-${this.#debitCreditSubForm.debitCredit}-modal`;
this.#descriptionControl.classList.remove("accounting-disabled");
this.#descriptionControl.classList.add("accounting-clickable");
this.#accountControl.dataset.bsToggle = "modal";
this.#accountControl.dataset.bsTarget = `#accounting-account-selector-${this.#debitCreditSubForm.debitCredit}-modal`;
this.#accountControl.classList.remove("accounting-disabled");
this.#accountControl.classList.add("accounting-clickable");
} else {
this.#descriptionControl.dataset.bsToggle = "";
this.#descriptionControl.dataset.bsTarget = "";
this.#descriptionControl.classList.add("accounting-disabled");
this.#descriptionControl.classList.remove("accounting-clickable");
this.#accountControl.dataset.bsToggle = "";
this.#accountControl.dataset.bsTarget = "";
this.#accountControl.classList.add("accounting-disabled");

View File

@ -21,7 +21,7 @@ from accounting.models import Currency
from accounting.utils.options import options
def currency_options() -> str:
def currency_options() -> list[Currency]:
"""Returns the currency options.
:return: The currency options.

View File

@ -6,10 +6,10 @@
#
msgid ""
msgstr ""
"Project-Id-Version: mia-accounting 1.1.1\n"
"Project-Id-Version: mia-accounting 1.4.0\n"
"Report-Msgid-Bugs-To: imacat@mail.imacat.idv.tw\n"
"POT-Creation-Date: 2023-04-09 01:41+0800\n"
"PO-Revision-Date: 2023-04-09 01:41+0800\n"
"POT-Creation-Date: 2023-04-18 09:32+0800\n"
"PO-Revision-Date: 2023-04-18 09:32+0800\n"
"Last-Translator: imacat <imacat@mail.imacat.idv.tw>\n"
"Language: zh_Hant\n"
"Language-Team: zh_Hant <imacat@mail.imacat.idv.tw>\n"
@ -20,7 +20,7 @@ msgstr ""
"Generated-By: Babel 2.12.1\n"
#: src/accounting/forms.py:33
#: src/accounting/static/js/journal-entry-form.js:1065
#: src/accounting/static/js/journal-entry-form.js:1080
#: src/accounting/static/js/journal-entry-line-item-editor.js:411
#: src/accounting/static/js/option-form.js:537
#: src/accounting/static/js/option-form.js:803
@ -302,11 +302,11 @@ msgstr "金額不可超過原始分錄凈額 %(balance)s 。"
msgid "The amount must not be less than the offset total %(total)s."
msgstr "金額不可低於抵銷總額 %(total)s 。"
#: src/accounting/journal_entry/forms/line_item.py:413
#: src/accounting/journal_entry/forms/line_item.py:426
msgid "This account is not for debit line items."
msgstr "科目不是借方科目。"
#: src/accounting/journal_entry/forms/line_item.py:465
#: src/accounting/journal_entry/forms/line_item.py:478
msgid "This account is not for credit line items."
msgstr "科目不是貸方科目。"
@ -354,6 +354,15 @@ msgstr "設定未異動。"
msgid "The settings are saved successfully."
msgstr "設定存好了。"
#: src/accounting/report/views.py:401
msgid "No more offset to match automatically."
msgstr "無法自動配對抵銷。"
#: src/accounting/report/views.py:408
#, python-format
msgid "Matched %(matches)s offsets."
msgstr "抵銷了 %(matches)s 筆。"
#: src/accounting/report/period/description.py:33
msgid "for all time"
msgstr "全部"
@ -423,16 +432,16 @@ msgstr "全部"
#: src/accounting/templates/accounting/journal-entry/receipt/detail.html:43
#: src/accounting/templates/accounting/journal-entry/transfer/detail.html:39
#: src/accounting/templates/accounting/journal-entry/transfer/detail.html:55
#: src/accounting/templates/accounting/report/balance-sheet.html:59
#: src/accounting/templates/accounting/report/balance-sheet.html:71
#: src/accounting/templates/accounting/report/balance-sheet.html:81
#: src/accounting/templates/accounting/report/balance-sheet.html:65
#: src/accounting/templates/accounting/report/balance-sheet.html:77
#: src/accounting/templates/accounting/report/balance-sheet.html:87
#: src/accounting/templates/accounting/report/balance-sheet.html:96
#: src/accounting/templates/accounting/report/balance-sheet.html:103
#: src/accounting/templates/accounting/report/balance-sheet.html:93
#: src/accounting/templates/accounting/report/balance-sheet.html:102
#: src/accounting/templates/accounting/report/balance-sheet.html:109
#: src/accounting/templates/accounting/report/income-expenses.html:81
#: src/accounting/templates/accounting/report/income-statement.html:83
#: src/accounting/templates/accounting/report/income-statement.html:89
#: src/accounting/templates/accounting/report/ledger.html:82
#: src/accounting/templates/accounting/report/trial-balance.html:74
#: src/accounting/templates/accounting/report/trial-balance.html:80
msgid "Total"
msgstr "合計"
@ -444,42 +453,47 @@ msgstr "前期轉入"
#: src/accounting/report/reports/income_expenses.py:407
#: src/accounting/report/reports/journal.py:158
#: src/accounting/report/reports/ledger.py:366
#: src/accounting/report/reports/unapplied.py:137
#: src/accounting/report/reports/unapplied.py:148
#: src/accounting/report/reports/unmatched.py:158
#: src/accounting/templates/accounting/journal-entry/include/form.html:50
#: src/accounting/templates/accounting/report/include/period-chooser.html:111
#: src/accounting/templates/accounting/report/income-expenses.html:55
#: src/accounting/templates/accounting/report/journal.html:53
#: src/accounting/templates/accounting/report/ledger.html:55
#: src/accounting/templates/accounting/report/search.html:50
#: src/accounting/templates/accounting/report/unapplied.html:50
#: src/accounting/templates/accounting/report/unapplied.html:52
#: src/accounting/templates/accounting/report/unmatched.html:93
msgid "Date"
msgstr "日期"
#: src/accounting/report/reports/income_expenses.py:407
#: src/accounting/report/reports/journal.py:159
#: src/accounting/report/reports/trial_balance.py:225
#: src/accounting/report/reports/unapplied_accounts.py:109
#: src/accounting/report/reports/unapplied_accounts.py:122
#: src/accounting/report/reports/unmatched_accounts.py:122
#: src/accounting/templates/accounting/journal-entry/include/journal-entry-line-item-editor-modal.html:57
#: src/accounting/templates/accounting/option/include/recurring-item-editor-modal.html:39
#: src/accounting/templates/accounting/report/include/toolbar-buttons.html:90
#: src/accounting/templates/accounting/report/income-expenses.html:56
#: src/accounting/templates/accounting/report/journal.html:55
#: src/accounting/templates/accounting/report/search.html:52
#: src/accounting/templates/accounting/report/trial-balance.html:55
#: src/accounting/templates/accounting/report/trial-balance.html:61
msgid "Account"
msgstr "科目"
#: src/accounting/report/reports/income_expenses.py:408
#: src/accounting/report/reports/journal.py:159
#: src/accounting/report/reports/ledger.py:366
#: src/accounting/report/reports/unapplied.py:138
#: src/accounting/report/reports/unapplied.py:149
#: src/accounting/report/reports/unmatched.py:159
#: src/accounting/templates/accounting/journal-entry/include/description-editor-modal.html:28
#: src/accounting/templates/accounting/journal-entry/include/journal-entry-line-item-editor-modal.html:49
#: src/accounting/templates/accounting/report/income-expenses.html:57
#: src/accounting/templates/accounting/report/journal.html:56
#: src/accounting/templates/accounting/report/ledger.html:56
#: src/accounting/templates/accounting/report/search.html:53
#: src/accounting/templates/accounting/report/unapplied.html:52
#: src/accounting/templates/accounting/report/unapplied.html:53
#: src/accounting/templates/accounting/report/unmatched.html:94
msgid "Description"
msgstr "摘要"
@ -495,8 +509,10 @@ msgstr "支出"
#: src/accounting/report/reports/income_expenses.py:409
#: src/accounting/report/reports/ledger.py:368
#: src/accounting/report/reports/unmatched.py:160
#: src/accounting/templates/accounting/report/income-expenses.html:60
#: src/accounting/templates/accounting/report/ledger.html:60
#: src/accounting/templates/accounting/report/unmatched.html:97
msgid "Balance"
msgstr "餘額"
@ -533,64 +549,88 @@ msgid "net income or loss for current period"
msgstr "本期損益"
#: src/accounting/report/reports/income_statement.py:301
#: src/accounting/report/reports/unapplied.py:138
#: src/accounting/report/reports/unapplied.py:149
#: src/accounting/templates/accounting/journal-entry/include/journal-entry-line-item-editor-modal.html:65
#: src/accounting/templates/accounting/report/income-statement.html:55
#: src/accounting/templates/accounting/report/unapplied.html:53
#: src/accounting/templates/accounting/report/income-statement.html:61
#: src/accounting/templates/accounting/report/unapplied.html:54
msgid "Amount"
msgstr "金額"
#: src/accounting/report/reports/journal.py:158
#: src/accounting/report/reports/unapplied.py:137
#: src/accounting/report/reports/unapplied.py:148
#: src/accounting/report/reports/unmatched.py:158
#: src/accounting/templates/accounting/journal-entry/include/form-currency.html:33
#: src/accounting/templates/accounting/report/include/toolbar-buttons.html:73
#: src/accounting/templates/accounting/report/journal.html:54
#: src/accounting/templates/accounting/report/search.html:51
#: src/accounting/templates/accounting/report/unapplied.html:51
msgid "Currency"
msgstr "貨幣"
#: src/accounting/report/reports/journal.py:160
#: src/accounting/report/reports/ledger.py:367
#: src/accounting/report/reports/trial_balance.py:225
#: src/accounting/report/reports/unmatched.py:159
#: src/accounting/templates/accounting/journal-entry/transfer/detail.html:33
#: src/accounting/templates/accounting/journal-entry/transfer/include/form-currency.html:30
#: src/accounting/templates/accounting/report/journal.html:57
#: src/accounting/templates/accounting/report/ledger.html:57
#: src/accounting/templates/accounting/report/search.html:54
#: src/accounting/templates/accounting/report/trial-balance.html:56
#: src/accounting/templates/accounting/report/trial-balance.html:62
#: src/accounting/templates/accounting/report/unmatched.html:95
msgid "Debit"
msgstr "借方"
#: src/accounting/report/reports/journal.py:160
#: src/accounting/report/reports/ledger.py:367
#: src/accounting/report/reports/trial_balance.py:226
#: src/accounting/report/reports/unmatched.py:160
#: src/accounting/templates/accounting/journal-entry/transfer/detail.html:49
#: src/accounting/templates/accounting/journal-entry/transfer/include/form-currency.html:41
#: src/accounting/templates/accounting/report/journal.html:58
#: src/accounting/templates/accounting/report/ledger.html:58
#: src/accounting/templates/accounting/report/search.html:55
#: src/accounting/templates/accounting/report/trial-balance.html:57
#: src/accounting/templates/accounting/report/trial-balance.html:63
#: src/accounting/templates/accounting/report/unmatched.html:96
msgid "Credit"
msgstr "貸方"
#: src/accounting/report/reports/unapplied.py:121
#: src/accounting/report/reports/unapplied_accounts.py:93
#: src/accounting/report/reports/unapplied.py:132
#: src/accounting/report/reports/unapplied_accounts.py:107
#: src/accounting/report/reports/unmatched.py:142
#: src/accounting/report/reports/unmatched_accounts.py:107
#: src/accounting/templates/accounting/include/nav.html:39
msgid "Accounts"
msgstr "科目"
#: src/accounting/report/reports/unapplied.py:139
#: src/accounting/templates/accounting/report/unapplied.html:54
#: src/accounting/report/reports/unapplied.py:150
#: src/accounting/templates/accounting/report/unapplied.html:55
msgid "Net Balance"
msgstr "淨額"
#: src/accounting/report/reports/unapplied_accounts.py:109
#: src/accounting/templates/accounting/report/unapplied-accounts.html:47
#: src/accounting/report/reports/unapplied_accounts.py:122
#: src/accounting/report/reports/unmatched_accounts.py:122
#: src/accounting/templates/accounting/report/unapplied-accounts.html:59
#: src/accounting/templates/accounting/report/unmatched-accounts.html:59
msgid "Count"
msgstr "數量"
#: src/accounting/report/utils/report_chooser.py:82
#: src/accounting/report/utils/offset_matcher.py:163
msgid "There is no unmatched offset."
msgstr "沒有遺漏的抵銷分錄"
#: src/accounting/report/utils/offset_matcher.py:167
#, python-format
msgid "%(total)s unmatched offsets without original items."
msgstr "%(total)s 筆遺漏的抵銷分錄無法自動抵銷。"
#: src/accounting/report/utils/offset_matcher.py:172
#, python-format
msgid ""
"%(matches)s unmatched offsets out of %(total)s can match with their "
"original items."
msgstr "%(total)s 筆遺漏的抵銷分錄中,可配對抵銷掉 %(matches)s 筆。"
#: src/accounting/report/utils/report_chooser.py:86
#: src/accounting/templates/accounting/account/include/form.html:98
#: src/accounting/templates/accounting/account/list.html:40
#: src/accounting/templates/accounting/base-account/list.html:34
@ -606,114 +646,119 @@ msgstr "數量"
msgid "Search"
msgstr "搜尋"
#: src/accounting/report/utils/report_chooser.py:93
#: src/accounting/report/utils/report_chooser.py:97
msgid "Income and Expenses Log"
msgstr "收支帳"
#: src/accounting/report/utils/report_chooser.py:106
#: src/accounting/report/utils/report_chooser.py:110
msgid "Ledger"
msgstr "分類帳"
#: src/accounting/report/utils/report_chooser.py:118
#: src/accounting/report/utils/report_chooser.py:122
msgid "Journal"
msgstr "日記簿"
#: src/accounting/report/utils/report_chooser.py:128
#: src/accounting/report/utils/report_chooser.py:132
msgid "Trial Balance"
msgstr "試算表"
#: src/accounting/report/utils/report_chooser.py:139
#: src/accounting/report/utils/report_chooser.py:143
msgid "Income Statement"
msgstr "損益表"
#: src/accounting/report/utils/report_chooser.py:150
#: src/accounting/report/utils/report_chooser.py:154
msgid "Balance Sheet"
msgstr "資產負債表"
#: src/accounting/report/utils/report_chooser.py:163
#: src/accounting/report/utils/report_chooser.py:167
msgid "Unapplied Original Line Items"
msgstr "未抵銷原始分錄"
#: src/accounting/report/utils/report_chooser.py:171
msgid "Unapplied Items"
msgstr "未抵銷項目"
#: src/accounting/report/utils/report_chooser.py:184
#: src/accounting/report/utils/report_chooser.py:188
msgid "Unmatched Offsets"
msgstr "遺漏的抵銷項目"
#: src/accounting/static/js/account-form.js:206
msgid "Please fill in the title."
msgstr "請填上標題。"
#: src/accounting/static/js/description-editor.js:951
#: src/accounting/static/js/description-editor.js:1129
#: src/accounting/static/js/description-editor.js:952
#: src/accounting/static/js/description-editor.js:1130
msgid "Please fill in the tag."
msgstr "請填上標籤。"
#: src/accounting/static/js/description-editor.js:961
#: src/accounting/static/js/description-editor.js:1149
#: src/accounting/static/js/description-editor.js:962
#: src/accounting/static/js/description-editor.js:1150
msgid "Please fill in the origin."
msgstr "請填上起點。"
#: src/accounting/static/js/description-editor.js:971
#: src/accounting/static/js/description-editor.js:1159
#: src/accounting/static/js/description-editor.js:972
#: src/accounting/static/js/description-editor.js:1160
msgid "Please fill in the destination."
msgstr "請填上終點。"
#: src/accounting/static/js/description-editor.js:1139
#: src/accounting/static/js/description-editor.js:1140
msgid "Please fill in the route."
msgstr "請填上路線名稱。"
#: src/accounting/static/js/description-editor.js:1192
#: src/accounting/static/js/description-editor.js:1193
msgid "January"
msgstr "一月"
#: src/accounting/static/js/description-editor.js:1192
#: src/accounting/static/js/description-editor.js:1193
msgid "February"
msgstr "二月"
#: src/accounting/static/js/description-editor.js:1192
#: src/accounting/static/js/description-editor.js:1193
msgid "March"
msgstr "三月"
#: src/accounting/static/js/description-editor.js:1192
#: src/accounting/static/js/description-editor.js:1193
msgid "April"
msgstr "四月"
#: src/accounting/static/js/description-editor.js:1193
#: src/accounting/static/js/description-editor.js:1194
msgid "May"
msgstr "五月"
#: src/accounting/static/js/description-editor.js:1193
#: src/accounting/static/js/description-editor.js:1194
msgid "June"
msgstr "六月"
#: src/accounting/static/js/description-editor.js:1193
#: src/accounting/static/js/description-editor.js:1194
msgid "July"
msgstr "七月"
#: src/accounting/static/js/description-editor.js:1193
#: src/accounting/static/js/description-editor.js:1194
msgid "August"
msgstr "八月"
#: src/accounting/static/js/description-editor.js:1194
#: src/accounting/static/js/description-editor.js:1195
msgid "September"
msgstr "九月"
#: src/accounting/static/js/description-editor.js:1194
#: src/accounting/static/js/description-editor.js:1195
msgid "October"
msgstr "十月"
#: src/accounting/static/js/description-editor.js:1194
#: src/accounting/static/js/description-editor.js:1195
msgid "November"
msgstr "十一月"
#: src/accounting/static/js/description-editor.js:1194
#: src/accounting/static/js/description-editor.js:1195
msgid "December"
msgstr "十二月"
#: src/accounting/static/js/journal-entry-form.js:1070
#: src/accounting/static/js/journal-entry-form.js:1085
#: src/accounting/static/js/journal-entry-line-item-editor.js:430
msgid "Please fill in the amount."
msgstr "請填上金額。"
#: src/accounting/static/js/journal-entry-form.js:1092
#: src/accounting/templates/accounting/journal-entry/include/detail-line-items.html:34
#: src/accounting/templates/accounting/journal-entry/include/form-line-item.html:38
#: src/accounting/static/js/journal-entry-form.js:1107
#: src/accounting/templates/accounting/journal-entry/include/detail-line-items.html:37
#: src/accounting/templates/accounting/journal-entry/include/form-line-item.html:41
#, python-format
msgid "Offset %(item)s"
msgstr "抵銷 %(item)s"
@ -756,7 +801,6 @@ msgstr "新增科目"
#: src/accounting/templates/accounting/journal-entry/include/form.html:38
#: src/accounting/templates/accounting/journal-entry/order.html:36
#: src/accounting/templates/accounting/option/form.html:36
#: src/accounting/templates/accounting/unmatched-offset/list.html:31
msgid "Back"
msgstr "回上頁"
@ -797,7 +841,7 @@ msgstr "確認刪除科目"
#: src/accounting/templates/accounting/option/include/recurring-item-editor-modal.html:28
#: src/accounting/templates/accounting/report/include/period-chooser.html:27
#: src/accounting/templates/accounting/report/include/search-modal.html:28
#: src/accounting/templates/accounting/unmatched-offset/list.html:54
#: src/accounting/templates/accounting/report/unmatched.html:58
msgid "Close"
msgstr "關閉"
@ -815,7 +859,7 @@ msgstr "你確定要刪掉這個科目嗎?"
#: src/accounting/templates/accounting/option/include/recurring-account-selector-modal.html:48
#: src/accounting/templates/accounting/option/include/recurring-item-editor-modal.html:65
#: src/accounting/templates/accounting/report/include/search-modal.html:37
#: src/accounting/templates/accounting/unmatched-offset/list.html:70
#: src/accounting/templates/accounting/report/unmatched.html:74
msgid "Cancel"
msgstr "取消"
@ -823,7 +867,7 @@ msgstr "取消"
#: src/accounting/templates/accounting/currency/detail.html:80
#: src/accounting/templates/accounting/journal-entry/include/detail.html:85
#: src/accounting/templates/accounting/report/include/period-chooser.html:141
#: src/accounting/templates/accounting/unmatched-offset/list.html:71
#: src/accounting/templates/accounting/report/unmatched.html:75
msgid "Confirm"
msgstr "確定"
@ -871,22 +915,22 @@ msgstr "新增"
#: src/accounting/templates/accounting/base-account/list.html:51
#: src/accounting/templates/accounting/currency/list.html:65
#: src/accounting/templates/accounting/journal-entry/include/account-selector-modal.html:46
#: src/accounting/templates/accounting/journal-entry/include/original-line-item-selector-modal.html:51
#: src/accounting/templates/accounting/journal-entry/include/original-line-item-selector-modal.html:58
#: src/accounting/templates/accounting/journal-entry/order.html:82
#: src/accounting/templates/accounting/option/detail.html:67
#: src/accounting/templates/accounting/option/detail.html:83
#: src/accounting/templates/accounting/option/include/recurring-account-selector-modal.html:45
#: src/accounting/templates/accounting/report/balance-sheet.html:110
#: src/accounting/templates/accounting/report/balance-sheet.html:116
#: src/accounting/templates/accounting/report/income-expenses.html:113
#: src/accounting/templates/accounting/report/income-statement.html:96
#: src/accounting/templates/accounting/report/income-statement.html:102
#: src/accounting/templates/accounting/report/journal.html:103
#: src/accounting/templates/accounting/report/ledger.html:116
#: src/accounting/templates/accounting/report/search.html:100
#: src/accounting/templates/accounting/report/trial-balance.html:82
#: src/accounting/templates/accounting/report/unapplied-accounts.html:61
#: src/accounting/templates/accounting/report/unapplied.html:98
#: src/accounting/templates/accounting/unmatched-offset/dashboard.html:37
#: src/accounting/templates/accounting/unmatched-offset/list.html:104
#: src/accounting/templates/accounting/report/trial-balance.html:88
#: src/accounting/templates/accounting/report/unapplied-accounts.html:76
#: src/accounting/templates/accounting/report/unapplied.html:90
#: src/accounting/templates/accounting/report/unmatched-accounts.html:76
#: src/accounting/templates/accounting/report/unmatched.html:147
msgid "There is no data."
msgstr "沒有資料。"
@ -984,12 +1028,7 @@ msgstr "基本科目"
msgid "Currencies"
msgstr "貨幣"
#: src/accounting/templates/accounting/include/nav.html:57
#: src/accounting/templates/accounting/unmatched-offset/dashboard.html:24
msgid "Unmatched Offsets"
msgstr "遺漏的抵銷分錄"
#: src/accounting/templates/accounting/include/nav.html:64
#: src/accounting/templates/accounting/include/nav.html:58
#: src/accounting/templates/accounting/option/detail.html:24
#: src/accounting/templates/accounting/option/detail.html:41
#: src/accounting/templates/accounting/option/form.html:29
@ -1088,23 +1127,23 @@ msgstr "路線"
msgid "The Number of Items"
msgstr "數量"
#: src/accounting/templates/accounting/journal-entry/include/detail-line-items.html:42
#: src/accounting/templates/accounting/journal-entry/include/form-line-item.html:43
#: src/accounting/templates/accounting/journal-entry/include/detail-line-items.html:45
#: src/accounting/templates/accounting/journal-entry/include/form-line-item.html:46
msgid "Offsets"
msgstr "抵銷"
#: src/accounting/templates/accounting/journal-entry/include/detail-line-items.html:55
#: src/accounting/templates/accounting/journal-entry/include/form-line-item.html:54
#: src/accounting/templates/accounting/journal-entry/include/detail-line-items.html:58
#: src/accounting/templates/accounting/journal-entry/include/form-line-item.html:57
msgid "Net balance"
msgstr "淨額"
#: src/accounting/templates/accounting/journal-entry/include/detail-line-items.html:60
#: src/accounting/templates/accounting/journal-entry/include/form-line-item.html:51
#: src/accounting/templates/accounting/journal-entry/include/detail-line-items.html:63
#: src/accounting/templates/accounting/journal-entry/include/form-line-item.html:54
msgid "Fully offset"
msgstr "全部抵銷"
#: src/accounting/templates/accounting/journal-entry/include/detail-line-items.html:65
#: src/accounting/templates/accounting/journal-entry/include/form-line-item.html:59
#: src/accounting/templates/accounting/journal-entry/include/detail-line-items.html:68
#: src/accounting/templates/accounting/journal-entry/include/form-line-item.html:62
msgid "Unmatched"
msgstr "未抵銷"
@ -1217,18 +1256,35 @@ msgid "Water bill for {last_bimonthly_name}"
msgstr "水費{last_bimonthly_number}月"
#: src/accounting/templates/accounting/report/balance-sheet.html:29
#: src/accounting/templates/accounting/report/balance-sheet.html:49
#: src/accounting/templates/accounting/report/balance-sheet.html:51
#, python-format
msgid "Balance Sheet %(period)s"
msgstr "%(period)s資產負債表"
#: src/accounting/templates/accounting/report/balance-sheet.html:29
#: src/accounting/templates/accounting/report/balance-sheet.html:53
#, python-format
msgid "Balance Sheet of %(currency)s %(period)s"
msgstr "%(period)s%(currency)s資產負債表"
#: src/accounting/templates/accounting/report/income-expenses.html:29
#, python-format
msgid "Income and Expenses Log of %(account)s %(period)s"
msgstr "%(period)s%(account)s收支帳"
#: src/accounting/templates/accounting/report/income-expenses.html:29
#, python-format
msgid "Income and Expenses Log of %(account)s in %(currency)s %(period)s"
msgstr "%(period)s%(currency)s%(account)s收支帳"
#: src/accounting/templates/accounting/report/income-statement.html:29
#: src/accounting/templates/accounting/report/income-statement.html:49
#: src/accounting/templates/accounting/report/income-statement.html:51
#, python-format
msgid "Income Statement %(period)s"
msgstr "%(period)s損益表"
#: src/accounting/templates/accounting/report/income-statement.html:29
#: src/accounting/templates/accounting/report/income-statement.html:53
#, python-format
msgid "Income Statement of %(currency)s %(period)s"
msgstr "%(period)s%(currency)s損益表"
@ -1238,31 +1294,89 @@ msgstr "%(period)s%(currency)s損益表"
msgid "Journal %(period)s"
msgstr "%(period)s日記簿"
#: src/accounting/templates/accounting/report/ledger.html:29
#, python-format
msgid "Ledger of %(account)s %(period)s"
msgstr "%(period)s%(account)s分類帳"
#: src/accounting/templates/accounting/report/ledger.html:29
#, python-format
msgid "Ledger of %(account)s in %(currency)s %(period)s"
msgstr "%(period)s%(currency)s%(account)s分類帳"
#: src/accounting/templates/accounting/report/trial-balance.html:29
#: src/accounting/templates/accounting/report/trial-balance.html:49
#: src/accounting/templates/accounting/report/trial-balance.html:51
#, python-format
msgid "Trial Balance %(period)s"
msgstr "%(period)s試算表"
#: src/accounting/templates/accounting/report/trial-balance.html:29
#: src/accounting/templates/accounting/report/trial-balance.html:53
#, python-format
msgid "Trial Balance of %(currency)s %(period)s"
msgstr "%(period)s%(currency)s試算表"
#: src/accounting/templates/accounting/report/unapplied-accounts.html:24
#: src/accounting/templates/accounting/report/unapplied-accounts.html:41
msgid "Accounts with Unapplied Original Line Items"
msgstr "未抵銷原始分錄的科目"
#: src/accounting/templates/accounting/report/unapplied-accounts.html:29
#: src/accounting/templates/accounting/report/unapplied-accounts.html:49
msgid "Accounts with Unapplied Items"
msgstr "未抵銷項目的科目"
#: src/accounting/templates/accounting/report/unapplied.html:28
#: src/accounting/templates/accounting/report/unapplied-accounts.html:29
#: src/accounting/templates/accounting/report/unapplied-accounts.html:51
#, python-format
msgid "Unapplied Original Line Items of %(account)s"
msgstr "%(account)s未抵銷原始分錄"
msgid "Accounts with Unapplied Items in %(currency)s"
msgstr "%(currency)s未抵銷項目的科目"
#: src/accounting/templates/accounting/report/unapplied.html:65
#: src/accounting/templates/accounting/report/unapplied.html:29
#, python-format
msgid "Can match %(offset)s"
msgstr "可抵銷 %(offset)s"
msgid "Unapplied Items of %(account)s"
msgstr "%(account)s未抵銷項目"
#: src/accounting/templates/accounting/report/unapplied.html:29
#, python-format
msgid "Unapplied Items of %(account)s in %(currency)s"
msgstr "%(currency)s%(account)s未抵銷項目"
#: src/accounting/templates/accounting/report/unmatched-accounts.html:29
#: src/accounting/templates/accounting/report/unmatched-accounts.html:49
msgid "Accounts with Unmatched Offsets"
msgstr "含遺漏抵銷項目的科目"
#: src/accounting/templates/accounting/report/unmatched-accounts.html:29
#: src/accounting/templates/accounting/report/unmatched-accounts.html:51
#, python-format
msgid "Accounts with Unmatched Offsets in %(currency)s"
msgstr "%(currency)s含遺漏抵銷項目的科目"
#: src/accounting/templates/accounting/report/unmatched.html:29
#, python-format
msgid "Unmatched Offsets of %(account)s"
msgstr "%(account)s遺漏的抵銷項目"
#: src/accounting/templates/accounting/report/unmatched.html:29
#, python-format
msgid "Unmatched Offsets of %(account)s in %(currency)s"
msgstr "%(currency)s%(account)s遺漏的抵銷項目"
#: src/accounting/templates/accounting/report/unmatched.html:47
msgid "Match"
msgstr "抵銷"
#: src/accounting/templates/accounting/report/unmatched.html:57
msgid "Confirm Match Offsets"
msgstr "確認抵銷"
#: src/accounting/templates/accounting/report/unmatched.html:61
msgid ""
"Do you really want to match the following original line items with their "
"offsets? This cannot be undone. Please backup your database first, and "
"review before you confirm."
msgstr "你確定要抵銷下列原始分錄與抵銷分錄嗎?結果無法復原。請先備份資料庫,並仔細核對抵銷分錄是否正確。"
#: src/accounting/templates/accounting/report/unmatched.html:107
#, python-format
msgid "Can match %(item)s"
msgstr "可抵銷 %(item)s"
#: src/accounting/templates/accounting/report/include/period-chooser.html:26
msgid "Period Chooser"
@ -1297,65 +1411,6 @@ msgstr "期間"
msgid "Download"
msgstr "下載"
#: src/accounting/templates/accounting/unmatched-offset/list.html:24
#, python-format
msgid "Unmatched Offsets in %(account)s"
msgstr "%(account)s遺漏的抵銷分錄"
#: src/accounting/templates/accounting/unmatched-offset/list.html:28
msgid "Toolbar"
msgstr "工具列"
#: src/accounting/templates/accounting/unmatched-offset/list.html:36
#: src/accounting/templates/accounting/unmatched-offset/list.html:41
msgid "Match"
msgstr "抵銷"
#: src/accounting/templates/accounting/unmatched-offset/list.html:53
msgid "Confirm Match Offsets"
msgstr "確認抵銷"
#: src/accounting/templates/accounting/unmatched-offset/list.html:57
msgid ""
"Do you really want to match the following original line items with their "
"offsets? This cannot be undone. Please backup your database first, and "
"review before you confirm."
msgstr "你確定要抵銷下列原始分錄與抵銷分錄嗎?結果無法復原。請先備份資料庫,並仔細核對抵銷分錄是否正確。"
#: src/accounting/templates/accounting/unmatched-offset/list.html:81
#, python-format
msgid ""
"%(matches)s unapplied original line items out of %(total)s can match with"
" their offsets."
msgstr "%(total)s 筆未抵銷原始分錄中,可配對抵銷掉 %(matches)s 筆。"
#: src/accounting/templates/accounting/unmatched-offset/list.html:83
#, python-format
msgid "%(total)s unapplied original line items without matching offsets."
msgstr "%(total)s 筆未抵銷原始分錄,無法自動配對抵銷。"
#: src/accounting/templates/accounting/unmatched-offset/list.html:85
msgid "Go to unapplied original line items."
msgstr "查閱未抵銷原始分錄。"
#: src/accounting/templates/accounting/unmatched-offset/list.html:87
msgid "All original line items are fully offset."
msgstr "原始分錄已全部抵銷。"
#: src/accounting/templates/accounting/unmatched-offset/list.html:98
#, python-format
msgid "Can match %(item)s"
msgstr "可抵銷 %(item)s"
#: src/accounting/unmatched_offset/views.py:71
msgid "No more offset to match automatically."
msgstr "無法自動配對抵銷。"
#: src/accounting/unmatched_offset/views.py:77
#, python-format
msgid "Matches %(matches)s from %(total)s unapplied line items."
msgstr "%(total)s 筆未抵銷原始分錄中,配對抵銷掉 %(matches)s 筆。"
#: src/accounting/utils/current_account.py:65
msgid "current assets and liabilities"
msgstr "流動資產與負債"

View File

@ -25,16 +25,6 @@ import typing as t
import sqlalchemy as sa
def be(expression: t.Any) -> sa.BinaryExpression:
"""Casts the SQLAlchemy binary expression to the binary expression type.
:param expression: The binary expression.
:return: The binary expression itself.
"""
assert isinstance(expression, sa.BinaryExpression)
return expression
def s(message: t.Any) -> str:
"""Casts the LazyString message to the string type.