ppob-backend/src/transaction/transaction.service.ts

363 lines
12 KiB
TypeScript

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<Transactions>,
@InjectRepository(TransactionType)
private transactionTypeRepository: Repository<TransactionType>,
@InjectRepository(TransactionJournal)
private transactionJournalRepository: Repository<TransactionJournal>,
@InjectRepository(COA)
private coaRepository: Repository<COA>,
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;
}
}