10 Commits

Author SHA1 Message Date
884e37fe1b Advanced to version 0.6.0. 2023-03-18 23:38:41 +08:00
cc6a73211e Updated the Sphinx documentation. 2023-03-18 23:38:19 +08:00
2299b86d0f Updated the translation. 2023-03-18 23:37:08 +08:00
6d293a1aac Added the JavaScript getInstances method to the SummaryEditor and AccountSelector classes, so that it is easier to deal with the case when the debit and credit versions are not both exist. 2023-03-18 23:36:38 +08:00
a2311aee24 Revised to prevent word wrapping in the button to choose the original entry in the summary editor. 2023-03-18 23:20:49 +08:00
5571c0d01f Renamed all the is_XXX_needed properties to is_need_XXX. For example, especially the is_offset_needed property to is_need_offset, to be clear and understandable. 2023-03-18 22:52:29 +08:00
98e1bad413 Renamed the test_not_needed test to test_not_need in the PaginationTestCase test case. 2023-03-18 22:43:53 +08:00
7ff52d99e6 Removed the unused is_offset_chooser_needed property from the AccountOption data model. 2023-03-18 22:40:56 +08:00
cc440a4110 Renamed the "_is_payable_needed" and "_is_receivable_needed" properties in the TransactionForm form to "_is_need_payable" and "_is_need_receivable", respectively, for readability and understandability. 2023-03-18 22:39:26 +08:00
f5149a0c37 Replaced the long parameter list with the JournalEntrySubForm instance in the onEdit method of the JavaScript JournalEntryEditor, to simplify the code. 2023-03-18 22:36:50 +08:00
28 changed files with 728 additions and 508 deletions

View File

@ -0,0 +1,45 @@
accounting.transaction.forms package
====================================
Submodules
----------
accounting.transaction.forms.currency module
--------------------------------------------
.. automodule:: accounting.transaction.forms.currency
:members:
:undoc-members:
:show-inheritance:
accounting.transaction.forms.journal\_entry module
--------------------------------------------------
.. automodule:: accounting.transaction.forms.journal_entry
:members:
:undoc-members:
:show-inheritance:
accounting.transaction.forms.reorder module
-------------------------------------------
.. automodule:: accounting.transaction.forms.reorder
:members:
:undoc-members:
:show-inheritance:
accounting.transaction.forms.transaction module
-----------------------------------------------
.. automodule:: accounting.transaction.forms.transaction
:members:
:undoc-members:
:show-inheritance:
Module contents
---------------
.. automodule:: accounting.transaction.forms
:members:
:undoc-members:
:show-inheritance:

View File

@ -1,6 +1,15 @@
accounting.transaction package
==============================
Subpackages
-----------
.. toctree::
:maxdepth: 4
accounting.transaction.forms
accounting.transaction.utils
Submodules
----------
@ -12,30 +21,6 @@ accounting.transaction.converters module
:undoc-members:
:show-inheritance:
accounting.transaction.forms module
-----------------------------------
.. automodule:: accounting.transaction.forms
:members:
:undoc-members:
:show-inheritance:
accounting.transaction.operators module
---------------------------------------
.. automodule:: accounting.transaction.operators
:members:
:undoc-members:
:show-inheritance:
accounting.transaction.summary\_editor module
---------------------------------------------
.. automodule:: accounting.transaction.summary_editor
:members:
:undoc-members:
:show-inheritance:
accounting.transaction.template\_filters module
-----------------------------------------------

View File

@ -0,0 +1,53 @@
accounting.transaction.utils package
====================================
Submodules
----------
accounting.transaction.utils.account\_option module
---------------------------------------------------
.. automodule:: accounting.transaction.utils.account_option
:members:
:undoc-members:
:show-inheritance:
accounting.transaction.utils.offset\_alias module
-------------------------------------------------
.. automodule:: accounting.transaction.utils.offset_alias
:members:
:undoc-members:
:show-inheritance:
accounting.transaction.utils.operators module
---------------------------------------------
.. automodule:: accounting.transaction.utils.operators
:members:
:undoc-members:
:show-inheritance:
accounting.transaction.utils.original\_entries module
-----------------------------------------------------
.. automodule:: accounting.transaction.utils.original_entries
:members:
:undoc-members:
:show-inheritance:
accounting.transaction.utils.summary\_editor module
---------------------------------------------------
.. automodule:: accounting.transaction.utils.summary_editor
:members:
:undoc-members:
:show-inheritance:
Module contents
---------------
.. automodule:: accounting.transaction.utils
:members:
:undoc-members:
:show-inheritance:

