Compare commits

...

10 Commits

14 changed files with 118 additions and 75 deletions

View File

@ -17,7 +17,7 @@
[metadata]
name = mia-accounting-flask
version = 0.3.0
version = 0.3.1
author = imacat
author_email = imacat@mail.imacat.idv.tw
description = The Mia! Accounting Flask project.

View File

@ -530,17 +530,17 @@ function filterAccountOptions(prefix) {
const more = document.getElementById(prefix + "-more");
const queryNoResult = document.getElementById(prefix + "-option-no-result");
const codesInUse = getAccountCodeUsedInForm();
let hasAnyMatched = false;
let shouldAnyShow = false;
options.forEach(function (option) {
const isMatched = shouldAccountOptionShow(option, more, codesInUse, query);
if (isMatched) {
const shouldShow = shouldAccountOptionShow(option, more, codesInUse, query);
if (shouldShow) {
option.classList.remove("d-none");
hasAnyMatched = true;
shouldAnyShow = true;
} else {
option.classList.add("d-none");
}
});
if (!hasAnyMatched) {
if (!shouldAnyShow && more.classList.contains("d-none")) {
optionList.classList.add("d-none");
queryNoResult.classList.remove("d-none");
} else {

View File

@ -21,6 +21,13 @@ First written: 2023/2/26
#}
{% extends "accounting/transaction/include/detail.html" %}
{% block to_transfer %}
<a class="btn btn-primary" href="{{ url_for("accounting.transaction.edit", txn=obj)|accounting_txn_to_transfer|accounting_inherit_next }}">
<i class="fa-solid fa-bars-staggered"></i>
{{ A_("To Transfer") }}
</a>
{% endblock %}
{% block transaction_currencies %}
{% for currency in obj.currencies %}
<div class="mb-3">
@ -43,7 +50,7 @@ First written: 2023/2/26
{% endfor %}
<li class="list-group-item accounting-transaction-entry accounting-transaction-entry-total">
<div class="d-flex justify-content-between">
<div>{{ _("Total") }}</div>
<div>{{ A_("Total") }}</div>
<div>{{ currency.debit_total|accounting_txn_format_amount }}</div>
</div>
</li>

View File

@ -55,7 +55,7 @@ First written: 2023/2/25
account_text = entry_form.account_text,
summary_data = "" if entry_form.summary.data is none else entry_form.summary.data,
summary_errors = entry_form.summary.errors,
amount_data = "" if entry_form.amount.data is none else entry_form.amount.data,
amount_data = "" if entry_form.amount.data is none else entry_form.amount.data|accounting_txn_format_amount_input,
amount_errors = entry_form.amount.errors,
amount_text = entry_form.amount.data|accounting_txn_format_amount,
entry_errors = entry_form.all_errors %}

View File

@ -41,6 +41,7 @@ First written: 2023/2/26
{{ A_("Order") }}
</a>
{% if accounting_can_edit() %}
{% block to_transfer %}{% endblock %}
<button class="btn btn-danger" type="button" data-bs-toggle="modal" data-bs-target="#accounting-delete-modal">
<i class="fa-solid fa-trash"></i>
{{ A_("Delete") }}

View File

@ -34,7 +34,7 @@ First written: 2023/2/25
<div id="accounting-currency-{{ currency_index }}-{{ entry_type }}-{{ entry_index }}-account-text" class="small">{{ account_text }}</div>
<div id="accounting-currency-{{ currency_index }}-{{ entry_type }}-{{ entry_index }}-summary-text">{{ "" if summary_data is none else summary_data }}</div>
</div>
<div><span id="accounting-currency-{{ currency_index }}-{{ entry_type }}-{{ entry_index }}-amount-text" class="badge rounded-pill bg-primary">{{ amount_data }}</span></div>
<div><span id="accounting-currency-{{ currency_index }}-{{ entry_type }}-{{ entry_index }}-amount-text" class="badge rounded-pill bg-primary">{{ amount_text }}</span></div>
</div>
<div id="accounting-currency-{{ currency_index }}-{{ entry_type }}-{{ entry_index }}-error" class="invalid-feedback">{% if entry_errors %}{{ entry_errors[0] }}{% endif %}</div>
</div>

View File

@ -21,6 +21,13 @@ First written: 2023/2/26
#}
{% extends "accounting/transaction/include/detail.html" %}
{% block to_transfer %}
<a class="btn btn-primary" href="{{ url_for("accounting.transaction.edit", txn=obj)|accounting_txn_to_transfer|accounting_inherit_next }}">
<i class="fa-solid fa-bars-staggered"></i>
{{ A_("To Transfer") }}
</a>
{% endblock %}
{% block transaction_currencies %}
{% for currency in obj.currencies %}
<div class="mb-3">
@ -43,7 +50,7 @@ First written: 2023/2/26
{% endfor %}
<li class="list-group-item accounting-transaction-entry accounting-transaction-entry-total">
<div class="d-flex justify-content-between">
<div>{{ _("Total") }}</div>
<div>{{ A_("Total") }}</div>
<div>{{ currency.debit_total|accounting_txn_format_amount }}</div>
</div>
</li>

View File

@ -55,7 +55,7 @@ First written: 2023/2/25
account_text = entry_form.account_text,
summary_data = "" if entry_form.summary.data is none else entry_form.summary.data,
summary_errors = entry_form.summary.errors,
amount_data = "" if entry_form.amount.data is none else entry_form.amount.data,
amount_data = "" if entry_form.amount.data is none else entry_form.amount.data|accounting_txn_format_amount_input,
amount_errors = entry_form.amount.errors,
amount_text = entry_form.amount.data|accounting_txn_format_amount,
entry_errors = entry_form.all_errors %}

View File

@ -46,7 +46,7 @@ First written: 2023/2/26
{% endfor %}
<li class="list-group-item accounting-transaction-entry accounting-transaction-entry-total">
<div class="d-flex justify-content-between">
<div>{{ _("Total") }}</div>
<div>{{ A_("Total") }}</div>
<div>{{ currency.debit_total|accounting_txn_format_amount }}</div>
</div>
</li>
@ -72,7 +72,7 @@ First written: 2023/2/26
{% endfor %}
<li class="list-group-item accounting-transaction-entry accounting-transaction-entry-total">
<div class="d-flex justify-content-between">
<div>{{ _("Total") }}</div>
<div>{{ A_("Total") }}</div>
<div>{{ currency.debit_total|accounting_txn_format_amount }}</div>
</div>
</li>

View File

@ -97,7 +97,7 @@ First written: 2023/2/25
account_text = entry_form.account_text,
summary_data = "" if entry_form.summary.data is none else entry_form.summary.data,
summary_errors = entry_form.summary.errors,
amount_data = "" if entry_form.amount.data is none else entry_form.amount.data,
amount_data = "" if entry_form.amount.data is none else entry_form.amount.data|accounting_txn_format_amount_input,
amount_errors = entry_form.amount.errors,
amount_text = entry_form.amount.data|accounting_txn_format_amount,
entry_errors = entry_form.all_errors %}

View File

@ -284,13 +284,11 @@ class TransactionForm(FlaskForm):
self.__set_date(obj, self.date.data)
obj.note = self.note.data
entries: list[JournalEntry] = obj.entries
collector_cls: t.Type[JournalEntryCollector] = self.collector
collector: collector_cls = collector_cls(self, obj.id, entries,
obj.currencies)
collector: collector_cls = collector_cls(self, obj)
collector.collect()
to_delete: set[int] = {x.id for x in entries
to_delete: set[int] = {x.id for x in obj.entries
if x.id not in collector.to_keep}
if len(to_delete) > 0:
JournalEntry.query.filter(JournalEntry.id.in_(to_delete)).delete()
@ -373,27 +371,24 @@ T = t.TypeVar("T", bound=TransactionForm)
class JournalEntryCollector(t.Generic[T], ABC):
"""The journal entry collector."""
def __init__(self, form: T, txn_id: int, entries: list[JournalEntry],
currencies: list[TransactionCurrency]):
def __init__(self, form: T, obj: Transaction):
"""Constructs the journal entry collector.
:param form: The transaction form.
:param txn_id: The transaction ID.
:param entries: The existing journal entries.
:param currencies: The currencies in the transaction.
:param obj: The transaction.
"""
self.form: T = form
"""The transaction form."""
self.entries: list[JournalEntry] = entries
self.__obj: Transaction = obj
"""The transaction object."""
self.__entries: list[JournalEntry] = list(obj.entries)
"""The existing journal entries."""
self.txn_id: int = txn_id
"""The transaction ID."""
self.__entries_by_id: dict[int, JournalEntry] \
= {x.id: x for x in entries}
= {x.id: x for x in self.__entries}
"""A dictionary from the entry ID to their entries."""
self.__no_by_id: dict[int, int] = {x.id: x.no for x in entries}
self.__no_by_id: dict[int, int] = {x.id: x.no for x in self.__entries}
"""A dictionary from the entry number to their entries."""
self.__currencies: list[TransactionCurrency] = currencies
self.__currencies: list[TransactionCurrency] = obj.currencies
"""The currencies in the transaction."""
self._debit_no: int = 1
"""The number index for the debit entries."""
@ -420,7 +415,6 @@ class JournalEntryCollector(t.Generic[T], ABC):
"""
entry: JournalEntry | None = self.__entries_by_id.get(form.eid.data)
if entry is not None:
self.to_keep.add(entry.id)
entry.currency_code = currency_code
form.populate_obj(entry)
entry.no = no
@ -428,12 +422,12 @@ class JournalEntryCollector(t.Generic[T], ABC):
self.form.is_modified = True
else:
entry = JournalEntry()
entry.transaction_id = self.txn_id
entry.currency_code = currency_code
form.populate_obj(entry)
entry.no = no
db.session.add(entry)
self.__obj.entries.append(entry)
self.form.is_modified = True
self.to_keep.add(entry.id)
def _make_cash_entry(self, forms: list[JournalEntryForm], is_debit: bool,
currency_code: str, no: int) -> None:
@ -447,14 +441,13 @@ class JournalEntryCollector(t.Generic[T], ABC):
:param no: The number of the entry.
:return: None.
"""
candidates: list[JournalEntry] = [x for x in self.entries
candidates: list[JournalEntry] = [x for x in self.__entries
if x.is_debit == is_debit
and x.currency_code == currency_code]
entry: JournalEntry
if len(candidates) > 0:
candidates.sort(key=lambda x: x.no)
entry = candidates[0]
self.to_keep.add(entry.id)
entry.account_id = Account.cash().id
entry.summary = None
entry.amount = sum([x.amount.data for x in forms])
@ -464,15 +457,15 @@ class JournalEntryCollector(t.Generic[T], ABC):
else:
entry = JournalEntry()
entry.id = new_id(JournalEntry)
entry.transaction_id = self.txn_id
entry.is_debit = is_debit
entry.currency_code = currency_code
entry.account_id = Account.cash().id
entry.summary = None
entry.amount = sum([x.amount.data for x in forms])
entry.no = no
db.session.add(entry)
self.__obj.entries.append(entry)
self.form.is_modified = True
self.to_keep.add(entry.id)
def _sort_entry_forms(self, forms: list[JournalEntryForm]) -> None:
"""Sorts the journal entry forms.

View File

@ -40,13 +40,28 @@ def with_type(uri: str) -> str:
return uri
uri_p: ParseResult = urlparse(uri)
params: list[tuple[str, str]] = parse_qsl(uri_p.query)
params = [x for x in params if x[0] != "next"]
params = [x for x in params if x[0] != "as"]
params.append(("as", request.args["as"]))
parts: list[str] = list(uri_p)
parts[4] = urlencode(params)
return urlunparse(parts)
def to_transfer(uri: str) -> str:
"""Adds the transfer transaction type to the URI.
:param uri: The URI.
:return: The result URL, with the transfer transaction type added.
"""
uri_p: ParseResult = urlparse(uri)
params: list[tuple[str, str]] = parse_qsl(uri_p.query)
params = [x for x in params if x[0] != "as"]
params.append(("as", "transfer"))
parts: list[str] = list(uri_p)
parts[4] = urlencode(params)
return urlunparse(parts)
def format_amount(value: Decimal | None) -> str:
"""Formats an amount for readability.
@ -60,6 +75,17 @@ def format_amount(value: Decimal | None) -> str:
return "{:,}".format(whole) + str(frac)[1:]
def format_amount_input(value: Decimal) -> str:
"""Format an amount for an input value.
:param value: The amount.
:return: The formatted amount text for an input value.
"""
whole: int = int(value)
frac: Decimal = (value - whole).normalize()
return str(whole) + str(frac)[1:]
def format_date(value: date) -> str:
"""Formats a date to be human-friendly.

View File

@ -34,15 +34,19 @@ from accounting.utils.pagination import Pagination
from accounting.utils.permission import has_permission, can_view, can_edit
from accounting.utils.user import get_current_user_pk
from .dispatcher import TransactionType, get_txn_type, TXN_TYPE_OBJ
from .template import with_type, format_amount, format_date, text2html, \
currency_options, default_currency_code
from .forms import sort_transactions_in, TransactionReorderForm
from .query import get_transaction_query
from .template import with_type, to_transfer, format_amount, \
format_amount_input, format_date, text2html, currency_options, \
default_currency_code
bp: Blueprint = Blueprint("transaction", __name__)
"""The view blueprint for the transaction management."""
bp.add_app_template_filter(with_type, "accounting_txn_with_type")
bp.add_app_template_filter(to_transfer, "accounting_txn_to_transfer")
bp.add_app_template_filter(format_amount, "accounting_txn_format_amount")
bp.add_app_template_filter(format_amount_input,
"accounting_txn_format_amount_input")
bp.add_app_template_filter(format_date, "accounting_txn_format_date")
bp.add_app_template_filter(text2html, "accounting_txn_text2html")
bp.add_app_template_global(currency_options, "accounting_txn_currency_options")

View File

@ -8,8 +8,8 @@ msgid ""
msgstr ""
"Project-Id-Version: Mia! Accounting Flask 0.0.0\n"
"Report-Msgid-Bugs-To: imacat@mail.imacat.idv.tw\n"
"POT-Creation-Date: 2023-02-27 15:28+0800\n"
"PO-Revision-Date: 2023-02-27 15:29+0800\n"
"POT-Creation-Date: 2023-02-27 18:59+0800\n"
"PO-Revision-Date: 2023-02-27 18:59+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"
@ -61,23 +61,23 @@ msgstr "逐筆核銷"
msgid "The account is added successfully"
msgstr "科目加好了。"
#: src/accounting/account/views.py:142
#: src/accounting/account/views.py:141
msgid "The account was not modified."
msgstr "科目未異動。"
#: src/accounting/account/views.py:148
#: src/accounting/account/views.py:146
msgid "The account is updated successfully."
msgstr "科目存好了。"
#: src/accounting/account/views.py:165
#: src/accounting/account/views.py:162
msgid "The account is deleted successfully."
msgstr "科目刪掉了"
#: src/accounting/account/views.py:192 src/accounting/transaction/views.py:210
#: src/accounting/account/views.py:189 src/accounting/transaction/views.py:214
msgid "The order was not modified."
msgstr "順序未異動。"
#: src/accounting/account/views.py:195 src/accounting/transaction/views.py:213
#: src/accounting/account/views.py:192 src/accounting/transaction/views.py:217
msgid "The order is updated successfully."
msgstr "順序存好了。"
@ -110,15 +110,15 @@ msgstr "請填上名稱。"
msgid "The currency is added successfully"
msgstr "貨幣加好了。"
#: src/accounting/currency/views.py:145
#: src/accounting/currency/views.py:144
msgid "The currency was not modified."
msgstr "貨幣未異動。"
#: src/accounting/currency/views.py:151
#: src/accounting/currency/views.py:149
msgid "The currency is updated successfully."
msgstr "貨幣存好了。"
#: src/accounting/currency/views.py:167
#: src/accounting/currency/views.py:164
msgid "The currency is deleted successfully."
msgstr "貨幣刪掉了"
@ -185,7 +185,7 @@ msgstr "次序"
#: src/accounting/templates/accounting/account/detail.html:46
#: src/accounting/templates/accounting/currency/detail.html:42
#: src/accounting/templates/accounting/transaction/include/detail.html:46
#: src/accounting/templates/accounting/transaction/include/detail.html:47
msgid "Delete"
msgstr "刪除"
@ -198,7 +198,7 @@ msgstr "科目刪除確認"
#: src/accounting/templates/accounting/currency/detail.html:66
#: src/accounting/templates/accounting/transaction/include/credit-account-modal.html:27
#: src/accounting/templates/accounting/transaction/include/debit-account-modal.html:27
#: src/accounting/templates/accounting/transaction/include/detail.html:70
#: src/accounting/templates/accounting/transaction/include/detail.html:71
#: src/accounting/templates/accounting/transaction/include/entry-form-modal.html:28
msgid "Close"
msgstr "關閉"
@ -212,26 +212,26 @@ msgstr "你確定要刪掉這個科目嗎?"
#: src/accounting/templates/accounting/currency/detail.html:72
#: src/accounting/templates/accounting/transaction/include/credit-account-modal.html:49
#: src/accounting/templates/accounting/transaction/include/debit-account-modal.html:49
#: src/accounting/templates/accounting/transaction/include/detail.html:76
#: src/accounting/templates/accounting/transaction/include/detail.html:77
#: src/accounting/templates/accounting/transaction/include/entry-form-modal.html:52
msgid "Cancel"
msgstr "取消"
#: src/accounting/templates/accounting/account/detail.html:77
#: src/accounting/templates/accounting/currency/detail.html:73
#: src/accounting/templates/accounting/transaction/include/detail.html:77
#: src/accounting/templates/accounting/transaction/include/detail.html:78
msgid "Confirm"
msgstr "確定"
#: src/accounting/templates/accounting/account/detail.html:94
#: src/accounting/templates/accounting/currency/detail.html:85
#: src/accounting/templates/accounting/transaction/include/detail.html:106
#: src/accounting/templates/accounting/transaction/include/detail.html:107
msgid "Created"
msgstr "建檔"
#: src/accounting/templates/accounting/account/detail.html:95
#: src/accounting/templates/accounting/currency/detail.html:86
#: src/accounting/templates/accounting/transaction/include/detail.html:107
#: src/accounting/templates/accounting/transaction/include/detail.html:108
msgid "Updated"
msgstr "更新"
@ -375,23 +375,23 @@ msgstr "代碼"
msgid "Name"
msgstr "名稱"
#: src/accounting/templates/accounting/include/nav.html:26
#: src/accounting/templates/accounting/include/nav.html:27
msgid "Accounting"
msgstr "記帳"
#: src/accounting/templates/accounting/include/nav.html:32
#: src/accounting/templates/accounting/include/nav.html:33
msgid "Transactions"
msgstr "傳票"
#: src/accounting/templates/accounting/include/nav.html:38
#: src/accounting/templates/accounting/include/nav.html:39
msgid "Accounts"
msgstr "科目"
#: src/accounting/templates/accounting/include/nav.html:44
#: src/accounting/templates/accounting/include/nav.html:45
msgid "Base Accounts"
msgstr "基本科目"
#: src/accounting/templates/accounting/include/nav.html:50
#: src/accounting/templates/accounting/include/nav.html:51
msgid "Currencies"
msgstr "貨幣"
@ -425,17 +425,22 @@ msgstr "%(date)s的傳票"
msgid "Add a New Cash Expense Transaction"
msgstr "新增現金支出傳票"
#: src/accounting/templates/accounting/transaction/expense/detail.html:30
#: src/accounting/templates/accounting/transaction/expense/detail.html:27
#: src/accounting/templates/accounting/transaction/income/detail.html:27
msgid "To Transfer"
msgstr "改轉帳"
#: src/accounting/templates/accounting/transaction/expense/detail.html:37
#: src/accounting/templates/accounting/transaction/expense/include/form-currency-item.html:45
#: src/accounting/templates/accounting/transaction/include/form.html:52
#: src/accounting/templates/accounting/transaction/income/detail.html:30
#: src/accounting/templates/accounting/transaction/income/detail.html:37
#: src/accounting/templates/accounting/transaction/income/include/form-currency-item.html:45
msgid "Content"
msgstr "內容"
#: src/accounting/templates/accounting/transaction/expense/detail.html:46
#: src/accounting/templates/accounting/transaction/expense/detail.html:53
#: src/accounting/templates/accounting/transaction/expense/include/form-currency-item.html:68
#: src/accounting/templates/accounting/transaction/income/detail.html:46
#: src/accounting/templates/accounting/transaction/income/detail.html:53
#: src/accounting/templates/accounting/transaction/income/include/form-currency-item.html:68
#: src/accounting/templates/accounting/transaction/transfer/detail.html:49
#: src/accounting/templates/accounting/transaction/transfer/detail.html:75
@ -478,11 +483,11 @@ msgstr "更多…"
msgid "Select Debit Account"
msgstr "選擇借方科目"
#: src/accounting/templates/accounting/transaction/include/detail.html:69
#: src/accounting/templates/accounting/transaction/include/detail.html:70
msgid "Delete Transaction Confirmation"
msgstr "傳票刪除確認"
#: src/accounting/templates/accounting/transaction/include/detail.html:73
#: src/accounting/templates/accounting/transaction/include/detail.html:74
msgid "Do you really want to delete this transaction?"
msgstr "你確定要刪掉這張傳票嗎?"
@ -552,39 +557,39 @@ msgstr "科目不是借方科目。"
msgid "This account is not for credit entries."
msgstr "科目不是貸方科目。"
#: src/accounting/transaction/template.py:71
#: src/accounting/transaction/template.py:97
msgid "Today"
msgstr "今天"
#: src/accounting/transaction/template.py:73
#: src/accounting/transaction/template.py:99
msgid "Yesterday"
msgstr "昨天"
#: src/accounting/transaction/template.py:75
#: src/accounting/transaction/template.py:101
msgid "Tomorrow"
msgstr "明天"
#: src/accounting/transaction/template.py:79
#: src/accounting/transaction/template.py:105
msgid "The day before yesterday"
msgstr "前天"
#: src/accounting/transaction/template.py:81
#: src/accounting/transaction/template.py:107
msgid "The day after tomorrow"
msgstr "後天"
#: src/accounting/transaction/views.py:104
#: src/accounting/transaction/views.py:108
msgid "The transaction is added successfully"
msgstr "傳票加好了。"
#: src/accounting/transaction/views.py:158
#: src/accounting/transaction/views.py:162
msgid "The transaction was not modified."
msgstr "傳票未異動。"
#: src/accounting/transaction/views.py:163
#: src/accounting/transaction/views.py:167
msgid "The transaction is updated successfully."
msgstr "傳票存好了。"
#: src/accounting/transaction/views.py:179
#: src/accounting/transaction/views.py:183
msgid "The transaction is deleted successfully."
msgstr "傳票刪掉了"