import { HttpException, HttpStatus, Injectable, Logger } 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 { Between, Connection, EntityNotFoundError, 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 { 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'; import { ProductHistoryPriceService } from '../product/history-price/history-price.service'; import { CommissionService } from '../configurable/commission.service'; import { DepositReturnDto } from './dto/deposit_return.dto'; import { UserDetail } from '../users/entities/user_detail.entity'; import { doTransaction } from '../helper/irs-api'; import { ProductHistoryPrice } from '../product/entities/product-history-price.entity'; import axios from 'axios'; import { CheckBillHistory } from './entities/check-bill-history.entity'; @Injectable() export class TransactionService { private readonly logger = new Logger(TransactionService.name); constructor( @InjectRepository(Transactions) private transactionRepository: Repository, @InjectRepository(TransactionJournal) private transactionJournalRepository: Repository, @InjectRepository(COA) private coaRepository: Repository, @InjectRepository(CheckBillHistory) private checkBillHistoryRepository: Repository, private coaService: CoaService, private productService: ProductService, private productHistoryPriceService: ProductHistoryPriceService, private userService: UsersService, private commissionService: CommissionService, private supplierService: SupplierService, private connection: Connection, ) {} async addSupplierSaldo(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_SUPPLIER; 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, ); const supplier = await this.supplierService.findByActive(); try { if (userData.roles.name != 'Admin') { throw new HttpException( { statusCode: HttpStatus.NOT_ACCEPTABLE, error: 'Roles Not Admin', }, HttpStatus.NOT_ACCEPTABLE, ); } // GET COA const coaBudget = await this.coaService.findByName( `${coaType[coaType.BUDGET]}-${supplier.code}`, ); if (coaBudget.amount < distributeTransactionDto.amount) { throw new HttpException( { statusCode: HttpStatus.INTERNAL_SERVER_ERROR, error: `Transaction Failed because saldo not enough`, }, HttpStatus.INTERNAL_SERVER_ERROR, ); } const coaContraBudget = await this.coaService.findByName( `${coaType[coaType.CONTRA_BUDGET]}-${supplier.code}`, ); const coaAR = await this.coaService.findByTwoUser( distributeTransactionDto.destination, currentUser.userId, coaType.ACCOUNT_RECEIVABLE, ); const coaWallet = await this.coaService.findByUser( distributeTransactionDto.destination, coaType.WALLET, ); 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.user_destination = distributeTransactionDto.destination; 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; } catch (e) { throw e; } } 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, ); if (coaSenderWallet.amount < distributeTransactionDto.amount) { throw new HttpException( { statusCode: HttpStatus.INTERNAL_SERVER_ERROR, error: `Transaction Failed because saldo not enough`, }, HttpStatus.INTERNAL_SERVER_ERROR, ); } await this.connection.transaction(async (manager) => { const transactionData = new Transactions(); transactionData.id = uuid.v4(); transactionData.amount = distributeTransactionDto.amount; transactionData.user = userData.id; transactionData.user_destination = distributeTransactionDto.destination; 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, ) { const trxId = Array(6) .fill(null) .map(() => { return Math.round(Math.random() * 16).toString(16); }) .join(''); //GET USER const userData = await this.userService.findByUsername( currentUser.username, ); //GET PRODUCT const product = await this.productService.findOne( orderTransactionDto.productCode, 'prepaid' ); const product_price = await this.productHistoryPriceService.findOne( product.id, userData.partner?.id, ); let supervisorData = []; let profit = product_price.mark_up_price; //GET COA const coaAccount = await this.coaService.findByUser( userData.id, coaType.WALLET, ); const coaInventory = await this.coaService.findByName( `${coaType[coaType.INVENTORY]}-${product.supplier.code}`, ); const coaCostOfSales = await this.coaService.findByName( `${coaType[coaType.COST_OF_SALES]}-${product.supplier.code}`, ); const coaSales = await this.coaService.findByName( `${coaType[coaType.SALES]}-SYSTEM`, ); const coaExpense = await this.coaService.findByName( `${coaType[coaType.EXPENSE]}-SYSTEM`, ); if (!userData.partner) { //GET SALES supervisorData = await this.calculateCommission( supervisorData, profit, userData, ); profit = supervisorData .map((item) => { return item.credit; }) .reduce((prev, curr) => { return prev + curr; }, 0); supervisorData = supervisorData.concat([ { coa_id: coaExpense.id, debit: profit, }, ]); } if (coaAccount.amount < product_price.mark_up_price + product_price.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.mark_up_price + product_price.price; transactionData.user = userData.id; transactionData.status = statusTransaction.SUCCESS; transactionData.type = typeTransaction.ORDER; transactionData.product_price = product_price; transactionData.destination = orderTransactionDto.destination; transactionData.partner_trx_id = orderTransactionDto.trx_id; transactionData.supplier_trx_id = trxId; await manager.insert(Transactions, transactionData); await this.accountingTransaction({ createTransaction: false, transactionalEntityManager: manager, transaction: transactionData, amount: transactionData.amount, journals: [ { coa_id: coaInventory.id, credit: product_price.price, }, { coa_id: coaCostOfSales.id, debit: product_price.price, }, { coa_id: coaAccount.id, debit: product_price.mark_up_price + product_price.price, }, { // eslint-disable-next-line camelcase coa_id: coaSales.id, credit: product_price.mark_up_price + product_price.price, }, ].concat(supervisorData), }); }); } catch (e) { throw e; } return { trx_id: trxId, client_trx_id: orderTransactionDto.trx_id, product: orderTransactionDto.productCode, amount: product_price.mark_up_price + product_price.price, status: statusTransaction[statusTransaction.SUCCESS], }; } async orderTransactionProd( orderTransactionDto: OrderTransactionDto, currentUser: any, ) { let status; const amount = 0; //GET USER DATA const userData = await this.userService.findByUsername( currentUser.username, ); //GET PRODUCT AND PRICE const product = await this.productService.findOne( orderTransactionDto.productCode, 'prepaid', ); const supplier = await this.supplierService.findByCode( product.supplier.code, ); let product_price = await this.productHistoryPriceService.findOne( product.id, userData.partner?.id, ); //GET COA const coaAccount = await this.coaService.findByUser( userData.id, coaType.WALLET, ); const coaInventory = await this.coaService.findByName( `${coaType[coaType.INVENTORY]}-${product.supplier.code}`, ); const coaCostOfSales = await this.coaService.findByName( `${coaType[coaType.COST_OF_SALES]}-${product.supplier.code}`, ); const coaSales = await this.coaService.findByName( `${coaType[coaType.SALES]}-SYSTEM`, ); if (orderTransactionDto.bill_trx_id) { try { const billId = await this.checkBillHistoryRepository.findOneOrFail({ where: { trx_id: orderTransactionDto.bill_trx_id }, }); product_price.price = billId.amount; } catch (e) { if (e instanceof EntityNotFoundError) { throw new HttpException( { statusCode: HttpStatus.NOT_FOUND, error: 'Bill not found', }, HttpStatus.NOT_FOUND, ); } else { throw e; } } } if (coaAccount.amount < product_price.mark_up_price + product_price.price) { throw new HttpException( { statusCode: HttpStatus.INTERNAL_SERVER_ERROR, error: `Transaction Failed because saldo not enough`, }, HttpStatus.INTERNAL_SERVER_ERROR, ); } //HIT API SUPPLIER const trxId = Array(6) .fill(null) .map(() => { return Math.round(Math.random() * 16).toString(16); }) .join(''); let hitSupplier = await doTransaction( orderTransactionDto.productCode, orderTransactionDto.destination, trxId, supplier, ); // let hitSupplier; if (supplier.code != 'IRS') { const parsingResponse = hitSupplier.split(' '); console.log const newHitSupplier = { success: hitSupplier.includes('diproses'), harga: parseInt( parsingResponse[parsingResponse.length - 2].replace(/\./g,' '), ), msg: hitSupplier, }; hitSupplier = newHitSupplier; if(orderTransactionDto.bill_trx_id !== null){ hitSupplier.harga = product_price.price; } } // const hitSupplier = { // harga: 2000, // success: true, // msg: 'Berhasil', // }; this.logger.log({ responseAPISupplier: hitSupplier, }); let costInventory = product_price.price; if (hitSupplier.harga != product_price.price) { product_price.endDate = new Date(); costInventory = hitSupplier.harga; const listActivePrice = await this.productHistoryPriceService.getAllActivePriceByProduct( product.id, ); await this.productHistoryPriceService.updateEndDate(product.id); listActivePrice.map(async (x) => { const newProductPrice = new ProductHistoryPrice(); newProductPrice.id = uuid.v4(); newProductPrice.type = x.type; newProductPrice.price = hitSupplier.harga; newProductPrice.mark_up_price = x.mark_up_price; newProductPrice.startDate = new Date(); newProductPrice.product = product; newProductPrice.partner = x.partner; await this.productHistoryPriceService.create(newProductPrice); product_price = newProductPrice; }); } try { //TRANSACTION DATA await this.connection.transaction(async (manager) => { const transactionData = new Transactions(); transactionData.id = uuid.v4(); transactionData.amount = product_price.mark_up_price + product_price.price; transactionData.user = userData.id; transactionData.type = typeTransaction.ORDER; transactionData.product_price = product_price; transactionData.destination = orderTransactionDto.destination; transactionData.partner_trx_id = orderTransactionDto.trx_id; transactionData.supplier_trx_id = trxId; transactionData.check_bill = orderTransactionDto.bill_trx_id; if (!hitSupplier.success) { transactionData.status = statusTransaction.FAILED; status = statusTransaction[transactionData.status]; await this.transactionRepository.insert(transactionData); throw new HttpException( { statusCode: HttpStatus.INTERNAL_SERVER_ERROR, error: hitSupplier.msg, }, HttpStatus.INTERNAL_SERVER_ERROR, ); } else { transactionData.status = statusTransaction.PENDING; status = statusTransaction[transactionData.status]; } await manager.insert(Transactions, transactionData); await this.accountingTransaction({ createTransaction: false, transactionalEntityManager: manager, transaction: transactionData, amount: transactionData.amount, journals: [ { coa_id: coaInventory.id, credit: costInventory, }, { coa_id: coaCostOfSales.id, debit: costInventory, }, { coa_id: coaAccount.id, debit: product_price.mark_up_price + costInventory, }, { // eslint-disable-next-line camelcase coa_id: coaSales.id, credit: product_price.mark_up_price + costInventory, }, ], }); }); } catch (e) { throw e; } return { trx_id: trxId, client_trx_id: orderTransactionDto.trx_id, product: orderTransactionDto.productCode, amount: product_price.mark_up_price + product_price.price, status: status, }; } async orderTransactionBillProd( orderTransactionDto: OrderTransactionDto, currentUser: any, ) { let status; const amount = 0; //GET USER DATA const userData = await this.userService.findByUsername( currentUser.username, ); //GET PRODUCT AND PRICE const product = await this.productService.findOne( orderTransactionDto.productCode, 'postpaid', ); const supplier = await this.supplierService.findByCode( product.supplier.code, ); let product_price = await this.productHistoryPriceService.findOne( product.id, userData.partner?.id, ); //GET COA const coaAccount = await this.coaService.findByUser( userData.id, coaType.WALLET, ); const coaInventory = await this.coaService.findByName( `${coaType[coaType.INVENTORY]}-${product.supplier.code}`, ); const coaCostOfSales = await this.coaService.findByName( `${coaType[coaType.COST_OF_SALES]}-${product.supplier.code}`, ); const coaSales = await this.coaService.findByName( `${coaType[coaType.SALES]}-SYSTEM`, ); if (coaAccount.amount < product_price.mark_up_price + product_price.price) { throw new HttpException( { statusCode: HttpStatus.INTERNAL_SERVER_ERROR, error: `Transaction Failed because saldo not enough`, }, HttpStatus.INTERNAL_SERVER_ERROR, ); } //HIT API SUPPLIER const trxId = Array(6) .fill(null) .map(() => { return Math.round(Math.random() * 16).toString(16); }) .join(''); let hitSupplier = await doTransaction( orderTransactionDto.productCode, orderTransactionDto.destination, trxId, supplier, ); if (supplier.code != 'IRS') { const parsingResponse = hitSupplier.split(' '); hitSupplier = { success: hitSupplier.includes('diproses'), harga: parseInt( parsingResponse[parsingResponse.length - 2].replaceAll('.', ''), ), msg: hitSupplier, }; } // const hitSupplier = { // harga: 2000, // success: true, // msg: 'Berhasil', // }; this.logger.log({ responseAPISupplier: hitSupplier, }); let costInventory = product_price.price; if (hitSupplier.harga != product_price.price) { product_price.endDate = new Date(); costInventory = hitSupplier.harga; } try { //TRANSACTION DATA await this.connection.transaction(async (manager) => { const transactionData = new Transactions(); transactionData.id = uuid.v4(); transactionData.amount = product_price.mark_up_price + product_price.price; transactionData.user = userData.id; transactionData.type = typeTransaction.ORDER; transactionData.product_price = product_price; transactionData.destination = orderTransactionDto.destination; transactionData.partner_trx_id = orderTransactionDto.trx_id; transactionData.supplier_trx_id = trxId; if (!hitSupplier.success) { transactionData.status = statusTransaction.FAILED; status = statusTransaction[transactionData.status]; await this.transactionRepository.insert(transactionData); throw new HttpException( { statusCode: HttpStatus.INTERNAL_SERVER_ERROR, error: hitSupplier.msg, }, HttpStatus.INTERNAL_SERVER_ERROR, ); } else { transactionData.status = statusTransaction.PENDING; status = statusTransaction[transactionData.status]; } await manager.insert(Transactions, transactionData); await this.accountingTransaction({ createTransaction: false, transactionalEntityManager: manager, transaction: transactionData, amount: transactionData.amount, journals: [ { coa_id: coaInventory.id, credit: costInventory, }, { coa_id: coaCostOfSales.id, debit: costInventory, }, { coa_id: coaAccount.id, debit: product_price.mark_up_price + costInventory, }, { // eslint-disable-next-line camelcase coa_id: coaSales.id, credit: product_price.mark_up_price + costInventory, }, ], }); }); } catch (e) { throw e; } return { trx_id: trxId, client_trx_id: orderTransactionDto.trx_id, product: orderTransactionDto.productCode, amount: product_price.mark_up_price + product_price.price, status: status, }; } async checkBill(orderTransactionDto: OrderTransactionDto, currentUser: any) { //GET USER DATA const userData = await this.userService.findByUsername( currentUser.username, ); //GET PRODUCT AND PRICE const product = await this.productService.findOne( orderTransactionDto.productCode, 'postpaid', ); const supplier = await this.supplierService.findByCode( product.supplier.code, ); let product_price = await this.productHistoryPriceService.findOne( product.id, userData.partner?.id, ); //HIT API SUPPLIER const trxId = Array(6) .fill(null) .map(() => { return Math.round(Math.random() * 16).toString(16); }) .join(''); let status; try { let hitSupplier = await doTransaction( 'CEK' + orderTransactionDto.productCode.slice(3), orderTransactionDto.destination, trxId, supplier, ); const parsingResponse = hitSupplier.split(' '); hitSupplier = { success: hitSupplier.includes('diproses'), msg: hitSupplier, }; if (!hitSupplier.success) { status = statusTransaction[statusTransaction.FAILED]; throw new HttpException( { statusCode: HttpStatus.INTERNAL_SERVER_ERROR, error: hitSupplier.msg, }, HttpStatus.INTERNAL_SERVER_ERROR, ); } else { status = statusTransaction[statusTransaction.SUCCESS]; await this.checkBillHistoryRepository.insert({ trx_id: trxId, user: userData.id, callback_json: JSON.stringify(hitSupplier), destination: orderTransactionDto.destination, product_code: orderTransactionDto.productCode, partner_trx_id: orderTransactionDto.trx_id, product_price: product_price, status: 'PENDING', }); } } catch (e) { throw e; } return { trx_id: trxId, client_trx_id: orderTransactionDto.trx_id, product: orderTransactionDto.productCode, status: status, }; } async createDepositReturn(currentUser, depositReturnDto: DepositReturnDto) { const userData = await this.userService.findByUsername( currentUser.username, ); try { const transactionData = new Transactions(); transactionData.id = uuid.v4(); transactionData.amount = depositReturnDto.amount; transactionData.user = userData.id; transactionData.user_destination = depositReturnDto.destination; transactionData.status = statusTransaction.PENDING; transactionData.type = typeTransaction.DEPOSIT_RETURN; transactionData.image_prove = depositReturnDto.image_prove; await this.connection.transaction(async (manager) => { await manager.insert(Transactions, transactionData); }); return transactionData; } catch (e) { throw e; } } async confirmationDepositReturn( id: string, userData, statusApproval: string, ) { const transactionData = await this.findApprovalDepositReturn(id); // const coaSenderWallet = await this.coaService.findByUser( // transactionData.user, // coaType.WALLET, // ); // // const coaAP = await this.coaService.findByUserWithRelated( // transactionData.user, // userData.userId, // coaType.ACCOUNT_PAYABLE, // ); // // const coaReceiverWallet = await this.coaService.findByUser( // transactionData.user, // coaType.WALLET, // ); // // const coaAR = await this.coaService.findByUserWithRelated( // transactionData.user, // userData.userId, // coaType.ACCOUNT_RECEIVABLE, // ); const userFind = await this.userService.findByUsername(userData.username); try { await this.connection.transaction(async (manager) => { if (statusApproval === 'accept') { if (userFind.roles.name != 'Admin') { transactionData.user_destination = userFind.superior.id; } else { transactionData.status = statusTransaction.APPROVED; } // await this.accountingTransaction({ // createTransaction: false, // transactionalEntityManager: manager, // transaction: transactionData, // amount: transactionData.amount, // journals: [ // { // coa_id: coaSenderWallet.id, // credit: transactionData.amount, // }, // { // coa_id: coaReceiverWallet.id, // debit: transactionData.amount, // }, // { // coa_id: coaAR.id, // credit: transactionData.amount, // }, // { // coa_id: coaAP.id, // debit: transactionData.amount, // }, // ], // }); } else { transactionData.status = statusTransaction.REJECTED; } await manager.save(transactionData); }); return transactionData; } catch (e) { throw e; } return transactionData; } async confirmationAdminDepositReturn( id: string, userData, statusApproval: string, ) { const transactionData = await this.findApprovalDepositReturn(id); const coaAR = await this.coaService.findByUserWithRelated( userData.userId, transactionData.user_destination, coaType.ACCOUNT_RECEIVABLE, ); const coaBank = await this.coaService.findByName( `${coaType[coaType.BANK]}-SYSTEM`, ); try { await this.connection.transaction(async (manager) => { transactionData.status = statusApproval === 'Accept' ? statusTransaction.APPROVED : statusTransaction.REJECTED; await manager.save(transactionData); await this.accountingTransaction({ createTransaction: false, transactionalEntityManager: manager, transaction: transactionData, amount: transactionData.amount, journals: [ { coa_id: coaAR.id, credit: transactionData.amount, }, { coa_id: coaBank.id, debit: transactionData.amount, }, ], }); }); return transactionData; } catch (e) { throw e; } return transactionData; } async callbackOrderFailed(supplier_trx_id: string, callback: any) { const dataTransaction = await this.transactionRepository.findOne({ where: { supplier_trx_id: supplier_trx_id, }, relations: ['product_price'], }); const dataMsg = callback.msg; const failedReason = dataMsg.split('.'); dataTransaction.status = statusTransaction.FAILED; dataTransaction.callback_json = callback; dataTransaction.failed_reason = `${failedReason[0]}, ${failedReason[1]}`; const userData = await this.userService.findExist(dataTransaction.user); const product_price = await this.productHistoryPriceService.findById( dataTransaction.product_price.id, ); const product = await this.productService.findOneById( product_price.product.id, ); //GET COA const coaAccount = await this.coaService.findByUser( userData.id, coaType.WALLET, ); const coaInventory = await this.coaService.findByName( `${coaType[coaType.INVENTORY]}-${product.supplier.code}`, ); const coaCostOfSales = await this.coaService.findByName( `${coaType[coaType.COST_OF_SALES]}-${product.supplier.code}`, ); const coaSales = await this.coaService.findByName( `${coaType[coaType.SALES]}-SYSTEM`, ); try { await this.connection.transaction(async (manager) => { await manager.save(dataTransaction); await this.accountingTransaction({ createTransaction: false, transactionalEntityManager: manager, transaction: dataTransaction, amount: dataTransaction.amount, journals: [ { coa_id: coaInventory.id, debit: product_price.price, }, { coa_id: coaCostOfSales.id, credit: product_price.price, }, { coa_id: coaAccount.id, credit: product_price.mark_up_price + product_price.price, }, { coa_id: coaSales.id, debit: product_price.mark_up_price + product_price.price, }, ], }); }); } catch (e) { throw e; } if (userData.partner) { const message = `Transaksi ${product.code} dengan tujuan ${dataTransaction.destination} telah gagal.`; this.callbackToPartner( userData.id, message, dataTransaction.partner_trx_id, dataTransaction.amount, product.code, dataTransaction.destination, '-', 'gagal', ); } } async callbackOrderSuccess(supplier_trx_id: string, callback: any) { const dataTransaction = await this.transactionRepository.findOne({ where: { supplier_trx_id: supplier_trx_id, }, relations: ['product_price'], }); dataTransaction.status = statusTransaction.SUCCESS; dataTransaction.seri_number = callback['sn']; dataTransaction.callback_json = callback; const userData = await this.userService.findExist(dataTransaction.user); let supervisorData = []; const product_price = await this.productHistoryPriceService.findById( dataTransaction.product_price.id, ); const product = await this.productService.findOneById( product_price.product.id, ); let profit = product_price.mark_up_price; //GET COA const coaExpense = await this.coaService.findByName( `${coaType[coaType.EXPENSE]}-SYSTEM`, ); if (userData.partner != null) { //GET SALES supervisorData = await this.calculateCommission( supervisorData, profit, userData, ); profit = supervisorData .map((item) => { return item.credit; }) .reduce((prev, curr) => { return prev + curr; }, 0); supervisorData = supervisorData.concat([ { coa_id: coaExpense.id, debit: profit, }, ]); } try { await this.connection.transaction(async (manager) => { await manager.save(dataTransaction); await this.accountingTransaction({ createTransaction: false, transactionalEntityManager: manager, transaction: dataTransaction, amount: dataTransaction.amount, journals: supervisorData, }); }); } catch (e) { throw e; } if (userData.partner) { const message = `Transaksi ${product.code} dengan tujuan ${dataTransaction.destination} telah berhasil.`; this.callbackToPartner( userData.id, message, dataTransaction.partner_trx_id, dataTransaction.amount, product.code, dataTransaction.destination, dataTransaction.seri_number, 'berhasil', ); } } async callbackToPartner( partnerId: string, message: string, trxId: string, harga: number, productCode: string, destination: string, seriNumber: string, status: string, ) { const partnerData = await this.userService.findPartner(partnerId); const res = await axios.get( `${partnerData.callback_url}?status=${status}&memberID=${partnerData.code}&trxid=${trxId}&harga=${harga}&product=${productCode}&dest=${destination}&seriNumber=${seriNumber}&message=${message}`, ); } async withdrawBenefit(user) { const userData = await this.userService.findExist(user); const coaProfit = await this.coaService.findByUser(user, coaType.PROFIT); const coaBank = await this.coaService.findByName( `${coaType[coaType.BANK]}-SYSTEM`, ); try { const transactionData = new Transactions(); transactionData.id = uuid.v4(); transactionData.amount = coaProfit.amount; transactionData.user = userData.id; transactionData.status = statusTransaction.APPROVED; transactionData.type = typeTransaction.WITHDRAW; await this.connection.transaction(async (manager) => { 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: coaProfit.id, debit: transactionData.amount, }, ], }); }); return transactionData; } catch (e) { throw e; } } async transactionHistoryByUser( page: number, user: string, startDate: string, endDate: string, pageSize?: number, ) { const userData = await this.userService.findExist(user); let userBySupperior = []; if ( userData.roles.id != 'e4dfb6a3-2338-464a-8fb8-5cbc089d4209' && userData.roles.id != '21dceea2-416e-4b55-b74c-12605e1f8d1b' ) { let roleNumber; if (userData.roles.id == '3196cdf4-ae5f-4677-9bcd-98be35c72321') { roleNumber = 3; } else if (userData.roles.id == '3196cdf4-ae5f-4677-9bcd-98be35c72322') { roleNumber = 2; } else if (userData.roles.id == 'e4dfb6a3-2348-464a-8fb8-5cbc089d4209') { roleNumber = 1; } const listUser = await this.userService.findAllSubordinate( userData.id, roleNumber, ); if (listUser.length < 1) { userBySupperior.push(userData.id); } else { userBySupperior = listUser; } } else { userBySupperior.push(userData.id); } const baseQuery = this.transactionRepository .createQueryBuilder('transaction') .select('transaction.id', 'id') .addSelect('transaction.created_at', 'created_at') .where('transaction.user IN (:...id) and transaction.type = 1', { id: userBySupperior, }) .leftJoinAndMapOne( 'transaction.userData', UserDetail, 'userData', 'userData.user = transaction.user', ) .leftJoin('transaction.product_price', 'product_price') .leftJoin('product_price.product', 'product') .addSelect('transaction.amount', 'price') .addSelect('transaction.destination') .addSelect('transaction.seri_number', 'seri_number') .addSelect('transaction.supplier_trx_id', 'transaction_code') .addSelect('transaction.status', 'status') .addSelect('transaction.partner_trx_id', 'partner_transaction_code') .addSelect('transaction.failed_reason', 'failed_reason') .addSelect('userData.name', 'buyer') .addSelect('product.name', 'name') .addSelect('product.id', 'product_id') .orderBy('transaction.created_at', 'DESC'); if (startDate && endDate) { baseQuery.andWhere( 'transaction.created_at between :startDate and :enDate', { startDate: new Date(startDate), enDate: new Date(endDate), }, ); } const data = await baseQuery .offset(page * (pageSize || 10)) .limit(pageSize || 10) .getRawMany(); const totalData = await baseQuery.getCount(); return { data, count: totalData, }; } async findOne(user: string, id: string) { try { return this.checkBillHistoryRepository.findOneOrFail({ where: { trx_id: id, user: user, }, }); } catch (e) { throw new HttpException( { statusCode: HttpStatus.NOT_FOUND, error: 'Billing not found', }, HttpStatus.NOT_FOUND, ); } } async findAll(user: string, page, pageSize?) { try { return await this.checkBillHistoryRepository.findAndCount({ where: { user: user, }, select: [ 'amount', 'admin_price', 'product_code', 'destination', 'trx_id', 'partner_trx_id', 'status', 'createdAt', ], skip: page * (pageSize || 10), take: pageSize || 10, order: { createdAt: 'DESC', } }); } catch (e) { throw new HttpException( { statusCode: HttpStatus.NOT_FOUND, error: 'Billing not found', }, HttpStatus.NOT_FOUND, ); } } async topUpHistoryByUser( page: number, user: string, destinationUser: string, type: string, pageSize?: number, ) { const userData = await this.userService.findExist(user); const baseQuery = this.transactionRepository .createQueryBuilder('transaction') .leftJoinAndMapOne( 'transaction.userData', UserDetail, 'userData', 'userData.user = transaction.user', ); if ( userData.roles.name == 'Admin' || userData.roles.name == 'Customer Service' || type == 'profile' ) { baseQuery.where( 'transaction.type = 0 and transaction.user_destination = :destinationId', { destinationId: destinationUser, }, ); } else { baseQuery.where( 'transaction.user = :id and transaction.type = 0 and transaction.user_destination = :destinationId', { id: user, destinationId: destinationUser, }, ); } baseQuery .select([ 'transaction.id', 'transaction.created_at as transaction_date', 'amount', 'userData.name as sender_name', ]) .orderBy('transaction.created_at', 'DESC'); const data = await baseQuery .offset(page * (pageSize || 10)) .limit(pageSize || 10) .getRawMany(); const totalData = await baseQuery.getCount(); return { data, count: totalData, }; } async findApprovalDepositReturn(id: string) { try { return await this.transactionRepository.findOneOrFail({ where: { id: id, type: typeTransaction.DEPOSIT_RETURN, status: statusTransaction.PENDING, }, }); } catch (e) { if (e instanceof EntityNotFoundError) { throw new HttpException( { statusCode: HttpStatus.NOT_FOUND, error: 'Return Deposit not found', }, HttpStatus.NOT_FOUND, ); } else { throw e; } } } async getAllDepositReturnFromUser( user: string, page: number, startDate: string, endDate: string, pageSize?: number, ) { const filter = { user: user, type: typeTransaction.DEPOSIT_RETURN, }; if (startDate && endDate) { filter['start'] = Between(new Date(startDate), new Date(endDate)); } const baseQuery = this.transactionRepository.findAndCount({ skip: page * (pageSize || 10), take: pageSize || 10, where: filter, order: { createdAt: 'DESC', }, }); return baseQuery; } async getAllDepositReturnToUser( user: string, page: number, sender: string, startDate: string, endDate: string, pageSize?: number, ) { const baseQuery = this.transactionRepository .createQueryBuilder('transaction') .where('transaction.user_destination = :id and transaction.type = 3', { id: user, }) .leftJoinAndMapOne( 'transaction.userData', UserDetail, 'userData', 'userData.user = transaction.user', ) .select('transaction.id', 'id') .addSelect([ 'transaction.created_at', 'image_prove', 'amount', 'status', 'userData.name', ]) .orderBy('transaction.created_at', 'DESC'); if (startDate && endDate) { baseQuery.andWhere( 'transaction.created_at between :startDate and :enDate', { startDate: new Date(startDate), enDate: new Date(endDate), }, ); } if (sender) { baseQuery.andWhere('transaction.user = :sender', { sender, }); } const data = await baseQuery .offset(page * (pageSize || 10)) .limit(pageSize || 10) .getRawMany(); const totalData = await baseQuery.getCount(); return { data, count: totalData, }; return this.transactionRepository.findAndCount({ skip: page * (pageSize || 10), take: pageSize || 10, where: { user_destination: user, type: typeTransaction.DEPOSIT_RETURN, }, order: { createdAt: 'DESC', }, }); } async getTotalSell(currentUser) { const baseQuery = this.transactionRepository .createQueryBuilder('transactions') .innerJoin('transactions.product_price', 'product_price') .where( 'transactions.type = 1 and partner_trx_id is NULL and transactions.status = 1', ); const data = await baseQuery .select('SUM(transactions.amount) as total_amount') .addSelect('SUM(product_price.price) as total_modal') .addSelect('SUM(product_price.mark_up_price) as total_profit') .addSelect('COUNT(transactions.id) as total_transaction') .getRawOne(); const { total_expense } = await baseQuery .select('SUM(transaction_journal.amount) as total_expense') .innerJoin('transactions.transactionJournal', 'transaction_journal') .where( `transaction_journal.type = '0' and transaction_journal.amount < product_price.price`, ) .getRawOne(); return { total_amount: parseInt(data.total_amount), total_transaction: parseInt(data.total_transaction), total_modal: parseInt(data.total_modal), total_profit: parseInt(data.total_profit), total_commission: parseInt(data.total_profit) - parseInt(total_expense), }; } async getTotalSellB2B(currentUser) { const baseQuery = this.transactionRepository .createQueryBuilder('transactions') .innerJoin('transactions.product_price', 'product_price') .where( 'transactions.type = 1 and partner_trx_id is not NULL and transactions.status = 1', ); const data = await baseQuery .select('SUM(transactions.amount) as total_amount') .addSelect('SUM(product_price.price) as total_modal') .addSelect('SUM(product_price.mark_up_price) as total_profit') .addSelect('COUNT(transactions.id) as total_transaction') .getRawOne(); return { total_amount: parseInt(data.total_amount), total_transaction: parseInt(data.total_transaction), total_modal: parseInt(data.total_modal), total_profit: parseInt(data.total_profit), }; } async getTotalSellPartner(currentUser) { const userData = await this.userService.findByUsername( currentUser.username, ); const baseQuery = this.transactionRepository .createQueryBuilder('transactions') .innerJoin('transactions.product_price', 'product_price') .where('transactions.type = 1 and transactions.status = 1') .andWhere('transactions.user = :id', { id: userData.id, }); const data = await baseQuery .select('SUM(transactions.amount) as total_amount') .addSelect('COUNT(transactions.id) as total_transaction') .getRawOne(); return { total_amount: parseInt(data.total_amount), total_transaction: parseInt(data.total_transaction), }; } async calculateCommission(data, totalPrice, userData) { const supervisorData = []; supervisorData.push( await this.userService.findByUsername(userData.superior.username), ); //GET Supervisor supervisorData.push( await this.userService.findByUsername( supervisorData[0].superior.username, ), ); return Promise.all( supervisorData.map(async (it) => { const coaAccount = await this.coaService.findByUser( it.id, coaType.PROFIT, ); const commissionValue = await this.commissionService.findOne( it.roles.id, ); return { coa_id: coaAccount.id, credit: Math.floor((totalPrice * commissionValue.commission) / 100), }; }), ); } 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)); const coa = coas.find((it) => { return 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; } async updateBill( trxId: string, amount: number, admin: number, status: boolean, message: string, ) { const billData = await this.findOneBillById(trxId); console.log(billData,"ini dia") const userData = await this.userService.findExist(billData.user); const product_price = await this.productHistoryPriceService.findById( billData.product_price.id, ); await this.checkBillHistoryRepository.update( { trx_id: trxId, }, { amount: status ? amount + product_price.partner_fee + product_price.mark_up_price : 0, admin_price: admin, status: status ? 'SUCCESS' : 'FAILED', callback_json: JSON.stringify(message), }, ); if (userData.partner) { const message = status ? `Bill dari ${billData.destination} adalah ${ amount + product_price.partner_fee + product_price.mark_up_price }.` : ''; const statusResponse = status ? 'berhasil' : 'gagal'; this.callbackToPartner( userData.id, message, billData.partner_trx_id, amount + product_price.partner_fee + product_price.mark_up_price, billData.product_code, billData.destination, '-', statusResponse, ); } } async findOneBillById(trxId: string) { try { return await this.checkBillHistoryRepository.findOneOrFail({ relations: ['product_price'], where: { trx_id: trxId, }, }); } catch (e) { if (e instanceof EntityNotFoundError) { throw new HttpException( { statusCode: HttpStatus.NOT_FOUND, error: 'Bill not found', }, HttpStatus.NOT_FOUND, ); } else { throw e; } } } }