管理后台
This commit is contained in:
parent
e8439c82c1
commit
461bc64a6f
|
|
@ -622,6 +622,26 @@
|
||||||
至少填写一种货币金额,两个都填表示同时支付了两种货币
|
至少填写一种货币金额,两个都填表示同时支付了两种货币
|
||||||
</el-alert>
|
</el-alert>
|
||||||
</el-form-item>
|
</el-form-item>
|
||||||
|
<el-form-item label="比索成本">
|
||||||
|
<el-input-number
|
||||||
|
v-model="paymentForm.costPeso"
|
||||||
|
:min="0"
|
||||||
|
:precision="2"
|
||||||
|
:step="100"
|
||||||
|
style="width: 180px"
|
||||||
|
/>
|
||||||
|
<span style="margin-left: 10px; color: #909399;">₱ 比索(可选)</span>
|
||||||
|
</el-form-item>
|
||||||
|
<el-form-item label="人民币成本">
|
||||||
|
<el-input-number
|
||||||
|
v-model="paymentForm.costRmb"
|
||||||
|
:min="0"
|
||||||
|
:precision="2"
|
||||||
|
:step="10"
|
||||||
|
style="width: 180px"
|
||||||
|
/>
|
||||||
|
<span style="margin-left: 10px; color: #909399;">¥ 人民币(可选)</span>
|
||||||
|
</el-form-item>
|
||||||
<el-form-item label="支付时间" prop="paymentTime">
|
<el-form-item label="支付时间" prop="paymentTime">
|
||||||
<el-date-picker
|
<el-date-picker
|
||||||
v-model="paymentForm.paymentTime"
|
v-model="paymentForm.paymentTime"
|
||||||
|
|
@ -731,6 +751,8 @@ const paymentForm = reactive({
|
||||||
serviceContent: '',
|
serviceContent: '',
|
||||||
amountPeso: null,
|
amountPeso: null,
|
||||||
amountRmb: null,
|
amountRmb: null,
|
||||||
|
costPeso: null,
|
||||||
|
costRmb: null,
|
||||||
paymentTime: '',
|
paymentTime: '',
|
||||||
paymentProof: '',
|
paymentProof: '',
|
||||||
notes: ''
|
notes: ''
|
||||||
|
|
@ -979,6 +1001,8 @@ function handleCreatePaymentOrder(row) {
|
||||||
paymentForm.serviceContent = row.service?.titleZh || row.hotService?.name_zh || row.serviceType || ''
|
paymentForm.serviceContent = row.service?.titleZh || row.hotService?.name_zh || row.serviceType || ''
|
||||||
paymentForm.amountPeso = null
|
paymentForm.amountPeso = null
|
||||||
paymentForm.amountRmb = null
|
paymentForm.amountRmb = null
|
||||||
|
paymentForm.costPeso = null
|
||||||
|
paymentForm.costRmb = null
|
||||||
// 默认设置为当前本地时间
|
// 默认设置为当前本地时间
|
||||||
const now = new Date()
|
const now = new Date()
|
||||||
const year = now.getFullYear()
|
const year = now.getFullYear()
|
||||||
|
|
@ -1021,6 +1045,8 @@ async function confirmCreatePaymentOrder() {
|
||||||
appointmentId: paymentForm.appointmentNo,
|
appointmentId: paymentForm.appointmentNo,
|
||||||
amountPeso: hasPeso ? paymentForm.amountPeso : undefined,
|
amountPeso: hasPeso ? paymentForm.amountPeso : undefined,
|
||||||
amountRmb: hasRmb ? paymentForm.amountRmb : undefined,
|
amountRmb: hasRmb ? paymentForm.amountRmb : undefined,
|
||||||
|
costPeso: paymentForm.costPeso > 0 ? paymentForm.costPeso : undefined,
|
||||||
|
costRmb: paymentForm.costRmb > 0 ? paymentForm.costRmb : undefined,
|
||||||
serviceContent: paymentForm.serviceContent,
|
serviceContent: paymentForm.serviceContent,
|
||||||
paymentTime: paymentForm.paymentTime,
|
paymentTime: paymentForm.paymentTime,
|
||||||
paymentProof: paymentForm.paymentProof,
|
paymentProof: paymentForm.paymentProof,
|
||||||
|
|
|
||||||
|
|
@ -214,6 +214,16 @@
|
||||||
<span class="payment-amount">{{ row.currency === 'PHP' ? '₱' : '¥' }}{{ row.paymentAmount }}</span>
|
<span class="payment-amount">{{ row.currency === 'PHP' ? '₱' : '¥' }}{{ row.paymentAmount }}</span>
|
||||||
</template>
|
</template>
|
||||||
</el-table-column>
|
</el-table-column>
|
||||||
|
<el-table-column prop="costAmount" label="成本金额" width="120">
|
||||||
|
<template #default="{ row }">
|
||||||
|
<span class="cost-amount">{{ row.currency === 'PHP' ? '₱' : '¥' }}{{ row.costAmount }}</span>
|
||||||
|
</template>
|
||||||
|
</el-table-column>
|
||||||
|
<el-table-column prop="profitAmount" label="营利金额" width="120">
|
||||||
|
<template #default="{ row }">
|
||||||
|
<span class="profit-amount">{{ row.currency === 'PHP' ? '₱' : '¥' }}{{ row.profitAmount }}</span>
|
||||||
|
</template>
|
||||||
|
</el-table-column>
|
||||||
<el-table-column prop="commissionRate" label="佣金比例" width="100" />
|
<el-table-column prop="commissionRate" label="佣金比例" width="100" />
|
||||||
<el-table-column prop="commissionAmount" label="佣金金额" width="120">
|
<el-table-column prop="commissionAmount" label="佣金金额" width="120">
|
||||||
<template #default="{ row }">
|
<template #default="{ row }">
|
||||||
|
|
@ -685,6 +695,16 @@ onMounted(() => {
|
||||||
font-weight: 500;
|
font-weight: 500;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.cost-amount {
|
||||||
|
color: #f56c6c;
|
||||||
|
font-weight: 500;
|
||||||
|
}
|
||||||
|
|
||||||
|
.profit-amount {
|
||||||
|
color: #67c23a;
|
||||||
|
font-weight: 600;
|
||||||
|
}
|
||||||
|
|
||||||
.commission-amount {
|
.commission-amount {
|
||||||
color: #67c23a;
|
color: #67c23a;
|
||||||
font-weight: 600;
|
font-weight: 600;
|
||||||
|
|
|
||||||
|
|
@ -49,6 +49,22 @@
|
||||||
<span class="statistics-label">比索总额</span>
|
<span class="statistics-label">比索总额</span>
|
||||||
<span class="statistics-value peso">₱{{ statistics.totalPeso }}</span>
|
<span class="statistics-value peso">₱{{ statistics.totalPeso }}</span>
|
||||||
</div>
|
</div>
|
||||||
|
<div class="statistics-item">
|
||||||
|
<span class="statistics-label">人民币成本</span>
|
||||||
|
<span class="statistics-value cost">¥{{ statistics.totalCostRmb }}</span>
|
||||||
|
</div>
|
||||||
|
<div class="statistics-item">
|
||||||
|
<span class="statistics-label">比索成本</span>
|
||||||
|
<span class="statistics-value cost">₱{{ statistics.totalCostPeso }}</span>
|
||||||
|
</div>
|
||||||
|
<div class="statistics-item">
|
||||||
|
<span class="statistics-label">人民币营利</span>
|
||||||
|
<span class="statistics-value profit">¥{{ statistics.profitRmb }}</span>
|
||||||
|
</div>
|
||||||
|
<div class="statistics-item">
|
||||||
|
<span class="statistics-label">比索营利</span>
|
||||||
|
<span class="statistics-value profit">₱{{ statistics.profitPeso }}</span>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</el-card>
|
</el-card>
|
||||||
|
|
||||||
|
|
@ -149,6 +165,14 @@
|
||||||
<el-form-item label="服务内容" prop="serviceContent">
|
<el-form-item label="服务内容" prop="serviceContent">
|
||||||
<el-input v-model="createForm.serviceContent" type="textarea" :rows="3" placeholder="输入服务内容" />
|
<el-input v-model="createForm.serviceContent" type="textarea" :rows="3" placeholder="输入服务内容" />
|
||||||
</el-form-item>
|
</el-form-item>
|
||||||
|
<el-form-item label="比索成本">
|
||||||
|
<el-input-number v-model="createForm.costPeso" :min="0" :precision="2" placeholder="比索成本" />
|
||||||
|
<span class="currency-hint">₱ 比索(可选)</span>
|
||||||
|
</el-form-item>
|
||||||
|
<el-form-item label="人民币成本">
|
||||||
|
<el-input-number v-model="createForm.costRmb" :min="0" :precision="2" placeholder="人民币成本" />
|
||||||
|
<span class="currency-hint">¥ 人民币(可选)</span>
|
||||||
|
</el-form-item>
|
||||||
<el-form-item label="支付时间" prop="paymentTime">
|
<el-form-item label="支付时间" prop="paymentTime">
|
||||||
<el-date-picker
|
<el-date-picker
|
||||||
v-model="createForm.paymentTime"
|
v-model="createForm.paymentTime"
|
||||||
|
|
@ -203,6 +227,13 @@
|
||||||
<span v-if="!currentOrder.amountPeso && !currentOrder.amountRmb && currentOrder.amount">¥{{ currentOrder.amount }}</span>
|
<span v-if="!currentOrder.amountPeso && !currentOrder.amountRmb && currentOrder.amount">¥{{ currentOrder.amount }}</span>
|
||||||
</div>
|
</div>
|
||||||
</el-descriptions-item>
|
</el-descriptions-item>
|
||||||
|
<el-descriptions-item label="成本金额">
|
||||||
|
<div class="amount-detail">
|
||||||
|
<span v-if="currentOrder.costPeso" class="peso">₱{{ currentOrder.costPeso }} (比索)</span>
|
||||||
|
<span v-if="currentOrder.costRmb" class="rmb">¥{{ currentOrder.costRmb }} (人民币)</span>
|
||||||
|
<span v-if="!currentOrder.costPeso && !currentOrder.costRmb">-</span>
|
||||||
|
</div>
|
||||||
|
</el-descriptions-item>
|
||||||
<el-descriptions-item label="服务内容" :span="2">{{ currentOrder.serviceContent }}</el-descriptions-item>
|
<el-descriptions-item label="服务内容" :span="2">{{ currentOrder.serviceContent }}</el-descriptions-item>
|
||||||
<el-descriptions-item label="支付时间">{{ formatDate(currentOrder.paymentTime) }}</el-descriptions-item>
|
<el-descriptions-item label="支付时间">{{ formatDate(currentOrder.paymentTime) }}</el-descriptions-item>
|
||||||
<el-descriptions-item label="创建时间">{{ formatDate(currentOrder.createdAt) }}</el-descriptions-item>
|
<el-descriptions-item label="创建时间">{{ formatDate(currentOrder.createdAt) }}</el-descriptions-item>
|
||||||
|
|
@ -295,6 +326,10 @@ const filters = reactive({
|
||||||
const statistics = reactive({
|
const statistics = reactive({
|
||||||
totalRmb: '0.00',
|
totalRmb: '0.00',
|
||||||
totalPeso: '0.00',
|
totalPeso: '0.00',
|
||||||
|
totalCostRmb: '0.00',
|
||||||
|
totalCostPeso: '0.00',
|
||||||
|
profitRmb: '0.00',
|
||||||
|
profitPeso: '0.00',
|
||||||
})
|
})
|
||||||
|
|
||||||
const pagination = reactive({
|
const pagination = reactive({
|
||||||
|
|
@ -307,6 +342,8 @@ const createForm = reactive({
|
||||||
userId: '',
|
userId: '',
|
||||||
amountPeso: null,
|
amountPeso: null,
|
||||||
amountRmb: null,
|
amountRmb: null,
|
||||||
|
costPeso: null,
|
||||||
|
costRmb: null,
|
||||||
serviceContent: '',
|
serviceContent: '',
|
||||||
paymentTime: '',
|
paymentTime: '',
|
||||||
paymentProof: '',
|
paymentProof: '',
|
||||||
|
|
@ -346,9 +383,17 @@ async function fetchOrders() {
|
||||||
if (response.data.data.statistics) {
|
if (response.data.data.statistics) {
|
||||||
statistics.totalRmb = response.data.data.statistics.totalRmb || '0.00'
|
statistics.totalRmb = response.data.data.statistics.totalRmb || '0.00'
|
||||||
statistics.totalPeso = response.data.data.statistics.totalPeso || '0.00'
|
statistics.totalPeso = response.data.data.statistics.totalPeso || '0.00'
|
||||||
|
statistics.totalCostRmb = response.data.data.statistics.totalCostRmb || '0.00'
|
||||||
|
statistics.totalCostPeso = response.data.data.statistics.totalCostPeso || '0.00'
|
||||||
|
statistics.profitRmb = response.data.data.statistics.profitRmb || '0.00'
|
||||||
|
statistics.profitPeso = response.data.data.statistics.profitPeso || '0.00'
|
||||||
} else {
|
} else {
|
||||||
statistics.totalRmb = '0.00'
|
statistics.totalRmb = '0.00'
|
||||||
statistics.totalPeso = '0.00'
|
statistics.totalPeso = '0.00'
|
||||||
|
statistics.totalCostRmb = '0.00'
|
||||||
|
statistics.totalCostPeso = '0.00'
|
||||||
|
statistics.profitRmb = '0.00'
|
||||||
|
statistics.profitPeso = '0.00'
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
|
|
@ -399,6 +444,8 @@ function showCreateDialog() {
|
||||||
userId: '',
|
userId: '',
|
||||||
amountPeso: null,
|
amountPeso: null,
|
||||||
amountRmb: null,
|
amountRmb: null,
|
||||||
|
costPeso: null,
|
||||||
|
costRmb: null,
|
||||||
serviceContent: '',
|
serviceContent: '',
|
||||||
paymentTime: currentTime,
|
paymentTime: currentTime,
|
||||||
paymentProof: '',
|
paymentProof: '',
|
||||||
|
|
@ -471,6 +518,8 @@ async function handleCreate() {
|
||||||
userId: createForm.userId,
|
userId: createForm.userId,
|
||||||
amountPeso: hasPeso ? createForm.amountPeso : undefined,
|
amountPeso: hasPeso ? createForm.amountPeso : undefined,
|
||||||
amountRmb: hasRmb ? createForm.amountRmb : undefined,
|
amountRmb: hasRmb ? createForm.amountRmb : undefined,
|
||||||
|
costPeso: createForm.costPeso > 0 ? createForm.costPeso : undefined,
|
||||||
|
costRmb: createForm.costRmb > 0 ? createForm.costRmb : undefined,
|
||||||
serviceContent: createForm.serviceContent,
|
serviceContent: createForm.serviceContent,
|
||||||
paymentTime: createForm.paymentTime,
|
paymentTime: createForm.paymentTime,
|
||||||
paymentProof: createForm.paymentProof,
|
paymentProof: createForm.paymentProof,
|
||||||
|
|
@ -577,6 +626,7 @@ onMounted(() => {
|
||||||
|
|
||||||
.statistics-wrapper {
|
.statistics-wrapper {
|
||||||
display: flex;
|
display: flex;
|
||||||
|
flex-wrap: wrap;
|
||||||
gap: 40px;
|
gap: 40px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -602,6 +652,14 @@ onMounted(() => {
|
||||||
&.peso {
|
&.peso {
|
||||||
color: #409eff;
|
color: #409eff;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
&.cost {
|
||||||
|
color: #f56c6c;
|
||||||
|
}
|
||||||
|
|
||||||
|
&.profit {
|
||||||
|
color: #67c23a;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
45
backend/add-cost-fields.js
Normal file
45
backend/add-cost-fields.js
Normal file
|
|
@ -0,0 +1,45 @@
|
||||||
|
/**
|
||||||
|
* Migration: Add cost_peso and cost_rmb fields to payment_orders table
|
||||||
|
*/
|
||||||
|
const { sequelize } = require('./src/config/database');
|
||||||
|
|
||||||
|
async function migrate() {
|
||||||
|
try {
|
||||||
|
const queryInterface = sequelize.getQueryInterface();
|
||||||
|
|
||||||
|
// Check if columns already exist
|
||||||
|
const tableDesc = await queryInterface.describeTable('payment_orders');
|
||||||
|
|
||||||
|
if (!tableDesc.cost_peso) {
|
||||||
|
await queryInterface.addColumn('payment_orders', 'cost_peso', {
|
||||||
|
type: require('sequelize').DataTypes.DECIMAL(10, 2),
|
||||||
|
allowNull: true,
|
||||||
|
defaultValue: null,
|
||||||
|
comment: '成本金额(比索)',
|
||||||
|
});
|
||||||
|
console.log('Added cost_peso column');
|
||||||
|
} else {
|
||||||
|
console.log('cost_peso column already exists');
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!tableDesc.cost_rmb) {
|
||||||
|
await queryInterface.addColumn('payment_orders', 'cost_rmb', {
|
||||||
|
type: require('sequelize').DataTypes.DECIMAL(10, 2),
|
||||||
|
allowNull: true,
|
||||||
|
defaultValue: null,
|
||||||
|
comment: '成本金额(人民币)',
|
||||||
|
});
|
||||||
|
console.log('Added cost_rmb column');
|
||||||
|
} else {
|
||||||
|
console.log('cost_rmb column already exists');
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log('Migration completed successfully');
|
||||||
|
process.exit(0);
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Migration failed:', error);
|
||||||
|
process.exit(1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
migrate();
|
||||||
|
|
@ -12,7 +12,7 @@ const paymentOrderService = require('../services/paymentOrderService');
|
||||||
const createPaymentOrder = async (req, res) => {
|
const createPaymentOrder = async (req, res) => {
|
||||||
try {
|
try {
|
||||||
const adminId = req.adminId || req.admin?.id;
|
const adminId = req.adminId || req.admin?.id;
|
||||||
const { userId, appointmentId, amount, amountPeso, amountRmb, serviceContent, paymentTime, notes, paymentProof } = req.body;
|
const { userId, appointmentId, amount, amountPeso, amountRmb, costPeso, costRmb, serviceContent, paymentTime, notes, paymentProof } = req.body;
|
||||||
|
|
||||||
const result = await paymentOrderService.createPaymentOrder({
|
const result = await paymentOrderService.createPaymentOrder({
|
||||||
userId: userId ? userId.trim() : userId, // Trim whitespace
|
userId: userId ? userId.trim() : userId, // Trim whitespace
|
||||||
|
|
@ -20,6 +20,8 @@ const createPaymentOrder = async (req, res) => {
|
||||||
amount,
|
amount,
|
||||||
amountPeso,
|
amountPeso,
|
||||||
amountRmb,
|
amountRmb,
|
||||||
|
costPeso,
|
||||||
|
costRmb,
|
||||||
serviceContent,
|
serviceContent,
|
||||||
paymentTime,
|
paymentTime,
|
||||||
notes,
|
notes,
|
||||||
|
|
|
||||||
|
|
@ -97,6 +97,20 @@ const PaymentOrder = sequelize.define('PaymentOrder', {
|
||||||
field: 'cancel_reason',
|
field: 'cancel_reason',
|
||||||
comment: '取消原因',
|
comment: '取消原因',
|
||||||
},
|
},
|
||||||
|
costPeso: {
|
||||||
|
type: DataTypes.DECIMAL(10, 2),
|
||||||
|
allowNull: true,
|
||||||
|
defaultValue: null,
|
||||||
|
field: 'cost_peso',
|
||||||
|
comment: '成本金额(比索)',
|
||||||
|
},
|
||||||
|
costRmb: {
|
||||||
|
type: DataTypes.DECIMAL(10, 2),
|
||||||
|
allowNull: true,
|
||||||
|
defaultValue: null,
|
||||||
|
field: 'cost_rmb',
|
||||||
|
comment: '成本金额(人民币)',
|
||||||
|
},
|
||||||
paymentProof: {
|
paymentProof: {
|
||||||
type: DataTypes.STRING(500),
|
type: DataTypes.STRING(500),
|
||||||
allowNull: true,
|
allowNull: true,
|
||||||
|
|
|
||||||
|
|
@ -43,14 +43,18 @@ const calculateCommission = async (paymentOrderId) => {
|
||||||
|
|
||||||
// Check RMB amount (including legacy 'amount' field)
|
// Check RMB amount (including legacy 'amount' field)
|
||||||
const rmbAmount = parseFloat(paymentOrder.amountRmb || 0) || parseFloat(paymentOrder.amount || 0);
|
const rmbAmount = parseFloat(paymentOrder.amountRmb || 0) || parseFloat(paymentOrder.amount || 0);
|
||||||
|
const rmbCost = parseFloat(paymentOrder.costRmb || 0);
|
||||||
|
const rmbProfit = rmbAmount - rmbCost;
|
||||||
if (rmbAmount > 0) {
|
if (rmbAmount > 0) {
|
||||||
currencyAmounts.push({ currency: 'RMB', paymentAmount: rmbAmount });
|
currencyAmounts.push({ currency: 'RMB', paymentAmount: rmbAmount, profitAmount: rmbProfit > 0 ? rmbProfit : 0 });
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check Peso amount
|
// Check Peso amount
|
||||||
const pesoAmount = parseFloat(paymentOrder.amountPeso || 0);
|
const pesoAmount = parseFloat(paymentOrder.amountPeso || 0);
|
||||||
|
const pesoCost = parseFloat(paymentOrder.costPeso || 0);
|
||||||
|
const pesoProfit = pesoAmount - pesoCost;
|
||||||
if (pesoAmount > 0) {
|
if (pesoAmount > 0) {
|
||||||
currencyAmounts.push({ currency: 'PHP', paymentAmount: pesoAmount });
|
currencyAmounts.push({ currency: 'PHP', paymentAmount: pesoAmount, profitAmount: pesoProfit > 0 ? pesoProfit : 0 });
|
||||||
}
|
}
|
||||||
|
|
||||||
if (currencyAmounts.length === 0) {
|
if (currencyAmounts.length === 0) {
|
||||||
|
|
@ -67,8 +71,9 @@ const calculateCommission = async (paymentOrderId) => {
|
||||||
const inviter = await User.findByPk(inviteeUser.invitedBy, { transaction });
|
const inviter = await User.findByPk(inviteeUser.invitedBy, { transaction });
|
||||||
|
|
||||||
// Create commission record for each currency
|
// Create commission record for each currency
|
||||||
for (const { currency, paymentAmount } of currencyAmounts) {
|
for (const { currency, paymentAmount, profitAmount } of currencyAmounts) {
|
||||||
const commissionAmount = paymentAmount * commissionRate;
|
// Commission = profit * rate (profit = payment - cost)
|
||||||
|
const commissionAmount = profitAmount * commissionRate;
|
||||||
|
|
||||||
// Create commission record
|
// Create commission record
|
||||||
const commission = await Commission.create({
|
const commission = await Commission.create({
|
||||||
|
|
@ -374,7 +379,7 @@ const getAllCommissions = async (options = {}) => {
|
||||||
{
|
{
|
||||||
model: PaymentOrder,
|
model: PaymentOrder,
|
||||||
as: 'paymentOrder',
|
as: 'paymentOrder',
|
||||||
attributes: ['id', 'orderNo', 'amount'],
|
attributes: ['id', 'orderNo', 'amount', 'amountPeso', 'amountRmb', 'costPeso', 'costRmb'],
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
order: [['createdAt', 'DESC']],
|
order: [['createdAt', 'DESC']],
|
||||||
|
|
@ -382,18 +387,40 @@ const getAllCommissions = async (options = {}) => {
|
||||||
offset: parseInt(offset),
|
offset: parseInt(offset),
|
||||||
});
|
});
|
||||||
|
|
||||||
const records = rows.map(commission => ({
|
const records = rows.map(commission => {
|
||||||
|
const po = commission.paymentOrder;
|
||||||
|
const currency = commission.currency || 'RMB';
|
||||||
|
|
||||||
|
// Calculate cost and profit for this commission's currency
|
||||||
|
let costAmount = 0;
|
||||||
|
let profitAmount = 0;
|
||||||
|
if (po) {
|
||||||
|
if (currency === 'PHP') {
|
||||||
|
costAmount = parseFloat(po.costPeso || 0);
|
||||||
|
const payAmt = parseFloat(po.amountPeso || 0);
|
||||||
|
profitAmount = payAmt - costAmount;
|
||||||
|
} else {
|
||||||
|
costAmount = parseFloat(po.costRmb || 0);
|
||||||
|
const payAmt = parseFloat(po.amountRmb || 0) || parseFloat(po.amount || 0);
|
||||||
|
profitAmount = payAmt - costAmount;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
id: commission.id,
|
id: commission.id,
|
||||||
inviter: commission.inviter,
|
inviter: commission.inviter,
|
||||||
invitee: commission.invitee,
|
invitee: commission.invitee,
|
||||||
paymentOrder: commission.paymentOrder,
|
paymentOrder: po ? { id: po.id, orderNo: po.orderNo } : null,
|
||||||
paymentAmount: parseFloat(commission.paymentAmount).toFixed(2),
|
paymentAmount: parseFloat(commission.paymentAmount).toFixed(2),
|
||||||
|
costAmount: costAmount.toFixed(2),
|
||||||
|
profitAmount: profitAmount.toFixed(2),
|
||||||
commissionRate: `${(parseFloat(commission.commissionRate) * 100).toFixed(2)}%`,
|
commissionRate: `${(parseFloat(commission.commissionRate) * 100).toFixed(2)}%`,
|
||||||
commissionAmount: parseFloat(commission.commissionAmount).toFixed(2),
|
commissionAmount: parseFloat(commission.commissionAmount).toFixed(2),
|
||||||
currency: commission.currency || 'RMB',
|
currency: currency,
|
||||||
status: commission.status,
|
status: commission.status,
|
||||||
createdAt: commission.createdAt,
|
createdAt: commission.createdAt,
|
||||||
}));
|
};
|
||||||
|
});
|
||||||
|
|
||||||
return {
|
return {
|
||||||
records,
|
records,
|
||||||
|
|
|
||||||
|
|
@ -28,7 +28,7 @@ const generateOrderNo = () => {
|
||||||
* @returns {Object} Created payment order with commission info
|
* @returns {Object} Created payment order with commission info
|
||||||
*/
|
*/
|
||||||
const createPaymentOrder = async (data, adminId) => {
|
const createPaymentOrder = async (data, adminId) => {
|
||||||
const { userId, appointmentId, amount, amountPeso, amountRmb, serviceContent, paymentTime, notes, paymentProof } = data;
|
const { userId, appointmentId, amount, amountPeso, amountRmb, costPeso, costRmb, serviceContent, paymentTime, notes, paymentProof } = data;
|
||||||
|
|
||||||
// Validate required fields
|
// Validate required fields
|
||||||
if (!userId) {
|
if (!userId) {
|
||||||
|
|
@ -104,6 +104,10 @@ const createPaymentOrder = async (data, adminId) => {
|
||||||
// Calculate total amount for commission (check any currency)
|
// Calculate total amount for commission (check any currency)
|
||||||
const totalAmountForCommission = rmbAmount > 0 ? rmbAmount : (pesoAmount > 0 ? pesoAmount : legacyAmount);
|
const totalAmountForCommission = rmbAmount > 0 ? rmbAmount : (pesoAmount > 0 ? pesoAmount : legacyAmount);
|
||||||
|
|
||||||
|
// Parse cost amounts
|
||||||
|
const costPesoAmount = costPeso !== null && costPeso !== undefined ? parseFloat(costPeso) : 0;
|
||||||
|
const costRmbAmount = costRmb !== null && costRmb !== undefined ? parseFloat(costRmb) : 0;
|
||||||
|
|
||||||
// Create payment order
|
// Create payment order
|
||||||
const paymentOrder = await PaymentOrder.create({
|
const paymentOrder = await PaymentOrder.create({
|
||||||
orderNo,
|
orderNo,
|
||||||
|
|
@ -112,6 +116,8 @@ const createPaymentOrder = async (data, adminId) => {
|
||||||
amount: legacyAmount > 0 ? legacyAmount : null, // Legacy field
|
amount: legacyAmount > 0 ? legacyAmount : null, // Legacy field
|
||||||
amountPeso: pesoAmount > 0 ? pesoAmount : null,
|
amountPeso: pesoAmount > 0 ? pesoAmount : null,
|
||||||
amountRmb: rmbAmount > 0 ? rmbAmount : null,
|
amountRmb: rmbAmount > 0 ? rmbAmount : null,
|
||||||
|
costPeso: costPesoAmount > 0 ? costPesoAmount : null,
|
||||||
|
costRmb: costRmbAmount > 0 ? costRmbAmount : null,
|
||||||
serviceContent,
|
serviceContent,
|
||||||
paymentTime: new Date(paymentTime),
|
paymentTime: new Date(paymentTime),
|
||||||
notes: notes || null,
|
notes: notes || null,
|
||||||
|
|
@ -152,6 +158,8 @@ const createPaymentOrder = async (data, adminId) => {
|
||||||
amount: paymentOrder.amount ? parseFloat(paymentOrder.amount).toFixed(2) : null,
|
amount: paymentOrder.amount ? parseFloat(paymentOrder.amount).toFixed(2) : null,
|
||||||
amountPeso: paymentOrder.amountPeso ? parseFloat(paymentOrder.amountPeso).toFixed(2) : null,
|
amountPeso: paymentOrder.amountPeso ? parseFloat(paymentOrder.amountPeso).toFixed(2) : null,
|
||||||
amountRmb: paymentOrder.amountRmb ? parseFloat(paymentOrder.amountRmb).toFixed(2) : null,
|
amountRmb: paymentOrder.amountRmb ? parseFloat(paymentOrder.amountRmb).toFixed(2) : null,
|
||||||
|
costPeso: paymentOrder.costPeso ? parseFloat(paymentOrder.costPeso).toFixed(2) : null,
|
||||||
|
costRmb: paymentOrder.costRmb ? parseFloat(paymentOrder.costRmb).toFixed(2) : null,
|
||||||
serviceContent: paymentOrder.serviceContent,
|
serviceContent: paymentOrder.serviceContent,
|
||||||
paymentTime: paymentOrder.paymentTime,
|
paymentTime: paymentOrder.paymentTime,
|
||||||
paymentProof: paymentOrder.paymentProof,
|
paymentProof: paymentOrder.paymentProof,
|
||||||
|
|
@ -233,6 +241,8 @@ const getPaymentOrders = async (options = {}) => {
|
||||||
attributes: [
|
attributes: [
|
||||||
[sequelize.fn('COALESCE', sequelize.fn('SUM', sequelize.col('amount_rmb')), 0), 'totalRmb'],
|
[sequelize.fn('COALESCE', sequelize.fn('SUM', sequelize.col('amount_rmb')), 0), 'totalRmb'],
|
||||||
[sequelize.fn('COALESCE', sequelize.fn('SUM', sequelize.col('amount_peso')), 0), 'totalPeso'],
|
[sequelize.fn('COALESCE', sequelize.fn('SUM', sequelize.col('amount_peso')), 0), 'totalPeso'],
|
||||||
|
[sequelize.fn('COALESCE', sequelize.fn('SUM', sequelize.col('cost_rmb')), 0), 'totalCostRmb'],
|
||||||
|
[sequelize.fn('COALESCE', sequelize.fn('SUM', sequelize.col('cost_peso')), 0), 'totalCostPeso'],
|
||||||
],
|
],
|
||||||
raw: true,
|
raw: true,
|
||||||
}),
|
}),
|
||||||
|
|
@ -241,9 +251,18 @@ const getPaymentOrders = async (options = {}) => {
|
||||||
const { count, rows } = ordersResult;
|
const { count, rows } = ordersResult;
|
||||||
|
|
||||||
// Format statistics with two decimal places
|
// Format statistics with two decimal places
|
||||||
|
const totalRmb = parseFloat(statisticsResult?.totalRmb || 0);
|
||||||
|
const totalPeso = parseFloat(statisticsResult?.totalPeso || 0);
|
||||||
|
const totalCostRmb = parseFloat(statisticsResult?.totalCostRmb || 0);
|
||||||
|
const totalCostPeso = parseFloat(statisticsResult?.totalCostPeso || 0);
|
||||||
|
|
||||||
const statistics = {
|
const statistics = {
|
||||||
totalRmb: parseFloat(statisticsResult?.totalRmb || 0).toFixed(2),
|
totalRmb: totalRmb.toFixed(2),
|
||||||
totalPeso: parseFloat(statisticsResult?.totalPeso || 0).toFixed(2),
|
totalPeso: totalPeso.toFixed(2),
|
||||||
|
totalCostRmb: totalCostRmb.toFixed(2),
|
||||||
|
totalCostPeso: totalCostPeso.toFixed(2),
|
||||||
|
profitRmb: (totalRmb - totalCostRmb).toFixed(2),
|
||||||
|
profitPeso: (totalPeso - totalCostPeso).toFixed(2),
|
||||||
};
|
};
|
||||||
|
|
||||||
// Get all commissions for each order (dual-currency support)
|
// Get all commissions for each order (dual-currency support)
|
||||||
|
|
@ -368,6 +387,8 @@ const getPaymentOrderById = async (orderId) => {
|
||||||
amount: order.amount ? parseFloat(order.amount).toFixed(2) : null,
|
amount: order.amount ? parseFloat(order.amount).toFixed(2) : null,
|
||||||
amountPeso: order.amountPeso ? parseFloat(order.amountPeso).toFixed(2) : null,
|
amountPeso: order.amountPeso ? parseFloat(order.amountPeso).toFixed(2) : null,
|
||||||
amountRmb: order.amountRmb ? parseFloat(order.amountRmb).toFixed(2) : null,
|
amountRmb: order.amountRmb ? parseFloat(order.amountRmb).toFixed(2) : null,
|
||||||
|
costPeso: order.costPeso ? parseFloat(order.costPeso).toFixed(2) : null,
|
||||||
|
costRmb: order.costRmb ? parseFloat(order.costRmb).toFixed(2) : null,
|
||||||
serviceContent: order.serviceContent,
|
serviceContent: order.serviceContent,
|
||||||
paymentTime: order.paymentTime,
|
paymentTime: order.paymentTime,
|
||||||
paymentProof: order.paymentProof,
|
paymentProof: order.paymentProof,
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue
Block a user