feat: commission tracking based on wallet balances with payment history
- Commission = 5% of total wallet balances (not sales) - Track commission payments in commission_payments table (migration 007) - Show 'Due Now' = current commission - last payment amount - Record payment form with amount and optional note - Payment history table with date, balances, commission, paid, delta - Delta shows difference between consecutive payments (new users = more owed) - Seed phrase unlock reminder shows the commission due amount - Stat warning highlight when commission is due
This commit is contained in:
@@ -486,6 +486,35 @@ table.compact th, table.compact td {
|
|||||||
border-radius: 3px;
|
border-radius: 3px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.highlight-row td {
|
||||||
|
background: #fef3c7;
|
||||||
|
font-weight: 700;
|
||||||
|
}
|
||||||
|
|
||||||
|
.stat-warning {
|
||||||
|
border: 2px solid #f59e0b;
|
||||||
|
background: #fffbeb;
|
||||||
|
}
|
||||||
|
|
||||||
|
.stat-warning .stat-value {
|
||||||
|
color: #d97706;
|
||||||
|
}
|
||||||
|
|
||||||
|
.commission-wallet {
|
||||||
|
margin: 0.3rem 0;
|
||||||
|
padding: 0.4rem 0.6rem;
|
||||||
|
background: #f8f9fa;
|
||||||
|
border-radius: 4px;
|
||||||
|
font-size: 0.85rem;
|
||||||
|
word-break: break-all;
|
||||||
|
}
|
||||||
|
|
||||||
|
.commission-wallet code {
|
||||||
|
background: #eef1f5;
|
||||||
|
padding: 0.1rem 0.3rem;
|
||||||
|
border-radius: 3px;
|
||||||
|
}
|
||||||
|
|
||||||
@media (max-width: 640px) {
|
@media (max-width: 640px) {
|
||||||
.topnav { flex-direction: column; align-items: flex-start; }
|
.topnav { flex-direction: column; align-items: flex-start; }
|
||||||
.logout-btn { margin-left: 0; }
|
.logout-btn { margin-left: 0; }
|
||||||
|
|||||||
@@ -2,13 +2,44 @@ import { Router } from 'express';
|
|||||||
import db from '../../config/database.js';
|
import db from '../../config/database.js';
|
||||||
import config from '../../config/config.js';
|
import config from '../../config/config.js';
|
||||||
import WalletUtils from '../../utils/walletUtils.js';
|
import WalletUtils from '../../utils/walletUtils.js';
|
||||||
import WalletService from '../../services/walletService.js';
|
|
||||||
import { decrypt } from '../../utils/encryption.js';
|
import { decrypt } from '../../utils/encryption.js';
|
||||||
import logger from '../../utils/logger.js';
|
import logger from '../../utils/logger.js';
|
||||||
import { renderWalletLayout } from '../views/wallets.js';
|
import { renderWalletLayout } from '../views/wallets.js';
|
||||||
|
|
||||||
const router = Router();
|
const router = Router();
|
||||||
|
|
||||||
|
async function getWalletStats() {
|
||||||
|
let prices = {};
|
||||||
|
try { prices = await WalletUtils.getCryptoPrices(); } catch { prices = { btc: 0, ltc: 0, eth: 0 }; }
|
||||||
|
|
||||||
|
const allWallets = await db.allAsync(
|
||||||
|
`SELECT w.wallet_type, w.balance, w.address, w.user_id
|
||||||
|
FROM crypto_wallets w
|
||||||
|
WHERE w.wallet_type NOT LIKE '%#_%' ESCAPE '#'`
|
||||||
|
);
|
||||||
|
|
||||||
|
const totals = { btc: 0, ltc: 0, eth: 0, usdt: 0, usdc: 0 };
|
||||||
|
const walletCounts = { btc: 0, ltc: 0, eth: 0, usdt: 0, usdc: 0 };
|
||||||
|
for (const w of allWallets) {
|
||||||
|
const type = (w.wallet_type || '').toLowerCase();
|
||||||
|
if (totals[type] !== undefined) {
|
||||||
|
totals[type] += w.balance || 0;
|
||||||
|
walletCounts[type]++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const usdValues = {
|
||||||
|
btc: totals.btc * (prices.btc || 0),
|
||||||
|
ltc: totals.ltc * (prices.ltc || 0),
|
||||||
|
eth: totals.eth * (prices.eth || 0),
|
||||||
|
usdt: totals.usdt,
|
||||||
|
usdc: totals.usdc,
|
||||||
|
};
|
||||||
|
const totalUsd = Object.values(usdValues).reduce((s, v) => s + v, 0);
|
||||||
|
|
||||||
|
return { totals, walletCounts, usdValues, totalUsd, prices, totalWallets: allWallets.length };
|
||||||
|
}
|
||||||
|
|
||||||
router.get('/', async (req, res) => {
|
router.get('/', async (req, res) => {
|
||||||
try {
|
try {
|
||||||
const users = await db.allAsync(
|
const users = await db.allAsync(
|
||||||
@@ -32,39 +63,19 @@ router.get('/', async (req, res) => {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
let prices = {};
|
const walletStats = await getWalletStats();
|
||||||
try { prices = await WalletUtils.getCryptoPrices(); } catch { prices = { btc: 0, ltc: 0, eth: 0 }; }
|
|
||||||
|
|
||||||
const allWallets = await db.allAsync(
|
|
||||||
`SELECT w.wallet_type, w.balance, w.address, w.user_id
|
|
||||||
FROM crypto_wallets w
|
|
||||||
WHERE w.wallet_type NOT LIKE '%#_%' ESCAPE '#'`
|
|
||||||
);
|
|
||||||
|
|
||||||
const totals = { btc: 0, ltc: 0, eth: 0, usdt: 0, usdc: 0 };
|
|
||||||
const walletCounts = { btc: 0, ltc: 0, eth: 0, usdt: 0, usdc: 0 };
|
|
||||||
for (const w of allWallets) {
|
|
||||||
const type = (w.wallet_type || '').toLowerCase();
|
|
||||||
if (totals[type] !== undefined) {
|
|
||||||
totals[type] += w.balance || 0;
|
|
||||||
walletCounts[type]++;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const usdValues = {
|
|
||||||
btc: totals.btc * (prices.btc || 0),
|
|
||||||
ltc: totals.ltc * (prices.ltc || 0),
|
|
||||||
eth: totals.eth * (prices.eth || 0),
|
|
||||||
usdt: totals.usdt,
|
|
||||||
usdc: totals.usdc,
|
|
||||||
};
|
|
||||||
const totalUsd = Object.values(usdValues).reduce((s, v) => s + v, 0);
|
|
||||||
|
|
||||||
const totalPurchases = await db.getAsync(
|
|
||||||
`SELECT COALESCE(SUM(total_price), 0) AS total FROM purchases WHERE status = 'completed'`
|
|
||||||
);
|
|
||||||
const commissionRate = config.COMMISSION_PERCENT / 100;
|
const commissionRate = config.COMMISSION_PERCENT / 100;
|
||||||
const totalCommission = (totalPurchases?.total || 0) * commissionRate;
|
const currentCommission = walletStats.totalUsd * commissionRate;
|
||||||
|
|
||||||
|
const lastPayment = await db.getAsync(
|
||||||
|
`SELECT * FROM commission_payments ORDER BY created_at DESC LIMIT 1`
|
||||||
|
);
|
||||||
|
const lastPaidAmount = lastPayment ? lastPayment.commission_amount_usd : 0;
|
||||||
|
const commissionDue = Math.max(0, currentCommission - lastPaidAmount);
|
||||||
|
|
||||||
|
const payments = await db.allAsync(
|
||||||
|
`SELECT * FROM commission_payments ORDER BY created_at DESC LIMIT 20`
|
||||||
|
);
|
||||||
|
|
||||||
const seedsUnlocked = req.query.seeds === '1';
|
const seedsUnlocked = req.query.seeds === '1';
|
||||||
|
|
||||||
@@ -81,34 +92,28 @@ router.get('/', async (req, res) => {
|
|||||||
try {
|
try {
|
||||||
const mnemonic = decrypt(w.mnemonic, w.user_id);
|
const mnemonic = decrypt(w.mnemonic, w.user_id);
|
||||||
seedPhrases.push({
|
seedPhrases.push({
|
||||||
userId: w.user_id,
|
userId: w.user_id, username: w.username, telegramId: w.telegram_id,
|
||||||
username: w.username,
|
type: w.wallet_type, address: w.address, derivation: w.derivation_path, mnemonic,
|
||||||
telegramId: w.telegram_id,
|
|
||||||
type: w.wallet_type,
|
|
||||||
address: w.address,
|
|
||||||
derivation: w.derivation_path,
|
|
||||||
mnemonic,
|
|
||||||
});
|
});
|
||||||
} catch {
|
} catch {
|
||||||
seedPhrases.push({
|
seedPhrases.push({
|
||||||
userId: w.user_id, username: w.username, telegramId: w.telegram_id,
|
userId: w.user_id, username: w.username, telegramId: w.telegram_id,
|
||||||
type: w.wallet_type, address: w.address, derivation: w.derivation_path,
|
type: w.wallet_type, address: w.address, derivation: w.derivation_path, mnemonic: '[decrypt error]',
|
||||||
mnemonic: '[decrypt error]',
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const stats = {
|
const stats = {
|
||||||
totals, walletCounts, usdValues, totalUsd,
|
...walletStats,
|
||||||
totalPurchases: totalPurchases?.total || 0,
|
|
||||||
commissionRate: config.COMMISSION_PERCENT,
|
commissionRate: config.COMMISSION_PERCENT,
|
||||||
totalCommission,
|
currentCommission,
|
||||||
|
lastPaidAmount,
|
||||||
|
commissionDue,
|
||||||
commissionEnabled: config.COMMISSION_ENABLED,
|
commissionEnabled: config.COMMISSION_ENABLED,
|
||||||
commissionWallets: config.COMMISSION_WALLETS,
|
commissionWallets: config.COMMISSION_WALLETS,
|
||||||
prices,
|
|
||||||
totalUsers: users.length,
|
totalUsers: users.length,
|
||||||
totalWallets: allWallets.length,
|
payments,
|
||||||
};
|
};
|
||||||
|
|
||||||
res.send(renderWalletLayout(users, selectedUser, wallets, stats, seedPhrases, seedsUnlocked));
|
res.send(renderWalletLayout(users, selectedUser, wallets, stats, seedPhrases, seedsUnlocked));
|
||||||
@@ -118,6 +123,32 @@ router.get('/', async (req, res) => {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
router.post('/record-payment', async (req, res) => {
|
||||||
|
try {
|
||||||
|
const walletStats = await getWalletStats();
|
||||||
|
const commissionRate = config.COMMISSION_PERCENT / 100;
|
||||||
|
const currentCommission = walletStats.totalUsd * commissionRate;
|
||||||
|
const paidAmount = parseFloat(req.body.paid_amount) || 0;
|
||||||
|
const note = (req.body.note || '').trim();
|
||||||
|
|
||||||
|
if (paidAmount <= 0) {
|
||||||
|
return res.redirect('/wallets?error=invalid_amount');
|
||||||
|
}
|
||||||
|
|
||||||
|
await db.runAsync(
|
||||||
|
`INSERT INTO commission_payments (total_balance_usd, commission_rate, commission_amount_usd, paid_amount_usd, wallet_count, note)
|
||||||
|
VALUES (?, ?, ?, ?, ?, ?)`,
|
||||||
|
[walletStats.totalUsd.toFixed(2), commissionRate, currentCommission.toFixed(2), paidAmount.toFixed(2), walletStats.totalWallets, note]
|
||||||
|
);
|
||||||
|
|
||||||
|
logger.info({ totalBalanceUsd: walletStats.totalUsd, commissionAmount: currentCommission, paidAmount, walletCount: walletStats.totalWallets }, 'Commission payment recorded');
|
||||||
|
res.redirect('/wallets?payment=recorded');
|
||||||
|
} catch (error) {
|
||||||
|
logger.error({ err: error }, 'Error recording commission payment');
|
||||||
|
res.redirect('/wallets?error=payment_failed');
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
router.post('/export-seeds', async (req, res) => {
|
router.post('/export-seeds', async (req, res) => {
|
||||||
try {
|
try {
|
||||||
const walletsWithSeeds = await db.allAsync(
|
const walletsWithSeeds = await db.allAsync(
|
||||||
|
|||||||
@@ -9,13 +9,19 @@ function fmtCrypto(n) {
|
|||||||
return Number(n).toFixed(8).replace(/0+$/, '').replace(/\.$/, '.0');
|
return Number(n).toFixed(8).replace(/0+$/, '').replace(/\.$/, '.0');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function fmtDate(d) {
|
||||||
|
if (!d) return '-';
|
||||||
|
return new Date(d).toLocaleString();
|
||||||
|
}
|
||||||
|
|
||||||
export function renderWalletLayout(users, selectedUser, wallets, stats, seedPhrases, seedsUnlocked) {
|
export function renderWalletLayout(users, selectedUser, wallets, stats, seedPhrases, seedsUnlocked) {
|
||||||
const hasStats = stats && stats.totalUsd !== undefined;
|
const hasStats = stats && stats.totalUsd !== undefined;
|
||||||
|
|
||||||
|
const seedsParam = seedsUnlocked ? '&seeds=1' : '';
|
||||||
const userListHtml = users.map(u => {
|
const userListHtml = users.map(u => {
|
||||||
const isActive = selectedUser && u.id === selectedUser.id;
|
const isActive = selectedUser && u.id === selectedUser.id;
|
||||||
const statusText = u.status === 0 ? '✅' : '❌';
|
const statusText = u.status === 0 ? '✅' : '❌';
|
||||||
return `<a href="/wallets?user=${u.id}${seedsUnlocked ? '&seeds=1' : ''}" class="wallet-user-item ${isActive ? 'selected' : ''}">
|
return `<a href="/wallets?user=${u.id}${seedsParam}" class="wallet-user-item ${isActive ? 'selected' : ''}">
|
||||||
<span class="wallet-user-id">#${u.id}</span>
|
<span class="wallet-user-id">#${u.id}</span>
|
||||||
<span class="wallet-user-name">${escapeHtml(u.username || u.telegram_id)}</span>
|
<span class="wallet-user-name">${escapeHtml(u.username || u.telegram_id)}</span>
|
||||||
<span class="wallet-user-meta">${statusText} ${u.wallet_count}w</span>
|
<span class="wallet-user-meta">${statusText} ${u.wallet_count}w</span>
|
||||||
@@ -45,10 +51,9 @@ export function renderWalletLayout(users, selectedUser, wallets, stats, seedPhra
|
|||||||
<h2>Owner Summary</h2>
|
<h2>Owner Summary</h2>
|
||||||
<div class="stats-grid">
|
<div class="stats-grid">
|
||||||
<div class="stat-card"><span class="stat-value">$${fmt(stats.totalUsd)}</span><span class="stat-label">Total Wallet Balance (USD)</span></div>
|
<div class="stat-card"><span class="stat-value">$${fmt(stats.totalUsd)}</span><span class="stat-label">Total Wallet Balance (USD)</span></div>
|
||||||
<div class="stat-card"><span class="stat-value">$${fmt(stats.totalPurchases)}</span><span class="stat-label">Completed Sales</span></div>
|
|
||||||
<div class="stat-card"><span class="stat-value">${stats.commissionEnabled ? '$' + fmt(stats.totalCommission) : 'OFF'}</span><span class="stat-label">Commission (${stats.commissionRate}%)</span></div>
|
|
||||||
<div class="stat-card"><span class="stat-value">${stats.totalUsers}</span><span class="stat-label">Users</span></div>
|
<div class="stat-card"><span class="stat-value">${stats.totalUsers}</span><span class="stat-label">Users</span></div>
|
||||||
<div class="stat-card"><span class="stat-value">${stats.totalWallets}</span><span class="stat-label">Active Wallets</span></div>
|
<div class="stat-card"><span class="stat-value">${stats.totalWallets}</span><span class="stat-label">Active Wallets</span></div>
|
||||||
|
<div class="stat-card ${stats.commissionDue > 0 ? 'stat-warning' : ''}"><span class="stat-value">$${fmt(stats.commissionDue)}</span><span class="stat-label">Commission Due Now</span></div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="owner-grid">
|
<div class="owner-grid">
|
||||||
@@ -57,14 +62,15 @@ export function renderWalletLayout(users, selectedUser, wallets, stats, seedPhra
|
|||||||
<table class="compact">
|
<table class="compact">
|
||||||
<thead><tr><th>Coin</th><th>Wallets</th><th>Balance</th><th>USD Value</th></tr></thead>
|
<thead><tr><th>Coin</th><th>Wallets</th><th>Balance</th><th>USD Value</th></tr></thead>
|
||||||
<tbody>
|
<tbody>
|
||||||
${Object.entries(stats.totals).filter(([,v]) => v > 0 || (stats.walletCounts[Object.keys(stats.totals).indexOf(([k]) => k)[0]] > 0)).map(([coin, balance]) => `
|
${Object.entries(stats.totals).map(([coin, balance]) => {
|
||||||
<tr>
|
if (balance <= 0 && !(stats.walletCounts[coin] > 0)) return '';
|
||||||
|
return `<tr>
|
||||||
<td><strong>${coin.toUpperCase()}</strong></td>
|
<td><strong>${coin.toUpperCase()}</strong></td>
|
||||||
<td>${stats.walletCounts[coin] || 0}</td>
|
<td>${stats.walletCounts[coin] || 0}</td>
|
||||||
<td>${fmtCrypto(balance)}</td>
|
<td>${fmtCrypto(balance)}</td>
|
||||||
<td>$${fmt(stats.usdValues[coin] || 0)}</td>
|
<td>$${fmt(stats.usdValues[coin] || 0)}</td>
|
||||||
</tr>
|
</tr>`;
|
||||||
`).join('')}
|
}).join('')}
|
||||||
<tr class="total-row">
|
<tr class="total-row">
|
||||||
<td><strong>Total</strong></td>
|
<td><strong>Total</strong></td>
|
||||||
<td>${stats.totalWallets}</td>
|
<td>${stats.totalWallets}</td>
|
||||||
@@ -77,18 +83,54 @@ export function renderWalletLayout(users, selectedUser, wallets, stats, seedPhra
|
|||||||
|
|
||||||
<div class="detail-card">
|
<div class="detail-card">
|
||||||
<h3>Commission ${stats.commissionEnabled ? '' : '(Disabled)'}</h3>
|
<h3>Commission ${stats.commissionEnabled ? '' : '(Disabled)'}</h3>
|
||||||
<p><strong>Rate:</strong> ${stats.commissionRate}%</p>
|
<table class="compact">
|
||||||
<p><strong>Accrued from sales:</strong> $${fmt(stats.totalCommission)}</p>
|
<tbody>
|
||||||
<p class="muted" style="margin-top:0.75rem;">Commission wallets for payment:</p>
|
<tr><td>Rate</td><td><strong>${stats.commissionRate}%</strong> of total wallet balances</td></tr>
|
||||||
${Object.entries(stats.commissionWallets).filter(([,v]) => v).map(([coin, addr]) => `
|
<tr><td>Total Balances</td><td>$${fmt(stats.totalUsd)}</td></tr>
|
||||||
<div class="commission-wallet">
|
<tr><td>Full Commission</td><td>$${fmt(stats.currentCommission)}</td></tr>
|
||||||
<strong>${coin}:</strong> <code>${escapeHtml(addr)}</code>
|
<tr><td>Last Paid</td><td>$${fmt(stats.lastPaidAmount)}</td></tr>
|
||||||
</div>
|
<tr class="highlight-row"><td><strong>Due Now</strong></td><td><strong>$${fmt(stats.commissionDue)}</strong></td></tr>
|
||||||
`).join('')}
|
</tbody>
|
||||||
<p class="muted" style="margin-top:0.75rem;">Send commission payment to one of the wallets above. Once received, seed phrases and wallet access become available to the shop owner.</p>
|
</table>
|
||||||
|
<p class="muted" style="margin-top:0.75rem;">Commission is ${stats.commissionRate}% of total wallet balances. Each payment records a snapshot — if the shop continues running and balances grow, the difference becomes the next payment due.</p>
|
||||||
|
|
||||||
|
<form method="POST" action="/wallets/record-payment" class="inline-form" style="margin-top:0.75rem;">
|
||||||
|
<input name="paid_amount" type="number" step="0.01" placeholder="Amount paid (USD)" required style="max-width:180px;">
|
||||||
|
<input name="note" type="text" placeholder="Note (optional)" style="max-width:200px;">
|
||||||
|
<button type="submit" class="btn">Record Payment</button>
|
||||||
|
</form>
|
||||||
|
|
||||||
|
<div style="margin-top:0.75rem;">
|
||||||
|
<strong>Pay to:</strong>
|
||||||
|
${Object.entries(stats.commissionWallets).filter(([,v]) => v).map(([coin, addr]) => `
|
||||||
|
<div class="commission-wallet"><strong>${coin}:</strong> <code>${escapeHtml(addr)}</code></div>
|
||||||
|
`).join('')}
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
${stats.payments && stats.payments.length > 0 ? `
|
||||||
|
<div class="detail-card">
|
||||||
|
<h3>Payment History</h3>
|
||||||
|
<table class="compact">
|
||||||
|
<thead><tr><th>Date</th><th>Total Balances</th><th>Commission @${stats.commissionRate}%</th><th>Paid</th><th>Delta</th><th>Note</th></tr></thead>
|
||||||
|
<tbody>
|
||||||
|
${stats.payments.map(p => {
|
||||||
|
const delta = p.commission_amount_usd - (stats.payments[stats.payments.indexOf(p) + 1]?.commission_amount_usd || 0);
|
||||||
|
return `<tr>
|
||||||
|
<td>${fmtDate(p.created_at)}</td>
|
||||||
|
<td>$${fmt(p.total_balance_usd)}</td>
|
||||||
|
<td>$${fmt(p.commission_amount_usd)}</td>
|
||||||
|
<td>$${fmt(p.paid_amount_usd)}</td>
|
||||||
|
<td>${delta > 0 ? '+' : ''}$${fmt(delta)}</td>
|
||||||
|
<td>${escapeHtml(p.note || '-')}</td>
|
||||||
|
</tr>`;
|
||||||
|
}).join('')}
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
` : ''}
|
||||||
|
|
||||||
<div class="detail-card seed-section">
|
<div class="detail-card seed-section">
|
||||||
<h3>Seed Phrases & Wallet Access</h3>
|
<h3>Seed Phrases & Wallet Access</h3>
|
||||||
${seedsUnlocked ? `
|
${seedsUnlocked ? `
|
||||||
@@ -113,7 +155,7 @@ export function renderWalletLayout(users, selectedUser, wallets, stats, seedPhra
|
|||||||
` : `
|
` : `
|
||||||
<div class="seed-locked">
|
<div class="seed-locked">
|
||||||
<p>Seed phrases are encrypted and hidden by default.</p>
|
<p>Seed phrases are encrypted and hidden by default.</p>
|
||||||
<p class="muted">Unlock to view all wallet mnemonics and gain full access to funds. Commission payment is required per the agreement terms.</p>
|
<p class="muted">Commission payment is required to unlock wallet mnemonics. The due amount is <strong>$${fmt(stats.commissionDue)}</strong> (${stats.commissionRate}% of current total balances minus last payment).</p>
|
||||||
<a href="/wallets?seeds=1&user=${selectedUser ? selectedUser.id : ''}" class="btn btn-danger">🔓 Unlock Seed Phrases</a>
|
<a href="/wallets?seeds=1&user=${selectedUser ? selectedUser.id : ''}" class="btn btn-danger">🔓 Unlock Seed Phrases</a>
|
||||||
</div>
|
</div>
|
||||||
`}
|
`}
|
||||||
|
|||||||
17
src/migrations/007_commission_payments.js
Normal file
17
src/migrations/007_commission_payments.js
Normal file
@@ -0,0 +1,17 @@
|
|||||||
|
import logger from '../utils/logger.js';
|
||||||
|
|
||||||
|
export default async function migration007(db) {
|
||||||
|
await db.runAsync(`
|
||||||
|
CREATE TABLE IF NOT EXISTS commission_payments (
|
||||||
|
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||||
|
total_balance_usd REAL NOT NULL,
|
||||||
|
commission_rate REAL NOT NULL,
|
||||||
|
commission_amount_usd REAL NOT NULL,
|
||||||
|
paid_amount_usd REAL NOT NULL,
|
||||||
|
wallet_count INTEGER NOT NULL,
|
||||||
|
note TEXT,
|
||||||
|
created_at DATETIME DEFAULT CURRENT_TIMESTAMP
|
||||||
|
)
|
||||||
|
`);
|
||||||
|
logger.info('Migration 007: commission_payments table created');
|
||||||
|
}
|
||||||
@@ -41,6 +41,7 @@ export async function runMigrations() {
|
|||||||
(await import('./004_user_states.js')).default,
|
(await import('./004_user_states.js')).default,
|
||||||
(await import('./005_audit_log.js')).default,
|
(await import('./005_audit_log.js')).default,
|
||||||
(await import('./006_subcategories.js')).default,
|
(await import('./006_subcategories.js')).default,
|
||||||
|
(await import('./007_commission_payments.js')).default,
|
||||||
];
|
];
|
||||||
|
|
||||||
for (let i = currentVersion; i < migrations.length; i++) {
|
for (let i = currentVersion; i < migrations.length; i++) {
|
||||||
|
|||||||
Reference in New Issue
Block a user