Merge branch 'devops-staging' of https://gitlab.com/empatnusabangsa/ppob/ppob-backend into devops-production

This commit is contained in:
Fadli 2022-06-29 23:56:52 +07:00
commit b0a5079feb
19 changed files with 8822 additions and 92 deletions

7633
package-lock.json generated Normal file

File diff suppressed because it is too large Load Diff

View File

@ -6,13 +6,24 @@ const irs_pin = '04JFGC';
const irs_user = 'D10BD0'; const irs_user = 'D10BD0';
const irs_pass = '6251F3'; const irs_pass = '6251F3';
export const doTransaction = async (productCode, destination, idtrx) => { export const doTransaction = async (
productCode,
destination,
idtrx,
supplier,
) => {
try { try {
if(supplier.code == 'IRS'){
const res = await axios.get( 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}`, `${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; return res.data;
} else {
const res = await axios.get(
`${supplier.url}?memberID=${supplier.irs_id}&pin=${supplier.irs_pin}&password=${supplier.irs_pass}&product=${productCode}&dest=${destination}&counter=1&refID=${idtrx}`,
);
return res.data;
}
} catch (err) { } catch (err) {
throw err; throw err;
} }

View File

@ -11,6 +11,9 @@ export class UpdatePriceProductDto {
@IsNotEmpty() @IsNotEmpty()
type: productType; type: productType;
@IsNotEmpty()
productType: string;
startDate: Date; startDate: Date;
endDate: Date; endDate: Date;

View File

@ -32,4 +32,14 @@ export class ProductHistoryPrice extends BaseModel {
@Column('text') @Column('text')
type: productType; type: productType;
@Column({
default: 0,
})
admin_price: number;
@Column({
default: 0,
})
partner_fee: number;
} }

View File

@ -28,6 +28,11 @@ export class Product extends BaseModel {
}) })
basePrice: number; basePrice: number;
@Column({
default: 'prepaid',
})
type: string;
@ManyToOne( @ManyToOne(
() => { () => {
return ProductSubCategories; return ProductSubCategories;

View File

@ -55,6 +55,7 @@ export class ProductService {
const supplierData = await this.supplierService.findByCode(supplierCode); const supplierData = await this.supplierService.findByCode(supplierCode);
const data = await parsingFile(uploadFile); const data = await parsingFile(uploadFile);
data.shift(); data.shift();
await mapSeries(data, async (it) => { await mapSeries(data, async (it) => {
let dataHistoryPrice; let dataHistoryPrice;
@ -71,6 +72,7 @@ export class ProductService {
code: it[0], code: it[0],
supplier: supplierData, supplier: supplierData,
}); });
if (productData) { if (productData) {
//TODO : Handle Update Product //TODO : Handle Update Product
productData.name = it[1]; productData.name = it[1];
@ -108,18 +110,23 @@ export class ProductService {
type: productType.NORMAL, type: productType.NORMAL,
startDate: new Date(), startDate: new Date(),
partner: it[6] != '-' ? partnerData : null, partner: it[6] != '-' ? partnerData : null,
admin_price: it[8],
partner_fee: it[9],
}); });
} else { } else {
let partnerData; let partnerData;
if (it[6] != '-' && it[6] != '') { if (it[6] != '-' && it[6] != '') {
partnerData = await this.partnerService.findOne(it[6]); partnerData = await this.partnerService.findOne(it[6]);
} }
const savedProduct = await this.productRepository.insert({ const savedProduct = await this.productRepository.insert({
name: it[1], name: it[1],
code: it[0], code: it[0],
status: it[5] == 'active' ? 'ACTIVE' : 'NOT ACTIVE', status: it[5] == 'active' ? 'ACTIVE' : 'NOT ACTIVE',
sub_categories: subCategories, sub_categories: subCategories,
supplier: supplierData, supplier: supplierData,
type: it[7] == 'postpaid' ? 'postpaid' : 'prepaid',
}); });
return await this.productHistoryPrice.insert({ return await this.productHistoryPrice.insert({
@ -130,6 +137,8 @@ export class ProductService {
startDate: new Date(), startDate: new Date(),
endDate: null, endDate: null,
partner: partnerData, partner: partnerData,
admin_price: it[8],
partner_fee: it[9],
}); });
} }
}); });
@ -167,7 +176,7 @@ export class ProductService {
'product.currentPrice', 'product.currentPrice',
'product.priceHistory', 'product.priceHistory',
'current_price', 'current_price',
'current_price.partner_id is null and current_price.end_date is NULL', 'current_price.end_date is NULL',
) )
.select(['product.id']) .select(['product.id'])
.addSelect([ .addSelect([
@ -178,7 +187,9 @@ export class ProductService {
'category.name', 'category.name',
'product.status', 'product.status',
]) ])
.addSelect('current_price.price') .addSelect('current_price.price', 'price')
.addSelect('current_price.partner_fee', 'partner_fee')
.addSelect('current_price.admin_price', 'admin_price')
.addSelect( .addSelect(
'(current_price.price + current_price.mark_up_price) as mark_up_price', '(current_price.price + current_price.mark_up_price) as mark_up_price',
) )
@ -291,18 +302,34 @@ export class ProductService {
subCategories: string, subCategories: string,
username: string, username: string,
) { ) {
let filterSubCategories;
const user = await this.usersService.findOneByUsername(username); const user = await this.usersService.findOneByUsername(username);
const supplier = await this.supplierService.findByActive();
const baseQuery = this.productRepository if (subCategories) {
filterSubCategories = subCategories.split(',').map((data) => {
return data.trim();
});
}
if (user.partner === null) {
throw new HttpException(
{
statusCode: HttpStatus.NOT_FOUND,
error: 'Partner id not found',
},
HttpStatus.NOT_FOUND,
);
}
const baseQuery = await this.productRepository
.createQueryBuilder('product') .createQueryBuilder('product')
.leftJoin('product.sub_categories', 'sub_categories') .leftJoin('product.sub_categories', 'sub_categories')
.where( .leftJoinAndSelect(
`product.supplier_id = :supplier_id and product.status = 'ACTIVE'`, 'product.supplier',
{ 'supplier',
supplier_id: supplier.id, 'supplier.status = true',
},
) )
.where(`product.status = 'ACTIVE'`)
.innerJoinAndMapOne( .innerJoinAndMapOne(
'product.currentPrice', 'product.currentPrice',
'product.priceHistory', 'product.priceHistory',
@ -313,26 +340,64 @@ export class ProductService {
}, },
) )
.select(['product.id']) .select(['product.id'])
.addSelect(['product.name', 'product.code', 'sub_categories.name']) .addSelect([
.addSelect( 'product.name',
'(current_price.price + current_price.mark_up_price) as price', 'product.code',
); 'product.type',
'product.status',
'sub_categories.name',
'current_price.admin_price as admin_price',
'current_price.mark_up_price as markup_price',
'current_price.partner_fee as partner_fee',
'current_price.price as price',
])
// .addSelect(
// '(current_price.price + current_price.mark_up_price) as price',
// );
if ( // if (
subCategories != 'null' && // subCategories != 'null' &&
subCategories && // subCategories &&
subCategories != 'undefined' // subCategories != 'undefined'
) { // ) {
baseQuery.where('product.sub_categories_id = :id', { // baseQuery.where('product.sub_categories_id = :id', {
id: subCategories, // id: subCategories,
}); // });
// }
if (subCategories && filterSubCategories.length > 0) {
baseQuery.where('product.sub_categories_id IN (:...subCategoryId)', {
subCategoryId: filterSubCategories,
}).andWhere(`product.status = 'ACTIVE'`)
} }
const newData = []
const data = await baseQuery const data = await baseQuery
.offset(page * pageSize) .offset(page * pageSize)
.limit(pageSize) .limit(pageSize)
.getRawMany(); .getRawMany();
data.map((dataa) => {
let actualPrice = 0
if (dataa.product_type === 'prepaid') {
actualPrice = Number(dataa['price']) + Number(dataa['markup_price'])
}
if (dataa.product_type === 'postpaid') {
actualPrice = Number(dataa['admin_price'])- (Number(dataa['partner_fee']) + Number(dataa['markup_price']))
}
dataa.price = actualPrice
newData.push({
...dataa
})
})
const totalData = await baseQuery.getCount(); const totalData = await baseQuery.getCount();
return { return {
@ -341,7 +406,7 @@ export class ProductService {
}; };
} }
async findOne(code: string) { async findOne(code: string, type: string) {
try { try {
return await this.productRepository.findOneOrFail({ return await this.productRepository.findOneOrFail({
relations: ['supplier'], relations: ['supplier'],
@ -422,7 +487,7 @@ export class ProductService {
code: string, code: string,
updatePriceProductDto: UpdatePriceProductDto, updatePriceProductDto: UpdatePriceProductDto,
) { ) {
const product = await this.findOne(code); const product = await this.findOne(code, updatePriceProductDto.productType);
await this.productHistoryPrice.insert({ await this.productHistoryPrice.insert({
product: product, product: product,

View File

@ -9,4 +9,7 @@ export class OrderTransactionDto {
@IsOptional() @IsOptional()
trx_id: string; trx_id: string;
@IsOptional()
bill_trx_id: string;
} }

View File

@ -0,0 +1,18 @@
import { Column, Entity, ManyToOne, OneToMany } from 'typeorm';
import { BaseModel } from '../../config/basemodel.entity';
import { statusTransaction, typeTransaction } from '../../helper/enum-list';
import { ProductHistoryPrice } from '../../product/entities/product-history-price.entity';
import { UserDetail } from '../../users/entities/user_detail.entity';
import { TransactionJournal } from './transaction-journal.entity';
@Entity()
export class CallbackPartner extends BaseModel {
@Column()
trx_id: string;
@Column()
partner_trx_id: string;
@Column()
url: string;
}

View File

@ -0,0 +1,51 @@
import { Column, Entity, ManyToOne, OneToMany } from 'typeorm';
import { BaseModel } from '../../config/basemodel.entity';
import { statusTransaction, typeTransaction } from '../../helper/enum-list';
import { ProductHistoryPrice } from '../../product/entities/product-history-price.entity';
import { UserDetail } from '../../users/entities/user_detail.entity';
import { TransactionJournal } from './transaction-journal.entity';
@Entity()
export class CheckBillHistory extends BaseModel {
@Column()
trx_id: string;
@Column()
partner_trx_id: string;
@Column({ nullable: true })
amount: number;
@Column({ nullable: true })
admin_price: number;
@Column({
type: 'uuid',
nullable: true,
})
user: string;
@Column({
nullable: true,
})
destination: string;
@Column({
nullable: true,
})
request_json: string;
@Column({
nullable: true,
})
callback_json: string;
@Column()
product_code: string;
@Column()
status: string;
@ManyToOne(() => ProductHistoryPrice, (product) => product.id)
product_price: ProductHistoryPrice;
}

View File

@ -16,6 +16,11 @@ export class Transactions extends BaseModel {
@Column() @Column()
type: typeTransaction; type: typeTransaction;
@Column({
nullable: true,
})
check_bill: string;
@Column({ @Column({
type: 'uuid', type: 'uuid',
nullable: true, nullable: true,

View File

@ -19,19 +19,76 @@ export class PpobCallbackController {
if (response['statuscode'] == 2) { if (response['statuscode'] == 2) {
//TODO: UPDATE GAGAL //TODO: UPDATE GAGAL
const updateTransaction = await this.transactionService.checkCallbackOrderFailed(
await this.transactionService.callbackOrderFailed(
response['clientid'],
response,
);
} else {
//TODO: UPDATE BERHASIL
const updateTransaction =
await this.transactionService.callbackOrderSuccess(
response['clientid'], response['clientid'],
response, response,
); );
} }
//TODO: UPDATE BERHASIL
await this.transactionService.checkCallbackOrderSuccess(
response['clientid'],
response,
);
}
@Public()
@Get('/metro')
async getMetro(@Req() request: FastifyRequest) {
const response = request.query;
if (response['message'].toLowerCase().includes('cek tagihan')) {
if (response['status'] != 20) {
//TODO: UPDATE GAGAL
await this.transactionService.updateBill(
response['refid'],
null,
null,
false,
response['message'],
);
return {
statusCode: HttpStatus.OK,
message: 'success',
};
}
const splitMessage = response['message'].split('"');
//TODO: UPDATE BERHASIL
await this.transactionService.updateBill(
response['refid'],
Number(splitMessage[21].replace(/^\D+/g, '')),
Number(splitMessage[17].replace(/^\D+/g, '')),
true,
response['message'],
);
//
} else {
if (response['status'].toString() != '20') {
//TODO: UPDATE GAGAL
const updateTransaction =
await this.transactionService.callbackOrderFailed(
response['refid'],
response,
);
return {
updateTransaction,
statusCode: HttpStatus.BAD_REQUEST,
message: 'failed to proccess',
};
}
//TODO: UPDATE BERHASIL
const updateTransaction =
await this.transactionService.callbackOrderSuccess(
response['refid'],
response,
);
}
this.logger.log({ this.logger.log({
requestQuery: request.query, requestQuery: request.query,
}); });

View File

@ -87,6 +87,36 @@ export class TransactionController {
}; };
} }
@Post('order-bill-prod')
async orderTransactioBillnProd(
@Body() orderTransactionDto: OrderTransactionDto,
@Request() req,
) {
return {
data: await this.transactionService.orderTransactionBillProd(
orderTransactionDto,
req.user,
),
statusCode: HttpStatus.CREATED,
message: 'success',
};
}
@Post('check-bill')
async checkBill(
@Body() orderTransactionDto: OrderTransactionDto,
@Request() req,
) {
return {
data: await this.transactionService.checkBill(
orderTransactionDto,
req.user,
),
statusCode: HttpStatus.CREATED,
message: 'success',
};
}
@Post('deposit-return') @Post('deposit-return')
async depositReturn( async depositReturn(
@Body() depositReturnDto: DepositReturnDto, @Body() depositReturnDto: DepositReturnDto,
@ -102,6 +132,40 @@ export class TransactionController {
}; };
} }
@Post('rollback-jurnal')
async rollbackJurnal(@Body() request, @Request() req) {
const data = await this.transactionService.rollbackJurnal(request.trxId);
return {
data,
statusCode: HttpStatus.OK,
message: 'success',
};
}
@Get('resend-partner/success/:code')
async resendSuccess(@Request() req, @Param('code') code: string) {
const data = await this.transactionService.resendOrderToPartner(code, true);
return {
data,
statusCode: HttpStatus.OK,
message: 'success',
};
}
@Get('resend-partner/failed/:code')
async resendFailed(@Request() req, @Param('code') code: string) {
const data = await this.transactionService.resendOrderToPartner(
code,
false,
);
return {
data,
statusCode: HttpStatus.OK,
message: 'success',
};
}
@Get('history') @Get('history')
async getHistoryTransactionUser( async getHistoryTransactionUser(
@Query('page') page: number, @Query('page') page: number,
@ -125,6 +189,27 @@ export class TransactionController {
}; };
} }
@Get('check-bill-history')
async getCheckBillHistory(
@Param('id') id: string,
@Query('page') page: number,
@Query('pageSize') pageSize: number,
@Request() req,
) {
const [data, count] = await this.transactionService.findAll(
req.user.userId,
page,
pageSize,
);
return {
data,
count,
statusCode: HttpStatus.OK,
message: 'success',
};
}
@Get('history-user/:id') @Get('history-user/:id')
async getHistoryTransactionUserByParam( async getHistoryTransactionUserByParam(
@Query('page') page: number, @Query('page') page: number,

View File

@ -10,10 +10,18 @@ import { CoaService } from './coa.service';
import { ProductModule } from '../product/product.module'; import { ProductModule } from '../product/product.module';
import { UsersModule } from 'src/users/users.module'; import { UsersModule } from 'src/users/users.module';
import { ConfigurableModule } from '../configurable/configurable.module'; import { ConfigurableModule } from '../configurable/configurable.module';
import { CheckBillHistory } from './entities/check-bill-history.entity';
import { CallbackPartner } from './entities/callback-partner.entity';
@Module({ @Module({
imports: [ imports: [
TypeOrmModule.forFeature([COA, TransactionJournal, Transactions]), TypeOrmModule.forFeature([
COA,
TransactionJournal,
Transactions,
CheckBillHistory,
CallbackPartner,
]),
ProductModule, ProductModule,
ConfigurableModule, ConfigurableModule,
forwardRef(() => UsersModule), forwardRef(() => UsersModule),

View File

@ -3,19 +3,14 @@ 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';
import {Transactions} from './entities/transactions.entity'; import {Transactions} from './entities/transactions.entity';
import { Between, Connection, EntityNotFoundError, Repository } from 'typeorm'; import {Between, Connection, EntityNotFoundError, In, Repository} from 'typeorm';
import {COA} from './entities/coa.entity'; import {COA} from './entities/coa.entity';
import {TransactionJournal} from './entities/transaction-journal.entity'; import {TransactionJournal} from './entities/transaction-journal.entity';
import {CoaService} from './coa.service'; import {CoaService} from './coa.service';
import * as uuid from 'uuid'; import * as uuid from 'uuid';
import {uniq} from 'lodash'; import {uniq} from 'lodash';
import {Decimal} from 'decimal.js'; import {Decimal} from 'decimal.js';
import { import {balanceType, coaType, statusTransaction, typeTransaction,} from '../helper/enum-list';
balanceType,
coaType,
statusTransaction,
typeTransaction,
} from '../helper/enum-list';
import {ProductService} from '../product/product.service'; import {ProductService} from '../product/product.service';
import {CreateJournalDto} from './dto/create-journal.dto'; import {CreateJournalDto} from './dto/create-journal.dto';
import {UsersService} from 'src/users/users.service'; import {UsersService} from 'src/users/users.service';
@ -27,6 +22,9 @@ 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 {doTransaction} from '../helper/irs-api';
import {ProductHistoryPrice} from '../product/entities/product-history-price.entity'; import {ProductHistoryPrice} from '../product/entities/product-history-price.entity';
import axios from 'axios';
import {CheckBillHistory} from './entities/check-bill-history.entity';
import {CallbackPartner} from './entities/callback-partner.entity';
@Injectable() @Injectable()
export class TransactionService { export class TransactionService {
@ -39,6 +37,10 @@ export class TransactionService {
private transactionJournalRepository: Repository<TransactionJournal>, private transactionJournalRepository: Repository<TransactionJournal>,
@InjectRepository(COA) @InjectRepository(COA)
private coaRepository: Repository<COA>, private coaRepository: Repository<COA>,
@InjectRepository(CheckBillHistory)
private checkBillHistoryRepository: Repository<CheckBillHistory>,
@InjectRepository(CallbackPartner)
private callbackPartnerRepository: Repository<CallbackPartner>,
private coaService: CoaService, private coaService: CoaService,
private productService: ProductService, private productService: ProductService,
private productHistoryPriceService: ProductHistoryPriceService, private productHistoryPriceService: ProductHistoryPriceService,
@ -312,6 +314,7 @@ export class TransactionService {
//GET PRODUCT //GET PRODUCT
const product = await this.productService.findOne( const product = await this.productService.findOne(
orderTransactionDto.productCode, orderTransactionDto.productCode,
'prepaid'
); );
const product_price = await this.productHistoryPriceService.findOne( const product_price = await this.productHistoryPriceService.findOne(
@ -446,6 +449,230 @@ export class TransactionService {
//GET PRODUCT AND PRICE //GET PRODUCT AND PRICE
const product = await this.productService.findOne( const product = await this.productService.findOne(
orderTransactionDto.productCode, 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( let product_price = await this.productHistoryPriceService.findOne(
@ -489,12 +716,23 @@ export class TransactionService {
}) })
.join(''); .join('');
const hitSupplier = await doTransaction( let hitSupplier = await doTransaction(
orderTransactionDto.productCode, orderTransactionDto.productCode,
orderTransactionDto.destination, orderTransactionDto.destination,
trxId, 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 = { // const hitSupplier = {
// harga: 2000, // harga: 2000,
// success: true, // success: true,
@ -510,27 +748,6 @@ export class TransactionService {
if (hitSupplier.harga != product_price.price) { if (hitSupplier.harga != product_price.price) {
product_price.endDate = new Date(); product_price.endDate = new Date();
costInventory = hitSupplier.harga; 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 { try {
@ -605,6 +822,84 @@ export class TransactionService {
}; };
} }
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) { async createDepositReturn(currentUser, depositReturnDto: DepositReturnDto) {
const userData = await this.userService.findByUsername( const userData = await this.userService.findByUsername(
currentUser.username, currentUser.username,
@ -759,6 +1054,109 @@ export class TransactionService {
return transactionData; return transactionData;
} }
async checkCallbackOrderFailed(supplier_trx_id: string, callback: any) {
const transactionData = await this.findDataTransactionBySupplierTrxId(
supplier_trx_id
);
if (transactionData.status == statusTransaction.FAILED) {
throw new HttpException(
{
statusCode: HttpStatus.BAD_REQUEST,
error: 'failed to update, the transaction already failed',
},
HttpStatus.BAD_REQUEST,
);
} else if (transactionData.status == statusTransaction.SUCCESS) {
throw new HttpException(
{
statusCode: HttpStatus.BAD_REQUEST,
error: 'failed to update, the transaction already success',
},
HttpStatus.BAD_REQUEST,
);
} else {
const updateTransaction =
await this.callbackOrderFailed(
supplier_trx_id,
callback,
);
return {
updateTransaction,
statusCode: HttpStatus.BAD_REQUEST,
message: 'failed to proccess',
};
}
}
async checkCallbackOrderSuccess(supplier_trx_id: string, callback: any) {
const transactionData = await this.findDataTransactionBySupplierTrxId(
supplier_trx_id
);
if (transactionData.status == statusTransaction.FAILED) {
throw new HttpException(
{
statusCode: HttpStatus.BAD_REQUEST,
error: 'failed to update, the transaction already failed',
},
HttpStatus.BAD_REQUEST,
);
} else if (transactionData.status == statusTransaction.SUCCESS) {
throw new HttpException(
{
statusCode: HttpStatus.BAD_REQUEST,
error: 'failed to update, the transaction already success',
},
HttpStatus.BAD_REQUEST,
);
} else {
const updateTransaction =
await this.callbackOrderSuccess(
supplier_trx_id,
callback,
);
return {
updateTransaction,
statusCode: HttpStatus.OK,
message: 'success',
};
}
}
async findDataTransactionBySupplierTrxId(supplier_trx_id: string) {
try {
return await this.transactionRepository.findOneOrFail({
where: {
supplier_trx_id: supplier_trx_id,
},
relations: ['product_price'],
});
} catch (e) {
if (e instanceof EntityNotFoundError) {
throw new HttpException(
{
statusCode: HttpStatus.NOT_FOUND,
error: 'data not found',
},
HttpStatus.NOT_FOUND,
);
} else {
throw e;
}
}
}
async callbackOrderFailed(supplier_trx_id: string, callback: any) { async callbackOrderFailed(supplier_trx_id: string, callback: any) {
const dataTransaction = await this.transactionRepository.findOne({ const dataTransaction = await this.transactionRepository.findOne({
where: { where: {
@ -834,6 +1232,20 @@ export class TransactionService {
} catch (e) { } catch (e) {
throw e; throw e;
} }
if (userData.partner) {
const message = `Transaksi ${product.code} dengan tujuan ${dataTransaction.destination} telah gagal.`;
this.callbackToPartner(
userData.partner.id,
message,
dataTransaction.partner_trx_id,
dataTransaction.amount,
product.code,
dataTransaction.destination,
'-',
'gagal',
);
}
} }
async callbackOrderSuccess(supplier_trx_id: string, callback: any) { async callbackOrderSuccess(supplier_trx_id: string, callback: any) {
@ -845,7 +1257,14 @@ export class TransactionService {
}); });
dataTransaction.status = statusTransaction.SUCCESS; dataTransaction.status = statusTransaction.SUCCESS;
if(callback['sn']){
dataTransaction.seri_number = callback['sn']; dataTransaction.seri_number = callback['sn'];
} else {
const response = callback['message'];
const responseBaru = response.split(' ');
dataTransaction.seri_number =
responseBaru[10].length > 1 ? responseBaru[10] : responseBaru[9];
}
dataTransaction.callback_json = callback; dataTransaction.callback_json = callback;
const userData = await this.userService.findExist(dataTransaction.user); const userData = await this.userService.findExist(dataTransaction.user);
@ -867,7 +1286,7 @@ export class TransactionService {
`${coaType[coaType.EXPENSE]}-SYSTEM`, `${coaType[coaType.EXPENSE]}-SYSTEM`,
); );
if (!userData.partner) { if (userData.partner == null) {
//GET SALES //GET SALES
supervisorData = await this.calculateCommission( supervisorData = await this.calculateCommission(
supervisorData, supervisorData,
@ -905,6 +1324,147 @@ export class TransactionService {
} catch (e) { } catch (e) {
throw e; throw e;
} }
if (userData.partner) {
const message = `Transaksi ${product.code} dengan tujuan ${dataTransaction.destination} telah berhasil.`;
this.callbackToPartner(
userData.partner.id,
message,
dataTransaction.partner_trx_id,
dataTransaction.amount,
product.code,
dataTransaction.destination,
dataTransaction.seri_number,
'berhasil',
);
}
}
async resendOrderToPartner(supplier_trx_id: string, status: boolean){
const dataTransaction = await this.transactionRepository.findOne({
where: {
supplier_trx_id: supplier_trx_id,
},
relations: ['product_price'],
});
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,
);
if (status) {
const message = `Transaksi ${product.code} dengan tujuan ${dataTransaction.destination} telah berhasil.`;
await this.callbackToPartner(
userData.partner.id,
message,
dataTransaction.partner_trx_id,
dataTransaction.amount,
product.code,
dataTransaction.destination,
dataTransaction.seri_number,
'berhasil',
);
} else {
const message = `Transaksi ${product.code} dengan tujuan ${dataTransaction.destination} telah gagal.`;
this.callbackToPartner(
userData.partner.id,
message,
dataTransaction.partner_trx_id,
dataTransaction.amount,
product.code,
dataTransaction.destination,
'-',
'gagal',
);
}
}
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 userData = await this.userService.findOneByPartner(partnerId);
const coaAccount = await this.coaService.findByUser(
userData.id,
coaType.WALLET,
);
const url = `${partnerData.callback_url}?status=${status}&memberID=${partnerData.code}&trxid=${trxId}&harga=${harga}&product=${productCode}&dest=${destination}&seriNumber=${seriNumber}&message=${message}&saldo=${coaAccount.amount}`;
const result = await this.callbackPartnerRepository.insert({
partner_trx_id: partnerId,
trx_id: trxId,
url: url,
});
const res = await axios.get(url);
return res;
}
async rollbackJurnal(trxId: string[]) {
// const dataTransaction = await this.transactionRepository.findOne({
// where: {
// id: trxId,
// },
// relations: ['product_price'],
// });
if (trxId.length % 2 != 0) {
throw Error("Not Balance")
}
const dataTransactionJurnal = await this.transactionJournalRepository.find({
where: {
id: In(trxId),
},
relations: ['coa'],
});
let dataRollbackJurnal = [];
dataTransactionJurnal.map((it) => {
let data = {
coa_id: it.coa.id,
};
if (it.type == 0) {
data['credit'] = it.amount;
} else {
data['debit'] = it.amount;
}
dataRollbackJurnal.push(data);
});
const dataTransaction = new Transactions();
try {
await this.connection.transaction(async (manager) => {
await this.accountingTransaction({
createTransaction: false,
transactionalEntityManager: manager,
transaction: dataTransaction,
amount: dataTransaction.amount,
journals: dataRollbackJurnal,
});
});
} catch (e) {
throw e;
}
} }
async withdrawBenefit(user) { async withdrawBenefit(user) {
@ -1041,6 +1601,58 @@ export class TransactionService {
}; };
} }
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( async topUpHistoryByUser(
page: number, page: number,
user: string, user: string,
@ -1449,4 +2061,79 @@ export class TransactionService {
return transaction; 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 -
admin +
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;
}
}
}
} }

View File

@ -6,4 +6,19 @@ export class CreateSupplierDto {
@IsNotEmpty() @IsNotEmpty()
code: string; code: string;
@IsNotEmpty()
url: string;
@IsNotEmpty()
irs_id: string;
@IsNotEmpty()
irs_pin: string;
@IsNotEmpty()
irs_user: string;
@IsNotEmpty()
irs_pass: string;
} }

View File

@ -27,4 +27,9 @@ export class Partner extends BaseModel {
@Column({ default: true }) @Column({ default: true })
status: boolean; status: boolean;
@Column({
default: '',
})
callback_url: string;
} }

View File

@ -16,6 +16,31 @@ export class Supplier extends BaseModel {
@Column() @Column()
status: boolean; status: boolean;
@Column({
nullable: true
})
url: string;
@Column({
nullable: true
})
irs_id: string;
@Column({
nullable: true
})
irs_pin: string;
@Column({
nullable: true
})
irs_user: string;
@Column({
nullable: true
})
irs_pass: string;
coa: COA; coa: COA;
coa_undistribute: COA; coa_undistribute: COA;

View File

@ -43,6 +43,11 @@ export class SupplierService {
supplierData.id = uuid.v4(); supplierData.id = uuid.v4();
supplierData.name = createSupplierDto.name; supplierData.name = createSupplierDto.name;
supplierData.code = createSupplierDto.code; supplierData.code = createSupplierDto.code;
supplierData.url = createSupplierDto.url;
supplierData.irs_id = createSupplierDto.irs_id;
supplierData.irs_pin = createSupplierDto.irs_pin;
supplierData.irs_user = createSupplierDto.irs_user;
supplierData.irs_pass = createSupplierDto.irs_pass;
supplierData.status = false; supplierData.status = false;
await this.connection.transaction(async (manager) => { await this.connection.transaction(async (manager) => {
@ -99,6 +104,11 @@ export class SupplierService {
const supplierData = new Supplier(); const supplierData = new Supplier();
supplierData.name = updateSupplierDto.name; supplierData.name = updateSupplierDto.name;
supplierData.url = updateSupplierDto.url;
supplierData.irs_id = updateSupplierDto.irs_id;
supplierData.irs_pin = updateSupplierDto.irs_pin;
supplierData.irs_user = updateSupplierDto.irs_user;
supplierData.irs_pass = updateSupplierDto.irs_pass;
supplierData.status = true; supplierData.status = true;
await this.connection.transaction(async (manager) => { await this.connection.transaction(async (manager) => {

View File

@ -20,12 +20,15 @@ import * as uuid from 'uuid';
import { UserDetail } from './entities/user_detail.entity'; import { UserDetail } from './entities/user_detail.entity';
import { COA } from '../transaction/entities/coa.entity'; import { COA } from '../transaction/entities/coa.entity';
import { mapSeries } from 'bluebird'; import { mapSeries } from 'bluebird';
import { Partner } from './entities/partner.entity';
@Injectable() @Injectable()
export class UsersService { export class UsersService {
constructor( constructor(
@InjectRepository(User) @InjectRepository(User)
private usersRepository: Repository<User>, private usersRepository: Repository<User>,
@InjectRepository(Partner)
private partnerRepository: Repository<Partner>,
@InjectRepository(UserDetail) @InjectRepository(UserDetail)
private userDetailRepository: Repository<UserDetail>, private userDetailRepository: Repository<UserDetail>,
@Inject( @Inject(
@ -363,7 +366,7 @@ export class UsersService {
where: { where: {
id: id, id: id,
}, },
relations: ['superior', 'roles'], relations: ['superior', 'roles', 'partner'],
}); });
} catch (e) { } catch (e) {
if (e instanceof EntityNotFoundError) { if (e instanceof EntityNotFoundError) {
@ -410,6 +413,7 @@ export class UsersService {
.leftJoinAndSelect('users.roles', 'roles') .leftJoinAndSelect('users.roles', 'roles')
.leftJoinAndSelect('users.superior', 'superior') .leftJoinAndSelect('users.superior', 'superior')
.leftJoinAndSelect('users.userDetail', 'userDetail') .leftJoinAndSelect('users.userDetail', 'userDetail')
.leftJoinAndSelect('users.partner', 'partner')
.where('users.id = :id', { .where('users.id = :id', {
id: id, id: id,
}) })
@ -429,6 +433,14 @@ export class UsersService {
'userDetail.identity_number', 'userDetail.identity_number',
'userDetail.image_identity', 'userDetail.image_identity',
'userDetail.image_store', 'userDetail.image_store',
'partner.id',
'partner.name',
'partner.code',
'partner.npwp',
'partner.address',
'partner.phone_number',
'partner.callback_url'
]) ])
.getOne(); .getOne();
const coa = await this.coaService.findByUser(id, coaType.WALLET); const coa = await this.coaService.findByUser(id, coaType.WALLET);
@ -676,4 +688,26 @@ export class UsersService {
} }
} }
} }
async findPartner(partnerId: string) {
try {
return this.partnerRepository.findOneOrFail({
where: {
id: partnerId,
},
});
} catch (e) {
if (e instanceof EntityNotFoundError) {
throw new HttpException(
{
statusCode: HttpStatus.NOT_FOUND,
error: 'Partner not found',
},
HttpStatus.NOT_FOUND,
);
} else {
throw e;
}
}
}
} }