feat: add total balance calculation and display in account management; update account card to show calculated balance
All checks were successful
Deploy with Docker Compose / deploy (push) Successful in 1m26s
All checks were successful
Deploy with Docker Compose / deploy (push) Successful in 1m26s
This commit is contained in:
@@ -99,6 +99,7 @@ export default function AccountsPage() {
|
|||||||
folderId: "folder-root",
|
folderId: "folder-root",
|
||||||
externalUrl: "",
|
externalUrl: "",
|
||||||
initialBalance: 0,
|
initialBalance: 0,
|
||||||
|
totalBalance: 0,
|
||||||
});
|
});
|
||||||
|
|
||||||
// Folder management state
|
// Folder management state
|
||||||
@@ -131,7 +132,7 @@ export default function AccountsPage() {
|
|||||||
|
|
||||||
// Convert accountsWithStats to regular accounts for compatibility
|
// Convert accountsWithStats to regular accounts for compatibility
|
||||||
const accounts = accountsWithStats.map(
|
const accounts = accountsWithStats.map(
|
||||||
({ transactionCount: _transactionCount, ...account }) => account,
|
({ transactionCount: _transactionCount, calculatedBalance: _calculatedBalance, ...account }) => account,
|
||||||
);
|
);
|
||||||
|
|
||||||
const formatCurrency = (amount: number) => {
|
const formatCurrency = (amount: number) => {
|
||||||
@@ -143,12 +144,14 @@ export default function AccountsPage() {
|
|||||||
|
|
||||||
const handleEdit = (account: Account) => {
|
const handleEdit = (account: Account) => {
|
||||||
setEditingAccount(account);
|
setEditingAccount(account);
|
||||||
|
const totalBalance = getAccountBalance(account);
|
||||||
setFormData({
|
setFormData({
|
||||||
name: account.name,
|
name: account.name,
|
||||||
type: account.type,
|
type: account.type,
|
||||||
folderId: account.folderId || "folder-root",
|
folderId: account.folderId || "folder-root",
|
||||||
externalUrl: account.externalUrl || "",
|
externalUrl: account.externalUrl || "",
|
||||||
initialBalance: account.initialBalance || 0,
|
initialBalance: account.initialBalance || 0,
|
||||||
|
totalBalance: totalBalance,
|
||||||
});
|
});
|
||||||
setIsDialogOpen(true);
|
setIsDialogOpen(true);
|
||||||
};
|
};
|
||||||
@@ -157,13 +160,21 @@ export default function AccountsPage() {
|
|||||||
if (!editingAccount) return;
|
if (!editingAccount) return;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
|
// Calculer le balance à partir du solde total et du solde initial
|
||||||
|
// balance = totalBalance - initialBalance
|
||||||
|
const balance = formData.totalBalance - formData.initialBalance;
|
||||||
|
|
||||||
|
// Convertir "folder-root" en null
|
||||||
|
const folderId = formData.folderId === "folder-root" ? null : formData.folderId;
|
||||||
|
|
||||||
const updatedAccount = {
|
const updatedAccount = {
|
||||||
...editingAccount,
|
...editingAccount,
|
||||||
name: formData.name,
|
name: formData.name,
|
||||||
type: formData.type,
|
type: formData.type,
|
||||||
folderId: formData.folderId,
|
folderId: folderId,
|
||||||
externalUrl: formData.externalUrl || null,
|
externalUrl: formData.externalUrl || null,
|
||||||
initialBalance: formData.initialBalance,
|
initialBalance: formData.initialBalance,
|
||||||
|
balance: balance,
|
||||||
};
|
};
|
||||||
await updateAccount(updatedAccount);
|
await updateAccount(updatedAccount);
|
||||||
invalidateAllAccountQueries(queryClient);
|
invalidateAllAccountQueries(queryClient);
|
||||||
@@ -334,7 +345,7 @@ export default function AccountsPage() {
|
|||||||
// Update cache directly
|
// Update cache directly
|
||||||
queryClient.setQueryData(
|
queryClient.setQueryData(
|
||||||
["accounts-with-stats"],
|
["accounts-with-stats"],
|
||||||
(old: Array<Account & { transactionCount: number }> | undefined) => {
|
(old: Array<Account & { transactionCount: number; calculatedBalance: number }> | undefined) => {
|
||||||
if (!old) return old;
|
if (!old) return old;
|
||||||
return old.map((a) => (a.id === accountId ? updatedAccount : a));
|
return old.map((a) => (a.id === accountId ? updatedAccount : a));
|
||||||
},
|
},
|
||||||
@@ -508,21 +519,25 @@ export default function AccountsPage() {
|
|||||||
(f: FolderType) => f.id === account.folderId,
|
(f: FolderType) => f.id === account.folderId,
|
||||||
);
|
);
|
||||||
|
|
||||||
return (
|
const accountWithStats = accountsWithStats.find(
|
||||||
<AccountCard
|
(a) => a.id === account.id,
|
||||||
key={account.id}
|
);
|
||||||
account={account}
|
return (
|
||||||
folder={folder}
|
<AccountCard
|
||||||
transactionCount={getTransactionCount(account.id)}
|
key={account.id}
|
||||||
onEdit={handleEdit}
|
account={account}
|
||||||
onDelete={handleDelete}
|
folder={folder}
|
||||||
formatCurrency={formatCurrency}
|
transactionCount={getTransactionCount(account.id)}
|
||||||
isSelected={selectedAccounts.has(account.id)}
|
calculatedBalance={accountWithStats?.calculatedBalance}
|
||||||
onSelect={toggleSelectAccount}
|
onEdit={handleEdit}
|
||||||
draggableId={`account-${account.id}`}
|
onDelete={handleDelete}
|
||||||
compact={isCompactView}
|
formatCurrency={formatCurrency}
|
||||||
/>
|
isSelected={selectedAccounts.has(account.id)}
|
||||||
);
|
onSelect={toggleSelectAccount}
|
||||||
|
draggableId={`account-${account.id}`}
|
||||||
|
compact={isCompactView}
|
||||||
|
/>
|
||||||
|
);
|
||||||
})}
|
})}
|
||||||
</div>
|
</div>
|
||||||
</FolderDropZone>
|
</FolderDropZone>
|
||||||
@@ -625,12 +640,16 @@ export default function AccountsPage() {
|
|||||||
(f: FolderType) => f.id === account.folderId,
|
(f: FolderType) => f.id === account.folderId,
|
||||||
);
|
);
|
||||||
|
|
||||||
|
const accountWithStats = accountsWithStats.find(
|
||||||
|
(a) => a.id === account.id,
|
||||||
|
);
|
||||||
return (
|
return (
|
||||||
<AccountCard
|
<AccountCard
|
||||||
key={account.id}
|
key={account.id}
|
||||||
account={account}
|
account={account}
|
||||||
folder={accountFolder}
|
folder={accountFolder}
|
||||||
transactionCount={getTransactionCount(account.id)}
|
transactionCount={getTransactionCount(account.id)}
|
||||||
|
calculatedBalance={accountWithStats?.calculatedBalance}
|
||||||
onEdit={handleEdit}
|
onEdit={handleEdit}
|
||||||
onDelete={handleDelete}
|
onDelete={handleDelete}
|
||||||
formatCurrency={formatCurrency}
|
formatCurrency={formatCurrency}
|
||||||
|
|||||||
@@ -29,6 +29,7 @@ interface AccountCardProps {
|
|||||||
account: Account;
|
account: Account;
|
||||||
folder?: Folder;
|
folder?: Folder;
|
||||||
transactionCount: number;
|
transactionCount: number;
|
||||||
|
calculatedBalance?: number;
|
||||||
onEdit: (account: Account) => void;
|
onEdit: (account: Account) => void;
|
||||||
onDelete: (accountId: string) => void;
|
onDelete: (accountId: string) => void;
|
||||||
formatCurrency: (amount: number) => string;
|
formatCurrency: (amount: number) => string;
|
||||||
@@ -42,6 +43,7 @@ export function AccountCard({
|
|||||||
account,
|
account,
|
||||||
folder,
|
folder,
|
||||||
transactionCount,
|
transactionCount,
|
||||||
|
calculatedBalance,
|
||||||
onEdit,
|
onEdit,
|
||||||
onDelete,
|
onDelete,
|
||||||
formatCurrency,
|
formatCurrency,
|
||||||
@@ -53,6 +55,8 @@ export function AccountCard({
|
|||||||
const isMobile = useIsMobile();
|
const isMobile = useIsMobile();
|
||||||
const Icon = accountTypeIcons[account.type];
|
const Icon = accountTypeIcons[account.type];
|
||||||
const realBalance = getAccountBalance(account);
|
const realBalance = getAccountBalance(account);
|
||||||
|
const hasBalanceDifference = calculatedBalance !== undefined &&
|
||||||
|
Math.abs(account.balance - calculatedBalance) > 0.01;
|
||||||
|
|
||||||
const {
|
const {
|
||||||
attributes,
|
attributes,
|
||||||
@@ -172,21 +176,35 @@ export function AccountCard({
|
|||||||
className={cn(isMobile ? "px-2 pb-2 pt-1" : "pt-1", compact && "pt-0")}
|
className={cn(isMobile ? "px-2 pb-2 pt-1" : "pt-1", compact && "pt-0")}
|
||||||
>
|
>
|
||||||
<div className="flex items-center justify-between gap-1.5">
|
<div className="flex items-center justify-between gap-1.5">
|
||||||
<div
|
<div className="flex-1 min-w-0">
|
||||||
className={cn(
|
<div
|
||||||
"font-bold truncate",
|
className={cn(
|
||||||
compact
|
"font-bold truncate",
|
||||||
? isMobile
|
compact
|
||||||
? "text-sm"
|
? isMobile
|
||||||
: "text-lg"
|
? "text-sm"
|
||||||
: isMobile
|
: "text-lg"
|
||||||
? "text-base"
|
: isMobile
|
||||||
: "text-xl",
|
? "text-base"
|
||||||
!compact && !isMobile && "mb-1.5",
|
: "text-xl",
|
||||||
realBalance >= 0 ? "text-emerald-600" : "text-red-600",
|
!compact && !isMobile && "mb-1.5",
|
||||||
|
realBalance >= 0 ? "text-emerald-600" : "text-red-600",
|
||||||
|
)}
|
||||||
|
>
|
||||||
|
{formatCurrency(realBalance)}
|
||||||
|
</div>
|
||||||
|
{!compact && calculatedBalance !== undefined && (
|
||||||
|
<div className="flex items-center gap-2 mt-0.5">
|
||||||
|
<span className="text-xs text-muted-foreground">
|
||||||
|
Calculé: {formatCurrency(calculatedBalance)}
|
||||||
|
</span>
|
||||||
|
{hasBalanceDifference && (
|
||||||
|
<span className="text-xs text-destructive font-semibold">
|
||||||
|
(diff: {formatCurrency(account.balance - calculatedBalance)})
|
||||||
|
</span>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
)}
|
)}
|
||||||
>
|
|
||||||
{formatCurrency(realBalance)}
|
|
||||||
</div>
|
</div>
|
||||||
{compact && (
|
{compact && (
|
||||||
<Link
|
<Link
|
||||||
|
|||||||
@@ -25,6 +25,7 @@ interface AccountFormData {
|
|||||||
folderId: string;
|
folderId: string;
|
||||||
externalUrl: string;
|
externalUrl: string;
|
||||||
initialBalance: number;
|
initialBalance: number;
|
||||||
|
totalBalance: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
interface AccountEditDialogProps {
|
interface AccountEditDialogProps {
|
||||||
@@ -101,21 +102,42 @@ export function AccountEditDialog({
|
|||||||
</Select>
|
</Select>
|
||||||
</div>
|
</div>
|
||||||
<div className="space-y-2">
|
<div className="space-y-2">
|
||||||
<Label>Solde initial</Label>
|
<Label>Solde total</Label>
|
||||||
<Input
|
<Input
|
||||||
type="number"
|
type="number"
|
||||||
step="0.01"
|
step="0.01"
|
||||||
value={formData.initialBalance}
|
value={formData.totalBalance}
|
||||||
onChange={(e) =>
|
onChange={(e) =>
|
||||||
onFormDataChange({
|
onFormDataChange({
|
||||||
...formData,
|
...formData,
|
||||||
initialBalance: parseFloat(e.target.value) || 0,
|
totalBalance: parseFloat(e.target.value) || 0,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
placeholder="0.00"
|
placeholder="0.00"
|
||||||
/>
|
/>
|
||||||
<p className="text-xs text-muted-foreground">
|
<p className="text-xs text-muted-foreground">
|
||||||
Solde de départ pour équilibrer le compte
|
Solde total du compte (balance + solde initial)
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
<div className="space-y-2">
|
||||||
|
<Label>Solde initial</Label>
|
||||||
|
<Input
|
||||||
|
type="number"
|
||||||
|
step="0.01"
|
||||||
|
value={formData.initialBalance}
|
||||||
|
onChange={(e) => {
|
||||||
|
const newInitialBalance = parseFloat(e.target.value) || 0;
|
||||||
|
onFormDataChange({
|
||||||
|
...formData,
|
||||||
|
initialBalance: newInitialBalance,
|
||||||
|
// Ajuster le solde total pour maintenir la cohérence
|
||||||
|
totalBalance: formData.totalBalance,
|
||||||
|
});
|
||||||
|
}}
|
||||||
|
placeholder="0.00"
|
||||||
|
/>
|
||||||
|
<p className="text-xs text-muted-foreground">
|
||||||
|
Solde de départ pour équilibrer le compte. Le balance sera calculé automatiquement (solde total - solde initial).
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
<div className="space-y-2">
|
<div className="space-y-2">
|
||||||
|
|||||||
@@ -199,7 +199,7 @@ export function useAccountsWithStats() {
|
|||||||
throw new Error("Failed to fetch accounts with stats");
|
throw new Error("Failed to fetch accounts with stats");
|
||||||
}
|
}
|
||||||
return response.json() as Promise<
|
return response.json() as Promise<
|
||||||
Array<Account & { transactionCount: number }>
|
Array<Account & { transactionCount: number; calculatedBalance: number }>
|
||||||
>;
|
>;
|
||||||
},
|
},
|
||||||
staleTime: 60 * 1000, // 1 minute
|
staleTime: 60 * 1000, // 1 minute
|
||||||
|
|||||||
76
scripts/fix-account-balances.ts
Normal file
76
scripts/fix-account-balances.ts
Normal file
@@ -0,0 +1,76 @@
|
|||||||
|
import { prisma } from "../lib/prisma";
|
||||||
|
|
||||||
|
async function main() {
|
||||||
|
const accountNumbers = process.argv.slice(2);
|
||||||
|
|
||||||
|
if (accountNumbers.length === 0) {
|
||||||
|
console.error("Usage: tsx scripts/fix-account-balances.ts <accountNumber1> [accountNumber2] ...");
|
||||||
|
console.error("Exemple: tsx scripts/fix-account-balances.ts 0748461N022 7555880857A");
|
||||||
|
process.exit(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
for (const accountNumber of accountNumbers) {
|
||||||
|
console.log(`\n=== Correction du compte: ${accountNumber} ===\n`);
|
||||||
|
|
||||||
|
const account = await prisma.account.findFirst({
|
||||||
|
where: {
|
||||||
|
accountNumber: accountNumber,
|
||||||
|
},
|
||||||
|
include: {
|
||||||
|
transactions: {
|
||||||
|
select: {
|
||||||
|
amount: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!account) {
|
||||||
|
console.log(`❌ Compte non trouvé: ${accountNumber}`);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log(`Compte trouvé:`);
|
||||||
|
console.log(` ID: ${account.id}`);
|
||||||
|
console.log(` Nom: ${account.name}`);
|
||||||
|
console.log(` Bank ID: ${account.bankId}`);
|
||||||
|
console.log(` Balance actuelle: ${account.balance}`);
|
||||||
|
console.log(` Initial Balance: ${account.initialBalance}`);
|
||||||
|
console.log(` Transactions: ${account.transactions.length}`);
|
||||||
|
|
||||||
|
// Recalculer le solde à partir des transactions
|
||||||
|
const calculatedBalance = account.transactions.reduce(
|
||||||
|
(sum, t) => sum + t.amount,
|
||||||
|
0,
|
||||||
|
);
|
||||||
|
|
||||||
|
console.log(` Balance calculée: ${calculatedBalance}`);
|
||||||
|
|
||||||
|
if (Math.abs(account.balance - calculatedBalance) > 0.01) {
|
||||||
|
console.log(`\n⚠️ Différence détectée: ${Math.abs(account.balance - calculatedBalance).toFixed(2)}`);
|
||||||
|
console.log(`Mise à jour du solde...`);
|
||||||
|
|
||||||
|
await prisma.account.update({
|
||||||
|
where: {
|
||||||
|
id: account.id,
|
||||||
|
},
|
||||||
|
data: {
|
||||||
|
balance: calculatedBalance,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
console.log(`✅ Solde corrigé: ${calculatedBalance}`);
|
||||||
|
} else {
|
||||||
|
console.log(`\n✅ Le solde est déjà correct.`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
await prisma.$disconnect();
|
||||||
|
}
|
||||||
|
|
||||||
|
main()
|
||||||
|
.catch((e) => {
|
||||||
|
console.error("Erreur:", e);
|
||||||
|
process.exit(1);
|
||||||
|
});
|
||||||
|
|
||||||
168
scripts/merge-duplicate-accounts.ts
Normal file
168
scripts/merge-duplicate-accounts.ts
Normal file
@@ -0,0 +1,168 @@
|
|||||||
|
import { prisma } from "../lib/prisma";
|
||||||
|
|
||||||
|
async function main() {
|
||||||
|
const accountNumber = process.argv[2];
|
||||||
|
|
||||||
|
if (!accountNumber) {
|
||||||
|
console.error("Usage: tsx scripts/merge-duplicate-accounts.ts <accountNumber>");
|
||||||
|
process.exit(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log(`Fusion des comptes dupliqués pour: ${accountNumber}\n`);
|
||||||
|
|
||||||
|
// Trouver tous les comptes avec ce numéro
|
||||||
|
const accounts = await prisma.account.findMany({
|
||||||
|
where: {
|
||||||
|
accountNumber: accountNumber,
|
||||||
|
},
|
||||||
|
include: {
|
||||||
|
transactions: {
|
||||||
|
select: {
|
||||||
|
id: true,
|
||||||
|
date: true,
|
||||||
|
amount: true,
|
||||||
|
},
|
||||||
|
orderBy: {
|
||||||
|
date: "desc",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
folder: true,
|
||||||
|
},
|
||||||
|
orderBy: {
|
||||||
|
createdAt: "desc", // Le plus récent en premier
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
if (accounts.length < 2) {
|
||||||
|
console.log("Aucun doublon trouvé.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log(`Trouvé ${accounts.length} comptes à fusionner:\n`);
|
||||||
|
accounts.forEach((acc, idx) => {
|
||||||
|
console.log(`Compte ${idx + 1}:`);
|
||||||
|
console.log(` ID: ${acc.id}`);
|
||||||
|
console.log(` Bank ID: ${acc.bankId}`);
|
||||||
|
console.log(` Balance: ${acc.balance}`);
|
||||||
|
console.log(` Transactions: ${acc.transactions.length}`);
|
||||||
|
console.log(` Folder: ${acc.folder?.name || "Aucun"}`);
|
||||||
|
console.log(` Created: ${acc.createdAt}`);
|
||||||
|
console.log("");
|
||||||
|
});
|
||||||
|
|
||||||
|
// Le compte avec bankId numérique (pas "FR") est le bon - le garder comme principal
|
||||||
|
// Si pas de bankId numérique, garder le plus récent
|
||||||
|
const primaryAccount = accounts.find(acc => acc.bankId !== "FR" && acc.bankId !== "") || accounts[0];
|
||||||
|
const accountsToMerge = accounts.filter(acc => acc.id !== primaryAccount.id);
|
||||||
|
|
||||||
|
console.log(`\nCompte principal (conservé): ${primaryAccount.id} (bankId: ${primaryAccount.bankId})`);
|
||||||
|
console.log(`Comptes à fusionner: ${accountsToMerge.map((a) => `${a.id} (bankId: ${a.bankId})`).join(", ")}\n`);
|
||||||
|
|
||||||
|
// Calculer l'initialBalance total (somme des initialBalance)
|
||||||
|
const totalInitialBalance = accounts.reduce(
|
||||||
|
(sum, acc) => sum + acc.initialBalance,
|
||||||
|
0,
|
||||||
|
);
|
||||||
|
|
||||||
|
console.log(`Initial balance totale: ${totalInitialBalance}\n`);
|
||||||
|
|
||||||
|
// Fusionner les transactions
|
||||||
|
let totalTransactionsMoved = 0;
|
||||||
|
for (const accountToMerge of accountsToMerge) {
|
||||||
|
console.log(
|
||||||
|
`Déplacement des transactions du compte ${accountToMerge.id}...`,
|
||||||
|
);
|
||||||
|
|
||||||
|
// Déplacer toutes les transactions vers le compte principal
|
||||||
|
const updateResult = await prisma.transaction.updateMany({
|
||||||
|
where: {
|
||||||
|
accountId: accountToMerge.id,
|
||||||
|
},
|
||||||
|
data: {
|
||||||
|
accountId: primaryAccount.id,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
totalTransactionsMoved += updateResult.count;
|
||||||
|
console.log(` → ${updateResult.count} transactions déplacées`);
|
||||||
|
|
||||||
|
// Supprimer le compte fusionné
|
||||||
|
await prisma.account.delete({
|
||||||
|
where: {
|
||||||
|
id: accountToMerge.id,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
console.log(` → Compte ${accountToMerge.id} supprimé\n`);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Recalculer le solde à partir de toutes les transactions du compte fusionné
|
||||||
|
const allTransactions = await prisma.transaction.findMany({
|
||||||
|
where: {
|
||||||
|
accountId: primaryAccount.id,
|
||||||
|
},
|
||||||
|
select: {
|
||||||
|
amount: true,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
const calculatedBalance = allTransactions.reduce(
|
||||||
|
(sum, t) => sum + t.amount,
|
||||||
|
0,
|
||||||
|
);
|
||||||
|
|
||||||
|
console.log(`Balance calculée à partir des transactions: ${calculatedBalance}`);
|
||||||
|
|
||||||
|
// Mettre à jour la balance du compte principal
|
||||||
|
// Garder le bankId du compte principal (celui qui est correct)
|
||||||
|
await prisma.account.update({
|
||||||
|
where: {
|
||||||
|
id: primaryAccount.id,
|
||||||
|
},
|
||||||
|
data: {
|
||||||
|
balance: calculatedBalance,
|
||||||
|
initialBalance: totalInitialBalance,
|
||||||
|
// Garder le bankId du compte principal (le bon)
|
||||||
|
bankId: primaryAccount.bankId,
|
||||||
|
// Garder le dernier import le plus récent parmi tous les comptes
|
||||||
|
lastImport:
|
||||||
|
accounts.reduce((latest, acc) => {
|
||||||
|
if (!acc.lastImport) return latest;
|
||||||
|
if (!latest) return acc.lastImport;
|
||||||
|
return acc.lastImport > latest ? acc.lastImport : latest;
|
||||||
|
}, null as string | null),
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
console.log(`\n✅ Fusion terminée:`);
|
||||||
|
console.log(` - Compte principal: ${primaryAccount.id}`);
|
||||||
|
console.log(` - Transactions totales déplacées: ${totalTransactionsMoved}`);
|
||||||
|
console.log(` - Nouvelle balance (calculée): ${calculatedBalance}`);
|
||||||
|
console.log(` - Comptes supprimés: ${accountsToMerge.length}`);
|
||||||
|
|
||||||
|
// Vérification finale
|
||||||
|
const finalAccount = await prisma.account.findUnique({
|
||||||
|
where: { id: primaryAccount.id },
|
||||||
|
include: {
|
||||||
|
transactions: {
|
||||||
|
select: { id: true },
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
if (finalAccount) {
|
||||||
|
console.log(`\nVérification finale:`);
|
||||||
|
console.log(` - Transactions dans le compte: ${finalAccount.transactions.length}`);
|
||||||
|
console.log(` - Balance: ${finalAccount.balance}`);
|
||||||
|
console.log(` - Bank ID: ${finalAccount.bankId}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
await prisma.$disconnect();
|
||||||
|
}
|
||||||
|
|
||||||
|
main()
|
||||||
|
.catch((e) => {
|
||||||
|
console.error("Erreur:", e);
|
||||||
|
process.exit(1);
|
||||||
|
});
|
||||||
|
|
||||||
@@ -369,7 +369,7 @@ export const bankingService = {
|
|||||||
},
|
},
|
||||||
|
|
||||||
async getAccountsWithStats(): Promise<
|
async getAccountsWithStats(): Promise<
|
||||||
Array<Account & { transactionCount: number }>
|
Array<Account & { transactionCount: number; calculatedBalance: number }>
|
||||||
> {
|
> {
|
||||||
const accounts = await prisma.account.findMany({
|
const accounts = await prisma.account.findMany({
|
||||||
include: {
|
include: {
|
||||||
@@ -377,32 +377,40 @@ export const bankingService = {
|
|||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
// Get transaction counts for all accounts in one query
|
// Get transaction counts and sums for all accounts in one query
|
||||||
const transactionCounts = await prisma.transaction.groupBy({
|
const transactionStats = await prisma.transaction.groupBy({
|
||||||
by: ["accountId"],
|
by: ["accountId"],
|
||||||
_count: {
|
_count: {
|
||||||
id: true,
|
id: true,
|
||||||
},
|
},
|
||||||
|
_sum: {
|
||||||
|
amount: true,
|
||||||
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
const countMap = new Map<string, number>();
|
const countMap = new Map<string, number>();
|
||||||
transactionCounts.forEach((tc) => {
|
const balanceMap = new Map<string, number>();
|
||||||
countMap.set(tc.accountId, tc._count.id);
|
transactionStats.forEach((ts) => {
|
||||||
|
countMap.set(ts.accountId, ts._count.id);
|
||||||
|
balanceMap.set(ts.accountId, ts._sum.amount || 0);
|
||||||
});
|
});
|
||||||
|
|
||||||
return accounts.map((a): Account & { transactionCount: number } => ({
|
return accounts.map(
|
||||||
id: a.id,
|
(a): Account & { transactionCount: number; calculatedBalance: number } => ({
|
||||||
name: a.name,
|
id: a.id,
|
||||||
bankId: a.bankId,
|
name: a.name,
|
||||||
accountNumber: a.accountNumber,
|
bankId: a.bankId,
|
||||||
type: a.type as Account["type"],
|
accountNumber: a.accountNumber,
|
||||||
folderId: a.folderId,
|
type: a.type as Account["type"],
|
||||||
balance: a.balance,
|
folderId: a.folderId,
|
||||||
initialBalance: a.initialBalance,
|
balance: a.balance,
|
||||||
currency: a.currency,
|
initialBalance: a.initialBalance,
|
||||||
lastImport: a.lastImport,
|
currency: a.currency,
|
||||||
externalUrl: a.externalUrl,
|
lastImport: a.lastImport,
|
||||||
transactionCount: countMap.get(a.id) || 0,
|
externalUrl: a.externalUrl,
|
||||||
}));
|
transactionCount: countMap.get(a.id) || 0,
|
||||||
|
calculatedBalance: balanceMap.get(a.id) || 0,
|
||||||
|
}),
|
||||||
|
);
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|||||||
Reference in New Issue
Block a user