Skip to content
Open
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
11 changes: 8 additions & 3 deletions nexus/backend/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,12 @@
"audit:verify-chain": "ts-node -r tsconfig-paths/register scripts/verify-audit-chain.ts"
},
"dependencies": {
"@nestjs/bullmq": "11.0.4",
"@nestjs/cache-manager": "^3.1.2",
"@nestjs/config": "^4.0.4",
"@nestjs/jwt": "^11.0.2",
"@nestjs/platform-socket.io": "^11.1.19",
"@nestjs/websockets": "^11.1.19",
"@nexus/shared": "*",
"@opentelemetry/api": "^1.9.0",
"@opentelemetry/auto-instrumentations-node": "^0.70.1",
Expand All @@ -43,10 +49,10 @@
"@sentry/profiling-node": "^10.42.0",
"@types/multer": "^2.0.0",
"@types/streamifier": "^0.1.2",
"ajv": "^8.18.0",
"axios": "^1.13.5",
"bcrypt": "^6.0.0",
"bullmq": "5.70.1",
"@nestjs/bullmq": "11.0.4",
"cache-manager": "^7.2.8",
"class-transformer": "^0.5.1",
"class-validator": "^0.14.1",
Expand All @@ -70,8 +76,7 @@
"streamifier": "^0.1.1",
"swagger-ui-express": "^5.0.1",
"winston": "^3.19.0",
"zod": "^4.3.6",
"ajv": "^8.18.0"
"zod": "^4.3.6"
},
"devDependencies": {
"@nestjs/cli": "^11.0.16",
Expand Down
4 changes: 1 addition & 3 deletions nexus/backend/src/accounting/services/brs.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -84,9 +84,7 @@ export class BrsService {
const possibleMatches = allTransactions.filter((t) => {
if (usedTransactionIds.has(t.id)) return false;
// strict match on precise amount
if (
!new Decimal(t.amount as any).equals(new Decimal(line.amount as any))
)
if (!new Decimal(t.amount).equals(new Decimal(line.amount)))
return false;
const txTime = new Date(t.date).getTime();
return Math.abs(txTime - lineTime) <= threeDaysMs;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -173,7 +173,7 @@ export class CreditNoteService {
date: creditNote.date.toISOString(),
description: `Credit Note: ${noteNumber} (Return for ${invoiceId || 'Direct'})`,
reference: creditNote.id,
transactions: journalTransactions as any,
transactions: journalTransactions,
},
tx,
);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -173,7 +173,7 @@ export class DebitNoteService {
date: debitNote.date.toISOString(),
description: `Debit Note: ${noteNumber} (Return for PO ${purchaseOrderId || 'Direct'})`,
reference: debitNote.id,
transactions: journalTransactions as any,
transactions: journalTransactions,
},
tx,
);
Expand Down
6 changes: 3 additions & 3 deletions nexus/backend/src/accounting/services/fixed-asset.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -183,14 +183,14 @@ export class FixedAssetService {

// 1. Calculate Monthly Depreciation (Straight Line Method)
// Formula: (Purchase Value - Salvage Value) / Useful Life (months)
const cost = new Decimal(asset.purchaseValue as any);
const salvage = new Decimal(asset.salvageValue as any);
const cost = new Decimal(asset.purchaseValue);
const salvage = new Decimal(asset.salvageValue);
const life = new Decimal(asset.usefulLife);

const monthlyDep = this.ledger.round2(cost.sub(salvage).div(life));

// 2. Check if we've already reached salvage value
const currentAccDep = new Decimal(asset.accumulatedDepreciation as any);
const currentAccDep = new Decimal(asset.accumulatedDepreciation);
const remainingLife = cost.sub(salvage).sub(currentAccDep);

if (remainingLife.lessThanOrEqualTo(0)) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -236,7 +236,7 @@ export class Gstr1ExportService {
const nilKey = isInterState ? 'INTRB2B' : 'INTRB2C';
if (!nilMap[nilKey]) {
nilMap[nilKey] = {
sply_ty: nilKey as any,
sply_ty: nilKey,
nil_amt: 0,
expt_amt: 0,
ngsup_amt: 0,
Expand Down
8 changes: 3 additions & 5 deletions nexus/backend/src/accounting/services/ledger.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -95,7 +95,7 @@ export class LedgerService {
const amount = new Decimal(openingBalance);
const isDebitNormal = (
[AccountType.Asset, AccountType.Expense] as string[]
).includes(account.type as string);
).includes(account.type);
const isDebitEntry = amount.isPositive(); // +ve amount as opening balance is treated as 'Normal' for that account type

// For Assets: +ve is Debit
Expand All @@ -117,15 +117,13 @@ export class LedgerService {
transactions: [
{
accountId: account.id,
type: type as TransactionType,
type: type,
amount: amount.abs().toNumber(),
description: 'Opening Balance Entry',
},
{
accountId: obAcc.id,
type: (type === 'Debit'
? 'Credit'
: 'Debit') as TransactionType,
type: type === 'Debit' ? 'Credit' : 'Debit',
amount: amount.abs().toNumber(),
description: 'Opening Balance Entry',
},
Expand Down
4 changes: 2 additions & 2 deletions nexus/backend/src/accounting/services/onboarding.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -98,13 +98,13 @@ export class OnboardingService {
transactions: [
{
accountId: account.id,
type: entryType as any,
type: entryType,
amount: amount.abs().toNumber(),
description: 'Trial Balance Migration',
},
{
accountId: obAcc.id,
type: (entryType === 'Debit' ? 'Credit' : 'Debit') as any,
type: entryType === 'Debit' ? 'Credit' : 'Debit',
amount: amount.abs().toNumber(),
description: 'Offsetting Equity',
},
Expand Down
2 changes: 1 addition & 1 deletion nexus/backend/src/accounting/services/payment.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -341,7 +341,7 @@ export class PaymentService {
);
ledgerTransactions.push({
accountId: tdsPayableAccount.id,
type: 'Credit' as any,
type: 'Credit',
amount: tdsAmount.toNumber(),
description: `TDS Deducted (${data.tdsSection})`,
});
Expand Down
10 changes: 4 additions & 6 deletions nexus/backend/src/accounting/services/tally-export.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -786,23 +786,21 @@ export class TallyService {
chunk += ` <ISDEEMEDPOSITIVE>YES</ISDEEMEDPOSITIVE>\n`;
chunk += ` <HSNCODE>${this.escapeXml(wo.bom.product.hsnCode || '')}</HSNCODE>\n`;
chunk += ` <RATE>${wo.bom.product.costPrice}</RATE>\n`;
chunk += ` <AMOUNT>-${new Decimal(wo.producedQuantity as any).mul(new Decimal(wo.bom.product.costPrice as any)).toFixed(2)}</AMOUNT>\n`;
chunk += ` <AMOUNT>-${new Decimal(wo.producedQuantity).mul(new Decimal(wo.bom.product.costPrice)).toFixed(2)}</AMOUNT>\n`;
chunk += ` <ACTUALQTY>${wo.producedQuantity} Nos</ACTUALQTY>\n`;
chunk += ` <BILLEDQTY>${wo.producedQuantity} Nos</BILLEDQTY>\n`;
chunk += ` </INVENTORYENTRIES.LIST>\n`;

// CONSUMPTION (Raw Materials)
for (const item of wo.bom.items) {
const consumedQty = new Decimal(item.quantity as any).mul(
new Decimal(wo.producedQuantity as any).add(
new Decimal(wo.scrapQuantity as any),
),
const consumedQty = new Decimal(item.quantity).mul(
new Decimal(wo.producedQuantity).add(new Decimal(wo.scrapQuantity)),
);
chunk += ` <INVENTORYENTRIES.LIST>\n`;
chunk += ` <STOCKITEMNAME>${this.escapeXml(item.product.name)}</STOCKITEMNAME>\n`;
chunk += ` <ISDEEMEDPOSITIVE>NO</ISDEEMEDPOSITIVE>\n`;
chunk += ` <HSNCODE>${this.escapeXml(item.product.hsnCode || '')}</HSNCODE>\n`;
chunk += ` <AMOUNT>${consumedQty.mul(new Decimal(item.product.costPrice as any)).toFixed(2)}</AMOUNT>\n`;
chunk += ` <AMOUNT>${consumedQty.mul(new Decimal(item.product.costPrice)).toFixed(2)}</AMOUNT>\n`;
chunk += ` <ACTUALQTY>${consumedQty} Nos</ACTUALQTY>\n <BILLEDQTY>${consumedQty} Nos</BILLEDQTY>\n`;
chunk += ` </INVENTORYENTRIES.LIST>\n`;
}
Expand Down
6 changes: 3 additions & 3 deletions nexus/backend/src/auth/auth.controller.ts
Original file line number Diff line number Diff line change
Expand Up @@ -61,7 +61,7 @@ export class AuthController {
const result = await this.authService.login(
loginDto,
'WEB',
req.ip || (req.get('X-Forwarded-For') as string),
req.ip || req.get('X-Forwarded-For'),
);
const authResult = result as AuthResponse;
if (authResult && authResult.accessToken && authResult.refreshToken) {
Expand All @@ -77,7 +77,7 @@ export class AuthController {
return this.authService.login(
loginDto,
'MOBILE',
req.ip || (req.get('X-Forwarded-For') as string),
req.ip || req.get('X-Forwarded-For'),
);
}

Expand All @@ -92,7 +92,7 @@ export class AuthController {
): Promise<AuthResponse> {
const result = await this.authService.adminLogin(
loginDto,
req.ip || (req.get('X-Forwarded-For') as string),
req.ip || req.get('X-Forwarded-For'),
);
const authResult = result as AuthResponse;
if (authResult && authResult.accessToken && authResult.refreshToken) {
Expand Down
107 changes: 107 additions & 0 deletions nexus/backend/src/auth/auth.service.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,107 @@
import { Test, TestingModule } from '@nestjs/testing';
import { AuthService } from './auth.service';
import { PrismaService } from '../prisma/prisma.service';
import { JwtService } from '@nestjs/jwt';
import { ConfigService } from '@nestjs/config';
import { AccountingService } from '../accounting/accounting.service';
import { TenantContextService } from '../prisma/tenant-context.service';
import { MailService } from '../system/services/mail.service';
import { GoogleAuthService } from './google-auth.service';
import { LoggingService } from '../common/services/logging.service';
import { AnomalyAlertService } from '../common/services/anomaly-alert.service';
import { MfaCryptoService } from './mfa-crypto.service';
import { ConflictException } from '@nestjs/common';
import * as bcrypt from 'bcrypt';

jest.mock('bcrypt', () => ({
genSalt: jest.fn().mockResolvedValue('salt'),
hash: jest.fn().mockResolvedValue('hash'),
}));

describe('AuthService', () => {
let service: AuthService;
let prismaService: any;
let loggingService: any;

beforeEach(async () => {
prismaService = {
user: {
findUnique: jest.fn(),
},
$transaction: jest.fn(),
};

loggingService = {
log: jest.fn(),
};

const module: TestingModule = await Test.createTestingModule({
providers: [
AuthService,
{ provide: PrismaService, useValue: prismaService },
{ provide: JwtService, useValue: {} },
{ provide: ConfigService, useValue: {} },
{ provide: AccountingService, useValue: {} },
{ provide: TenantContextService, useValue: {} },
{ provide: MailService, useValue: {} },
{ provide: GoogleAuthService, useValue: {} },
{ provide: LoggingService, useValue: loggingService },
{ provide: AnomalyAlertService, useValue: {} },
{ provide: MfaCryptoService, useValue: {} },
],
}).compile();

service = module.get<AuthService>(AuthService);
});

describe('register', () => {
it('should successfully register a user even if telemetry logging fails', async () => {
// Setup mock implementation
prismaService.user.findUnique.mockResolvedValueOnce(null); // No existing user

const mockUser = { id: 'user-1', email: 'test@test.com' };
const mockTenant = { id: 'tenant-1', type: 'Retail' };

// We need to mock $transaction to actually execute the callback
prismaService.$transaction.mockImplementation(async (callback) => {
const tx = {
user: { create: jest.fn().mockResolvedValue(mockUser) },
tenant: {
findFirst: jest.fn().mockResolvedValue(null),
create: jest.fn().mockResolvedValue(mockTenant)
},
tenantUser: { create: jest.fn() },
};
return callback(tx);
});

// Mock generateAuthResponse
service['generateAuthResponse'] = jest.fn().mockResolvedValue({ token: 'test-token' });

// Mock accountingService
(service as any).accountingService = {
initializeTenantAccounts: jest.fn().mockResolvedValue(undefined)
};

// Set up the telemetry logging to throw an error
loggingService.log.mockRejectedValueOnce(new Error('Telemetry service offline'));

// We should spy on logger to ensure we logged the warning
const loggerWarnSpy = jest.spyOn(service['logger'], 'warn').mockImplementation();

const result = await service.register({
email: 'test@test.com',
password: 'password123',
fullName: 'Test User',
tenantName: 'Test Tenant',
companyType: 'Retail'
});

expect(result).toBeDefined();
expect(loggingService.log).toHaveBeenCalled();
expect(loggerWarnSpy).toHaveBeenCalledWith(
expect.stringContaining('[AUTH_REGISTER] Telemetry logging failed')
);
});
});
});
12 changes: 6 additions & 6 deletions nexus/backend/src/auth/auth.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -124,7 +124,7 @@ export class AuthService {
await this.accountingService.initializeTenantAccounts(
tenant.id,
tx,
tenantType as string,
tenantType,
);

return tenant;
Expand Down Expand Up @@ -240,7 +240,7 @@ export class AuthService {
await this.accountingService.initializeTenantAccounts(
tenant.id,
tx,
tenant.type as string,
tenant.type,
);

// Telemetry (Phase 4)
Expand Down Expand Up @@ -522,7 +522,7 @@ export class AuthService {
const isNewUser = !user;

if (user) {
await this.checkBruteForce(user as User);
await this.checkBruteForce(user);
// Identity Safety Check (Rule E)
if (user.authProvider === AuthProvider.Email) {
throw new ConflictException(
Expand All @@ -538,7 +538,7 @@ export class AuthService {
}
} else {
// Create new Google User (Rule C: No tenant created yet, must onboard)
user = (await this.prisma.user.create({
user = await this.prisma.user.create({
data: {
email: googleUser.email!,
fullName: googleUser.fullName,
Expand All @@ -547,10 +547,10 @@ export class AuthService {
providerId: googleUser.providerId,
},
include: { memberships: { include: { tenant: true } } },
})) as any;
});
}

const finalUser = user! as any;
const finalUser = user as any;

// Fail-safe (SYS-011): Sync SuperAdmin status if config admin logs in via social flow
const configAdminEmail = this.config
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ describe('MobileWhitelistGuard - Kill Switch (MOB-009)', () => {

beforeEach(() => {
reflector = new Reflector();
logging = { log: jest.fn() } as any;
logging = { log: jest.fn() };
guard = new MobileWhitelistGuard(reflector, logging as any);
});

Expand Down
23 changes: 23 additions & 0 deletions nexus/backend/src/config/cors.config.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
export const getAllowedOrigins = (): (string | RegExp)[] => {
const allowedOrigins: (string | RegExp)[] = [
'https://klypso.in',
'https://www.klypso.in',
/\.klypso\.in$/, // Trust all klypso.in subdomains
'http://localhost:3000',
'http://localhost:5173', // Vite dev server
/^http:\/\/localhost:\d+$/, // Desktop app dynamic ports
/^http:\/\/127\.0\.0\.1:\d+$/, // Desktop app dynamic ports
];

const extraOrigins = [
process.env.NEXUS_FRONTEND_URL, // Primary -- set this in Render env vars
process.env.KLYPSO_FRONTEND_URL, // Legacy key (backwards compat)
process.env.CORS_ORIGIN, // Escape hatch for additional origins
];

for (const o of extraOrigins) {
if (o && !allowedOrigins.includes(o)) allowedOrigins.push(o);
}

return allowedOrigins;
};
Loading