View File

@ -4,6 +4,14 @@ accounting.utils package
Submodules
----------
accounting.utils.cast module
----------------------------
.. automodule:: accounting.utils.cast
:members:
:undoc-members:
:show-inheritance:
accounting.utils.flash\_errors module
-------------------------------------

View File

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

View File

@ -29,7 +29,7 @@ from accounting.utils.user import has_user, get_user_pk
AccountData = tuple[int, str, int, str, str, str, bool]
"""The format of the account data, as a list of (ID, base account code, number,
English, Traditional Chinese, Simplified Chinese, is-offset-needed) tuples."""
English, Traditional Chinese, Simplified Chinese, is-need-offset) tuples."""
def __validate_username(ctx: click.core.Context, param: click.core.Option,
@ -92,14 +92,14 @@ def init_accounts_command(username: str) -> None:
data: list[AccountData] = []
for base in bases_to_add:
l10n: dict[str, str] = {x.locale: x.title for x in base.l10n}
is_offset_needed: bool = __is_offset_needed(base.code)
is_need_offset: bool = __is_need_offset(base.code)
data.append((get_new_id(), base.code, 1, base.title_l10n,
l10n["zh_Hant"], l10n["zh_Hans"], is_offset_needed))
l10n["zh_Hant"], l10n["zh_Hans"], is_need_offset))
__add_accounting_accounts(data, creator_pk)
click.echo(F"{len(data)} added. Accounting accounts initialized.")
def __is_offset_needed(base_code: str) -> bool:
def __is_need_offset(base_code: str) -> bool:
"""Checks that whether entries in the account need offset.
:param base_code: The code of the base account.
@ -134,7 +134,7 @@ def __add_accounting_accounts(data: list[AccountData], creator_pk: int)\
base_code=x[1],
no=x[2],
title_l10n=x[3],
is_offset_needed=x[6],
is_need_offset=x[6],
created_by_id=creator_pk,
updated_by_id=creator_pk)
for x in data]

View File

@ -80,7 +80,7 @@ class AccountForm(FlaskForm):
filters=[strip_text],
validators=[DataRequired(lazy_gettext("Please fill in the title"))])
"""The title."""
is_offset_needed = BooleanField(
is_need_offset = BooleanField(
validators=[NoOffsetNominalAccount()])
"""Whether the the entries of this account need offset."""
@ -103,9 +103,9 @@ class AccountForm(FlaskForm):
obj.no = count + 1
obj.title = self.title.data
if self.base_code.data[0] in {"1", "2", "3"}:
obj.is_offset_needed = self.is_offset_needed.data
obj.is_need_offset = self.is_need_offset.data
else:
obj.is_offset_needed = False
obj.is_need_offset = False
if is_new:
current_user_pk: int = get_current_user_pk()
obj.created_by_id = current_user_pk

View File

@ -48,7 +48,7 @@ def get_account_query() -> list[Account]:
code.contains(k),
Account.id.in_(l10n_matches)]
if k in gettext("Need offset"):
sub_conditions.append(Account.is_offset_needed)
sub_conditions.append(Account.is_need_offset)
conditions.append(sa.or_(*sub_conditions))
return Account.query.filter(*conditions)\

View File

