diff --git a/package.json b/package.json index 05a3a9b..b788325 100644 --- a/package.json +++ b/package.json @@ -32,6 +32,7 @@ "@nestjs/platform-fastify": "^8.0.8", "@nestjs/typeorm": "^8.0.2", "axios": "^0.24.0", + "bluebird": "^3.7.2", "class-transformer": "^0.4.0", "class-validator": "^0.13.1", "crypto": "^1.0.1", diff --git a/src/helper/irs-api.ts b/src/helper/irs-api.ts new file mode 100644 index 0000000..1c5b439 --- /dev/null +++ b/src/helper/irs-api.ts @@ -0,0 +1,19 @@ +import axios from 'axios'; + +const irs_url = 'http://h2h.elangpangarep.com/api/h2h'; +const irs_id = 'PT0005'; +const irs_pin = '04JFGC'; +const irs_user = 'D10BD0'; +const irs_pass = '6251F3'; + +export const doTransaction = async (productCode, destination, idtrx) => { + try { + const res = await axios.get( + `${irs_url}?id=${irs_id}&pin=${irs_pin}&user=${irs_user}&pass=${irs_pass}&kodeproduk=${productCode}&tujuan=${destination}&counter=1&idtrx=${idtrx}`, + ); + + return res.data; + } catch (err) { + throw err; + } +}; diff --git a/src/product/history-price/history-price.service.ts b/src/product/history-price/history-price.service.ts index ee1325f..694dedd 100644 --- a/src/product/history-price/history-price.service.ts +++ b/src/product/history-price/history-price.service.ts @@ -10,6 +10,12 @@ export class ProductHistoryPriceService { private productHistoryPriceService: Repository, ) {} + async create(dataProduct: ProductHistoryPrice) { + const result = await this.productHistoryPriceService.save(dataProduct); + + return result; + } + async findOne(product: string, partner: string) { try { return await this.productHistoryPriceService.findOneOrFail({ @@ -34,6 +40,24 @@ export class ProductHistoryPriceService { } } + async findById(id: string) { + try { + return await this.productHistoryPriceService.findOneOrFail(id); + } catch (e) { + if (e instanceof EntityNotFoundError) { + throw new HttpException( + { + statusCode: HttpStatus.NOT_FOUND, + error: 'Price not found', + }, + HttpStatus.NOT_FOUND, + ); + } else { + throw e; + } + } + } + async findOneByProductId( page: number, productId: string, diff --git a/src/product/product.service.ts b/src/product/product.service.ts index 227afa1..55573ef 100644 --- a/src/product/product.service.ts +++ b/src/product/product.service.ts @@ -12,6 +12,7 @@ import { UsersService } from '../users/users.service'; import { SupplierService } from '../users/supplier/supplier.service'; import { parsingFile } from '../helper/csv-parser'; import { PartnerService } from '../users/partner/partner.service'; +import { mapSeries } from 'bluebird'; import { isNull } from 'util'; export class ProductService { @@ -56,82 +57,81 @@ export class ProductService { const data = await parsingFile(uploadFile); data.shift(); - await Promise.all( - data.map(async (it) => { - let dataHistoryPrice; - let partnerData; + await mapSeries(data, async (it) => { + let dataHistoryPrice; + let partnerData; - const subCategories = - await this.productSubCategoriesService.findOneForCSVParser(it[2]); + const subCategories = + await this.productSubCategoriesService.findOneForCSVParser(it[2]); - if (!subCategories) { + if (!subCategories) { + return; + } + + const productData = await this.productRepository.findOne({ + code: it[0], + supplier: supplierData, + }); + if (productData) { + //TODO : Handle Update Product + productData.name = it[1]; + productData.status = it[5] == 'active' ? 'ACTIVE' : 'NOT ACTIVE'; + await this.productRepository.save(productData); + + //TODO : Handle History Price + if (it[6] != '-' && it[6] != '') { + partnerData = await this.partnerService.findOne(it[6]); + dataHistoryPrice = await this.productHistoryPrice.findOne({ + where: { + product: productData.id, + partner: partnerData.id, + }, + }); + } else { + dataHistoryPrice = await this.productHistoryPrice.findOne({ + product: productData, + }); + } + + if (!dataHistoryPrice) { return; } - const productData = await this.productRepository.findOne({ + dataHistoryPrice.endDate = new Date(); + await this.productHistoryPrice.save(dataHistoryPrice); + + await this.productHistoryPrice.insert({ + product: productData, + mark_up_price: it[4], + price: it[3], + type: productType.NORMAL, + startDate: new Date(), + partner: it[6] != '-' ? partnerData : null, + }); + } else { + let partnerData; + if (it[6] != '-' && it[6] != '') { + partnerData = await this.partnerService.findOne(it[6]); + } + const savedProduct = await this.productRepository.insert({ + name: it[1], code: it[0], + status: it[5] == 'active' ? 'ACTIVE' : 'NOT ACTIVE', + sub_categories: subCategories, supplier: supplierData, }); - if (productData) { - //TODO : Handle Update Product - productData.name = it[1]; - productData.status = it[5] == 'active' ? 'ACTIVE' : 'NOT ACTIVE'; - await this.productRepository.save(productData); - //TODO : Handle History Price - if (it[6] != '-' && it[6] != '') { - partnerData = await this.partnerService.findOne(it[6]); - dataHistoryPrice = await this.productHistoryPrice.findOne({ - product: productData, - partner: partnerData, - }); - } else { - dataHistoryPrice = await this.productHistoryPrice.findOne({ - product: productData, - }); - } - - if(!dataHistoryPrice){ - console.log(productData,"productnya",partnerData); - return; - } - - dataHistoryPrice.endDate = new Date(); - await this.productHistoryPrice.save(dataHistoryPrice); - - await this.productHistoryPrice.insert({ - product: productData, - mark_up_price: it[4], - price: it[3], - type: productType.NORMAL, - startDate: new Date(), - partner: it[6] != '-' ? partnerData : null, - }); - } else { - let partnerData; - if (it[6] != '-' && it[6] != '') { - partnerData = await this.partnerService.findOne(it[6]); - } - const savedProduct = await this.productRepository.insert({ - name: it[1], - code: it[0], - status: it[5] == 'active' ? 'ACTIVE' : 'NOT ACTIVE', - sub_categories: subCategories, - supplier: supplierData, - }); - - await this.productHistoryPrice.insert({ - product: savedProduct.identifiers[0], - mark_up_price: it[4], - price: it[3], - type: productType.NORMAL, - startDate: new Date(), - endDate: null, - partner: partnerData, - }); - } - }), - ); + return await this.productHistoryPrice.insert({ + product: savedProduct.identifiers[0], + mark_up_price: it[4], + price: it[3], + type: productType.NORMAL, + startDate: new Date(), + endDate: null, + partner: partnerData, + }); + } + }); return data; } @@ -162,7 +162,7 @@ export class ProductService { .leftJoin('sub_categories.category', 'category') .leftJoin('product.supplier', 'supplier') .where('supplier.status = :status', { status: true }) - .leftJoinAndMapOne( + .innerJoinAndMapOne( 'product.currentPrice', 'product.priceHistory', 'current_price', diff --git a/src/transaction/dto/order-transaction.dto.ts b/src/transaction/dto/order-transaction.dto.ts index 462682c..413a4b2 100644 --- a/src/transaction/dto/order-transaction.dto.ts +++ b/src/transaction/dto/order-transaction.dto.ts @@ -3,4 +3,7 @@ import { IsNotEmpty, IsUUID } from 'class-validator'; export class OrderTransactionDto { @IsNotEmpty() productCode: string; + + @IsNotEmpty() + phoneNumber: string; } diff --git a/src/transaction/entities/transactions.entity.ts b/src/transaction/entities/transactions.entity.ts index a9937e8..c9b4c74 100644 --- a/src/transaction/entities/transactions.entity.ts +++ b/src/transaction/entities/transactions.entity.ts @@ -16,6 +16,7 @@ import { Partner } from '../../users/entities/partner.entity'; import { ProductHistoryPrice } from '../../product/entities/product-history-price.entity'; import { User } from '../../users/entities/user.entity'; import { UserDetail } from '../../users/entities/user_detail.entity'; +import { TransactionJournal } from './transaction-journal.entity'; @Entity() export class Transactions extends BaseModel { @@ -47,7 +48,19 @@ export class Transactions extends BaseModel { }) image_prove: string; + @Column({ + nullable: true, + }) + supplier_trx_id: string; + + @Column({ + nullable: true, + }) + phone_number: string; + mark_up_price: number; userData: UserDetail; + + transactionJournal: TransactionJournal; } diff --git a/src/transaction/ppob_callback.controller.ts b/src/transaction/ppob_callback.controller.ts index 0044656..e7064ec 100644 --- a/src/transaction/ppob_callback.controller.ts +++ b/src/transaction/ppob_callback.controller.ts @@ -24,7 +24,20 @@ export class PpobCallbackController { constructor(private readonly transactionService: TransactionService) {} @Get() - get(@Req() request: FastifyRequest) { + async get(@Req() request: FastifyRequest) { + const response = request.query; + + if (response['statuscode'] == 2) { + //TODO: UPDATE GAGAL + const updateTransaction = + await this.transactionService.callbackOrderFailed(response['clientid']); + } else { + //TODO: UPDATE BERHASIL + const updateTransaction = + await this.transactionService.callbackOrderSuccess( + response['clientid'], + ); + } this.logger.log({ requestQuery: request.query, }); diff --git a/src/transaction/transaction.controller.ts b/src/transaction/transaction.controller.ts index 58c896d..d540938 100644 --- a/src/transaction/transaction.controller.ts +++ b/src/transaction/transaction.controller.ts @@ -83,6 +83,21 @@ export class TransactionController { }; } + @Post('order-prod') + async orderTransactionProd( + @Body() orderTransactionDto: OrderTransactionDto, + @Request() req, + ) { + return { + data: await this.transactionService.orderTransactionProd( + orderTransactionDto, + req.user, + ), + statusCode: HttpStatus.CREATED, + message: 'success', + }; + } + @Post('deposit-return') async depositReturn( @Body() depositReturnDto: DepositReturnDto, @@ -159,6 +174,17 @@ export class TransactionController { }; } + @Get('total-order') + async findTotalOrder(@Request() req) { + const data = await this.transactionService.getTotalSell(); + + return { + data, + statusCode: HttpStatus.OK, + message: 'success', + }; + } + @Get('deposit-return/confirmation') async findDepositReturnConfirmation( @Query('page') page: number, diff --git a/src/transaction/transaction.service.ts b/src/transaction/transaction.service.ts index 0541b9b..dc64cee 100644 --- a/src/transaction/transaction.service.ts +++ b/src/transaction/transaction.service.ts @@ -1,4 +1,4 @@ -import { HttpException, HttpStatus, Injectable } from '@nestjs/common'; +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'; @@ -25,6 +25,8 @@ import { ProductHistoryPriceService } from '../product/history-price/history-pri 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'; interface JournalEntry { coa_id: string; @@ -34,6 +36,8 @@ interface JournalEntry { @Injectable() export class TransactionService { + private readonly logger = new Logger(TransactionService.name); + constructor( @InjectRepository(Transactions) private transactionRepository: Repository, @@ -371,11 +375,13 @@ export class TransactionService { const transactionData = new Transactions(); transactionData.id = uuid.v4(); - transactionData.amount = product_price.mark_up_price; + 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.phone_number = orderTransactionDto.phoneNumber; await manager.insert(Transactions, transactionData); await this.accountingTransaction({ @@ -414,6 +420,82 @@ export class TransactionService { return true; } + async orderTransactionProd( + orderTransactionDto: OrderTransactionDto, + currentUser: any, + ) { + //TODO GET USER DATA + const userData = await this.userService.findByUsername( + currentUser.username, + ); + + //TODO GET PRODUCT AND PRICE + const product = await this.productService.findOne( + orderTransactionDto.productCode, + ); + + const product_price = await this.productHistoryPriceService.findOne( + product.id, + userData.partner?.id, + ); + + //TODO HIT API SUPPLIER + const trxId = Array(6) + .fill(null) + .map(() => Math.round(Math.random() * 16).toString(16)) + .join(''); + + const hitSupplier = await doTransaction( + orderTransactionDto.productCode, + orderTransactionDto.phoneNumber, + trxId, + ); + + this.logger.log({ + responseAPISupplier: hitSupplier, + }); + + //TODO TRANSACTION DAT + 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.phone_number = orderTransactionDto.phoneNumber; + transactionData.supplier_trx_id = trxId; + + if (!hitSupplier.success) { + transactionData.status = statusTransaction.FAILED; + 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; + await this.transactionRepository.insert(transactionData); + } + + if (hitSupplier.harga > product_price.price) { + product_price.endDate = new Date(); + + let newProductPrice = new ProductHistoryPrice(); + newProductPrice = product_price; + newProductPrice.id = uuid.v4(); + newProductPrice.price = hitSupplier.harga; + + await this.productHistoryPriceService.create(product_price); + await this.productHistoryPriceService.create(newProductPrice); + } + + return hitSupplier; + } + async createDepositReturn(currentUser, depositReturnDto: DepositReturnDto) { const userData = await this.userService.findByUsername( currentUser.username, @@ -563,6 +645,115 @@ export class TransactionService { return transactionData; } + async callbackOrderFailed(supplier_trx_id: string) { + const dataTransaction = await this.transactionRepository.findOne({ + where: { + supplier_trx_id: supplier_trx_id, + }, + }); + dataTransaction.status = statusTransaction.FAILED; + + await this.transactionRepository.save(dataTransaction); + } + + async callbackOrderSuccess(supplier_trx_id: string) { + const dataTransaction = await this.transactionRepository.findOne({ + where: { + supplier_trx_id: supplier_trx_id, + }, + }); + dataTransaction.status = statusTransaction.FAILED; + + 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; + + 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); + } + + //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`, + ); + + 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, + 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, + }, + { + coa_id: coaSales.id, + credit: product_price.mark_up_price + product_price.price, + }, + { + coa_id: coaExpense.id, + debit: userData.partner ? 0 : profit, + }, + ].concat(supervisorData), + }); + }); + } catch (e) { + throw e; + } + } + async transactionHistoryByUser( page: number, user: string, @@ -763,6 +954,24 @@ export class TransactionService { } } + async getTotalSell() { + const { total_amount } = await this.transactionRepository + .createQueryBuilder('transactions') + .select('SUM(transactions.amount) as total_amount') + .getRawOne(); + + return parseInt(total_amount); + } + + async getTotalProfit() { + const { total_amount } = await this.transactionRepository + .createQueryBuilder('transactions') + .select('SUM(transactions.amount) as total_amount') + .getRawOne(); + + return parseInt(total_amount); + } + async calculateCommission(data, totalPrice, userData) { const supervisorData = []; diff --git a/yarn.lock b/yarn.lock index 24f3843..039e2b8 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1644,6 +1644,11 @@ bl@^4.1.0: inherits "^2.0.4" readable-stream "^3.4.0" +bluebird@^3.7.2: + version "3.7.2" + resolved "https://registry.yarnpkg.com/bluebird/-/bluebird-3.7.2.tgz#9f229c15be272454ffa973ace0dbee79a1b0c36f" + integrity sha512-XpNj6GDQzdfW+r2Wnn7xiSAd7TM3jzkxGXBGTtWKuSXv1xUV+azxAm8jdWZN06QTQk+2N2XB9jRDkvbmQmcRtg== + body-parser@1.19.0: version "1.19.0" resolved "https://registry.npmjs.org/body-parser/-/body-parser-1.19.0.tgz"