Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
26 changes: 13 additions & 13 deletions backend/package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

4 changes: 2 additions & 2 deletions backend/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -74,10 +74,10 @@
"jest": "^30.2.0",
"prettier": "^3.1.1",
"supertest": "^7.2.2",
"ts-jest": "^29.4.6",
"ts-jest": "^29.4.12",
"ts-node": "^10.9.2",
"ts-node-dev": "^2.0.0",
"tsx": "^4.21.0",
"typescript": "^5.9.3"
}
}
}
21 changes: 16 additions & 5 deletions backend/src/services/benefitsService.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import pool from '../config/database.js';
import { Money } from '../utils/money.js';

export interface BenefitPlan {
id: number;
Expand Down Expand Up @@ -61,7 +62,7 @@ export interface DraftPayslip {
}

function round7(n: number): number {
return parseFloat(n.toFixed(7));
return Money.from(n).toNumber();
}

export class BenefitsService {
Expand Down Expand Up @@ -337,8 +338,11 @@ export class BenefitsService {
}

const ruleValue = parseFloat(rule.value);
const grossMoney = Money.from(gross);
const amount =
rule.type === 'percentage' ? round7(gross * (ruleValue / 100)) : round7(ruleValue);
rule.type === 'percentage'
? grossMoney.percentage(ruleValue).toNumber()
: Money.from(ruleValue).toNumber();

let destinationWallet = rule.destination_wallet_address;
if (!destinationWallet) {
Expand Down Expand Up @@ -372,8 +376,11 @@ export class BenefitsService {

for (const tax of taxResult.rows as Array<{ id: number; name: string; type: 'percentage' | 'fixed'; value: string }>) {
const taxValue = parseFloat(tax.value);
const grossMoney = Money.from(gross);
const amount =
tax.type === 'percentage' ? round7(gross * (taxValue / 100)) : round7(taxValue);
tax.type === 'percentage'
? grossMoney.percentage(taxValue).toNumber()
: Money.from(taxValue).toNumber();

const treasuryWallet = await this.resolveTreasuryWalletAddress(input.organization_id, currency);

Expand All @@ -389,8 +396,12 @@ export class BenefitsService {
});
}

const totalDeductions = round7(lines.reduce((sum, l) => sum + l.amount, 0));
const net = Math.max(0, round7(gross - totalDeductions));
let totalDeductionsMoney = Money.zero();
for (const l of lines) {
totalDeductionsMoney = totalDeductionsMoney.add(Money.from(l.amount));
}
const totalDeductions = totalDeductionsMoney.toNumber();
const net = Math.max(0, Money.from(gross).sub(totalDeductionsMoney).toNumber());

return {
organization_id: input.organization_id,
Expand Down
24 changes: 17 additions & 7 deletions backend/src/services/payrollBonusService.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { pool } from '../config/database.js';
import { Money } from '../utils/money.js';
import logger from '../utils/logger.js';

export interface PayrollRun {
Expand Down Expand Up @@ -214,17 +215,26 @@ export class PayrollBonusService {
const baseItems = items.filter((item) => item.item_type === 'base');
const bonusItems = items.filter((item) => item.item_type === 'bonus');

let baseAmountMoney = Money.zero();
for (const item of baseItems) {
baseAmountMoney = baseAmountMoney.add(Money.from(item.amount));
}
let bonusAmountMoney = Money.zero();
for (const item of bonusItems) {
bonusAmountMoney = bonusAmountMoney.add(Money.from(item.amount));
}
let totalAmountMoney = Money.zero();
for (const item of items) {
totalAmountMoney = totalAmountMoney.add(Money.from(item.amount));
}

const summary = {
total_employees: uniqueEmployees.size,
total_base_items: baseItems.length,
total_bonus_items: bonusItems.length,
total_base_amount: baseItems
.reduce((sum, item) => sum + parseFloat(item.amount), 0)
.toFixed(7),
total_bonus_amount: bonusItems
.reduce((sum, item) => sum + parseFloat(item.amount), 0)
.toFixed(7),
total_amount: items.reduce((sum, item) => sum + parseFloat(item.amount), 0).toFixed(7),
total_base_amount: baseAmountMoney.toFixed(7),
total_bonus_amount: bonusAmountMoney.toFixed(7),
total_amount: totalAmountMoney.toFixed(7),
by_status: {
pending: items.filter((item) => item.status === 'pending').length,
completed: items.filter((item) => item.status === 'completed').length,
Expand Down
50 changes: 29 additions & 21 deletions backend/src/services/taxService.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { Pool } from 'pg';
import pool from '../config/database.js';
import { Money } from '../utils/money.js';
import { createTaxComplianceProvider } from './taxCompliance/factory.js';
import type { TaxComplianceProvider } from './taxCompliance/types.js';

Expand Down Expand Up @@ -245,35 +246,36 @@ export class TaxService {
): Promise<DeductionResult> {
const rules = await this.getRules(organizationId);
const deductions: TaxDeduction[] = [];
let totalTax = 0;
const gross = Money.from(grossAmount);
let totalTax = Money.zero();

for (const rule of rules) {
let deductedAmount = 0;
let deductedAmount = Money.zero();

if (rule.type === 'percentage') {
deductedAmount = parseFloat((grossAmount * (Number(rule.value) / 100)).toFixed(7));
deductedAmount = gross.percentage(Number(rule.value));
} else if (rule.type === 'fixed') {
deductedAmount = parseFloat(Number(rule.value).toFixed(7));
deductedAmount = Money.from(Number(rule.value));
}

deductions.push({
rule_id: rule.id,
rule_name: rule.name,
type: rule.type,
rule_value: Number(rule.value),
deducted_amount: deductedAmount,
deducted_amount: deductedAmount.toNumber(),
});

totalTax += deductedAmount;
totalTax = totalTax.add(deductedAmount);
}

const netAmount = parseFloat((grossAmount - totalTax).toFixed(7));
const netAmount = gross.sub(totalTax);

return {
gross_amount: grossAmount,
deductions,
total_tax: parseFloat(totalTax.toFixed(7)),
net_amount: Math.max(0, netAmount),
total_tax: totalTax.toNumber(),
net_amount: Math.max(0, netAmount.toNumber()),
};
}

Expand Down Expand Up @@ -340,21 +342,27 @@ export class TaxService {
const entries: TaxReportEntry[] = result.rows.map((row: any) => ({
rule_name: row.rule_name,
rule_type: row.rule_type,
rule_value: parseFloat(row.rule_value),
total_gross: parseFloat(row.total_gross),
total_tax: parseFloat(row.total_tax),
total_net: parseFloat(row.total_net),
rule_value: Money.from(row.rule_value).toNumber(),
total_gross: Money.from(row.total_gross).toNumber(),
total_tax: Money.from(row.total_tax).toNumber(),
total_net: Money.from(row.total_net).toNumber(),
transaction_count: parseInt(row.transaction_count, 10),
}));

const summary = entries.reduce(
(acc, entry) => ({
total_gross: acc.total_gross + entry.total_gross,
total_tax: acc.total_tax + entry.total_tax,
total_net: acc.total_net + entry.total_net,
}),
{ total_gross: 0, total_tax: 0, total_net: 0 }
);
let totalGross = Money.zero();
let totalTaxSum = Money.zero();
let totalNetSum = Money.zero();
for (const entry of entries) {
totalGross = totalGross.add(Money.from(entry.total_gross));
totalTaxSum = totalTaxSum.add(Money.from(entry.total_tax));
totalNetSum = totalNetSum.add(Money.from(entry.total_net));
}

const summary = {
total_gross: totalGross.toNumber(),
total_tax: totalTaxSum.toNumber(),
total_net: totalNetSum.toNumber(),
};

return {
organization_id: organizationId,
Expand Down
Loading
Loading