@ -114,7 +114,7 @@ class Account(db.Model):
"""The account number under the base account."""
title_l10n = db.Column("title", db.String, nullable=False)
"""The title."""
is_offset_needed = db.Column(db.Boolean, nullable=False, default=False)
is_need_offset = db.Column(db.Boolean, nullable=False, default=False)
"""Whether the entries of this account need offset."""
created_at = db.Column(db.DateTime(timezone=True), nullable=False,
server_default=db.func.now())
@ -702,7 +702,7 @@ class JournalEntry(db.Model):
:return: True if the entry needs offset, or False otherwise.
"""
if not self.account.is_offset_needed:
if not self.account.is_need_offset:
return False
if self.account.base_code[0] == "1" and not self.is_debit:
return False

View File

@ -97,7 +97,7 @@ class EntryCollector:
code.contains(k),
Account.id.in_(select_l10n)]
if k in gettext("Need offset"):
conditions.append(Account.is_offset_needed)
conditions.append(Account.is_need_offset)
return sa.select(Account.id).filter(sa.or_(*conditions))
@staticmethod

View File

@ -83,16 +83,16 @@ class AccountForm {
#titleError;
/**
* The control of the is-offset-needed option
* The control of the is-need-offset option
* @type {HTMLDivElement}
*/
#isOffsetNeededControl;
#isNeedOffsetControl;
/**
* The is-offset-needed option
* The is-need-offset option
* @type {HTMLInputElement}
*/
#isOffsetNeeded;
#isNeedOffset;
/**
* Constructs the account form.
@ -107,8 +107,8 @@ class AccountForm {
this.#baseError = document.getElementById("accounting-base-error");
this.#title = document.getElementById("accounting-title");
this.#titleError = document.getElementById("accounting-title-error");
this.#isOffsetNeededControl = document.getElementById("accounting-is-offset-needed-control");
this.#isOffsetNeeded = document.getElementById("accounting-is-offset-needed");
this.#isNeedOffsetControl = document.getElementById("accounting-is-need-offset-control");
this.#isNeedOffset = document.getElementById("accounting-is-need-offset");
this.#formElement.onsubmit = () => {
return this.#validateForm();
};
@ -138,12 +138,12 @@ class AccountForm {
this.#baseCode.value = code;
this.#base.innerText = text;
if (["1", "2", "3"].includes(code.substring(0, 1))) {
this.#isOffsetNeededControl.classList.remove("d-none");
this.#isOffsetNeeded.disabled = false;
this.#isNeedOffsetControl.classList.remove("d-none");
this.#isNeedOffset.disabled = false;
} else {
this.#isOffsetNeededControl.classList.add("d-none");
this.#isOffsetNeeded.disabled = true;
this.#isOffsetNeeded.checked = false;
this.#isNeedOffsetControl.classList.add("d-none");
this.#isNeedOffset.disabled = true;
this.#isNeedOffset.checked = false;
}
this.#validateBase();
}

View File

@ -105,7 +105,7 @@ class AccountSelector {
};
this.#clearButton.onclick = () => this.#entryEditor.clearAccount();
for (const option of this.#options) {
option.onclick = () => this.#entryEditor.saveAccount(option.dataset.code, option.dataset.content, option.classList.contains("accounting-account-is-offset-needed"));
option.onclick = () => this.#entryEditor.saveAccount(option.dataset.code, option.dataset.content, option.classList.contains("accounting-account-is-need-offset"));
}
this.#query.addEventListener("input", () => {
this.#filterOptions();
@ -206,4 +206,19 @@ class AccountSelector {
this.#clearButton.disabled = false;
}
}
/**
* Returns the account selector instances.
*
* @param entryEditor {JournalEntryEditor} the journal entry editor
* @return {{debit: AccountSelector, credit: AccountSelector}}
*/
static getInstances(entryEditor) {
const selectors = {}
const modals = Array.from(document.getElementsByClassName("accounting-account-selector"));
for (const modal of modals) {
selectors[modal.dataset.entryType] = new AccountSelector(entryEditor, modal.dataset.entryType);
}
return selectors;
}
}

View File

@ -200,13 +200,13 @@ class JournalEntryEditor {
* The summary editors
* @type {{debit: SummaryEditor, credit: SummaryEditor}}
*/
#summaryEditors = {};
#summaryEditors;
/**
* The account selectors
* @type {{debit: AccountSelector, credit: AccountSelector}}
*/
#accountSelectors = {};
#accountSelectors;
/**
* The original entry selector
@ -236,12 +236,8 @@ class JournalEntryEditor {
this.#accountError = document.getElementById(this.#prefix + "-account-error")
this.#amount = document.getElementById(this.#prefix + "-amount");
this.#amountError = document.getElementById(this.#prefix + "-amount-error");
for (const entryType of ["debit", "credit"]) {
this.#summaryEditors[entryType] = new SummaryEditor(this, entryType);
}
for (const entryType of ["debit", "credit"]) {
this.#accountSelectors[entryType] = new AccountSelector(this, entryType);
}
this.#summaryEditors = SummaryEditor.getInstances(this);
this.#accountSelectors = AccountSelector.getInstances(this);
this.originalEntrySelector = new OriginalEntrySelector();
this.#originalEntryControl.onclick = () => this.originalEntrySelector.onOpen(this, this.originalEntryId)
this.#originalEntryDelete.onclick = () => this.clearOriginalEntry();
@ -374,10 +370,10 @@ class JournalEntryEditor {
*
* @param code {string} the account code
* @param text {string} the account text
* @param isOffsetNeeded {boolean} true if the journal entries in the account need offset or false otherwise
* @param isNeedOffset {boolean} true if the journal entries in the account need offset or false otherwise
*/
saveAccount(code, text, isOffsetNeeded) {
this.isNeedOffset = isOffsetNeeded;
saveAccount(code, text, isNeedOffset) {
this.isNeedOffset = isNeedOffset;
this.#accountControl.classList.add("accounting-not-empty");
this.accountCode = code;
this.accountText = text;
@ -519,51 +515,43 @@ class JournalEntryEditor {
* The callback when editing a journal entry.
*
* @param entry {JournalEntrySubForm} the journal entry sub-form
* @param originalEntryId {string} the ID of the original entry
* @param originalEntryDate {string} the date of the original entry
* @param originalEntryText {string} the text of the original entry
* @param summary {string} the summary
* @param accountCode {string} the account code
* @param accountText {string} the account text
* @param amount {string} the amount
* @param amountMin {string} the minimal amount
*/
onEdit(entry, originalEntryId, originalEntryDate, originalEntryText, summary, accountCode, accountText, amount, amountMin) {
onEdit(entry) {
this.entry = entry;
this.#side = entry.side;
this.entryType = this.#side.entryType;
this.isNeedOffset = entry.isNeedOffset();
if (originalEntryId === "") {
this.originalEntryId = entry.getOriginalEntryId();
this.originalEntryDate = entry.getOriginalEntryDate();
this.originalEntryText = entry.getOriginalEntryText();
this.#originalEntry.innerText = this.originalEntryText;
if (this.originalEntryId === null) {
this.#originalEntryContainer.classList.add("d-none");
this.#originalEntryControl.classList.remove("accounting-not-empty");
} else {
this.#originalEntryContainer.classList.remove("d-none");
this.#originalEntryControl.classList.add("accounting-not-empty");
}
this.originalEntryId = originalEntryId === ""? null: originalEntryId;
this.originalEntryDate = originalEntryDate === ""? null: originalEntryDate;
this.originalEntryText = originalEntryText === ""? null: originalEntryText;
this.#originalEntry.innerText = originalEntryText;
this.#setEnableSummaryAccount(!entry.isMatched && originalEntryId === "");
if (summary === "") {
this.#setEnableSummaryAccount(!entry.isMatched && this.originalEntryId === null);
this.summary = entry.getSummary();
if (this.summary === null) {
this.#summaryControl.classList.remove("accounting-not-empty");
} else {
this.#summaryControl.classList.add("accounting-not-empty");
}
this.summary = summary === ""? null: summary;
this.#summary.innerText = summary;
if (accountCode === "") {
this.#summary.innerText = this.summary === null? "": this.summary;
if (entry.getAccountCode() === null) {
this.#accountControl.classList.remove("accounting-not-empty");
} else {
this.#accountControl.classList.add("accounting-not-empty");
}
this.accountCode = accountCode;
this.accountText = accountText;
this.#account.innerText = accountText;
this.#amount.value = amount;
this.accountCode = entry.getAccountCode();
this.accountText = entry.getAccountText();
this.#account.innerText = this.accountText;
this.#amount.value = entry.getAmount() === null? "": String(entry.getAmount());
const maxAmount = this.#getMaxAmount();
this.#amount.max = maxAmount === null? "": maxAmount;
this.#amount.min = amountMin;
this.#amount.min = entry.getAmountMin() === null? "": String(entry.getAmountMin());
this.#validate();
}

View File

@ -215,7 +215,7 @@ class SummaryEditor {
#submit() {
bootstrap.Modal.getOrCreateInstance(this.#modal).hide();
if (this.#selectedAccount !== null) {
this.#entryEditor.saveSummaryWithAccount(this.summary.value, this.#selectedAccount.dataset.code, this.#selectedAccount.dataset.text, this.#selectedAccount.classList.contains("accounting-account-is-offset-needed"));
this.#entryEditor.saveSummaryWithAccount(this.summary.value, this.#selectedAccount.dataset.code, this.#selectedAccount.dataset.text, this.#selectedAccount.classList.contains("accounting-account-is-need-offset"));
} else {
this.#entryEditor.saveSummary(this.summary.value);
}
@ -242,6 +242,21 @@ class SummaryEditor {
}
this.tabPlanes.general.switchToMe();
}
/**
* Returns the summary editor instances.
*
* @param entryEditor {JournalEntryEditor} the journal entry editor
* @return {{debit: SummaryEditor, credit: SummaryEditor}}
*/
static getInstances(entryEditor) {
const editors = {}
const forms = Array.from(document.getElementsByClassName("accounting-summary-editor"));
for (const form of forms) {
editors[form.dataset.entryType] = new SummaryEditor(entryEditor, form.dataset.entryType);
}
return editors;
}
}
/**

View File

@ -881,7 +881,7 @@ class JournalEntrySubForm {
this.#amount = document.getElementById(this.#prefix + "-amount");
this.#amountText = document.getElementById(this.#prefix + "-amount-text");
this.deleteButton = document.getElementById(this.#prefix + "-delete");
this.#control.onclick = () => this.side.currency.form.entryEditor.onEdit(this, this.#originalEntryId.value, this.#originalEntryId.dataset.date, this.#originalEntryId.dataset.text, this.#summary.value, this.#accountCode.value, this.#accountCode.dataset.text, this.#amount.value, this.#amount.dataset.min);
this.#control.onclick = () => this.side.currency.form.entryEditor.onEdit(this);
this.deleteButton.onclick = () => {
this.element.parentElement.removeChild(this.element);
this.side.deleteJournalEntry(this);
@ -915,6 +915,24 @@ class JournalEntrySubForm {
return this.#originalEntryId.dataset.date === ""? null: this.#originalEntryId.dataset.date;
}
/**
* Returns the text of the original entry.
*
* @return {string|null} the text of the original entry
*/
getOriginalEntryText() {
return this.#originalEntryId.dataset.text === ""? null: this.#originalEntryId.dataset.text;
}
/**
* Returns the summary.
*
* @return {string|null} the summary
*/
getSummary() {
return this.#summary.value === ""? null: this.#summary.value;
}
/**
* Returns the account code.
*
@ -924,6 +942,15 @@ class JournalEntrySubForm {
return this.#accountCode.value === ""? null: this.#accountCode.value;
}
/**
* Returns the account text.
*
* @return {string|null} the account text
*/
getAccountText() {
return this.#accountCode.dataset.text === ""? null: this.#accountCode.dataset.text;
}
/**
* Returns the amount.
*
@ -933,6 +960,15 @@ class JournalEntrySubForm {
return this.#amount.value === ""? null: new Decimal(this.#amount.value);
}
/**
* Returns the minimal amount.
*
* @return {Decimal|null} the minimal amount
*/
getAmountMin() {
return this.#amount.dataset.min === ""? null: new Decimal(this.#amount.dataset.min);
}
/**
* Validates the form.
*

View File

@ -85,7 +85,7 @@ First written: 2023/1/31
<div class="accounting-card col-sm-6">
<div class="accounting-card-title">{{ obj.title }}</div>
<div class="accounting-card-code">{{ obj.code }}</div>
{% if obj.is_offset_needed %}
{% if obj.is_need_offset %}
<div>
<span class="badge rounded-pill bg-info">{{ A_("Need offset") }}</span>
</div>

View File

@ -62,9 +62,9 @@ First written: 2023/2/1
<div id="accounting-title-error" class="invalid-feedback">{% if form.title.errors %}{{ form.title.errors[0] }}{% endif %}</div>
</div>
<div id="accounting-is-offset-needed-control" class="form-check form-switch mb-3 {% if form.base_code.data[0] not in ["1", "2", "3"] %} d-none {% endif %}">
<input id="accounting-is-offset-needed" class="form-check-input" type="checkbox" name="is_offset_needed" value="1" {% if form.is_offset_needed.data %} checked="checked" {% endif %}>
<label class="form-check-label" for="accounting-is-offset-needed">
<div id="accounting-is-need-offset-control" class="form-check form-switch mb-3 {% if form.base_code.data[0] not in ["1", "2", "3"] %} d-none {% endif %}">
<input id="accounting-is-need-offset" class="form-check-input" type="checkbox" name="is_need_offset" value="1" {% if form.is_need_offset.data %} checked="checked" {% endif %}>
<label class="form-check-label" for="accounting-is-need-offset">
{{ A_("The entries in the account need offset.") }}
</label>
</div>

View File

@ -58,7 +58,7 @@ First written: 2023/1/30
{% for item in list %}
<a class="list-group-item list-group-item-action" href="{{ url_for("accounting.account.detail", account=item)|accounting_append_next }}">
{{ item }}
{% if item.is_offset_needed %}
{% if item.is_need_offset %}
<span class="badge rounded-pill bg-info">{{ A_("Need offset") }}</span>
{% endif %}
</a>

View File

@ -19,7 +19,7 @@ account-selector-modal.html: The modal for the account selector
Author: imacat@mail.imacat.idv.tw (imacat)
First written: 2023/2/25
#}
<div id="accounting-account-selector-{{ entry_type }}-modal" class="modal fade" data-entry-type="{{ entry_type }}" tabindex="-1" aria-labelledby="accounting-account-selector-{{ entry_type }}-modal-label" aria-hidden="true">
<div id="accounting-account-selector-{{ entry_type }}-modal" class="modal fade accounting-account-selector" data-entry-type="{{ entry_type }}" tabindex="-1" aria-labelledby="accounting-account-selector-{{ entry_type }}-modal-label" aria-hidden="true">
<div class="modal-dialog">
<div class="modal-content">
<div class="modal-header">
@ -37,7 +37,7 @@ First written: 2023/2/25
<ul id="accounting-account-selector-{{ entry_type }}-option-list" class="list-group accounting-selector-list">
{% for account in account_options %}
<li id="accounting-account-selector-{{ entry_type }}-option-{{ account.code }}" class="list-group-item accounting-clickable accounting-account-selector-{{ entry_type }}-option {% if account.is_in_use %} accounting-account-in-use {% endif %} {% if account.is_offset_needed %} accounting-account-is-offset-needed {% endif %}" data-code="{{ account.code }}" data-content="{{ account }}" data-query-values="{{ account.query_values|tojson|forceescape }}" data-bs-toggle="modal" data-bs-target="#accounting-entry-editor-modal">
<li id="accounting-account-selector-{{ entry_type }}-option-{{ account.code }}" class="list-group-item accounting-clickable accounting-account-selector-{{ entry_type }}-option {% if account.is_in_use %} accounting-account-in-use {% endif %} {% if account.is_need_offset %} accounting-account-is-need-offset {% endif %}" data-code="{{ account.code }}" data-content="{{ account }}" data-query-values="{{ account.query_values|tojson|forceescape }}" data-bs-toggle="modal" data-bs-target="#accounting-entry-editor-modal">
{{ account }}
</li>
{% endfor %}

View File

@ -19,7 +19,7 @@ summary-editor-modal.html: The modal of the summary editor
Author: imacat@mail.imacat.idv.tw (imacat)
First written: 2023/2/28
#}
<form id="accounting-summary-editor-{{ summary_editor.type }}">
<form id="accounting-summary-editor-{{ summary_editor.type }}" class="accounting-summary-editor" data-entry-type="{{ summary_editor.type }}">
<div id="accounting-summary-editor-{{ summary_editor.type }}-modal" class="modal fade" tabindex="-1" aria-labelledby="accounting-summary-editor-{{ summary_editor.type }}-modal-label" aria-hidden="true">
<div class="modal-dialog">
<div class="modal-content">
@ -32,7 +32,7 @@ First written: 2023/2/28
<div class="modal-body">
<div class="d-flex justify-content-between mb-3">
<input id="accounting-summary-editor-{{ summary_editor.type }}-summary" class="form-control" type="text" aria-labelledby="accounting-summary-editor-{{ summary_editor.type }}-modal-label">
<button id="accounting-summary-editor-{{ summary_editor.type }}-offset" class="btn btn-primary ms-2" type="button" data-bs-toggle="modal" data-bs-target="#accounting-original-entry-selector-modal">
<button id="accounting-summary-editor-{{ summary_editor.type }}-offset" class="btn btn-primary text-nowrap ms-2" type="button" data-bs-toggle="modal" data-bs-target="#accounting-original-entry-selector-modal">
{{ A_("Offset...") }}
</button>
</div>
@ -177,7 +177,7 @@ First written: 2023/2/28
{# The suggested accounts #}
<div class="mt-3">
{% for account in summary_editor.accounts %}
<button class="btn btn-outline-primary d-none accounting-summary-editor-{{ summary_editor.type }}-account {% if account.is_offset_needed %} accounting-account-is-offset-needed {% endif %}" type="button" data-code="{{ account.code }}" data-text="{{ account }}">
<button class="btn btn-outline-primary d-none accounting-summary-editor-{{ summary_editor.type }}-account {% if account.is_need_offset %} accounting-account-is-need-offset {% endif %}" type="button" data-code="{{ account.code }}" data-text="{{ account }}">
{{ account }}
</button>
{% endfor %}

View File

@ -81,7 +81,7 @@ class OriginalEntryNeedOffset:
= db.session.get(JournalEntry, field.data)
if original_entry is None:
return
if not original_entry.account.is_offset_needed:
if not original_entry.account.is_need_offset:
raise ValidationError(lazy_gettext(
"The original entry does not need offset."))
@ -186,7 +186,7 @@ class NotStartPayableFromDebit:
or form.original_entry_id.data is not None:
return
account: Account | None = Account.find_by_code(field.data)
if account is not None and account.is_offset_needed:
if account is not None and account.is_need_offset:
raise ValidationError(lazy_gettext(
"A payable entry cannot start from the debit side."))
@ -202,7 +202,7 @@ class NotStartReceivableFromCredit:
or form.original_entry_id.data is not None:
return
account: Account | None = Account.find_by_code(field.data)
if account is not None and account.is_offset_needed:
if account is not None and account.is_need_offset:
raise ValidationError(lazy_gettext(
"A receivable entry cannot start from the credit side."))
@ -361,7 +361,7 @@ class JournalEntryForm(FlaskForm):
else:
return False
account: Account | None = Account.find_by_code(self.account_code.data)
return account is not None and account.is_offset_needed
return account is not None and account.is_need_offset
@property
def offsets(self) -> list[JournalEntry]:

View File

@ -129,9 +129,9 @@ class TransactionForm(FlaskForm):
provide their own collectors."""
self.obj: Transaction | None = kwargs.get("obj")
"""The transaction, when editing an existing one."""
self._is_payable_needed: bool = False
self._is_need_payable: bool = False
"""Whether we need the payable original entries."""
self._is_receivable_needed: bool = False
self._is_need_receivable: bool = False
"""Whether we need the receivable original entries."""
self.__original_entry_options: list[JournalEntry] | None = None
"""The options of the original entries."""
@ -219,7 +219,7 @@ class TransactionForm(FlaskForm):
"""
accounts: list[AccountOption] \
= [AccountOption(x) for x in Account.debit()
if not (x.code[0] == "2" and x.is_offset_needed)]
if not (x.code[0] == "2" and x.is_need_offset)]
in_use: set[int] = set(db.session.scalars(
sa.select(JournalEntry.account_id)
.filter(JournalEntry.is_debit)
@ -236,7 +236,7 @@ class TransactionForm(FlaskForm):
"""
accounts: list[AccountOption] \
= [AccountOption(x) for x in Account.credit()
if not (x.code[0] == "1" and x.is_offset_needed)]
if not (x.code[0] == "1" and x.is_need_offset)]
in_use: set[int] = set(db.session.scalars(
sa.select(JournalEntry.account_id)
.filter(sa.not_(JournalEntry.is_debit))
@ -271,7 +271,7 @@ class TransactionForm(FlaskForm):
if self.__original_entry_options is None:
self.__original_entry_options = get_selectable_original_entries(
{x.eid.data for x in self.entries if x.eid.data is not None},
self._is_payable_needed, self._is_receivable_needed)
self._is_need_payable, self._is_need_receivable)
return self.__original_entry_options
@property
@ -459,7 +459,7 @@ class IncomeTransactionForm(TransactionForm):
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self._is_receivable_needed = True
self._is_need_receivable = True
class Collector(JournalEntryCollector[IncomeTransactionForm]):
"""The journal entry collector for the cash income transactions."""
@ -504,7 +504,7 @@ class ExpenseTransactionForm(TransactionForm):
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self._is_payable_needed = True
self._is_need_payable = True
class Collector(JournalEntryCollector[ExpenseTransactionForm]):
"""The journal entry collector for the cash expense
@ -550,8 +550,8 @@ class TransferTransactionForm(TransactionForm):
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self._is_payable_needed = True
self._is_receivable_needed = True
self._is_need_payable = True
self._is_need_receivable = True
class Collector(JournalEntryCollector[TransferTransactionForm]):
"""The journal entry collector for the transfer transactions."""

View File

@ -38,10 +38,8 @@ class AccountOption:
"""The string representation of the account option."""
self.is_in_use: bool = False
"""True if this account is in use, or False otherwise."""
self.is_offset_needed: bool = account.is_offset_needed
self.is_need_offset: bool = account.is_need_offset
"""True if this account needs offset, or False otherwise."""
self.is_offset_chooser_needed: bool = False
"""True if this account needs an offset chooser, or False otherwise."""
def __str__(self) -> str:
"""Returns the string representation of the account option.

View File

@ -50,7 +50,7 @@ def get_selectable_original_entries(
(offset.c.id.in_(entry_id_on_form), 0),
(be(offset.c.is_debit == JournalEntry.is_debit), offset.c.amount),
else_=-offset.c.amount))).label("net_balance")
conditions: list[sa.BinaryExpression] = [Account.is_offset_needed]
conditions: list[sa.BinaryExpression] = [Account.is_need_offset]
sub_conditions: list[sa.BinaryExpression] = []
if is_payable:
sub_conditions.append(sa.and_(Account.base_code.startswith("2"),

File diff suppressed because it is too large Load Diff

View File

@ -377,7 +377,7 @@ class AccountTestCase(unittest.TestCase):
data={"csrf_token": self.csrf_token,
"base_code": "6172",
"title": stock.title,
"is_offset_needed": "yes"})
"is_need_offset": "yes"})
self.assertEqual(response.status_code, 302)
self.assertEqual(response.headers["Location"], create_uri)
@ -484,7 +484,7 @@ class AccountTestCase(unittest.TestCase):
data={"csrf_token": self.csrf_token,
"base_code": "6172",
"title": stock.title,
"is_offset_needed": "yes"})
"is_need_offset": "yes"})
self.assertEqual(response.status_code, 302)
self.assertEqual(response.headers["Location"], edit_uri)

