feat(domain): add income calculation with commission support
This commit is contained in:
@@ -0,0 +1,33 @@
|
|||||||
|
import type { Shop } from "@/lib/types"
|
||||||
|
|
||||||
|
type ShopCommission = Pick<Shop, "commissionType" | "commissionValue">
|
||||||
|
|
||||||
|
export interface IncomeCalculation {
|
||||||
|
income: number
|
||||||
|
commissionLabel: string
|
||||||
|
}
|
||||||
|
|
||||||
|
function roundCurrency(amount: number) {
|
||||||
|
return Number(amount.toFixed(2))
|
||||||
|
}
|
||||||
|
|
||||||
|
export function calculateOrderIncome(totalPrice: number, shop?: ShopCommission): IncomeCalculation {
|
||||||
|
if (!shop) {
|
||||||
|
return {
|
||||||
|
income: roundCurrency(totalPrice),
|
||||||
|
commissionLabel: "独立接单无抽成",
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (shop.commissionType === "percentage") {
|
||||||
|
return {
|
||||||
|
income: roundCurrency(totalPrice * (1 - shop.commissionValue / 100)),
|
||||||
|
commissionLabel: `扣除${shop.commissionValue}%抽成`,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
income: roundCurrency(Math.max(0, totalPrice - shop.commissionValue)),
|
||||||
|
commissionLabel: `扣除¥${shop.commissionValue}固定抽成`,
|
||||||
|
}
|
||||||
|
}
|
||||||
+11
-5
@@ -1,6 +1,8 @@
|
|||||||
import { create } from "zustand"
|
import { create } from "zustand"
|
||||||
|
import { calculateOrderIncome } from "@/lib/domain/income"
|
||||||
import { generateId } from "@/lib/id"
|
import { generateId } from "@/lib/id"
|
||||||
import { mockTransactions, walletBalance } from "@/lib/mock"
|
import { mockTransactions, walletBalance } from "@/lib/mock"
|
||||||
|
import { useShopStore } from "@/store/shops"
|
||||||
import type { WalletTransaction } from "@/lib/types"
|
import type { WalletTransaction } from "@/lib/types"
|
||||||
|
|
||||||
interface WalletState {
|
interface WalletState {
|
||||||
@@ -10,7 +12,7 @@ interface WalletState {
|
|||||||
withdraw: (amount: number) => void
|
withdraw: (amount: number) => void
|
||||||
deductBalance: (orderId: string, amount: number) => boolean
|
deductBalance: (orderId: string, amount: number) => boolean
|
||||||
refundPayment: (orderId: string, amount: number) => boolean
|
refundPayment: (orderId: string, amount: number) => boolean
|
||||||
addIncome: (orderId: string, amount: number) => void
|
addIncome: (orderId: string, totalPrice: number, shopId?: string) => void
|
||||||
addTransaction: (transaction: WalletTransaction) => void
|
addTransaction: (transaction: WalletTransaction) => void
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -109,8 +111,8 @@ export const useWalletStore = create<WalletState>((set, get) => ({
|
|||||||
|
|
||||||
return true
|
return true
|
||||||
},
|
},
|
||||||
addIncome: (orderId, amount) => {
|
addIncome: (orderId, totalPrice, shopId) => {
|
||||||
if (!Number.isFinite(amount) || amount <= 0) return
|
if (!Number.isFinite(totalPrice) || totalPrice <= 0) return
|
||||||
|
|
||||||
const state = get()
|
const state = get()
|
||||||
const exists = state.transactions.some(
|
const exists = state.transactions.some(
|
||||||
@@ -118,15 +120,19 @@ export const useWalletStore = create<WalletState>((set, get) => ({
|
|||||||
)
|
)
|
||||||
if (exists) return
|
if (exists) return
|
||||||
|
|
||||||
|
const shop = shopId
|
||||||
|
? useShopStore.getState().shops.find((item) => item.id === shopId)
|
||||||
|
: undefined
|
||||||
|
const { income, commissionLabel } = calculateOrderIncome(totalPrice, shop)
|
||||||
|
|
||||||
const now = new Date().toISOString()
|
const now = new Date().toISOString()
|
||||||
const income = Number((amount * 0.85).toFixed(2))
|
|
||||||
set((prev) => ({
|
set((prev) => ({
|
||||||
transactions: [
|
transactions: [
|
||||||
{
|
{
|
||||||
id: generateId("tx"),
|
id: generateId("tx"),
|
||||||
type: "income",
|
type: "income",
|
||||||
amount: income,
|
amount: income,
|
||||||
description: `订单 ${orderId} 收入(扣除15%抽成)`,
|
description: `订单 ${orderId} 收入(${commissionLabel})`,
|
||||||
createdAt: now,
|
createdAt: now,
|
||||||
},
|
},
|
||||||
...prev.transactions,
|
...prev.transactions,
|
||||||
|
|||||||
@@ -0,0 +1,46 @@
|
|||||||
|
import { describe, expect, it } from "vitest"
|
||||||
|
import { calculateOrderIncome } from "@/lib/domain/income"
|
||||||
|
|
||||||
|
describe("calculateOrderIncome", () => {
|
||||||
|
it("calculates percentage commission income", () => {
|
||||||
|
const result = calculateOrderIncome(100, {
|
||||||
|
commissionType: "percentage",
|
||||||
|
commissionValue: 12,
|
||||||
|
})
|
||||||
|
|
||||||
|
expect(result).toEqual({
|
||||||
|
income: 88,
|
||||||
|
commissionLabel: "扣除12%抽成",
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
it("calculates fixed commission income", () => {
|
||||||
|
const result = calculateOrderIncome(60, {
|
||||||
|
commissionType: "fixed",
|
||||||
|
commissionValue: 8,
|
||||||
|
})
|
||||||
|
|
||||||
|
expect(result).toEqual({
|
||||||
|
income: 52,
|
||||||
|
commissionLabel: "扣除¥8固定抽成",
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
it("keeps full income for independent orders", () => {
|
||||||
|
const result = calculateOrderIncome(90)
|
||||||
|
|
||||||
|
expect(result).toEqual({
|
||||||
|
income: 90,
|
||||||
|
commissionLabel: "独立接单无抽成",
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
it("does not return negative income for fixed commission", () => {
|
||||||
|
const result = calculateOrderIncome(6, {
|
||||||
|
commissionType: "fixed",
|
||||||
|
commissionValue: 8,
|
||||||
|
})
|
||||||
|
|
||||||
|
expect(result.income).toBe(0)
|
||||||
|
})
|
||||||
|
})
|
||||||
Reference in New Issue
Block a user