Compare commits
3 Commits
ee5b447c23
...
8c58a9083a
Author | SHA1 | Date | |
---|---|---|---|
8c58a9083a | |||
f45663754c | |||
cda9e4e3c6 |
@ -17,15 +17,15 @@
|
|||||||
"""The console commands for the account management.
|
"""The console commands for the account management.
|
||||||
|
|
||||||
"""
|
"""
|
||||||
import typing as t
|
|
||||||
from secrets import randbelow
|
from secrets import randbelow
|
||||||
|
from typing import Any
|
||||||
|
|
||||||
import click
|
import click
|
||||||
|
import sqlalchemy as sa
|
||||||
|
|
||||||
from accounting import db
|
from accounting import db
|
||||||
from accounting.models import BaseAccount, Account, AccountL10n
|
from accounting.models import BaseAccount, Account, AccountL10n
|
||||||
from accounting.utils.user import get_user_pk
|
from accounting.utils.user import get_user_pk
|
||||||
import sqlalchemy as sa
|
|
||||||
|
|
||||||
AccountData = tuple[int, str, int, str, str, str, bool]
|
AccountData = tuple[int, str, int, str, str, str, bool]
|
||||||
"""The format of the account data, as a list of (ID, base account code, number,
|
"""The format of the account data, as a list of (ID, base account code, number,
|
||||||
@ -63,8 +63,8 @@ def init_accounts_command(username: str) -> None:
|
|||||||
existing_id.add(new_id)
|
existing_id.add(new_id)
|
||||||
return new_id
|
return new_id
|
||||||
|
|
||||||
data: list[dict[str, t.Any]] = []
|
data: list[dict[str, Any]] = []
|
||||||
l10n_data: list[dict[str, t.Any]] = []
|
l10n_data: list[dict[str, Any]] = []
|
||||||
for base in bases_to_add:
|
for base in bases_to_add:
|
||||||
l10n: dict[str, str] = {x.locale: x.title for x in base.l10n}
|
l10n: dict[str, str] = {x.locale: x.title for x in base.l10n}
|
||||||
account_id: int = get_new_id()
|
account_id: int = get_new_id()
|
||||||
|
@ -18,7 +18,7 @@
|
|||||||
|
|
||||||
"""
|
"""
|
||||||
import csv
|
import csv
|
||||||
import typing as t
|
from typing import Any
|
||||||
|
|
||||||
import sqlalchemy as sa
|
import sqlalchemy as sa
|
||||||
|
|
||||||
@ -39,7 +39,7 @@ def init_currencies_command(username: str) -> None:
|
|||||||
return
|
return
|
||||||
|
|
||||||
creator_pk: int = get_user_pk(username)
|
creator_pk: int = get_user_pk(username)
|
||||||
currency_data: list[dict[str, t.Any]] = [{"code": x["code"],
|
currency_data: list[dict[str, Any]] = [{"code": x["code"],
|
||||||
"name_l10n": x["name"],
|
"name_l10n": x["name"],
|
||||||
"created_by_id": creator_pk,
|
"created_by_id": creator_pk,
|
||||||
"updated_by_id": creator_pk}
|
"updated_by_id": creator_pk}
|
||||||
|
@ -18,8 +18,8 @@
|
|||||||
|
|
||||||
"""
|
"""
|
||||||
import datetime as dt
|
import datetime as dt
|
||||||
import typing as t
|
|
||||||
from abc import ABC, abstractmethod
|
from abc import ABC, abstractmethod
|
||||||
|
from typing import TypeVar, Generic, Type
|
||||||
|
|
||||||
import sqlalchemy as sa
|
import sqlalchemy as sa
|
||||||
from flask_babel import LazyString
|
from flask_babel import LazyString
|
||||||
@ -29,13 +29,13 @@ from wtforms import DateField, FieldList, FormField, TextAreaField, \
|
|||||||
from wtforms.validators import DataRequired, ValidationError
|
from wtforms.validators import DataRequired, ValidationError
|
||||||
|
|
||||||
from accounting import db
|
from accounting import db
|
||||||
|
from accounting.journal_entry.utils.account_option import AccountOption
|
||||||
|
from accounting.journal_entry.utils.description_editor import DescriptionEditor
|
||||||
|
from accounting.journal_entry.utils.original_line_items import \
|
||||||
|
get_selectable_original_line_items
|
||||||
from accounting.locale import lazy_gettext
|
from accounting.locale import lazy_gettext
|
||||||
from accounting.models import JournalEntry, Account, JournalEntryLineItem, \
|
from accounting.models import JournalEntry, Account, JournalEntryLineItem, \
|
||||||
JournalEntryCurrency
|
JournalEntryCurrency
|
||||||
from accounting.journal_entry.utils.account_option import AccountOption
|
|
||||||
from accounting.journal_entry.utils.original_line_items import \
|
|
||||||
get_selectable_original_line_items
|
|
||||||
from accounting.journal_entry.utils.description_editor import DescriptionEditor
|
|
||||||
from accounting.utils.random_id import new_id
|
from accounting.utils.random_id import new_id
|
||||||
from accounting.utils.strip_text import strip_multiline_text
|
from accounting.utils.strip_text import strip_multiline_text
|
||||||
from accounting.utils.user import get_current_user_pk
|
from accounting.utils.user import get_current_user_pk
|
||||||
@ -123,7 +123,7 @@ class JournalEntryForm(FlaskForm):
|
|||||||
super().__init__(*args, **kwargs)
|
super().__init__(*args, **kwargs)
|
||||||
self.is_modified: bool = False
|
self.is_modified: bool = False
|
||||||
"""Whether the journal entry is modified during populate_obj()."""
|
"""Whether the journal entry is modified during populate_obj()."""
|
||||||
self.collector: t.Type[LineItemCollector] = LineItemCollector
|
self.collector: Type[LineItemCollector] = LineItemCollector
|
||||||
"""The line item collector. The default is the base abstract
|
"""The line item collector. The default is the base abstract
|
||||||
collector only to provide the correct type. The subclass forms should
|
collector only to provide the correct type. The subclass forms should
|
||||||
provide their own collectors."""
|
provide their own collectors."""
|
||||||
@ -155,7 +155,7 @@ class JournalEntryForm(FlaskForm):
|
|||||||
self.__set_date(obj, self.date.data)
|
self.__set_date(obj, self.date.data)
|
||||||
obj.note = self.note.data
|
obj.note = self.note.data
|
||||||
|
|
||||||
collector_cls: t.Type[LineItemCollector] = self.collector
|
collector_cls: Type[LineItemCollector] = self.collector
|
||||||
collector: collector_cls = collector_cls(self, obj)
|
collector: collector_cls = collector_cls(self, obj)
|
||||||
collector.collect()
|
collector.collect()
|
||||||
|
|
||||||
@ -309,11 +309,11 @@ class JournalEntryForm(FlaskForm):
|
|||||||
return db.session.scalar(select)
|
return db.session.scalar(select)
|
||||||
|
|
||||||
|
|
||||||
T = t.TypeVar("T", bound=JournalEntryForm)
|
T = TypeVar("T", bound=JournalEntryForm)
|
||||||
"""A journal entry form variant."""
|
"""A journal entry form variant."""
|
||||||
|
|
||||||
|
|
||||||
class LineItemCollector(t.Generic[T], ABC):
|
class LineItemCollector(Generic[T], ABC):
|
||||||
"""The line item collector."""
|
"""The line item collector."""
|
||||||
|
|
||||||
def __init__(self, form: T, obj: JournalEntry):
|
def __init__(self, form: T, obj: JournalEntry):
|
||||||
|
@ -18,7 +18,7 @@
|
|||||||
|
|
||||||
"""
|
"""
|
||||||
import re
|
import re
|
||||||
import typing as t
|
from typing import Literal
|
||||||
|
|
||||||
import sqlalchemy as sa
|
import sqlalchemy as sa
|
||||||
|
|
||||||
@ -124,12 +124,12 @@ class DescriptionTag:
|
|||||||
class DescriptionType:
|
class DescriptionType:
|
||||||
"""A description type"""
|
"""A description type"""
|
||||||
|
|
||||||
def __init__(self, type_id: t.Literal["general", "travel", "bus"]):
|
def __init__(self, type_id: Literal["general", "travel", "bus"]):
|
||||||
"""Constructs a description type.
|
"""Constructs a description type.
|
||||||
|
|
||||||
:param type_id: The type ID, either "general", "travel", or "bus".
|
:param type_id: The type ID, either "general", "travel", or "bus".
|
||||||
"""
|
"""
|
||||||
self.id: t.Literal["general", "travel", "bus"] = type_id
|
self.id: Literal["general", "travel", "bus"] = type_id
|
||||||
"""The type ID."""
|
"""The type ID."""
|
||||||
self.__tag_dict: dict[str, DescriptionTag] = {}
|
self.__tag_dict: dict[str, DescriptionTag] = {}
|
||||||
"""A dictionary from the tag name to their corresponding tag."""
|
"""A dictionary from the tag name to their corresponding tag."""
|
||||||
@ -181,12 +181,12 @@ class DescriptionRecurring:
|
|||||||
class DescriptionDebitCredit:
|
class DescriptionDebitCredit:
|
||||||
"""The description on debit or credit."""
|
"""The description on debit or credit."""
|
||||||
|
|
||||||
def __init__(self, debit_credit: t.Literal["debit", "credit"]):
|
def __init__(self, debit_credit: Literal["debit", "credit"]):
|
||||||
"""Constructs the description on debit or credit.
|
"""Constructs the description on debit or credit.
|
||||||
|
|
||||||
:param debit_credit: Either "debit" or "credit".
|
:param debit_credit: Either "debit" or "credit".
|
||||||
"""
|
"""
|
||||||
self.debit_credit: t.Literal["debit", "credit"] = debit_credit
|
self.debit_credit: Literal["debit", "credit"] = debit_credit
|
||||||
"""Either debit or credit."""
|
"""Either debit or credit."""
|
||||||
self.general: DescriptionType = DescriptionType("general")
|
self.general: DescriptionType = DescriptionType("general")
|
||||||
"""The general tags."""
|
"""The general tags."""
|
||||||
@ -194,14 +194,14 @@ class DescriptionDebitCredit:
|
|||||||
"""The travel tags."""
|
"""The travel tags."""
|
||||||
self.bus: DescriptionType = DescriptionType("bus")
|
self.bus: DescriptionType = DescriptionType("bus")
|
||||||
"""The bus tags."""
|
"""The bus tags."""
|
||||||
self.__type_dict: dict[t.Literal["general", "travel", "bus"],
|
self.__type_dict: dict[Literal["general", "travel", "bus"],
|
||||||
DescriptionType] \
|
DescriptionType] \
|
||||||
= {x.id: x for x in {self.general, self.travel, self.bus}}
|
= {x.id: x for x in {self.general, self.travel, self.bus}}
|
||||||
"""A dictionary from the type ID to the corresponding tags."""
|
"""A dictionary from the type ID to the corresponding tags."""
|
||||||
self.recurring: list[DescriptionRecurring] = []
|
self.recurring: list[DescriptionRecurring] = []
|
||||||
"""The recurring transactions."""
|
"""The recurring transactions."""
|
||||||
|
|
||||||
def add_tag(self, tag_type: t.Literal["general", "travel", "bus"],
|
def add_tag(self, tag_type: Literal["general", "travel", "bus"],
|
||||||
name: str, account: Account, freq: int) -> None:
|
name: str, account: Account, freq: int) -> None:
|
||||||
"""Adds a tag.
|
"""Adds a tag.
|
||||||
|
|
||||||
@ -278,7 +278,7 @@ class DescriptionEditor:
|
|||||||
accounts: dict[int, Account] \
|
accounts: dict[int, Account] \
|
||||||
= {x.id: x for x in Account.query
|
= {x.id: x for x in Account.query
|
||||||
.filter(Account.id.in_({x.account_id for x in result})).all()}
|
.filter(Account.id.in_({x.account_id for x in result})).all()}
|
||||||
debit_credit_dict: dict[t.Literal["debit", "credit"],
|
debit_credit_dict: dict[Literal["debit", "credit"],
|
||||||
DescriptionDebitCredit] \
|
DescriptionDebitCredit] \
|
||||||
= {x.debit_credit: x for x in {self.debit, self.credit}}
|
= {x.debit_credit: x for x in {self.debit, self.credit}}
|
||||||
for row in result:
|
for row in result:
|
||||||
|
@ -17,19 +17,19 @@
|
|||||||
"""The operators for different journal entry types.
|
"""The operators for different journal entry types.
|
||||||
|
|
||||||
"""
|
"""
|
||||||
import typing as t
|
|
||||||
from abc import ABC, abstractmethod
|
from abc import ABC, abstractmethod
|
||||||
|
from typing import Type
|
||||||
|
|
||||||
from flask import render_template, request, abort
|
from flask import render_template, request, abort
|
||||||
from flask_wtf import FlaskForm
|
from flask_wtf import FlaskForm
|
||||||
|
|
||||||
from accounting.models import JournalEntry
|
|
||||||
from accounting.template_globals import default_currency_code
|
|
||||||
from accounting.utils.journal_entry_types import JournalEntryType
|
|
||||||
from accounting.journal_entry.forms import JournalEntryForm, \
|
from accounting.journal_entry.forms import JournalEntryForm, \
|
||||||
CashReceiptJournalEntryForm, CashDisbursementJournalEntryForm, \
|
CashReceiptJournalEntryForm, CashDisbursementJournalEntryForm, \
|
||||||
TransferJournalEntryForm
|
TransferJournalEntryForm
|
||||||
from accounting.journal_entry.forms.line_item import LineItemForm
|
from accounting.journal_entry.forms.line_item import LineItemForm
|
||||||
|
from accounting.models import JournalEntry
|
||||||
|
from accounting.template_globals import default_currency_code
|
||||||
|
from accounting.utils.journal_entry_types import JournalEntryType
|
||||||
|
|
||||||
|
|
||||||
class JournalEntryOperator(ABC):
|
class JournalEntryOperator(ABC):
|
||||||
@ -39,7 +39,7 @@ class JournalEntryOperator(ABC):
|
|||||||
|
|
||||||
@property
|
@property
|
||||||
@abstractmethod
|
@abstractmethod
|
||||||
def form(self) -> t.Type[JournalEntryForm]:
|
def form(self) -> Type[JournalEntryForm]:
|
||||||
"""Returns the form class.
|
"""Returns the form class.
|
||||||
|
|
||||||
:return: The form class.
|
:return: The form class.
|
||||||
@ -100,7 +100,7 @@ class CashReceiptJournalEntry(JournalEntryOperator):
|
|||||||
"""The order when checking the journal entry operator."""
|
"""The order when checking the journal entry operator."""
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def form(self) -> t.Type[JournalEntryForm]:
|
def form(self) -> Type[JournalEntryForm]:
|
||||||
"""Returns the form class.
|
"""Returns the form class.
|
||||||
|
|
||||||
:return: The form class.
|
:return: The form class.
|
||||||
@ -170,7 +170,7 @@ class CashDisbursementJournalEntry(JournalEntryOperator):
|
|||||||
"""The order when checking the journal entry operator."""
|
"""The order when checking the journal entry operator."""
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def form(self) -> t.Type[JournalEntryForm]:
|
def form(self) -> Type[JournalEntryForm]:
|
||||||
"""Returns the form class.
|
"""Returns the form class.
|
||||||
|
|
||||||
:return: The form class.
|
:return: The form class.
|
||||||
@ -243,7 +243,7 @@ class TransferJournalEntry(JournalEntryOperator):
|
|||||||
"""The order when checking the journal entry operator."""
|
"""The order when checking the journal entry operator."""
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def form(self) -> t.Type[JournalEntryForm]:
|
def form(self) -> Type[JournalEntryForm]:
|
||||||
"""Returns the form class.
|
"""Returns the form class.
|
||||||
|
|
||||||
:return: The form class.
|
:return: The form class.
|
||||||
|
@ -21,8 +21,8 @@ from __future__ import annotations
|
|||||||
|
|
||||||
import datetime as dt
|
import datetime as dt
|
||||||
import re
|
import re
|
||||||
import typing as t
|
|
||||||
from decimal import Decimal
|
from decimal import Decimal
|
||||||
|
from typing import Type, Annotated, Self
|
||||||
|
|
||||||
import sqlalchemy as sa
|
import sqlalchemy as sa
|
||||||
from babel import Locale
|
from babel import Locale
|
||||||
@ -34,16 +34,16 @@ from accounting import db
|
|||||||
from accounting.locale import gettext
|
from accounting.locale import gettext
|
||||||
from accounting.utils.user import user_cls, user_pk_column
|
from accounting.utils.user import user_cls, user_pk_column
|
||||||
|
|
||||||
timestamp: t.Type[dt.datetime] \
|
timestamp: Type[dt.datetime] \
|
||||||
= t.Annotated[dt.datetime, mapped_column(db.DateTime(timezone=True),
|
= Annotated[dt.datetime, mapped_column(db.DateTime(timezone=True),
|
||||||
server_default=db.func.now())]
|
server_default=db.func.now())]
|
||||||
"""The timestamp."""
|
"""The timestamp."""
|
||||||
user_pk: t.Type[int] \
|
user_pk: Type[int] \
|
||||||
= t.Annotated[int, mapped_column(db.ForeignKey(user_pk_column,
|
= Annotated[int, mapped_column(db.ForeignKey(user_pk_column,
|
||||||
onupdate="CASCADE"))]
|
onupdate="CASCADE"))]
|
||||||
"""The user primary key."""
|
"""The user primary key."""
|
||||||
random_pk: t.Type[int] \
|
random_pk: Type[int] \
|
||||||
= t.Annotated[int, mapped_column(primary_key=True, autoincrement=False)]
|
= Annotated[int, mapped_column(primary_key=True, autoincrement=False)]
|
||||||
"""The random primary key."""
|
"""The random primary key."""
|
||||||
|
|
||||||
|
|
||||||
@ -273,11 +273,11 @@ class Account(db.Model):
|
|||||||
:return: None.
|
:return: None.
|
||||||
"""
|
"""
|
||||||
AccountL10n.query.filter(AccountL10n.account == self).delete()
|
AccountL10n.query.filter(AccountL10n.account == self).delete()
|
||||||
cls: t.Type[t.Self] = self.__class__
|
cls: Type[Self] = self.__class__
|
||||||
cls.query.filter(cls.id == self.id).delete()
|
cls.query.filter(cls.id == self.id).delete()
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def find_by_code(cls, code: str) -> t.Self | None:
|
def find_by_code(cls, code: str) -> Self | None:
|
||||||
"""Finds an account by its code.
|
"""Finds an account by its code.
|
||||||
|
|
||||||
:param code: The code.
|
:param code: The code.
|
||||||
@ -290,7 +290,7 @@ class Account(db.Model):
|
|||||||
cls.no == int(m.group(2))).first()
|
cls.no == int(m.group(2))).first()
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def selectable_debit(cls) -> list[t.Self]:
|
def selectable_debit(cls) -> list[Self]:
|
||||||
"""Returns the selectable debit accounts.
|
"""Returns the selectable debit accounts.
|
||||||
Payable line items can not start from debit.
|
Payable line items can not start from debit.
|
||||||
|
|
||||||
@ -313,7 +313,7 @@ class Account(db.Model):
|
|||||||
.order_by(cls.base_code, cls.no).all()
|
.order_by(cls.base_code, cls.no).all()
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def selectable_credit(cls) -> list[t.Self]:
|
def selectable_credit(cls) -> list[Self]:
|
||||||
"""Returns the selectable debit accounts.
|
"""Returns the selectable debit accounts.
|
||||||
Receivable line items can not start from credit.
|
Receivable line items can not start from credit.
|
||||||
|
|
||||||
@ -335,7 +335,7 @@ class Account(db.Model):
|
|||||||
.order_by(cls.base_code, cls.no).all()
|
.order_by(cls.base_code, cls.no).all()
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def cash(cls) -> t.Self:
|
def cash(cls) -> Self:
|
||||||
"""Returns the cash account.
|
"""Returns the cash account.
|
||||||
|
|
||||||
:return: The cash account
|
:return: The cash account
|
||||||
@ -343,7 +343,7 @@ class Account(db.Model):
|
|||||||
return cls.find_by_code(cls.CASH_CODE)
|
return cls.find_by_code(cls.CASH_CODE)
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def accumulated_change(cls) -> t.Self:
|
def accumulated_change(cls) -> Self:
|
||||||
"""Returns the accumulated-change account.
|
"""Returns the accumulated-change account.
|
||||||
|
|
||||||
:return: The accumulated-change account
|
:return: The accumulated-change account
|
||||||
@ -467,7 +467,7 @@ class Currency(db.Model):
|
|||||||
:return: None.
|
:return: None.
|
||||||
"""
|
"""
|
||||||
CurrencyL10n.query.filter(CurrencyL10n.currency == self).delete()
|
CurrencyL10n.query.filter(CurrencyL10n.currency == self).delete()
|
||||||
cls: t.Type[t.Self] = self.__class__
|
cls: Type[Self] = self.__class__
|
||||||
cls.query.filter(cls.code == self.code).delete()
|
cls.query.filter(cls.code == self.code).delete()
|
||||||
|
|
||||||
|
|
||||||
@ -797,14 +797,14 @@ class JournalEntryLineItem(db.Model):
|
|||||||
setattr(self, "__balance", value)
|
setattr(self, "__balance", value)
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def offsets(self) -> list[t.Self]:
|
def offsets(self) -> list[Self]:
|
||||||
"""Returns the offset items.
|
"""Returns the offset items.
|
||||||
|
|
||||||
:return: The offset items.
|
:return: The offset items.
|
||||||
"""
|
"""
|
||||||
if not hasattr(self, "__offsets"):
|
if not hasattr(self, "__offsets"):
|
||||||
cls: t.Type[t.Self] = self.__class__
|
cls: Type[Self] = self.__class__
|
||||||
offsets: list[t.Self] = cls.query.join(JournalEntry)\
|
offsets: list[Self] = cls.query.join(JournalEntry)\
|
||||||
.filter(JournalEntryLineItem.original_line_item_id == self.id)\
|
.filter(JournalEntryLineItem.original_line_item_id == self.id)\
|
||||||
.order_by(JournalEntry.date, JournalEntry.no,
|
.order_by(JournalEntry.date, JournalEntry.no,
|
||||||
cls.is_debit, cls.no).all()
|
cls.is_debit, cls.no).all()
|
||||||
@ -831,7 +831,7 @@ class JournalEntryLineItem(db.Model):
|
|||||||
setattr(self, "__is_offset", value)
|
setattr(self, "__is_offset", value)
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def match(self) -> t.Self | None:
|
def match(self) -> Self | None:
|
||||||
"""Returns the match of the line item.
|
"""Returns the match of the line item.
|
||||||
|
|
||||||
:return: The match of the line item.
|
:return: The match of the line item.
|
||||||
@ -841,7 +841,7 @@ class JournalEntryLineItem(db.Model):
|
|||||||
return getattr(self, "__match")
|
return getattr(self, "__match")
|
||||||
|
|
||||||
@match.setter
|
@match.setter
|
||||||
def match(self, value: t.Self) -> None:
|
def match(self, value: Self) -> None:
|
||||||
"""Sets the match of the line item.
|
"""Sets the match of the line item.
|
||||||
|
|
||||||
:param value: The matcho of the line item.
|
:param value: The matcho of the line item.
|
||||||
|
@ -21,7 +21,7 @@ This file is largely taken from the NanoParma ERP project, first written in
|
|||||||
|
|
||||||
"""
|
"""
|
||||||
import datetime as dt
|
import datetime as dt
|
||||||
import typing as t
|
from collections.abc import Callable
|
||||||
|
|
||||||
from accounting.models import JournalEntry
|
from accounting.models import JournalEntry
|
||||||
from .period import Period
|
from .period import Period
|
||||||
@ -32,13 +32,13 @@ from .shortcuts import ThisMonth, LastMonth, SinceLastMonth, ThisYear, \
|
|||||||
class PeriodChooser:
|
class PeriodChooser:
|
||||||
"""The period chooser."""
|
"""The period chooser."""
|
||||||
|
|
||||||
def __init__(self, get_url: t.Callable[[Period], str]):
|
def __init__(self, get_url: Callable[[Period], str]):
|
||||||
"""Constructs a period chooser.
|
"""Constructs a period chooser.
|
||||||
|
|
||||||
:param get_url: The callback to return the URL of the current report in
|
:param get_url: The callback to return the URL of the current report in
|
||||||
a period.
|
a period.
|
||||||
"""
|
"""
|
||||||
self.__get_url: t.Callable[[Period], str] = get_url
|
self.__get_url: Callable[[Period], str] = get_url
|
||||||
"""The callback to return the URL of the current report in a period."""
|
"""The callback to return the URL of the current report in a period."""
|
||||||
|
|
||||||
# Shortcut periods
|
# Shortcut periods
|
||||||
|
@ -20,7 +20,8 @@
|
|||||||
import calendar
|
import calendar
|
||||||
import datetime as dt
|
import datetime as dt
|
||||||
import re
|
import re
|
||||||
import typing as t
|
from collections.abc import Callable
|
||||||
|
from typing import Type
|
||||||
|
|
||||||
from .period import Period
|
from .period import Period
|
||||||
from .shortcuts import ThisMonth, LastMonth, SinceLastMonth, ThisYear, \
|
from .shortcuts import ThisMonth, LastMonth, SinceLastMonth, ThisYear, \
|
||||||
@ -39,7 +40,7 @@ def get_period(spec: str | None = None) -> Period:
|
|||||||
"""
|
"""
|
||||||
if spec is None:
|
if spec is None:
|
||||||
return ThisMonth()
|
return ThisMonth()
|
||||||
named_periods: dict[str, t.Type[t.Callable[[], Period]]] = {
|
named_periods: dict[str, Type[Callable[[], Period]]] = {
|
||||||
"this-month": lambda: ThisMonth(),
|
"this-month": lambda: ThisMonth(),
|
||||||
"last-month": lambda: LastMonth(),
|
"last-month": lambda: LastMonth(),
|
||||||
"since-last-month": lambda: SinceLastMonth(),
|
"since-last-month": lambda: SinceLastMonth(),
|
||||||
|
@ -21,7 +21,7 @@ This file is largely taken from the NanoParma ERP project, first written in
|
|||||||
|
|
||||||
"""
|
"""
|
||||||
import datetime as dt
|
import datetime as dt
|
||||||
import typing as t
|
from typing import Self
|
||||||
|
|
||||||
from .description import get_desc
|
from .description import get_desc
|
||||||
from .month_end import month_end
|
from .month_end import month_end
|
||||||
@ -119,7 +119,7 @@ class Period:
|
|||||||
and not self.is_a_day
|
and not self.is_a_day
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def before(self) -> t.Self | None:
|
def before(self) -> Self | None:
|
||||||
"""Returns the period before this period.
|
"""Returns the period before this period.
|
||||||
|
|
||||||
:return: The period before this period.
|
:return: The period before this period.
|
||||||
|
@ -17,8 +17,9 @@
|
|||||||
"""The page parameters of a report.
|
"""The page parameters of a report.
|
||||||
|
|
||||||
"""
|
"""
|
||||||
import typing as t
|
|
||||||
from abc import ABC, abstractmethod
|
from abc import ABC, abstractmethod
|
||||||
|
from collections.abc import Callable
|
||||||
|
from typing import Type
|
||||||
from urllib.parse import urlparse, ParseResult, parse_qsl, urlencode, \
|
from urllib.parse import urlparse, ParseResult, parse_qsl, urlencode, \
|
||||||
urlunparse
|
urlunparse
|
||||||
|
|
||||||
@ -52,7 +53,7 @@ class BasePageParams(ABC):
|
|||||||
"""
|
"""
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def journal_entry_types(self) -> t.Type[JournalEntryType]:
|
def journal_entry_types(self) -> Type[JournalEntryType]:
|
||||||
"""Returns the journal entry types.
|
"""Returns the journal entry types.
|
||||||
|
|
||||||
:return: The journal entry types.
|
:return: The journal entry types.
|
||||||
@ -72,7 +73,7 @@ class BasePageParams(ABC):
|
|||||||
return urlunparse(parts)
|
return urlunparse(parts)
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def _get_currency_options(get_url: t.Callable[[Currency], str],
|
def _get_currency_options(get_url: Callable[[Currency], str],
|
||||||
active_currency: Currency) -> list[OptionLink]:
|
active_currency: Currency) -> list[OptionLink]:
|
||||||
"""Returns the currency options.
|
"""Returns the currency options.
|
||||||
|
|
||||||
|
@ -21,7 +21,7 @@ This file is largely taken from the NanoParma ERP project, first written in
|
|||||||
|
|
||||||
"""
|
"""
|
||||||
import re
|
import re
|
||||||
import typing as t
|
from collections.abc import Iterator
|
||||||
|
|
||||||
from flask_babel import LazyString
|
from flask_babel import LazyString
|
||||||
|
|
||||||
@ -190,7 +190,7 @@ class ReportChooser:
|
|||||||
self.__active_report == ReportType.UNMATCHED,
|
self.__active_report == ReportType.UNMATCHED,
|
||||||
fa_icon="fa-solid fa-file-circle-question")
|
fa_icon="fa-solid fa-file-circle-question")
|
||||||
|
|
||||||
def __iter__(self) -> t.Iterator[OptionLink]:
|
def __iter__(self) -> Iterator[OptionLink]:
|
||||||
"""Returns the iteration of the reports.
|
"""Returns the iteration of the reports.
|
||||||
|
|
||||||
:return: The iteration of the reports.
|
:return: The iteration of the reports.
|
||||||
|
@ -18,8 +18,8 @@
|
|||||||
|
|
||||||
"""
|
"""
|
||||||
import datetime as dt
|
import datetime as dt
|
||||||
import typing as t
|
|
||||||
from decimal import Decimal
|
from decimal import Decimal
|
||||||
|
from typing import Any
|
||||||
|
|
||||||
from flask_babel import get_locale
|
from flask_babel import get_locale
|
||||||
|
|
||||||
@ -71,7 +71,7 @@ def format_date(value: dt.date) -> str:
|
|||||||
return "{}/{}({})".format(value.month, value.day, weekday)
|
return "{}/{}({})".format(value.month, value.day, weekday)
|
||||||
|
|
||||||
|
|
||||||
def default(value: t.Any, default_value: t.Any = "") -> t.Any:
|
def default(value: Any, default_value: Any = "") -> Any:
|
||||||
"""Returns the default value if the given value is None.
|
"""Returns the default value if the given value is None.
|
||||||
|
|
||||||
:param value: The value.
|
:param value: The value.
|
||||||
|
@ -20,10 +20,10 @@ warnings from the IDE.
|
|||||||
This module should not import any other module from the application.
|
This module should not import any other module from the application.
|
||||||
|
|
||||||
"""
|
"""
|
||||||
import typing as t
|
from typing import Any
|
||||||
|
|
||||||
|
|
||||||
def s(message: t.Any) -> str:
|
def s(message: Any) -> str:
|
||||||
"""Casts the LazyString message to the string type.
|
"""Casts the LazyString message to the string type.
|
||||||
|
|
||||||
:param message: The message.
|
:param message: The message.
|
||||||
|
@ -17,12 +17,13 @@
|
|||||||
"""The current assets and liabilities account.
|
"""The current assets and liabilities account.
|
||||||
|
|
||||||
"""
|
"""
|
||||||
import typing as t
|
from typing import Self
|
||||||
|
|
||||||
|
import sqlalchemy as sa
|
||||||
|
|
||||||
from accounting import db
|
from accounting import db
|
||||||
from accounting.locale import gettext
|
from accounting.locale import gettext
|
||||||
from accounting.models import Account
|
from accounting.models import Account
|
||||||
import sqlalchemy as sa
|
|
||||||
|
|
||||||
|
|
||||||
class CurrentAccount:
|
class CurrentAccount:
|
||||||
@ -54,7 +55,7 @@ class CurrentAccount:
|
|||||||
return self.str
|
return self.str
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def current_assets_and_liabilities(cls) -> t.Self:
|
def current_assets_and_liabilities(cls) -> Self:
|
||||||
"""Returns the pseudo account for all current assets and liabilities.
|
"""Returns the pseudo account for all current assets and liabilities.
|
||||||
|
|
||||||
:return: The pseudo account for all current assets and liabilities.
|
:return: The pseudo account for all current assets and liabilities.
|
||||||
@ -67,7 +68,7 @@ class CurrentAccount:
|
|||||||
return account
|
return account
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def accounts(cls) -> list[t.Self]:
|
def accounts(cls) -> list[Self]:
|
||||||
"""Returns the current assets and liabilities accounts.
|
"""Returns the current assets and liabilities accounts.
|
||||||
|
|
||||||
:return: The current assets and liabilities accounts.
|
:return: The current assets and liabilities accounts.
|
||||||
|
@ -19,7 +19,7 @@
|
|||||||
This module should not import any other module from the application.
|
This module should not import any other module from the application.
|
||||||
|
|
||||||
"""
|
"""
|
||||||
import typing as t
|
from typing import Any
|
||||||
|
|
||||||
from flask import flash
|
from flask import flash
|
||||||
from flask_wtf import FlaskForm
|
from flask_wtf import FlaskForm
|
||||||
@ -34,7 +34,7 @@ def flash_form_errors(form: FlaskForm) -> None:
|
|||||||
__flash_errors(form.errors)
|
__flash_errors(form.errors)
|
||||||
|
|
||||||
|
|
||||||
def __flash_errors(error: t.Any) -> None:
|
def __flash_errors(error: Any) -> None:
|
||||||
"""Flash all errors recursively.
|
"""Flash all errors recursively.
|
||||||
|
|
||||||
:param error: The errors.
|
:param error: The errors.
|
||||||
|
@ -17,7 +17,7 @@
|
|||||||
"""The SQLAlchemy alias for the offset items.
|
"""The SQLAlchemy alias for the offset items.
|
||||||
|
|
||||||
"""
|
"""
|
||||||
import typing as t
|
from typing import Any
|
||||||
|
|
||||||
import sqlalchemy as sa
|
import sqlalchemy as sa
|
||||||
|
|
||||||
@ -30,10 +30,10 @@ def offset_alias() -> sa.Alias:
|
|||||||
:return: The SQLAlchemy alias for the offset items.
|
:return: The SQLAlchemy alias for the offset items.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def as_from(model_cls: t.Any) -> sa.FromClause:
|
def as_from(model_cls: Any) -> sa.FromClause:
|
||||||
return model_cls
|
return model_cls
|
||||||
|
|
||||||
def as_alias(alias: t.Any) -> sa.Alias:
|
def as_alias(alias: Any) -> sa.Alias:
|
||||||
return alias
|
return alias
|
||||||
|
|
||||||
return as_alias(sa.alias(as_from(JournalEntryLineItem), name="offset"))
|
return as_alias(sa.alias(as_from(JournalEntryLineItem), name="offset"))
|
||||||
|
@ -19,7 +19,7 @@
|
|||||||
This module should not import any other module from the application.
|
This module should not import any other module from the application.
|
||||||
|
|
||||||
"""
|
"""
|
||||||
import typing as t
|
from typing import TypeVar, Generic
|
||||||
from urllib.parse import urlparse, parse_qsl, urlencode, urlunparse, \
|
from urllib.parse import urlparse, parse_qsl, urlencode, urlunparse, \
|
||||||
ParseResult
|
ParseResult
|
||||||
|
|
||||||
@ -62,10 +62,10 @@ class Redirection(RequestRedirect):
|
|||||||
DEFAULT_PAGE_SIZE: int = 10
|
DEFAULT_PAGE_SIZE: int = 10
|
||||||
"""The default page size."""
|
"""The default page size."""
|
||||||
|
|
||||||
T = t.TypeVar("T")
|
T = TypeVar("T")
|
||||||
|
|
||||||
|
|
||||||
class Pagination(t.Generic[T]):
|
class Pagination(Generic[T]):
|
||||||
"""The pagination utility."""
|
"""The pagination utility."""
|
||||||
|
|
||||||
def __init__(self, items: list[T], is_reversed: bool = False):
|
def __init__(self, items: list[T], is_reversed: bool = False):
|
||||||
@ -91,7 +91,7 @@ class Pagination(t.Generic[T]):
|
|||||||
"""The options to the number of items in a page."""
|
"""The options to the number of items in a page."""
|
||||||
|
|
||||||
|
|
||||||
class AbstractPagination(t.Generic[T]):
|
class AbstractPagination(Generic[T]):
|
||||||
"""An abstract pagination."""
|
"""An abstract pagination."""
|
||||||
|
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
|
@ -19,21 +19,21 @@
|
|||||||
This module should not import any other module from the application.
|
This module should not import any other module from the application.
|
||||||
|
|
||||||
"""
|
"""
|
||||||
import typing as t
|
from collections.abc import Callable
|
||||||
|
|
||||||
from flask import abort, Blueprint, Response
|
from flask import abort, Blueprint, Response
|
||||||
|
|
||||||
from accounting.utils.user import get_current_user, UserUtilityInterface
|
from accounting.utils.user import get_current_user, UserUtilityInterface
|
||||||
|
|
||||||
|
|
||||||
def has_permission(rule: t.Callable[[], bool]) -> t.Callable:
|
def has_permission(rule: Callable[[], bool]) -> Callable:
|
||||||
"""The permission decorator to check whether the current user is allowed.
|
"""The permission decorator to check whether the current user is allowed.
|
||||||
|
|
||||||
:param rule: The permission rule.
|
:param rule: The permission rule.
|
||||||
:return: The view decorator.
|
:return: The view decorator.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def decorator(view: t.Callable) -> t.Callable:
|
def decorator(view: Callable) -> Callable:
|
||||||
"""The view decorator to decorate a view with permission tests.
|
"""The view decorator to decorate a view with permission tests.
|
||||||
|
|
||||||
:param view: The view.
|
:param view: The view.
|
||||||
@ -61,16 +61,16 @@ def has_permission(rule: t.Callable[[], bool]) -> t.Callable:
|
|||||||
return decorator
|
return decorator
|
||||||
|
|
||||||
|
|
||||||
__can_view_func: t.Callable[[], bool] = lambda: True
|
__can_view_func: Callable[[], bool] = lambda: True
|
||||||
"""The callback that returns whether the current user can view the accounting
|
"""The callback that returns whether the current user can view the accounting
|
||||||
data."""
|
data."""
|
||||||
__can_edit_func: t.Callable[[], bool] = lambda: True
|
__can_edit_func: Callable[[], bool] = lambda: True
|
||||||
"""The callback that returns whether the current user can edit the accounting
|
"""The callback that returns whether the current user can edit the accounting
|
||||||
data."""
|
data."""
|
||||||
__can_admin_func: t.Callable[[], bool] = lambda: True
|
__can_admin_func: Callable[[], bool] = lambda: True
|
||||||
"""The callback that returns whether the current user can administrate the
|
"""The callback that returns whether the current user can administrate the
|
||||||
accounting settings."""
|
accounting settings."""
|
||||||
_unauthorized_func: t.Callable[[], Response | None] \
|
_unauthorized_func: Callable[[], Response | None] \
|
||||||
= lambda: Response(status=403)
|
= lambda: Response(status=403)
|
||||||
"""The callback that returns the response to require the user to log in."""
|
"""The callback that returns the response to require the user to log in."""
|
||||||
|
|
||||||
|
@ -14,18 +14,18 @@
|
|||||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
# See the License for the specific language governing permissions and
|
# See the License for the specific language governing permissions and
|
||||||
# limitations under the License.
|
# limitations under the License.
|
||||||
"""The random ID mixin for the data models.
|
"""The random ID utility for the data models.
|
||||||
|
|
||||||
This module should not import any other module from the application.
|
This module should not import any other module from the application.
|
||||||
|
|
||||||
"""
|
"""
|
||||||
import typing as t
|
|
||||||
from secrets import randbelow
|
from secrets import randbelow
|
||||||
|
from typing import Type
|
||||||
|
|
||||||
from accounting import db
|
from accounting import db
|
||||||
|
|
||||||
|
|
||||||
def new_id(cls: t.Type):
|
def new_id(cls: Type[db.Model]):
|
||||||
"""Returns a new random ID for the data model.
|
"""Returns a new random ID for the data model.
|
||||||
|
|
||||||
:param cls: The data model.
|
:param cls: The data model.
|
||||||
|
@ -19,17 +19,17 @@
|
|||||||
This module should not import any other module from the application.
|
This module should not import any other module from the application.
|
||||||
|
|
||||||
"""
|
"""
|
||||||
import typing as t
|
|
||||||
from abc import ABC, abstractmethod
|
from abc import ABC, abstractmethod
|
||||||
|
from typing import TypeVar, Generic, Type
|
||||||
|
|
||||||
import sqlalchemy as sa
|
import sqlalchemy as sa
|
||||||
from flask import g, Response
|
from flask import g, Response
|
||||||
from flask_sqlalchemy.model import Model
|
from flask_sqlalchemy.model import Model
|
||||||
|
|
||||||
T = t.TypeVar("T", bound=Model)
|
T = TypeVar("T", bound=Model)
|
||||||
|
|
||||||
|
|
||||||
class UserUtilityInterface(t.Generic[T], ABC):
|
class UserUtilityInterface(Generic[T], ABC):
|
||||||
"""The interface for the user utilities."""
|
"""The interface for the user utilities."""
|
||||||
|
|
||||||
@abstractmethod
|
@abstractmethod
|
||||||
@ -72,7 +72,7 @@ class UserUtilityInterface(t.Generic[T], ABC):
|
|||||||
|
|
||||||
@property
|
@property
|
||||||
@abstractmethod
|
@abstractmethod
|
||||||
def cls(self) -> t.Type[T]:
|
def cls(self) -> Type[T]:
|
||||||
"""Returns the class of the user data model.
|
"""Returns the class of the user data model.
|
||||||
|
|
||||||
:return: The class of the user data model.
|
:return: The class of the user data model.
|
||||||
@ -112,7 +112,7 @@ class UserUtilityInterface(t.Generic[T], ABC):
|
|||||||
|
|
||||||
__user_utils: UserUtilityInterface
|
__user_utils: UserUtilityInterface
|
||||||
"""The user utilities."""
|
"""The user utilities."""
|
||||||
user_cls: t.Type[Model] = Model
|
user_cls: Type[Model] = Model
|
||||||
"""The user class."""
|
"""The user class."""
|
||||||
user_pk_column: sa.Column = sa.Column(sa.Integer)
|
user_pk_column: sa.Column = sa.Column(sa.Integer)
|
||||||
"""The primary key column of the user class."""
|
"""The primary key column of the user class."""
|
||||||
|
@ -18,8 +18,8 @@
|
|||||||
|
|
||||||
"""
|
"""
|
||||||
import csv
|
import csv
|
||||||
import typing as t
|
|
||||||
import unittest
|
import unittest
|
||||||
|
from typing import Any
|
||||||
|
|
||||||
import sqlalchemy as sa
|
import sqlalchemy as sa
|
||||||
from click.testing import Result
|
from click.testing import Result
|
||||||
@ -80,7 +80,7 @@ class ConsoleCommandTestCase(unittest.TestCase):
|
|||||||
from accounting.models import BaseAccount
|
from accounting.models import BaseAccount
|
||||||
|
|
||||||
with open(data_dir / "base_accounts.csv") as fp:
|
with open(data_dir / "base_accounts.csv") as fp:
|
||||||
data: dict[dict[str, t.Any]] \
|
data: dict[dict[str, Any]] \
|
||||||
= {x["code"]: {"code": x["code"],
|
= {x["code"]: {"code": x["code"],
|
||||||
"title": x["title"],
|
"title": x["title"],
|
||||||
"l10n": {y[5:]: x[y]
|
"l10n": {y[5:]: x[y]
|
||||||
@ -135,7 +135,7 @@ class ConsoleCommandTestCase(unittest.TestCase):
|
|||||||
from accounting.models import Currency
|
from accounting.models import Currency
|
||||||
|
|
||||||
with open(data_dir / "currencies.csv") as fp:
|
with open(data_dir / "currencies.csv") as fp:
|
||||||
data: dict[dict[str, t.Any]] \
|
data: dict[dict[str, Any]] \
|
||||||
= {x["code"]: {"code": x["code"],
|
= {x["code"]: {"code": x["code"],
|
||||||
"name": x["name"],
|
"name": x["name"],
|
||||||
"l10n": {y[5:]: x[y]
|
"l10n": {y[5:]: x[y]
|
||||||
|
@ -18,8 +18,8 @@
|
|||||||
|
|
||||||
"""
|
"""
|
||||||
import os
|
import os
|
||||||
import typing as t
|
|
||||||
from secrets import token_urlsafe
|
from secrets import token_urlsafe
|
||||||
|
from typing import Type
|
||||||
|
|
||||||
from click.testing import Result
|
from click.testing import Result
|
||||||
from flask import Flask, Blueprint, render_template, redirect, Response, \
|
from flask import Flask, Blueprint, render_template, redirect, Response, \
|
||||||
@ -94,7 +94,7 @@ def create_app(is_testing: bool = False) -> Flask:
|
|||||||
return redirect(append_next(url_for("auth.login-form")))
|
return redirect(append_next(url_for("auth.login-form")))
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def cls(self) -> t.Type[auth.User]:
|
def cls(self) -> Type[auth.User]:
|
||||||
return auth.User
|
return auth.User
|
||||||
|
|
||||||
@property
|
@property
|
||||||
|
@ -17,7 +17,7 @@
|
|||||||
"""The authentication for the Mia! Accounting demonstration website.
|
"""The authentication for the Mia! Accounting demonstration website.
|
||||||
|
|
||||||
"""
|
"""
|
||||||
import typing as t
|
from collections.abc import Callable
|
||||||
|
|
||||||
from flask import Blueprint, render_template, Flask, redirect, url_for, \
|
from flask import Blueprint, render_template, Flask, redirect, url_for, \
|
||||||
session, request, g, Response, abort
|
session, request, g, Response, abort
|
||||||
@ -96,7 +96,7 @@ def current_user() -> User | None:
|
|||||||
return g.user
|
return g.user
|
||||||
|
|
||||||
|
|
||||||
def admin_required(view: t.Callable) -> t.Callable:
|
def admin_required(view: Callable) -> Callable:
|
||||||
"""The view decorator to require the user to be an administrator.
|
"""The view decorator to require the user to be an administrator.
|
||||||
|
|
||||||
:param view: The view.
|
:param view: The view.
|
||||||
|
@ -20,10 +20,10 @@
|
|||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
import datetime as dt
|
import datetime as dt
|
||||||
import typing as t
|
|
||||||
from abc import ABC, abstractmethod
|
from abc import ABC, abstractmethod
|
||||||
from decimal import Decimal
|
from decimal import Decimal
|
||||||
from secrets import randbelow
|
from secrets import randbelow
|
||||||
|
from typing import Any
|
||||||
|
|
||||||
import sqlalchemy as sa
|
import sqlalchemy as sa
|
||||||
from flask import Flask
|
from flask import Flask
|
||||||
@ -193,8 +193,8 @@ class BaseTestData(ABC):
|
|||||||
.filter(User.username == username).first()
|
.filter(User.username == username).first()
|
||||||
assert current_user is not None
|
assert current_user is not None
|
||||||
self.__current_user_id: int = current_user.id
|
self.__current_user_id: int = current_user.id
|
||||||
self.__journal_entries: list[dict[str, t.Any]] = []
|
self.__journal_entries: list[dict[str, Any]] = []
|
||||||
self.__line_items: list[dict[str, t.Any]] = []
|
self.__line_items: list[dict[str, Any]] = []
|
||||||
self._init_data()
|
self._init_data()
|
||||||
|
|
||||||
@abstractmethod
|
@abstractmethod
|
||||||
@ -258,7 +258,7 @@ class BaseTestData(ABC):
|
|||||||
assert account is not None
|
assert account is not None
|
||||||
debit_no = debit_no + 1
|
debit_no = debit_no + 1
|
||||||
line_item.id = self.__new_id(existing_l_id)
|
line_item.id = self.__new_id(existing_l_id)
|
||||||
data: dict[str, t.Any] \
|
data: dict[str, Any] \
|
||||||
= {"id": line_item.id,
|
= {"id": line_item.id,
|
||||||
"journal_entry_id": journal_entry_data.id,
|
"journal_entry_id": journal_entry_data.id,
|
||||||
"is_debit": True,
|
"is_debit": True,
|
||||||
@ -277,7 +277,7 @@ class BaseTestData(ABC):
|
|||||||
assert account is not None
|
assert account is not None
|
||||||
credit_no = credit_no + 1
|
credit_no = credit_no + 1
|
||||||
line_item.id = self.__new_id(existing_l_id)
|
line_item.id = self.__new_id(existing_l_id)
|
||||||
data: dict[str, t.Any] \
|
data: dict[str, Any] \
|
||||||
= {"id": line_item.id,
|
= {"id": line_item.id,
|
||||||
"journal_entry_id": journal_entry_data.id,
|
"journal_entry_id": journal_entry_data.id,
|
||||||
"is_debit": False,
|
"is_debit": False,
|
||||||
|
@ -20,7 +20,7 @@
|
|||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
import re
|
import re
|
||||||
import typing as t
|
from typing import Literal
|
||||||
|
|
||||||
import httpx
|
import httpx
|
||||||
from flask import Flask, render_template_string
|
from flask import Flask, render_template_string
|
||||||
@ -108,7 +108,7 @@ def get_client(app: Flask, username: str) -> tuple[httpx.Client, str]:
|
|||||||
|
|
||||||
|
|
||||||
def set_locale(client: httpx.Client, csrf_token: str,
|
def set_locale(client: httpx.Client, csrf_token: str,
|
||||||
locale: t.Literal["en", "zh_Hant", "zh_Hans"]) -> None:
|
locale: Literal["en", "zh_Hant", "zh_Hans"]) -> None:
|
||||||
"""Sets the current locale.
|
"""Sets the current locale.
|
||||||
|
|
||||||
:param client: The test client.
|
:param client: The test client.
|
||||||
|
Loading…
Reference in New Issue
Block a user