View File

@ -113,7 +113,7 @@ class OffsetTestCase(unittest.TestCase):
# The original entry does not need offset
with self.app.app_context():
account = Account.find_by_code(Accounts.RECEIVABLE)
account.is_offset_needed = False
account.is_need_offset = False
db.session.commit()
response = self.client.post(store_uri,
data=txn_data.new_form(self.csrf_token))
@ -121,7 +121,7 @@ class OffsetTestCase(unittest.TestCase):
self.assertEqual(response.headers["Location"], create_uri)
with self.app.app_context():
account = Account.find_by_code(Accounts.RECEIVABLE)
account.is_offset_needed = True
account.is_need_offset = True
db.session.commit()
# The original entry is also an offset
@ -219,7 +219,7 @@ class OffsetTestCase(unittest.TestCase):
# The original entry does not need offset
with self.app.app_context():
account = Account.find_by_code(Accounts.RECEIVABLE)
account.is_offset_needed = False
account.is_need_offset = False
db.session.commit()
response = self.client.post(update_uri,
data=txn_data.update_form(self.csrf_token))
@ -227,7 +227,7 @@ class OffsetTestCase(unittest.TestCase):
self.assertEqual(response.headers["Location"], edit_uri)
with self.app.app_context():
account = Account.find_by_code(Accounts.RECEIVABLE)
account.is_offset_needed = True
account.is_need_offset = True
db.session.commit()
# The original entry is also an offset
@ -419,7 +419,7 @@ class OffsetTestCase(unittest.TestCase):
# The original entry does not need offset
with self.app.app_context():
account = Account.find_by_code(Accounts.PAYABLE)
account.is_offset_needed = False
account.is_need_offset = False
db.session.commit()
response = self.client.post(store_uri,
data=txn_data.new_form(self.csrf_token))
@ -427,7 +427,7 @@ class OffsetTestCase(unittest.TestCase):
self.assertEqual(response.headers["Location"], create_uri)
with self.app.app_context():
account = Account.find_by_code(Accounts.PAYABLE)
account.is_offset_needed = True
account.is_need_offset = True
db.session.commit()
# The original entry is also an offset
@ -525,7 +525,7 @@ class OffsetTestCase(unittest.TestCase):
# The original entry does not need offset
with self.app.app_context():
account = Account.find_by_code(Accounts.PAYABLE)
account.is_offset_needed = False
account.is_need_offset = False
db.session.commit()
response = self.client.post(update_uri,
data=txn_data.update_form(self.csrf_token))
@ -533,7 +533,7 @@ class OffsetTestCase(unittest.TestCase):
self.assertEqual(response.headers["Location"], edit_uri)
with self.app.app_context():
account = Account.find_by_code(Accounts.PAYABLE)
account.is_offset_needed = True
account.is_need_offset = True
db.session.commit()
# The original entry is also an offset

View File

@ -152,7 +152,7 @@ class PaginationTestCase(unittest.TestCase):
:param items: All the items in the list.
:param is_reversed: Whether the default page is the last page.
:param result: The expected items on the page.
:param is_paged: Whether the pagination is needed.
:param is_paged: Whether we need pagination.
"""
self.items: list[int] = items
self.is_reversed: bool | None = is_reversed
@ -192,7 +192,7 @@ class PaginationTestCase(unittest.TestCase):
:param query: The query string.
:param items: The original items.
:param result: The expected page content.
:param is_paged: Whether the pagination is needed.
:param is_paged: Whether we need pagination.
:param is_reversed: Whether the list is reversed.
:return: None.
"""
@ -247,8 +247,8 @@ class PaginationTestCase(unittest.TestCase):
self.__test_success("page-no=46&page-size=15", range(1, 687),
range(676, 687))
def test_not_needed(self) -> None:
"""Tests the pagination that is not needed.
def test_not_need(self) -> None:
"""Tests that the data does not need pagination.
:return: None.
"""