import { HttpException, HttpStatus, Injectable } from '@nestjs/common'; import { DistributeTransactionDto } from './dto/distribute-transaction.dto'; import { OrderTransactionDto } from './dto/order-transaction.dto'; import { InjectRepository } from '@nestjs/typeorm'; import { Transactions } from './entities/transactions.entity'; import { Connection, EntityManager, Repository } from 'typeorm'; import { COA } from './entities/coa.entity'; import { TransactionType } from './entities/transaction-type.entity'; import { TransactionJournal } from './entities/transaction-journal.entity'; import { CoaService } from './coa.service'; import * as uuid from "uuid"; import { uniq } from "lodash"; import {Decimal} from 'decimal.js'; import { balanceType, coaType, statusTransaction, typeTransaction, } from '../helper/enum-list'; import { ProductService } from '../product/product.service'; import * as irsService from '../helper/irs-service'; import { CreateJournalDto } from './dto/create-journal.dto'; import { UsersService } from 'src/users/users.service'; import { AddSaldoSupplier } from './dto/add-saldo-supplier.dto'; interface JournalEntry { coa_id: string; debit?: string; credit?: string; } @Injectable() export class TransactionService { constructor( @InjectRepository(Transactions) private transactionRepository: Repository, @InjectRepository(TransactionType) private transactionTypeRepository: Repository, @InjectRepository(TransactionJournal) private transactionJournalRepository: Repository, @InjectRepository(COA) private coaRepository: Repository, private coaService: CoaService, private productService: ProductService, private userService: UsersService, private connection: Connection, ) {} async addIRSWallet(addSaldoSupplier: AddSaldoSupplier,currentUser:any) { // GET COA const coaBank = await this.coaService.findByName( coaType[coaType.BANK]+'-SYSTEM', ); const coaInventory = await this.coaService.findByName( coaType[coaType.INVENTORY]+'-'+addSaldoSupplier.supplier, ); //GET USER const userData = await this.userService.findByUsername(currentUser.username); await this.connection.transaction(async (manager) => { //INSERT TRANSACTION let transactionData = new Transactions(); transactionData.id = uuid.v4(); transactionData.amount = addSaldoSupplier.amount, transactionData.user = userData.id, transactionData.status = statusTransaction.SUCCESS, transactionData.type = typeTransaction.DEPOSIT_IRS, await manager.insert(Transactions, transactionData); await this.accountingTransaction({ createTransaction: false, transactionalEntityManager:manager, transaction: transactionData, amount: transactionData.amount, journals: [{ coa_id: coaBank.id, debit: transactionData.amount }, { coa_id: coaInventory.id, credit: transactionData.amount }] }); }); return true; } async distributeFromAdmin(distributeTransactionDto: DistributeTransactionDto,currentUser:any) { // GET COA const coaAR = await this.coaService.findByUser( distributeTransactionDto.destination, coaType.ACCOUNT_RECEIVABLE, ); const coaWallet = await this.coaService.findByUser( distributeTransactionDto.destination, coaType.WALLET, ); //GET USER const userData = await this.userService.findByUsername(currentUser.username); await this.connection.transaction(async (manager) => { //INSERT TRANSACTION let transactionData = new Transactions(); transactionData.id = uuid.v4(); transactionData.amount = distributeTransactionDto.amount, transactionData.user = userData.id, transactionData.status = statusTransaction.SUCCESS, transactionData.type = typeTransaction.DISTRIBUTION, await manager.insert(Transactions, transactionData); await this.accountingTransaction({ createTransaction: false, transactionalEntityManager:manager, transaction: transactionData, amount: transactionData.amount, journals: [{ coa_id: coaAR.id, debit: transactionData.amount }, { coa_id: coaWallet.id, credit: transactionData.amount }] }); }); return true; } async distributeDeposit(distributeTransactionDto: DistributeTransactionDto,currentUser:any) { //GET USER const userData = await this.userService.findByUsername(currentUser.username); // GET COA const coaSenderWallet = await this.coaService.findByUser( userData.id, coaType.WALLET, ); const coaAP = await this.coaService.findByUserWithRelated( distributeTransactionDto.destination, userData.id, coaType.ACCOUNT_PAYABLE, ); const coaReceiverWallet = await this.coaService.findByUser( distributeTransactionDto.destination, coaType.WALLET, ); const coaAR = await this.coaService.findByUserWithRelated( distributeTransactionDto.destination, userData.id, coaType.ACCOUNT_RECEIVABLE, ); await this.connection.transaction(async (manager) => { let transactionData = new Transactions(); transactionData.id = uuid.v4(); transactionData.amount = distributeTransactionDto.amount, transactionData.user = userData.id, transactionData.status = statusTransaction.SUCCESS, transactionData.type = typeTransaction.DISTRIBUTION, await manager.insert(Transactions, transactionData); await this.accountingTransaction({ createTransaction: false, transactionalEntityManager:manager, transaction: transactionData, amount: transactionData.amount, journals: [{ coa_id: coaSenderWallet.id, debit: transactionData.amount }, { coa_id: coaReceiverWallet.id, credit: transactionData.amount }, { coa_id: coaAR.id, debit: transactionData.amount }, { coa_id: coaAP.id, credit: transactionData.amount }] }); }); return true; } async orderTransaction(orderTransactionDto: OrderTransactionDto,currentUser:any) { //GET PRODUCT const product = await this.productService.findOne( orderTransactionDto.productCode, ); //GET USER const userData = await this.userService.findByUsername(currentUser.username); let supervisorData = []; if(userData.superior != null){ supervisorData.push(await this.userService.findByUsername(currentUser.username)); if(supervisorData[0].superior != null){ supervisorData.push(await this.userService.findByUsername(currentUser.username)); if(supervisorData[0].superior != null){ supervisorData.push(await this.userService.findByUsername(currentUser.username)); } } } //GET COA const coaAccount = await this.coaService.findByUser( userData.id, coaType.WALLET, ); const coaInventory = await this.coaService.findByName( coaType[coaType.INVENTORY]+'-IRS', ); const coaCostOfSales = await this.coaService.findByName( coaType[coaType.COST_OF_SALES]+'-SYSTEM', ); const coaSales = await this.coaService.findByName( coaType[coaType.SALES]+'-SYSTEM', ); const coaExpense = await this.coaService.findByName( coaType[coaType.EXPENSE]+'-SYSTEM', ); supervisorData = supervisorData.map(async it =>{ const coaAccount = await this.coaService.findByUser( it.id, coaType.WALLET, ); return { coa_id: coaAccount.id, credit: 0 } }) if (coaAccount.amount <= product.price) { throw new HttpException( { statusCode: HttpStatus.INTERNAL_SERVER_ERROR, error: `Transaction Failed because saldo not enough`, }, HttpStatus.INTERNAL_SERVER_ERROR, ); } try { await this.connection.transaction(async (manager) => { let transactionData = new Transactions(); transactionData.id = uuid.v4(); transactionData.amount = product.price, transactionData.user = userData.id, transactionData.status = statusTransaction.SUCCESS, transactionData.type = typeTransaction.DISTRIBUTION, await manager.insert(Transactions, transactionData); await this.accountingTransaction({ createTransaction: false, transactionalEntityManager:manager, transaction: transactionData, amount: transactionData.amount, journals: [{ coa_id: coaInventory.id, credit: product.basePrice }, { coa_id: coaCostOfSales.id, debit: product.basePrice }, { coa_id: coaAccount.id, debit: product.price }, { coa_id: coaSales.id, credit: product.price },{ coa_id: coaExpense.id, credit: 0 }].concat(supervisorData) }); }); } catch (e) { throw e; } return true; } async accountingTransaction(createJournalDto: CreateJournalDto ) { let creditSum = createJournalDto.journals.map(it => it.credit).filter(it => it).reduce((a, b) => a.plus(b), new Decimal(0)); let debitSum = createJournalDto.journals.map(it => it.debit).filter(it => it).reduce((a, b) => a.plus(b), new Decimal(0)); let coaIds = uniq(createJournalDto.journals.map(it => it.coa_id)); if (!creditSum.equals(debitSum)) { throw new Error(`credit and debit doesn't match`); } const coas = await this.coaRepository.findByIds(coaIds); console.log("berhasil") const transaction = createJournalDto.transaction await Promise.all(createJournalDto.journals.map(journal => { const coa = coas.find(it => it.id === journal.coa_id); if (!coa) { throw new Error(`coa ${journal.coa_id} not found`); } const journalEntry = new TransactionJournal(); journalEntry.coa = coa; journalEntry.type = journal.debit ? balanceType.DEBIT : balanceType.CREDIT; journalEntry.amount = (journal.debit) ? journal.debit : journal.credit; journalEntry.transaction = transaction; return this.transactionJournalRepository.save(journalEntry); })); await Promise.all(coaIds.map(coaId => { let journalPerCoa = createJournalDto.journals.filter(journal => journal.coa_id == coaId); let creditSum = journalPerCoa.map(it => it.credit).filter(it => it).reduce((a, b) => a.plus(b), new Decimal(0)); let debitSum = journalPerCoa.map(it => it.debit).filter(it => it).reduce((a, b) => a.plus(b), new Decimal(0)); let coa = coas.find(it => it.id.toLowerCase() === coaId.toLowerCase()); let balance = new Decimal(coa.amount); if (coa.balanceType === balanceType.DEBIT) { balance = balance.plus(debitSum.minus(creditSum)); } else if (coa.balanceType === balanceType.CREDIT) { balance = balance.plus(creditSum.minus(debitSum)); } const diff = balance.minus(new Decimal(coa.amount)); return createJournalDto.transactionalEntityManager .createQueryBuilder() .update(COA) .set({ amount: () => "amount + " + diff.toString() }) .where("id = :id", { id: coa.id }) .execute(); })); return transaction; } }