feat(MemberOrder, MemberOrderHistory, MemberOrderService, OrdersPanel): add paidConfirmed field and update related logic
All checks were successful
Deploy tt-tagebuch / deploy (push) Successful in 42s

- Introduced a new boolean field `paidConfirmed` in MemberOrder and MemberOrderHistory models to track payment confirmation status.
- Updated serialization functions in MemberOrderService to include `paidConfirmed` in order and history entries.
- Enhanced OrdersPanel component to allow users to set and display the `paidConfirmed` status for orders.
- Added localization support for the new `paidConfirmed` label in German.
- Adjusted related logic to ensure proper handling of the `paidConfirmed` state throughout the application.
This commit is contained in:
Torsten Schulz (local)
2026-05-06 09:05:28 +02:00
parent 4bef76d6dd
commit 95bfbf86a4
6 changed files with 70 additions and 12 deletions

View File

@@ -0,0 +1,5 @@
ALTER TABLE `member_orders`
ADD COLUMN `paid_confirmed` TINYINT(1) NOT NULL DEFAULT 0 AFTER `budget`;
ALTER TABLE `member_order_history`
ADD COLUMN `paid_confirmed` TINYINT(1) NOT NULL DEFAULT 0 AFTER `budget`;

View File

@@ -54,6 +54,12 @@ const MemberOrder = sequelize.define('MemberOrder', {
type: DataTypes.DECIMAL(10, 2),
allowNull: false,
defaultValue: 0
},
paidConfirmed: {
type: DataTypes.BOOLEAN,
allowNull: false,
defaultValue: false,
field: 'paid_confirmed'
}
}, {
underscored: true,

View File

@@ -52,6 +52,12 @@ const MemberOrderHistory = sequelize.define('MemberOrderHistory', {
type: DataTypes.DECIMAL(10, 2),
allowNull: false,
defaultValue: 0
},
paidConfirmed: {
type: DataTypes.BOOLEAN,
allowNull: false,
defaultValue: false,
field: 'paid_confirmed'
}
}, {
underscored: true,

View File

@@ -21,11 +21,13 @@ const serializeOrder = (order) => {
const cost = normalizeAmount(plain.cost);
const paidAmount = normalizeAmount(plain.paidAmount);
const budget = normalizeAmount(plain.budget);
const paidConfirmed = Boolean(plain.paidConfirmed);
return {
...plain,
cost,
paidAmount,
budget,
paidConfirmed,
openAmount: Math.max(0, Number((cost - paidAmount).toFixed(2)))
};
};
@@ -35,11 +37,13 @@ const serializeHistoryEntry = (entry) => {
const cost = normalizeAmount(plain.cost);
const paidAmount = normalizeAmount(plain.paidAmount);
const budget = normalizeAmount(plain.budget);
const paidConfirmed = Boolean(plain.paidConfirmed);
return {
...plain,
cost,
paidAmount,
budget,
paidConfirmed,
openAmount: Math.max(0, Number((cost - paidAmount).toFixed(2)))
};
};
@@ -69,7 +73,8 @@ class MemberOrderService {
changedAt: new Date(),
cost: normalizeAmount(order.cost),
paidAmount: normalizeAmount(order.paidAmount),
budget: normalizeAmount(order.budget)
budget: normalizeAmount(order.budget),
paidConfirmed: Boolean(order.paidConfirmed)
}, transaction ? { transaction } : undefined);
}
@@ -111,6 +116,7 @@ class MemberOrderService {
const cost = normalizeAmount(payload?.cost);
const paidAmount = normalizeAmount(payload?.paidAmount);
const budget = normalizeAmount(payload?.budget);
const paidConfirmed = Boolean(payload?.paidConfirmed);
if (!item) {
return {
@@ -128,7 +134,8 @@ class MemberOrderService {
statusDate: new Date(),
cost,
paidAmount,
budget
budget,
paidConfirmed
});
await this._createHistorySnapshot(order);
@@ -179,6 +186,7 @@ class MemberOrderService {
const nextCost = payload?.cost != null ? normalizeAmount(payload.cost) : normalizeAmount(order.cost);
const nextPaidAmount = payload?.paidAmount != null ? normalizeAmount(payload.paidAmount) : normalizeAmount(order.paidAmount);
const nextBudget = payload?.budget != null ? normalizeAmount(payload.budget) : normalizeAmount(order.budget);
const nextPaidConfirmed = payload?.paidConfirmed != null ? Boolean(payload.paidConfirmed) : Boolean(order.paidConfirmed);
if (nextItem && nextItem !== order.item) {
order.item = nextItem;
@@ -201,6 +209,10 @@ class MemberOrderService {
order.budget = nextBudget;
changed = true;
}
if (nextPaidConfirmed !== Boolean(order.paidConfirmed)) {
order.paidConfirmed = nextPaidConfirmed;
changed = true;
}
if (!changed) {
const existing = await this.getMemberOrders(userToken, clubId, memberId);

View File

@@ -53,6 +53,10 @@
<span>{{ $t('orders.budget') }}</span>
<input v-model="newOrder.budget" type="number" min="0" step="0.01">
</label>
<label class="orders-checkbox-label">
<span>{{ $t('orders.paidConfirmed') }}</span>
<input v-model="newOrder.paidConfirmed" type="checkbox">
</label>
<label>
<span>{{ $t('orders.status') }}</span>
<select v-model="newOrder.status">
@@ -88,6 +92,7 @@
<th>{{ $t('orders.statusDate') }}</th>
<th>{{ $t('orders.cost') }}</th>
<th>{{ $t('orders.paid') }}</th>
<th>{{ $t('orders.paidConfirmed') }}</th>
<th>{{ $t('orders.budget') }}</th>
<th>{{ $t('orders.open') }}</th>
<th>{{ $t('orders.history') }}</th>
@@ -116,6 +121,9 @@
<td>
<input v-model="order.draftPaidAmount" type="number" min="0" step="0.01" class="orders-inline-input orders-inline-input-number">
</td>
<td class="orders-cell-center">
<input v-model="order.draftPaidConfirmed" type="checkbox">
</td>
<td>
<input v-model="order.draftBudget" type="number" min="0" step="0.01" class="orders-inline-input orders-inline-input-number">
</td>
@@ -127,7 +135,7 @@
<div v-for="entry in order.historyEntries || []" :key="entry.id" class="orders-history-entry">
<strong>{{ statusLabel(entry.status) }}</strong>
<span>{{ formatDateTime(entry.changedAt) }}</span>
<span>{{ formatCurrency(entry.cost) }} / {{ formatCurrency(entry.paidAmount) }} / {{ formatCurrency(entry.budget) }}</span>
<span>{{ formatCurrency(entry.cost) }} / {{ formatCurrency(entry.paidAmount) }} / {{ entry.paidConfirmed ? 'bezahlt' : 'offen' }} / {{ formatCurrency(entry.budget) }}</span>
</div>
</div>
</details>
@@ -209,7 +217,8 @@ export default {
status: 'requested',
cost: '',
paidAmount: '',
budget: ''
budget: '',
paidConfirmed: false
}
};
},
@@ -237,12 +246,9 @@ export default {
if (this.clubFilter && String(order.clubId) !== String(this.clubFilter)) {
return false;
}
const cost = normalizeAmount(order.cost);
const paidAmount = normalizeAmount(order.paidAmount);
const openAmount = Math.max(0, Number((cost - paidAmount).toFixed(2)));
const isPaid = cost > 0 && openAmount <= 0;
const isPaid = Boolean(order.paidConfirmed);
const isHandedOver = order.status === 'handed_over';
const isCompleted = isPaid || isHandedOver;
const isCompleted = isPaid && isHandedOver;
if (!this.showCompleted && isCompleted) {
return false;
}
@@ -296,6 +302,7 @@ export default {
draftStatus: order.status || 'requested',
draftCost: String(normalizeAmount(order.cost)),
draftPaidAmount: String(normalizeAmount(order.paidAmount)),
draftPaidConfirmed: Boolean(order.paidConfirmed),
draftBudget: String(normalizeAmount(order.budget))
};
},
@@ -331,7 +338,8 @@ export default {
status: this.newOrder.status,
cost: normalizeAmount(this.newOrder.cost),
paidAmount: normalizeAmount(this.newOrder.paidAmount),
budget: normalizeAmount(this.newOrder.budget)
budget: normalizeAmount(this.newOrder.budget),
paidConfirmed: Boolean(this.newOrder.paidConfirmed)
});
if (response.data?.order) {
this.orders.unshift(this.hydrateOrder(response.data.order));
@@ -340,7 +348,8 @@ export default {
status: 'requested',
cost: '',
paidAmount: '',
budget: ''
budget: '',
paidConfirmed: false
};
}
} catch (error) {
@@ -354,6 +363,7 @@ export default {
|| order.draftStatus !== order.status
|| normalizeAmount(order.draftCost) !== normalizeAmount(order.cost)
|| normalizeAmount(order.draftPaidAmount) !== normalizeAmount(order.paidAmount)
|| Boolean(order.draftPaidConfirmed) !== Boolean(order.paidConfirmed)
|| normalizeAmount(order.draftBudget) !== normalizeAmount(order.budget);
},
async saveOrder(order) {
@@ -365,7 +375,8 @@ export default {
status: order.draftStatus,
cost: normalizeAmount(order.draftCost),
paidAmount: normalizeAmount(order.draftPaidAmount),
budget: normalizeAmount(order.draftBudget)
budget: normalizeAmount(order.draftBudget),
paidConfirmed: Boolean(order.draftPaidConfirmed)
});
if (response.data?.order) {
const updated = this.hydrateOrder(response.data.order);
@@ -434,6 +445,23 @@ export default {
color: var(--text-color);
}
.orders-checkbox-label {
display: flex;
flex-direction: column;
justify-content: flex-end;
gap: 0.35rem;
}
.orders-checkbox-label input[type='checkbox'],
.orders-cell-center input[type='checkbox'] {
width: 1.05rem;
height: 1.05rem;
}
.orders-cell-center {
text-align: center;
}
.orders-filters {
justify-content: flex-start;
flex: 1;

View File

@@ -462,6 +462,7 @@
"statusHandedOver": "Artikel ausgehändigt",
"cost": "Kosten",
"paid": "Bezahlt",
"paidConfirmed": "Hat bezahlt",
"budget": "Budget",
"open": "Noch offen",
"history": "Verlauf",