feat: 添加货币和汇率管理功能

refactor: 重构前端路由和登录逻辑

docs: 更新业务闭环、任务和架构文档

style: 调整代码格式和文件结构

chore: 更新依赖项和配置文件
This commit is contained in:
2026-03-19 19:08:15 +08:00
parent 8de9ea0aaa
commit eafa1bbe94
203 changed files with 20240 additions and 39580 deletions

View File

@@ -0,0 +1,261 @@
import { Request, Response } from 'express';
import currencyService from '../../services/CurrencyService';
import exchangeRateService from '../../services/ExchangeRateService';
import currencyConversionService from '../../services/CurrencyConversionService';
import currencyCalculationService from '../../services/CurrencyCalculationService';
import { PriceCalculationParams, ProfitCalculationParams } from '../../services/CurrencyCalculationService';
export class CurrencyController {
// 货币管理相关接口
static async getCurrencies(req: Request, res: Response) {
try {
const currencies = await currencyService.getCurrencies();
res.json({ success: true, data: currencies });
} catch (error: any) {
res.status(500).json({ success: false, error: error.message });
}
}
static async getCurrencyByCode(req: Request, res: Response) {
const { code } = req.params;
try {
const currency = await currencyService.getCurrencyByCode(code);
if (currency) {
res.json({ success: true, data: currency });
} else {
res.status(404).json({ success: false, error: 'Currency not found' });
}
} catch (error: any) {
res.status(500).json({ success: false, error: error.message });
}
}
static async getDefaultCurrency(req: Request, res: Response) {
try {
const currency = await currencyService.getDefaultCurrency();
if (currency) {
res.json({ success: true, data: currency });
} else {
res.status(404).json({ success: false, error: 'Default currency not set' });
}
} catch (error: any) {
res.status(500).json({ success: false, error: error.message });
}
}
static async createCurrency(req: Request, res: Response) {
const { code, name, symbol, decimal_places, format, is_default } = req.body;
const { userId } = (req as any).traceContext;
try {
const currency = await currencyService.createCurrency({
code,
name,
symbol,
decimal_places,
format,
is_default,
created_by: userId,
});
res.status(201).json({ success: true, data: currency });
} catch (error: any) {
res.status(400).json({ success: false, error: error.message });
}
}
static async updateCurrency(req: Request, res: Response) {
const { id } = req.params;
const { name, symbol, decimal_places, format, is_active, is_default } = req.body;
const { userId } = (req as any).traceContext;
try {
const currency = await currencyService.updateCurrency(id, {
name,
symbol,
decimal_places,
format,
is_active,
is_default,
updated_by: userId,
});
res.json({ success: true, data: currency });
} catch (error: any) {
res.status(400).json({ success: false, error: error.message });
}
}
static async deactivateCurrency(req: Request, res: Response) {
const { id } = req.params;
const { userId } = (req as any).traceContext;
try {
const currency = await currencyService.deactivateCurrency(id, userId);
res.json({ success: true, data: currency });
} catch (error: any) {
res.status(400).json({ success: false, error: error.message });
}
}
static async setDefaultCurrency(req: Request, res: Response) {
const { id } = req.params;
const { userId } = (req as any).traceContext;
try {
const currency = await currencyService.setDefaultCurrency(id, userId);
res.json({ success: true, data: currency });
} catch (error: any) {
res.status(400).json({ success: false, error: error.message });
}
}
// 汇率相关接口
static async getExchangeRate(req: Request, res: Response) {
const { from, to } = req.params;
try {
const rate = await exchangeRateService.getExchangeRate(from, to);
if (rate) {
res.json({ success: true, data: rate });
} else {
res.status(404).json({ success: false, error: 'Exchange rate not found' });
}
} catch (error: any) {
res.status(500).json({ success: false, error: error.message });
}
}
static async updateExchangeRates(req: Request, res: Response) {
try {
const count = await exchangeRateService.updateExchangeRates();
res.json({ success: true, data: { updated_count: count } });
} catch (error: any) {
res.status(500).json({ success: false, error: error.message });
}
}
static async getExchangeRateHistory(req: Request, res: Response) {
const { from, to } = req.params;
const { days = 30 } = req.query;
try {
const history = await exchangeRateService.getExchangeRateHistory(from, to, parseInt(days as string));
res.json({ success: true, data: history });
} catch (error: any) {
res.status(500).json({ success: false, error: error.message });
}
}
static async getAllExchangeRates(req: Request, res: Response) {
try {
const rates = await exchangeRateService.getAllExchangeRates();
res.json({ success: true, data: rates });
} catch (error: any) {
res.status(500).json({ success: false, error: error.message });
}
}
// 货币转换相关接口
static async convertCurrency(req: Request, res: Response) {
const { amount, from, to } = req.body;
try {
const convertedAmount = await currencyConversionService.convert(amount, from, to);
res.json({ success: true, data: { amount: convertedAmount, currency: to } });
} catch (error: any) {
res.status(400).json({ success: false, error: error.message });
}
}
static async convertMultipleCurrencies(req: Request, res: Response) {
const { amounts, from, to } = req.body;
try {
const convertedAmounts = await currencyConversionService.convertMultiple(amounts, from, to);
res.json({ success: true, data: { amounts: convertedAmounts, currency: to } });
} catch (error: any) {
res.status(400).json({ success: false, error: error.message });
}
}
static async convertToDefaultCurrency(req: Request, res: Response) {
const { amount, from } = req.body;
try {
const convertedAmount = await currencyConversionService.convertToDefault(amount, from);
const defaultCurrency = await currencyService.getDefaultCurrency();
res.json({ success: true, data: { amount: convertedAmount, currency: defaultCurrency?.code } });
} catch (error: any) {
res.status(400).json({ success: false, error: error.message });
}
}
static async convertFromDefaultCurrency(req: Request, res: Response) {
const { amount, to } = req.body;
try {
const convertedAmount = await currencyConversionService.convertFromDefault(amount, to);
res.json({ success: true, data: { amount: convertedAmount, currency: to } });
} catch (error: any) {
res.status(400).json({ success: false, error: error.message });
}
}
// 货币计算相关接口
static async calculatePrice(req: Request, res: Response) {
const params: PriceCalculationParams = req.body;
try {
const price = await currencyCalculationService.calculatePrice(params);
res.json({ success: true, data: { price, currency: params.targetCurrency } });
} catch (error: any) {
res.status(400).json({ success: false, error: error.message });
}
}
static async calculateProfit(req: Request, res: Response) {
const params: ProfitCalculationParams = req.body;
try {
const profit = await currencyCalculationService.calculateProfit(params);
res.json({ success: true, data: { profit, currency: params.sellingCurrency } });
} catch (error: any) {
res.status(400).json({ success: false, error: error.message });
}
}
static async calculateProfitPercentage(req: Request, res: Response) {
const params: ProfitCalculationParams = req.body;
try {
const profitPercentage = await currencyCalculationService.calculateProfitPercentage(params);
res.json({ success: true, data: { profitPercentage } });
} catch (error: any) {
res.status(400).json({ success: false, error: error.message });
}
}
static async calculateBreakEvenPrice(req: Request, res: Response) {
const { costPrice, costCurrency, targetCurrency, additionalCosts, additionalCostCurrency } = req.body;
try {
const breakEvenPrice = await currencyCalculationService.calculateBreakEvenPrice(
costPrice,
costCurrency,
targetCurrency,
additionalCosts,
additionalCostCurrency
);
res.json({ success: true, data: { breakEvenPrice, currency: targetCurrency } });
} catch (error: any) {
res.status(400).json({ success: false, error: error.message });
}
}
static async calculateMarkupPercentage(req: Request, res: Response) {
const { sellingPrice, sellingCurrency, costPrice, costCurrency, additionalCosts, additionalCostCurrency } = req.body;
try {
const markupPercentage = await currencyCalculationService.calculateMarkupPercentage(
sellingPrice,
sellingCurrency,
costPrice,
costCurrency,
additionalCosts,
additionalCostCurrency
);
res.json({ success: true, data: { markupPercentage } });
} catch (error: any) {
res.status(400).json({ success: false, error: error.message });
}
}
}

