月次レポート機能の改善として、開始日・終了日によるフィルタリング機能を追加し、店舗区分および経費区分の選択肢を実装。経費区分と店舗マスタの管理画面を新規作成し、関連するマイグレーションを追加。CSV取込画面における経費区分の表示を改善。作業ログをdiary.mdに追記。

This commit is contained in:
president
2025-12-22 12:34:53 +09:00
parent 7ae367cd66
commit 814477b1e2
15 changed files with 684 additions and 37 deletions

View File

@@ -18,6 +18,8 @@
<nav>
<a href="{% url 'csv_upload' %}">CSV取込</a>
<a href="{% url 'expense_list' %}">明細編集</a>
<a href="{% url 'store_master' %}">店舗マスタ</a>
<a href="{% url 'expense_category_master' %}">経費区分マスタ</a>
<a href="{% url 'monthly_report' %}">月次レポート</a>
</nav>
</header>

View File

@@ -0,0 +1,74 @@
{% extends 'base.html' %}
{% block title %}経費区分マスタ管理{% endblock %}
{% block content %}
<section>
<h2>経費区分マスタ管理</h2>
{% if error_message %}
<p>{{ error_message }}</p>
{% endif %}
<h3>経費区分を追加</h3>
<form method="post">
{% csrf_token %}
<label>
経費区分名
<input type="text" name="name" required>
</label>
<label>
<input type="checkbox" name="is_active" value="1" checked>
有効
</label>
<button type="submit">追加</button>
</form>
<h3>一覧</h3>
<table>
<thead>
<tr>
<th>経費区分名</th>
<th>有効</th>
<th>更新</th>
<th>削除</th>
</tr>
</thead>
<tbody>
{% for category in categories %}
<tr>
<form method="post">
{% csrf_token %}
<input type="hidden" name="category_id" value="{{ category.id }}">
<td>
<input type="text" name="name" value="{{ category.name }}" required>
</td>
<td>
<input
type="checkbox"
name="is_active"
value="1"
{% if category.is_active %}checked{% endif %}
>
</td>
<td>
<button type="submit">保存</button>
</td>
</form>
<td>
<form method="post">
{% csrf_token %}
<input type="hidden" name="category_id" value="{{ category.id }}">
<input type="hidden" name="action" value="deactivate">
<button type="submit" {% if not category.is_active %}disabled{% endif %}>
無効化
</button>
</form>
</td>
</tr>
{% empty %}
<tr>
<td colspan="4">経費区分がありません。</td>
</tr>
{% endfor %}
</tbody>
</table>
</section>
{% endblock %}

View File

@@ -129,6 +129,12 @@
}
function coerceValue(field, value) {
if (value === '') {
return value;
}
if (field === 'store_id' || field === 'expense_category_id') {
return Number(value);
}
return value;
}

View File

