add: callback transaction

This commit is contained in:
ilham 2021-12-26 00:43:27 +07:00
parent c5e1df20b8
commit 4f50bad562
10 changed files with 384 additions and 71 deletions

View File

@ -32,6 +32,7 @@
"@nestjs/platform-fastify": "^8.0.8", "@nestjs/platform-fastify": "^8.0.8",
"@nestjs/typeorm": "^8.0.2", "@nestjs/typeorm": "^8.0.2",
"axios": "^0.24.0", "axios": "^0.24.0",
"bluebird": "^3.7.2",
"class-transformer": "^0.4.0", "class-transformer": "^0.4.0",
"class-validator": "^0.13.1", "class-validator": "^0.13.1",
"crypto": "^1.0.1", "crypto": "^1.0.1",

19
src/helper/irs-api.ts Normal file
View File

@ -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;
}
};

View File

@ -10,6 +10,12 @@ export class ProductHistoryPriceService {
private productHistoryPriceService: Repository<ProductHistoryPrice>, private productHistoryPriceService: Repository<ProductHistoryPrice>,
) {} ) {}
async create(dataProduct: ProductHistoryPrice) {
const result = await this.productHistoryPriceService.save(dataProduct);
return result;
}
async findOne(product: string, partner: string) { async findOne(product: string, partner: string) {
try { try {
return await this.productHistoryPriceService.findOneOrFail({ 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( async findOneByProductId(
page: number, page: number,
productId: string, productId: string,

View File

@ -12,6 +12,7 @@ import { UsersService } from '../users/users.service';
import { SupplierService } from '../users/supplier/supplier.service'; import { SupplierService } from '../users/supplier/supplier.service';
import { parsingFile } from '../helper/csv-parser'; import { parsingFile } from '../helper/csv-parser';
import { PartnerService } from '../users/partner/partner.service'; import { PartnerService } from '../users/partner/partner.service';
import { mapSeries } from 'bluebird';
import { isNull } from 'util'; import { isNull } from 'util';
export class ProductService { export class ProductService {
@ -56,82 +57,81 @@ export class ProductService {
const data = await parsingFile(uploadFile); const data = await parsingFile(uploadFile);
data.shift(); data.shift();
await Promise.all( await mapSeries(data, async (it) => {
data.map(async (it) => { let dataHistoryPrice;
let dataHistoryPrice; let partnerData;
let partnerData;
const subCategories = const subCategories =
await this.productSubCategoriesService.findOneForCSVParser(it[2]); 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; 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], code: it[0],
status: it[5] == 'active' ? 'ACTIVE' : 'NOT ACTIVE',
sub_categories: subCategories,
supplier: supplierData, 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 return await this.productHistoryPrice.insert({
if (it[6] != '-' && it[6] != '') { product: savedProduct.identifiers[0],
partnerData = await this.partnerService.findOne(it[6]); mark_up_price: it[4],
dataHistoryPrice = await this.productHistoryPrice.findOne({ price: it[3],
product: productData, type: productType.NORMAL,
partner: partnerData, startDate: new Date(),
}); endDate: null,
} else { partner: partnerData,
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 data; return data;
} }
@ -162,7 +162,7 @@ export class ProductService {
.leftJoin('sub_categories.category', 'category') .leftJoin('sub_categories.category', 'category')
.leftJoin('product.supplier', 'supplier') .leftJoin('product.supplier', 'supplier')
.where('supplier.status = :status', { status: true }) .where('supplier.status = :status', { status: true })
.leftJoinAndMapOne( .innerJoinAndMapOne(
'product.currentPrice', 'product.currentPrice',
'product.priceHistory', 'product.priceHistory',
'current_price', 'current_price',

View File

@ -3,4 +3,7 @@ import { IsNotEmpty, IsUUID } from 'class-validator';
export class OrderTransactionDto { export class OrderTransactionDto {
@IsNotEmpty() @IsNotEmpty()
productCode: string; productCode: string;
@IsNotEmpty()
phoneNumber: string;
} }

View File

@ -16,6 +16,7 @@ import { Partner } from '../../users/entities/partner.entity';
import { ProductHistoryPrice } from '../../product/entities/product-history-price.entity'; import { ProductHistoryPrice } from '../../product/entities/product-history-price.entity';
import { User } from '../../users/entities/user.entity'; import { User } from '../../users/entities/user.entity';
import { UserDetail } from '../../users/entities/user_detail.entity'; import { UserDetail } from '../../users/entities/user_detail.entity';
import { TransactionJournal } from './transaction-journal.entity';
@Entity() @Entity()
export class Transactions extends BaseModel { export class Transactions extends BaseModel {
@ -47,7 +48,19 @@ export class Transactions extends BaseModel {
}) })
image_prove: string; image_prove: string;
@Column({
nullable: true,
})
supplier_trx_id: string;
@Column({
nullable: true,
})
phone_number: string;
mark_up_price: number; mark_up_price: number;
userData: UserDetail; userData: UserDetail;
transactionJournal: TransactionJournal;
} }

View File

@ -24,7 +24,20 @@ export class PpobCallbackController {
constructor(private readonly transactionService: TransactionService) {} constructor(private readonly transactionService: TransactionService) {}
@Get() @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({ this.logger.log({
requestQuery: request.query, requestQuery: request.query,
}); });

View File

@ -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') @Post('deposit-return')
async depositReturn( async depositReturn(
@Body() depositReturnDto: DepositReturnDto, @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') @Get('deposit-return/confirmation')
async findDepositReturnConfirmation( async findDepositReturnConfirmation(
@Query('page') page: number, @Query('page') page: number,

View File

@ -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 { DistributeTransactionDto } from './dto/distribute-transaction.dto';
import { OrderTransactionDto } from './dto/order-transaction.dto'; import { OrderTransactionDto } from './dto/order-transaction.dto';
import { InjectRepository } from '@nestjs/typeorm'; import { InjectRepository } from '@nestjs/typeorm';
@ -25,6 +25,8 @@ import { ProductHistoryPriceService } from '../product/history-price/history-pri
import { CommissionService } from '../configurable/commission.service'; import { CommissionService } from '../configurable/commission.service';
import { DepositReturnDto } from './dto/deposit_return.dto'; import { DepositReturnDto } from './dto/deposit_return.dto';
import { UserDetail } from '../users/entities/user_detail.entity'; 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 { interface JournalEntry {
coa_id: string; coa_id: string;
@ -34,6 +36,8 @@ interface JournalEntry {
@Injectable() @Injectable()
export class TransactionService { export class TransactionService {
private readonly logger = new Logger(TransactionService.name);
constructor( constructor(
@InjectRepository(Transactions) @InjectRepository(Transactions)
private transactionRepository: Repository<Transactions>, private transactionRepository: Repository<Transactions>,
@ -371,11 +375,13 @@ export class TransactionService {
const transactionData = new Transactions(); const transactionData = new Transactions();
transactionData.id = uuid.v4(); 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.user = userData.id;
transactionData.status = statusTransaction.SUCCESS; transactionData.status = statusTransaction.SUCCESS;
transactionData.type = typeTransaction.ORDER; transactionData.type = typeTransaction.ORDER;
transactionData.product_price = product_price; transactionData.product_price = product_price;
transactionData.phone_number = orderTransactionDto.phoneNumber;
await manager.insert(Transactions, transactionData); await manager.insert(Transactions, transactionData);
await this.accountingTransaction({ await this.accountingTransaction({
@ -414,6 +420,82 @@ export class TransactionService {
return true; 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) { async createDepositReturn(currentUser, depositReturnDto: DepositReturnDto) {
const userData = await this.userService.findByUsername( const userData = await this.userService.findByUsername(
currentUser.username, currentUser.username,
@ -563,6 +645,115 @@ export class TransactionService {
return transactionData; 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( async transactionHistoryByUser(
page: number, page: number,
user: string, 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) { async calculateCommission(data, totalPrice, userData) {
const supervisorData = []; const supervisorData = [];

View File

@ -1644,6 +1644,11 @@ bl@^4.1.0:
inherits "^2.0.4" inherits "^2.0.4"
readable-stream "^3.4.0" 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: body-parser@1.19.0:
version "1.19.0" version "1.19.0"
resolved "https://registry.npmjs.org/body-parser/-/body-parser-1.19.0.tgz" resolved "https://registry.npmjs.org/body-parser/-/body-parser-1.19.0.tgz"