363 lines
12 KiB
TypeScript
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;
|
|
}
|
|
}
|