@@ -8,14 +8,87 @@
<h2>月次レポート</h2>
<form method="get">
<label>
年月
<input type="month" name="target_month" value="{{ target_month }}">
開始日
<input type="date" name="start_date" value="{{ start_date }}">
</label>
<label>
終了日
<input type="date" name="end_date" value="{{ end_date }}">
</label>
<label>
<input type="checkbox" name="all_time" value="1" {% if all_time %}checked{% endif %}>
全期間
</label>
<label>
店舗区分
<select name="store_id">
<option value="" {% if not selected_store %}selected{% endif %}>全て</option>
<option value="unassigned" {% if selected_store == "unassigned" %}selected{% endif %}>
未設定
</option>
{% for store in stores %}
<option value="{{ store.id }}" {% if selected_store == store.id|stringformat:"s" %}selected{% endif %}>
{{ store.name }}
</option>
{% endfor %}
</select>
</label>
<label>
経費区分
<select name="expense_category_id">
<option value="" {% if not selected_category %}selected{% endif %}>全て</option>
<option value="unassigned" {% if selected_category == "unassigned" %}selected{% endif %}>
未設定
</option>
{% for category in categories %}
<option value="{{ category.id }}" {% if selected_category == category.id|stringformat:"s" %}selected{% endif %}>
{{ category.name }}
</option>
{% endfor %}
</select>
</label>
<label>
利用先
<input type="search" name="description" value="{{ description_query }}">
</label>
<input type="hidden" name="show" value="1">
<button type="submit">表示</button>
{% if show_report %}
<a
href="{% url 'monthly_report_pdf' %}?start_date={{ start_date }}&end_date={{ end_date }}&store_id={{ selected_store }}&expense_category_id={{ selected_category }}&description={{ description_query|urlencode }}&all_time={% if all_time %}1{% endif %}"
target="_blank"
rel="noopener"
>
PDF出力
</a>
{% endif %}
</form>
<script>
const startInput = document.querySelector('input[name="start_date"]');
const endInput = document.querySelector('input[name="end_date"]');
const allTimeInput = document.querySelector('input[name="all_time"]');
if (startInput && endInput && allTimeInput) {
const syncAllTime = () => {
if (startInput.value || endInput.value) {
allTimeInput.checked = false;
}
};
startInput.addEventListener('change', syncAllTime);
endInput.addEventListener('change', syncAllTime);
allTimeInput.addEventListener('change', () => {
if (allTimeInput.checked) {
startInput.value = '';
endInput.value = '';
}
});
}
</script>
{% if error_message %}
<p style="color: #b00020;">{{ error_message }}</p>
{% endif %}
{% if not show_report %}
<p>条件を指定して表示ボタンを押してください。</p>
{% else %}
<div>
<h3>合計</h3>
<p class="amount">{{ report.total_amount|intcomma }} 円</p>
@@ -50,6 +123,7 @@
<thead>
<tr>
<th>日付</th>
<th>経費区分</th>
<th>利用先</th>
<th class="amount">金額</th>
<th>備考</th>
@@ -59,13 +133,14 @@
{% for detail in row.details %}
<tr>
<td>{{ detail.use_date }}</td>
<td>{{ detail.expense_category_name|default:"未設定" }}</td>
<td>{{ detail.description }}</td>
<td class="amount">{{ detail.amount|intcomma }}</td>
<td>{{ detail.note }}</td>
</tr>
{% empty %}
<tr>
<td colspan="4">対象データがありません。</td>
<td colspan="5">対象データがありません。</td>
</tr>
{% endfor %}
</tbody>
@@ -102,6 +177,7 @@
<thead>
<tr>
<th>日付</th>
<th>経費区分</th>
<th>利用先</th>
<th class="amount">金額</th>
<th>備考</th>
@@ -111,13 +187,14 @@
{% for detail in row.details %}
<tr>
<td>{{ detail.use_date }}</td>
<td>{{ detail.expense_category_name|default:"未設定" }}</td>
<td>{{ detail.description }}</td>
<td class="amount">{{ detail.amount|intcomma }}</td>
<td>{{ detail.note }}</td>
</tr>
{% empty %}
<tr>
<td colspan="4">対象データがありません。</td>
<td colspan="5">対象データがありません。</td>
</tr>
{% endfor %}
</tbody>
@@ -154,6 +231,7 @@
<thead>
<tr>
<th>日付</th>
<th>経費区分</th>
<th>利用先</th>
<th class="amount">金額</th>
<th>備考</th>
@@ -163,13 +241,14 @@
{% for detail in row.details %}
<tr>
<td>{{ detail.use_date }}</td>
<td>{{ detail.expense_category_name|default:"未設定" }}</td>
<td>{{ detail.description }}</td>
<td class="amount">{{ detail.amount|intcomma }}</td>
<td>{{ detail.note }}</td>
</tr>
{% empty %}
<tr>
<td colspan="4">対象データがありません。</td>
<td colspan="5">対象データがありません。</td>
</tr>
{% endfor %}
</tbody>
@@ -185,5 +264,6 @@
<li>対象総件数: {{ report.unclassified_counts.total }}</li>
</ul>
</div>
{% endif %}
</section>
{% endblock %}

View File

@@ -14,7 +14,7 @@
</head>
<body>
<h1>月次レポート</h1>
<p>対象年月: {{ target_month }}</p>
<p>対象期間: {{ target_period }}</p>
<h2>合計</h2>
<p class="amount">{{ report.total_amount|intcomma }} 円</p>
<h2>店舗別合計</h2>

View File

@@ -0,0 +1,74 @@
{% extends 'base.html' %}
{% block title %}店舗マスタ管理{% endblock %}
{% block content %}
<section>
<h2>店舗マスタ管理</h2>
{% if error_message %}
<p>{{ error_message }}</p>
{% endif %}
<h3>店舗を追加</h3>
<form method="post">
{% csrf_token %}
<label>
店舗名
<input type="text" name="name" required>
</label>
<label>
<input type="checkbox" name="is_active" value="1" checked>
有効
</label>
<button type="submit">追加</button>
</form>
<h3>一覧</h3>
<table>
<thead>
<tr>
<th>店舗名</th>
<th>有効</th>
<th>更新</th>
<th>削除</th>
</tr>
</thead>
<tbody>
{% for store in stores %}
<tr>
<form method="post">
{% csrf_token %}
<input type="hidden" name="store_id" value="{{ store.id }}">
<td>
<input type="text" name="name" value="{{ store.name }}" required>
</td>
<td>
<input
type="checkbox"
name="is_active"
value="1"
{% if store.is_active %}checked{% endif %}
>
</td>
<td>
<button type="submit">保存</button>
</td>
</form>
<td>
<form method="post">
{% csrf_token %}
<input type="hidden" name="store_id" value="{{ store.id }}">
<input type="hidden" name="action" value="deactivate">
<button type="submit" {% if not store.is_active %}disabled{% endif %}>
無効化
</button>
</form>
</td>
</tr>
{% empty %}
<tr>
<td colspan="4">店舗がありません。</td>
</tr>
{% endfor %}
</tbody>
</table>
</section>
{% endblock %}