ppob-backend/src/transaction/transaction.service.ts
2021-12-13 00:49:13 +07:00

504 lines
14 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 { 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';
import { SupplierService } from '../users/supplier/supplier.service';
interface JournalEntry {
coa_id: string;
debit?: string;
credit?: string;
}
@Injectable()
export class TransactionService {
constructor(
@InjectRepository(Transactions)
private transactionRepository: Repository<Transactions>,
@InjectRepository(TransactionJournal)
private transactionJournalRepository: Repository<TransactionJournal>,
@InjectRepository(COA)
private coaRepository: Repository<COA>,
private coaService: CoaService,
private productService: ProductService,
private userService: UsersService,
private supplierService: SupplierService,
private connection: Connection,
) {}
async addPartnerSaldo(addSaldoSupplier: AddSaldoSupplier, currentUser: any) {
const supplier = await this.supplierService.findByCode(
addSaldoSupplier.supplier,
);
// GET COA
const coaBank = await this.coaService.findByName(
`${coaType[coaType.BANK]}-SYSTEM`,
);
const coaInventory = await this.coaService.findByName(
`${coaType[coaType.INVENTORY]}-${supplier.code}`,
);
const coaBudget = await this.coaService.findByName(
`${coaType[coaType.BUDGET]}-${supplier.code}`,
);
const coaContraBudget = await this.coaService.findByName(
`${coaType[coaType.CONTRA_BUDGET]}-${supplier.code}`,
);
//GET USER
const userData = await this.userService.findByUsername(
currentUser.username,
);
await this.connection.transaction(async (manager) => {
//INSERT TRANSACTION
const 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,
credit: transactionData.amount,
},
{
coa_id: coaInventory.id,
debit: transactionData.amount,
},
{
coa_id: coaBudget.id,
debit: transactionData.amount,
},
{
coa_id: coaContraBudget.id,
credit: transactionData.amount,
},
],
});
});
return true;
}
async distributeFromAdmin(
distributeTransactionDto: DistributeTransactionDto,
currentUser: any,
) {
//GET USER
const userData = await this.userService.findByUsername(
currentUser.username,
);
if (userData.roles.name != 'Admin') {
throw new HttpException(
{
statusCode: HttpStatus.NOT_ACCEPTABLE,
error: 'Roles Not Admin',
},
HttpStatus.NOT_ACCEPTABLE,
);
}
//GET Supplier
const supplier = await this.supplierService.findByCode(
distributeTransactionDto.supplier,
);
// GET COA
const coaAR = await this.coaService.findByUser(
distributeTransactionDto.destination,
coaType.ACCOUNT_RECEIVABLE,
);
const coaWallet = await this.coaService.findByUser(
distributeTransactionDto.destination,
coaType.WALLET,
);
const coaBudget = await this.coaService.findByName(
`${coaType[coaType.BUDGET]}-${supplier.code}`,
);
const coaContraBudget = await this.coaService.findByName(
`${coaType[coaType.CONTRA_BUDGET]}-${supplier.code}`,
);
await this.connection.transaction(async (manager) => {
//INSERT TRANSACTION
const 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,
},
{
coa_id: coaBudget.id,
credit: transactionData.amount,
},
{
coa_id: coaContraBudget.id,
debit: 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) => {
const 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) => {
const 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) {
const creditSum = createJournalDto.journals
.map((it) => {
return it.credit;
})
.filter((it) => {
return it;
})
.reduce((a, b) => {
return a.plus(b);
}, new Decimal(0));
const debitSum = createJournalDto.journals
.map((it) => {
return it.debit;
})
.filter((it) => {
return it;
})
.reduce((a, b) => {
return a.plus(b);
}, new Decimal(0));
const coaIds = uniq(
createJournalDto.journals.map((it) => {
return it.coa_id;
}),
);
if (!creditSum.equals(debitSum)) {
throw new Error(`credit and debit doesn't match`);
}
const coas = await this.coaRepository.findByIds(coaIds);
const transaction = createJournalDto.transaction;
await Promise.all(
createJournalDto.journals.map((journal) => {
const coa = coas.find((it) => {
return 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_head = transaction;
return createJournalDto.transactionalEntityManager.save(journalEntry);
}),
);
await Promise.all(
coaIds.map((coaId) => {
const journalPerCoa = createJournalDto.journals.filter((journal) => {
return journal.coa_id == coaId;
});
const creditSum = journalPerCoa
.map((it) => {
return it.credit;
})
.filter((it) => {
return it;
})
.reduce((a, b) => {
return a.plus(b);
}, new Decimal(0));
const debitSum = journalPerCoa
.map((it) => {
return it.debit;
})
.filter((it) => {
return it;
})
.reduce((a, b) => {
return 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: () => {
return `amount + ${diff.toString()}`;
},
})
.where('id = :id', { id: coa.id })
.execute();
}),
);
return transaction;
}
}