aoiro 0.0.3__py3-none-any.whl → 0.1.1__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- aoiro/__init__.py +52 -1
- aoiro/__main__.py +1 -1
- aoiro/_ledger.py +298 -0
- aoiro/_multidimensional.py +186 -0
- aoiro/_sheets.py +123 -0
- aoiro/account.yml +68 -0
- aoiro/cli.py +97 -7
- aoiro/reader/__init__.py +12 -0
- aoiro/reader/_expenses.py +51 -0
- aoiro/reader/_io.py +177 -0
- aoiro/reader/_sales.py +170 -0
- {aoiro-0.0.3.dist-info → aoiro-0.1.1.dist-info}/LICENSE +243 -257
- aoiro-0.1.1.dist-info/METADATA +268 -0
- aoiro-0.1.1.dist-info/RECORD +17 -0
- aoiro/main.py +0 -3
- aoiro-0.0.3.dist-info/METADATA +0 -107
- aoiro-0.0.3.dist-info/RECORD +0 -10
- {aoiro-0.0.3.dist-info → aoiro-0.1.1.dist-info}/WHEEL +0 -0
- {aoiro-0.0.3.dist-info → aoiro-0.1.1.dist-info}/entry_points.txt +0 -0
aoiro/account.yml
ADDED
@@ -0,0 +1,68 @@
|
|
1
|
+
資産: #貸方
|
2
|
+
資産:
|
3
|
+
[
|
4
|
+
現金,
|
5
|
+
当座預金,
|
6
|
+
定期預金,
|
7
|
+
その他の預金,
|
8
|
+
受取手形,
|
9
|
+
売掛金,
|
10
|
+
有価証券,
|
11
|
+
棚卸資産,
|
12
|
+
前払金,
|
13
|
+
貸付金,
|
14
|
+
建物,
|
15
|
+
建物附属設備,
|
16
|
+
機械装置,
|
17
|
+
車両運搬具,
|
18
|
+
工具器具備品,
|
19
|
+
土地,
|
20
|
+
]
|
21
|
+
事業主貸: [事業主貸]
|
22
|
+
負債:
|
23
|
+
負債: [支払手形, 買掛金, 借入金, 未払金, 前受金, 預り金]
|
24
|
+
貸倒引当金: [貸倒引当金]
|
25
|
+
事業主借: [事業主借]
|
26
|
+
純資産:
|
27
|
+
純資産: [元入金]
|
28
|
+
収益: #貸方
|
29
|
+
売上: [売上, 雑収入]
|
30
|
+
売上原価: [仕入返品, 期末商品棚卸高]
|
31
|
+
各種引当金・準備金等: [貸倒引当金繰戻]
|
32
|
+
追加: [家事消費]
|
33
|
+
費用: #借方
|
34
|
+
売上: [売上返品]
|
35
|
+
売上原価: [仕入, 期首商品棚卸高]
|
36
|
+
経費:
|
37
|
+
[
|
38
|
+
租税公課,
|
39
|
+
荷造運費,
|
40
|
+
水道光熱費,
|
41
|
+
旅費交通費,
|
42
|
+
通信費,
|
43
|
+
広告宣伝費,
|
44
|
+
接待交際費,
|
45
|
+
損害保険料,
|
46
|
+
修繕費,
|
47
|
+
消耗品費,
|
48
|
+
減価償却費,
|
49
|
+
福利厚生費,
|
50
|
+
給料賃金,
|
51
|
+
外注工賃,
|
52
|
+
利子割引料,
|
53
|
+
地代家賃,
|
54
|
+
貸倒金,
|
55
|
+
雑費,
|
56
|
+
]
|
57
|
+
各種引当金・準備金等: [専従者給与, 貸倒引当金]
|
58
|
+
追加:
|
59
|
+
[
|
60
|
+
法定福利費,
|
61
|
+
税理士・弁護士報酬,
|
62
|
+
支払手数料,
|
63
|
+
新聞図書費,
|
64
|
+
車両費,
|
65
|
+
研修費,
|
66
|
+
会議費,
|
67
|
+
諸会費,
|
68
|
+
]
|
aoiro/cli.py
CHANGED
@@ -1,12 +1,102 @@
|
|
1
|
-
import
|
1
|
+
from datetime import datetime
|
2
|
+
from pathlib import Path
|
3
|
+
|
4
|
+
# from rich import print
|
5
|
+
import attrs
|
6
|
+
import cyclopts
|
7
|
+
import networkx as nx
|
8
|
+
import pandas as pd
|
9
|
+
from account_codes_jp import (
|
10
|
+
get_account_type_factory,
|
11
|
+
get_blue_return_accounts,
|
12
|
+
get_node_from_label,
|
13
|
+
)
|
14
|
+
from networkx.readwrite.text import generate_network_text
|
2
15
|
from rich import print
|
3
16
|
|
4
|
-
from .
|
17
|
+
from ._ledger import (
|
18
|
+
generalledger_to_multiledger,
|
19
|
+
multiledger_to_ledger,
|
20
|
+
)
|
21
|
+
from ._multidimensional import multidimensional_ledger_to_ledger
|
22
|
+
from ._sheets import get_sheets
|
23
|
+
from .reader._expenses import ledger_from_expenses
|
24
|
+
from .reader._io import read_general_ledger
|
25
|
+
from .reader._sales import ledger_from_sales
|
26
|
+
|
27
|
+
app = cyclopts.App(name="aoiro")
|
28
|
+
|
29
|
+
|
30
|
+
@app.default
|
31
|
+
def metrics(path: Path, year: int | None = None, drop: bool = True) -> None:
|
32
|
+
"""
|
33
|
+
Calculate metrics needed for tax declaration.
|
34
|
+
|
35
|
+
Parameters
|
36
|
+
----------
|
37
|
+
path : Path
|
38
|
+
The path to the directory containing CSV files.
|
39
|
+
year : int | None, optional
|
40
|
+
The year to calculate, by default None.
|
41
|
+
If None, the previous year would be used.
|
42
|
+
drop : bool, optional
|
43
|
+
Whether to drop unused accounts, by default True.
|
44
|
+
|
45
|
+
"""
|
46
|
+
if year is None:
|
47
|
+
year = datetime.now().year - 1
|
48
|
+
|
49
|
+
def patch_G(G: nx.DiGraph) -> nx.DiGraph:
|
50
|
+
G.add_node(-1, label="為替差益")
|
51
|
+
G.add_node(-2, label="為替差損")
|
52
|
+
G.add_edge(next(n for n, d in G.nodes(data=True) if d["label"] == "売上"), -1)
|
53
|
+
G.add_edge(
|
54
|
+
next(n for n, d in G.nodes(data=True) if d["label"] == "経費追加"), -2
|
55
|
+
)
|
56
|
+
return G
|
57
|
+
|
58
|
+
G = get_blue_return_accounts(patch_G)
|
59
|
+
|
60
|
+
gledger_vec = (
|
61
|
+
list(ledger_from_sales(path, G))
|
62
|
+
+ list(ledger_from_expenses(path))
|
63
|
+
+ list(read_general_ledger(path))
|
64
|
+
)
|
65
|
+
f = get_account_type_factory(G)
|
5
66
|
|
6
|
-
|
67
|
+
def is_debit(x: str) -> bool:
|
68
|
+
v = getattr(f(x), "debit", None)
|
69
|
+
if v is None:
|
70
|
+
raise ValueError(f"Account {x} not recognized")
|
71
|
+
return v
|
7
72
|
|
73
|
+
gledger = multidimensional_ledger_to_ledger(gledger_vec, is_debit=is_debit)
|
74
|
+
ledger = multiledger_to_ledger(
|
75
|
+
generalledger_to_multiledger(gledger, is_debit=is_debit)
|
76
|
+
)
|
77
|
+
ledger_now = [line for line in ledger if line.date.year == year]
|
78
|
+
with pd.option_context("display.max_rows", None, "display.max_columns", None):
|
79
|
+
print(
|
80
|
+
pd.DataFrame([attrs.asdict(line) for line in ledger_now]) # type: ignore
|
81
|
+
.set_index("date")
|
82
|
+
.sort_index(axis=0)
|
83
|
+
)
|
84
|
+
gledger_now = [line for line in gledger if line.date.year == year]
|
85
|
+
G = get_sheets(gledger_now, G, drop=drop)
|
86
|
+
G_print = G.copy()
|
87
|
+
for n, d in G_print.nodes(data=True):
|
88
|
+
G_print.nodes[n]["label"] = f"{d['label']}/{d['sum_natural'].get('', 0)}"
|
89
|
+
for line in generate_network_text(G_print, with_labels=True):
|
90
|
+
print(line)
|
8
91
|
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
92
|
+
# sales per month
|
93
|
+
print("Sales per month")
|
94
|
+
for month in range(1, 13):
|
95
|
+
G_month = get_sheets(
|
96
|
+
[line for line in gledger_now if line.date.month == month], G, drop=False
|
97
|
+
)
|
98
|
+
sales_deeper_node = get_node_from_label(
|
99
|
+
G, "売上", lambda x: not G.nodes[x]["abstract"]
|
100
|
+
)
|
101
|
+
sales_deeper = G_month.nodes[sales_deeper_node]["sum_natural"].get("", 0)
|
102
|
+
print(f"{month}: {sales_deeper}")
|
aoiro/reader/__init__.py
ADDED
@@ -0,0 +1,12 @@
|
|
1
|
+
from ._expenses import ledger_from_expenses
|
2
|
+
from ._io import read_all_csvs, read_general_ledger, read_simple_csvs
|
3
|
+
from ._sales import ledger_from_sales, withholding_tax
|
4
|
+
|
5
|
+
__all__ = [
|
6
|
+
"ledger_from_expenses",
|
7
|
+
"ledger_from_sales",
|
8
|
+
"read_all_csvs",
|
9
|
+
"read_general_ledger",
|
10
|
+
"read_simple_csvs",
|
11
|
+
"withholding_tax",
|
12
|
+
]
|
@@ -0,0 +1,51 @@
|
|
1
|
+
from collections.abc import Sequence
|
2
|
+
from pathlib import Path
|
3
|
+
from typing import Any
|
4
|
+
|
5
|
+
from .._ledger import GeneralLedgerLineImpl, LedgerElementImpl
|
6
|
+
from ._io import read_simple_csvs
|
7
|
+
|
8
|
+
|
9
|
+
def ledger_from_expenses(
|
10
|
+
path: Path,
|
11
|
+
) -> Sequence[GeneralLedgerLineImpl[Any, Any]]:
|
12
|
+
"""
|
13
|
+
Generate ledger from expenses.
|
14
|
+
|
15
|
+
The CSV files are assumed to have columns
|
16
|
+
["勘定科目"].
|
17
|
+
The relative path of the CSV file would be used as "取引先".
|
18
|
+
|
19
|
+
Parameters
|
20
|
+
----------
|
21
|
+
path : Path
|
22
|
+
The path to the directory containing CSV files.
|
23
|
+
|
24
|
+
Returns
|
25
|
+
-------
|
26
|
+
Sequence[GeneralLedgerLineImpl[Any, Any]]
|
27
|
+
The ledger lines.
|
28
|
+
|
29
|
+
"""
|
30
|
+
df = read_simple_csvs(path / "expenses")
|
31
|
+
if df.empty:
|
32
|
+
return []
|
33
|
+
df["取引先"] = df["path"]
|
34
|
+
res: list[GeneralLedgerLineImpl[Any, Any]] = []
|
35
|
+
for date, row in df.iterrows():
|
36
|
+
res.append(
|
37
|
+
GeneralLedgerLineImpl(
|
38
|
+
date=date,
|
39
|
+
values=[
|
40
|
+
LedgerElementImpl(
|
41
|
+
account="事業主借", amount=row["金額"], currency=row["通貨"]
|
42
|
+
),
|
43
|
+
LedgerElementImpl(
|
44
|
+
account=row["勘定科目"],
|
45
|
+
amount=row["金額"],
|
46
|
+
currency=row["通貨"],
|
47
|
+
),
|
48
|
+
],
|
49
|
+
)
|
50
|
+
)
|
51
|
+
return res
|
aoiro/reader/_io.py
ADDED
@@ -0,0 +1,177 @@
|
|
1
|
+
import re
|
2
|
+
import warnings
|
3
|
+
from collections.abc import Iterable
|
4
|
+
from decimal import Decimal
|
5
|
+
from pathlib import Path
|
6
|
+
from typing import Any
|
7
|
+
|
8
|
+
import pandas as pd
|
9
|
+
from dateparser import parse
|
10
|
+
|
11
|
+
from .._ledger import GeneralLedgerLineImpl, LedgerElementImpl
|
12
|
+
|
13
|
+
|
14
|
+
def read_all_csvs(path: Path, /, **kwargs: Any) -> pd.DataFrame:
|
15
|
+
"""
|
16
|
+
Read all CSV files in the path.
|
17
|
+
|
18
|
+
Parameters
|
19
|
+
----------
|
20
|
+
path : Path
|
21
|
+
The path to the directory containing CSV files.
|
22
|
+
**kwargs : Any
|
23
|
+
The keyword arguments for `pd.read_csv`.
|
24
|
+
|
25
|
+
Returns
|
26
|
+
-------
|
27
|
+
pd.DataFrame
|
28
|
+
The concatenated DataFrame with
|
29
|
+
column "path" containing the relative path of the CSV file added.
|
30
|
+
|
31
|
+
"""
|
32
|
+
dfs = []
|
33
|
+
for p in path.rglob("*.csv"):
|
34
|
+
df = pd.read_csv(p, **kwargs)
|
35
|
+
df["path"] = p.relative_to(path).as_posix()
|
36
|
+
dfs.append(df)
|
37
|
+
if not dfs:
|
38
|
+
return pd.DataFrame(columns=["path"])
|
39
|
+
return pd.concat(dfs)
|
40
|
+
|
41
|
+
|
42
|
+
def parse_date(s: str) -> pd.Timestamp:
|
43
|
+
"""
|
44
|
+
Parse date.
|
45
|
+
|
46
|
+
Prefer the last day of the month if the day is not provided.
|
47
|
+
|
48
|
+
Parameters
|
49
|
+
----------
|
50
|
+
s : str
|
51
|
+
The string to parse.
|
52
|
+
|
53
|
+
Returns
|
54
|
+
-------
|
55
|
+
pd.Timestamp
|
56
|
+
The parsed date.
|
57
|
+
|
58
|
+
"""
|
59
|
+
return pd.Timestamp(parse(s, settings={"PREFER_DAY_OF_MONTH": "last"}))
|
60
|
+
|
61
|
+
|
62
|
+
def parse_money(
|
63
|
+
s: str, currency: str | None = None
|
64
|
+
) -> tuple[Decimal | None, str | None]:
|
65
|
+
"""
|
66
|
+
Parse money.
|
67
|
+
|
68
|
+
Parameters
|
69
|
+
----------
|
70
|
+
s : str
|
71
|
+
The string to parse.
|
72
|
+
currency : str | None, optional
|
73
|
+
The currency, by default None.
|
74
|
+
If provided, the currency
|
75
|
+
in the string would be ignored and replaced by this.
|
76
|
+
|
77
|
+
Returns
|
78
|
+
-------
|
79
|
+
tuple[Decimal | None, str | None]
|
80
|
+
The amount and the currency.
|
81
|
+
|
82
|
+
"""
|
83
|
+
match = re.search(r"[\d.]+", s)
|
84
|
+
if match is None:
|
85
|
+
return None, None
|
86
|
+
amount = Decimal(match.group())
|
87
|
+
if currency is None:
|
88
|
+
currency = re.sub(r"\s+", "", s[: match.start()] + s[match.end() :])
|
89
|
+
return amount, currency
|
90
|
+
|
91
|
+
|
92
|
+
def read_simple_csvs(path: Path) -> pd.DataFrame:
|
93
|
+
"""
|
94
|
+
Read all CSV files in the path.
|
95
|
+
|
96
|
+
The CSV files are assumed to have columns
|
97
|
+
["発生日", "金額"].
|
98
|
+
|
99
|
+
Parameters
|
100
|
+
----------
|
101
|
+
path : Path
|
102
|
+
The path to the directory containing CSV files.
|
103
|
+
|
104
|
+
Returns
|
105
|
+
-------
|
106
|
+
pd.DataFrame
|
107
|
+
The concatenated DataFrame with columns
|
108
|
+
["発生日", "金額", "通貨", "path"].
|
109
|
+
|
110
|
+
"""
|
111
|
+
df = read_all_csvs(path, dtype=str)
|
112
|
+
for col in ["発生日", "金額"]:
|
113
|
+
if col not in df.columns:
|
114
|
+
df[col] = None
|
115
|
+
|
116
|
+
# parse date
|
117
|
+
for k in df.columns:
|
118
|
+
if "日" not in k:
|
119
|
+
continue
|
120
|
+
df[k] = df[k].map(parse_date)
|
121
|
+
|
122
|
+
# parse money
|
123
|
+
df[["金額", "通貨"]] = pd.DataFrame(
|
124
|
+
df["金額"].map(parse_money).tolist(), index=df.index
|
125
|
+
)
|
126
|
+
|
127
|
+
# set date as index
|
128
|
+
df.set_index("発生日", inplace=True, drop=False)
|
129
|
+
return df
|
130
|
+
|
131
|
+
|
132
|
+
def read_general_ledger(path: Path) -> Iterable[GeneralLedgerLineImpl[Any, Any]]:
|
133
|
+
"""
|
134
|
+
Read general ledger.
|
135
|
+
|
136
|
+
The first column is assumed to be the date.
|
137
|
+
For all n in N. the 2n-1-th column is assumed to be
|
138
|
+
the account name, and the 2n-th column
|
139
|
+
is assumed to be the amount.
|
140
|
+
|
141
|
+
Parameters
|
142
|
+
----------
|
143
|
+
path : Path
|
144
|
+
The path to the CSV file.
|
145
|
+
|
146
|
+
Returns
|
147
|
+
-------
|
148
|
+
Iterable[GeneralLedgerLineImpl[Any, Any]]
|
149
|
+
The general ledger.
|
150
|
+
|
151
|
+
"""
|
152
|
+
df = read_all_csvs(path / "general", header=None, dtype=str)
|
153
|
+
df.drop(columns="path", inplace=True)
|
154
|
+
if df.empty:
|
155
|
+
return
|
156
|
+
if len(df.columns) % 2 != 1:
|
157
|
+
raise ValueError("The number of columns should be odd.")
|
158
|
+
if len(df.columns) < 3:
|
159
|
+
raise ValueError("The number of columns should be at least 3.")
|
160
|
+
for _, row in df.iterrows():
|
161
|
+
values: list[LedgerElementImpl[Any, Any]] = []
|
162
|
+
for i in range(1, len(row), 2):
|
163
|
+
amount, currency = parse_money(row[i + 1])
|
164
|
+
if amount is None:
|
165
|
+
warnings.warn(f"Amount not found in {row[i + 1]}", stacklevel=2)
|
166
|
+
continue
|
167
|
+
values.append(
|
168
|
+
LedgerElementImpl(
|
169
|
+
account=row[i],
|
170
|
+
amount=amount,
|
171
|
+
currency=currency,
|
172
|
+
)
|
173
|
+
)
|
174
|
+
yield GeneralLedgerLineImpl(
|
175
|
+
values=values,
|
176
|
+
date=parse_date(row[0]),
|
177
|
+
)
|
aoiro/reader/_sales.py
ADDED
@@ -0,0 +1,170 @@
|
|
1
|
+
from collections.abc import Sequence
|
2
|
+
from decimal import ROUND_DOWN, Decimal, localcontext
|
3
|
+
from pathlib import Path
|
4
|
+
from typing import Any
|
5
|
+
|
6
|
+
import networkx as nx
|
7
|
+
import pandas as pd
|
8
|
+
from account_codes_jp import get_node_from_label
|
9
|
+
|
10
|
+
from .._ledger import GeneralLedgerLineImpl, LedgerElementImpl
|
11
|
+
from ._io import read_simple_csvs
|
12
|
+
|
13
|
+
|
14
|
+
def withholding_tax(amount: Decimal) -> Decimal:
|
15
|
+
"""
|
16
|
+
Withholding tax calculation for most 源泉徴収が必要な報酬・料金等.
|
17
|
+
|
18
|
+
Parameters
|
19
|
+
----------
|
20
|
+
amount : Decimal
|
21
|
+
The raw amount.
|
22
|
+
|
23
|
+
Returns
|
24
|
+
-------
|
25
|
+
Decimal
|
26
|
+
The withholding tax amount.
|
27
|
+
|
28
|
+
References
|
29
|
+
----------
|
30
|
+
https://www.nta.go.jp/taxes/shiraberu/taxanswer/gensen/2792.htm
|
31
|
+
|
32
|
+
"""
|
33
|
+
with localcontext() as ctx:
|
34
|
+
ctx.rounding = ROUND_DOWN
|
35
|
+
if amount > 1000000:
|
36
|
+
return round(
|
37
|
+
Decimal(
|
38
|
+
1000000 * Decimal("0.1021") + (amount - 1000000) * Decimal("0.2042")
|
39
|
+
),
|
40
|
+
0,
|
41
|
+
)
|
42
|
+
else:
|
43
|
+
return round(amount * Decimal("0.1021"), 0)
|
44
|
+
|
45
|
+
|
46
|
+
def ledger_from_sales(
|
47
|
+
path: Path,
|
48
|
+
G: nx.DiGraph | None = None,
|
49
|
+
) -> Sequence[GeneralLedgerLineImpl[Any, Any]]:
|
50
|
+
"""
|
51
|
+
Generate ledger from sales.
|
52
|
+
|
53
|
+
The CSV files are assumed to have columns
|
54
|
+
["発生日", "金額", "振込日", "源泉徴収", "手数料"].
|
55
|
+
If "源泉徴収" is True, the amount would be assumed by `withholding_tax()`.
|
56
|
+
If "源泉徴収" if False or NaN, the amount would be assumed as 0.
|
57
|
+
If "源泉徴収" is numeric, the amount would be assumed as 源泉徴収額.
|
58
|
+
The relative path of the CSV file would be used as "取引先".
|
59
|
+
|
60
|
+
Parameters
|
61
|
+
----------
|
62
|
+
path : Path
|
63
|
+
The path to the directory containing CSV files.
|
64
|
+
G : nx.DiGraph | None
|
65
|
+
The graph of accounts, by default None.
|
66
|
+
If provided, each "取引先" would be added as a child node of "売上".
|
67
|
+
|
68
|
+
Returns
|
69
|
+
-------
|
70
|
+
Sequence[GneralLedgerLineImpl[Any, Any]]
|
71
|
+
The ledger lines.
|
72
|
+
|
73
|
+
Raises
|
74
|
+
------
|
75
|
+
ValueError
|
76
|
+
If the transaction date is later than the transfer date.
|
77
|
+
ValueError
|
78
|
+
If withholding tax is included in transactions with different currencies.
|
79
|
+
|
80
|
+
"""
|
81
|
+
df = read_simple_csvs(path / "sales")
|
82
|
+
if df.empty:
|
83
|
+
return []
|
84
|
+
df["取引先"] = df["path"].str.replace(".csv", "")
|
85
|
+
df["手数料"] = df["手数料"].apply(
|
86
|
+
lambda x: Decimal(x) if pd.notna(x) else Decimal(0)
|
87
|
+
)
|
88
|
+
df["源泉徴収"] = df["源泉徴収"].replace(
|
89
|
+
{"True": True, "False": False, "true": True, "false": False}
|
90
|
+
)
|
91
|
+
df.fillna({"源泉徴収": Decimal(0)}, inplace=True)
|
92
|
+
|
93
|
+
if G is not None:
|
94
|
+
for ca in ["売上", "仮払税金"]:
|
95
|
+
parent_node = get_node_from_label(
|
96
|
+
G, ca, lambda x: not G.nodes[x]["abstract"]
|
97
|
+
)
|
98
|
+
parent_node_attrs = G.nodes[parent_node]
|
99
|
+
for t in df["取引先"].unique():
|
100
|
+
t_attrs = {**parent_node_attrs, "label": f"{ca}({t})"}
|
101
|
+
t_id = f"{ca}({t})"
|
102
|
+
G.add_node(t_id, **t_attrs)
|
103
|
+
G.add_edge(parent_node, t_id)
|
104
|
+
|
105
|
+
ledger_lines: list[GeneralLedgerLineImpl[Any, Any]] = []
|
106
|
+
for date, row in df.iterrows():
|
107
|
+
ledger_lines.append(
|
108
|
+
GeneralLedgerLineImpl(
|
109
|
+
date=date,
|
110
|
+
values=[
|
111
|
+
LedgerElementImpl(
|
112
|
+
account="売掛金", amount=row["金額"], currency=row["通貨"]
|
113
|
+
),
|
114
|
+
LedgerElementImpl(
|
115
|
+
account="売上" if G is None else f"売上({row['取引先']})",
|
116
|
+
amount=row["金額"],
|
117
|
+
currency=row["通貨"],
|
118
|
+
),
|
119
|
+
],
|
120
|
+
)
|
121
|
+
)
|
122
|
+
for (t, date, currency), df_ in df.groupby(["取引先", "振込日", "通貨"]):
|
123
|
+
fees = Decimal(df_["手数料"].sum())
|
124
|
+
receivable = Decimal(df_["金額"].sum())
|
125
|
+
recievable_without_fees = receivable - fees
|
126
|
+
if currency == "":
|
127
|
+
withholding = withholding_tax(
|
128
|
+
df_.loc[df_["源泉徴収"] == True, "金額"].sum()
|
129
|
+
)
|
130
|
+
values = [
|
131
|
+
LedgerElementImpl(
|
132
|
+
account="事業主貸",
|
133
|
+
amount=recievable_without_fees - withholding,
|
134
|
+
currency=currency,
|
135
|
+
)
|
136
|
+
]
|
137
|
+
if withholding > 0:
|
138
|
+
values.append(
|
139
|
+
LedgerElementImpl(
|
140
|
+
account=f"仮払税金({t})", amount=withholding, currency=currency
|
141
|
+
)
|
142
|
+
)
|
143
|
+
else:
|
144
|
+
if (df_["源泉徴収"] == True).any():
|
145
|
+
raise ValueError("通貨が異なる取引に源泉徴収が含まれています。")
|
146
|
+
values = [
|
147
|
+
LedgerElementImpl(
|
148
|
+
account="事業主貸",
|
149
|
+
amount=recievable_without_fees,
|
150
|
+
currency=currency,
|
151
|
+
)
|
152
|
+
]
|
153
|
+
if fees > 0:
|
154
|
+
values.append(
|
155
|
+
LedgerElementImpl(account="支払手数料", amount=fees, currency=currency)
|
156
|
+
)
|
157
|
+
ledger_lines.append(
|
158
|
+
GeneralLedgerLineImpl(
|
159
|
+
date=date,
|
160
|
+
values=[
|
161
|
+
*values,
|
162
|
+
LedgerElementImpl(
|
163
|
+
account="売掛金", amount=-receivable, currency=currency
|
164
|
+
),
|
165
|
+
],
|
166
|
+
)
|
167
|
+
)
|
168
|
+
if (df["発生日"] > df["振込日"]).any():
|
169
|
+
raise ValueError("発生日が振込日より後の取引があります。")
|
170
|
+
return ledger_lines
|