View File

@@ -0,0 +1,36 @@
import { Router } from 'express';
import { requireTraceContext } from '../../core/guards/trace-context.guard';
import { requirePermission } from '../../core/guards/rbac.guard';
import { CurrencyController } from '../controllers/CurrencyController';
const router = Router();
// 货币管理路由
router.get('/currencies', CurrencyController.getCurrencies);
router.get('/currencies/:code', CurrencyController.getCurrencyByCode);
router.get('/currencies/default', CurrencyController.getDefaultCurrency);
router.post('/currencies', requireTraceContext, requirePermission('currency:write'), CurrencyController.createCurrency);
router.put('/currencies/:id', requireTraceContext, requirePermission('currency:write'), CurrencyController.updateCurrency);
router.patch('/currencies/:id/deactivate', requireTraceContext, requirePermission('currency:write'), CurrencyController.deactivateCurrency);
router.patch('/currencies/:id/set-default', requireTraceContext, requirePermission('currency:write'), CurrencyController.setDefaultCurrency);
// 汇率管理路由
router.get('/exchange-rates/:from/:to', CurrencyController.getExchangeRate);
router.get('/exchange-rates', CurrencyController.getAllExchangeRates);
router.post('/exchange-rates/update', requireTraceContext, requirePermission('currency:write'), CurrencyController.updateExchangeRates);
router.get('/exchange-rates/history/:from/:to', CurrencyController.getExchangeRateHistory);
// 货币转换路由
router.post('/convert', CurrencyController.convertCurrency);
router.post('/convert/multiple', CurrencyController.convertMultipleCurrencies);
router.post('/convert/to-default', CurrencyController.convertToDefaultCurrency);
router.post('/convert/from-default', CurrencyController.convertFromDefaultCurrency);
// 货币计算路由
router.post('/calculate/price', CurrencyController.calculatePrice);
router.post('/calculate/profit', CurrencyController.calculateProfit);
router.post('/calculate/profit-percentage', CurrencyController.calculateProfitPercentage);
router.post('/calculate/break-even', CurrencyController.calculateBreakEvenPrice);
router.post('/calculate/markup-percentage', CurrencyController.calculateMarkupPercentage);
export default router;