Merge branch 'development' into 'master'

Development

See merge request empatnusabangsa/ppob/ppob-backend!1
This commit is contained in:
ilham dwi pratama 2021-12-09 11:21:41 +00:00
commit 67c9ccc8c4
59 changed files with 9624 additions and 191 deletions

15
.vscode/launch.json vendored Normal file
View File

@ -0,0 +1,15 @@
{
// Use IntelliSense to learn about possible attributes.
// Hover to view descriptions of existing attributes.
// For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387
"version": "0.2.0",
"configurations": [
{
"type": "pwa-chrome",
"request": "launch",
"name": "Launch Chrome against localhost",
"url": "http://localhost:8080",
"webRoot": "${workspaceFolder}"
}
]
}

7824
package-lock.json generated Normal file

File diff suppressed because it is too large Load Diff

View File

@ -24,14 +24,21 @@
"@nestjs/common": "^8.0.0",
"@nestjs/config": "^1.0.1",
"@nestjs/core": "^8.0.0",
"@nestjs/jwt": "^8.0.0",
"@nestjs/mapped-types": "*",
"@nestjs/passport": "^8.0.1",
"@nestjs/platform-express": "^8.0.0",
"@nestjs/platform-fastify": "^8.0.8",
"@nestjs/typeorm": "^8.0.2",
"axios": "^0.24.0",
"class-transformer": "^0.4.0",
"class-validator": "^0.13.1",
"crypto": "^1.0.1",
"joi": "^17.4.2",
"nestjs-pino": "^2.3.1",
"passport": "^0.5.0",
"passport-jwt": "^4.0.0",
"passport-local": "^1.0.0",
"pg": "^8.7.1",
"pino-http": "^6.3.0",
"reflect-metadata": "^0.1.13",
@ -47,6 +54,8 @@
"@types/express": "^4.17.13",
"@types/jest": "^27.0.1",
"@types/node": "^16.0.0",
"@types/passport-jwt": "^3.0.6",
"@types/passport-local": "^1.0.34",
"@types/supertest": "^2.0.11",
"@typescript-eslint/eslint-plugin": "^4.28.2",
"@typescript-eslint/parser": "^4.28.2",

View File

@ -6,8 +6,10 @@ import { UsersModule } from './users/users.module';
import { SnakeNamingStrategy } from 'typeorm-naming-strategies';
import { LoggerModule } from 'nestjs-pino';
import { TransactionModule } from './transaction/transaction.module';
import configuration from './config/configuration';
import { ProductModule } from './product/product.module';
import { ConfigurableModule } from './configurable/configurable.module';
import { AuthModule } from './auth/auth.module';
import configuration from './config/configuration';
@Module({
imports: [
@ -19,10 +21,11 @@ import { ConfigurableModule } from './configurable/configurable.module';
.valid('development', 'production', 'test', 'provision')
.default('development'),
PORT: Joi.number().default(3000),
DATABASE_CLIENT: Joi.valid('mysql', 'postgres'),
DATABASE_HOST: Joi.string(),
DATABASE_NAME: Joi.string(),
DATABASE_USERNAME: Joi.string(),
SECRET: Joi.string().required(),
DATABASE_CLIENT: Joi.valid('mysql', 'postgres').required(),
DATABASE_HOST: Joi.string().required(),
DATABASE_NAME: Joi.string().required(),
DATABASE_USERNAME: Joi.string().required(),
DATABASE_PASSWORD: Joi.string().empty('').default(''),
DATABASE_PORT: Joi.number().default(5432),
}),
@ -49,6 +52,8 @@ import { ConfigurableModule } from './configurable/configurable.module';
UsersModule,
TransactionModule,
ConfigurableModule,
ProductModule,
AuthModule,
],
})
export class AppModule {}

View File

@ -0,0 +1,18 @@
import { Test, TestingModule } from '@nestjs/testing';
import { AuthController } from './auth.controller';
describe('AuthController', () => {
let controller: AuthController;
beforeEach(async () => {
const module: TestingModule = await Test.createTestingModule({
controllers: [AuthController],
}).compile();
controller = module.get<AuthController>(AuthController);
});
it('should be defined', () => {
expect(controller).toBeDefined();
});
});

View File

@ -0,0 +1,26 @@
import { Controller, Post, UseGuards, Request, Get } from '@nestjs/common';
import { LocalAuthGuard } from './local-auth.guard';
import { AuthService } from './auth.service';
import { JwtAuthGuard } from './jwt-auth.guard';
import {Public} from "./public.decorator";
@Controller({
path: 'auth',
version: '1',
})
export class AuthController {
constructor(private authService: AuthService) {}
@Public()
@UseGuards(LocalAuthGuard)
@Post('login')
async login(@Request() req) {
return this.authService.login(req.user);
}
@UseGuards(JwtAuthGuard)
@Get('profile')
getProfile(@Request() req) {
return req.user;
}
}

41
src/auth/auth.module.ts Normal file
View File

@ -0,0 +1,41 @@
import { Module } from '@nestjs/common';
import { AuthService } from './auth.service';
import { UsersModule } from '../users/users.module';
import { PassportModule } from '@nestjs/passport';
import { LocalStrategy } from './local.strategy';
import { AuthController } from './auth.controller';
import { JwtModule } from '@nestjs/jwt';
import { ConfigModule, ConfigService } from '@nestjs/config';
import { JwtStrategy } from './jwt.strategy';
import { APP_GUARD } from '@nestjs/core';
import { JwtAuthGuard } from './jwt-auth.guard';
@Module({
imports: [
UsersModule,
PassportModule,
ConfigModule,
JwtModule.registerAsync({
imports: [ConfigModule],
inject: [ConfigService],
useFactory: (configService: ConfigService) => {
return {
secret: configService.get<string>('secret'),
signOptions: { expiresIn: '1d' },
};
},
}),
],
providers: [
AuthService,
LocalStrategy,
JwtStrategy,
{
provide: APP_GUARD,
useClass: JwtAuthGuard,
},
],
controllers: [AuthController],
exports: [AuthService],
})
export class AuthModule {}

View File

@ -0,0 +1,18 @@
import { Test, TestingModule } from '@nestjs/testing';
import { AuthService } from './auth.service';
describe('AuthService', () => {
let service: AuthService;
beforeEach(async () => {
const module: TestingModule = await Test.createTestingModule({
providers: [AuthService],
}).compile();
service = module.get<AuthService>(AuthService);
});
it('should be defined', () => {
expect(service).toBeDefined();
});
});

35
src/auth/auth.service.ts Normal file
View File

@ -0,0 +1,35 @@
import { Injectable } from '@nestjs/common';
import { UsersService } from '../users/users.service';
import { hashPassword } from '../helper/hash_password';
import { JwtService } from '@nestjs/jwt';
@Injectable()
export class AuthService {
constructor(
private usersService: UsersService,
private jwtService: JwtService,
) {}
async validateUser(username: string, pass: string): Promise<any> {
const user = await this.usersService.findOneByUsername(username);
if (user && user.password === (await hashPassword(pass, user.salt))) {
const { password, ...result } = user;
return result;
}
return null;
}
async login(user: any) {
const payload = {
username: user.username,
sub: user.userId,
};
return {
access_token: this.jwtService.sign(payload),
};
}
}

View File

@ -0,0 +1,24 @@
import { ExecutionContext, Injectable } from '@nestjs/common';
import { AuthGuard } from '@nestjs/passport';
import { Reflector } from '@nestjs/core';
import { IS_PUBLIC_KEY } from './public.decorator';
@Injectable()
export class JwtAuthGuard extends AuthGuard('jwt') {
constructor(private reflector: Reflector) {
super();
}
canActivate(context: ExecutionContext) {
const isPublic = this.reflector.getAllAndOverride<boolean>(IS_PUBLIC_KEY, [
context.getHandler(),
context.getClass(),
]);
if (isPublic) {
return true;
}
return super.canActivate(context);
}
}

19
src/auth/jwt.strategy.ts Normal file
View File

@ -0,0 +1,19 @@
import { ExtractJwt, Strategy } from 'passport-jwt';
import { PassportStrategy } from '@nestjs/passport';
import { Injectable } from '@nestjs/common';
import {ConfigService} from "@nestjs/config";
@Injectable()
export class JwtStrategy extends PassportStrategy(Strategy) {
constructor(configService: ConfigService) {
super({
jwtFromRequest: ExtractJwt.fromAuthHeaderAsBearerToken(),
ignoreExpiration: false,
secretOrKey: configService.get<string>('secret'),
});
}
async validate(payload: any) {
return { userId: payload.sub, username: payload.username };
}
}

View File

@ -0,0 +1,5 @@
import { Injectable } from '@nestjs/common';
import { AuthGuard } from '@nestjs/passport';
@Injectable()
export class LocalAuthGuard extends AuthGuard('local') {}

View File

@ -0,0 +1,21 @@
import { Strategy } from 'passport-local';
import { PassportStrategy } from '@nestjs/passport';
import { Injectable, UnauthorizedException } from '@nestjs/common';
import { AuthService } from './auth.service';
@Injectable()
export class LocalStrategy extends PassportStrategy(Strategy) {
constructor(private authService: AuthService) {
super();
}
async validate(username: string, password: string): Promise<any> {
const user = await this.authService.validateUser(username, password);
if (!user) {
throw new UnauthorizedException();
}
return user;
}
}

View File

@ -0,0 +1,4 @@
import { SetMetadata } from '@nestjs/common';
export const IS_PUBLIC_KEY = 'isPublic';
export const Public = () => SetMetadata(IS_PUBLIC_KEY, true);

View File

@ -1,6 +1,7 @@
export default () => {
return {
port: parseInt(process.env.PORT, 10) || 3000,
secret: process.env.SECRET,
database: {
client: process.env.DATABASE_CLIENT,
host: process.env.DATABASE_HOST,

View File

@ -1,6 +1,6 @@
import { Test, TestingModule } from '@nestjs/testing';
import { ConfigurableController } from './configurable.controller';
import { ConfigurableService } from './configurable.service';
import { RoleService } from './roles.service';
describe('ConfigurableController', () => {
let controller: ConfigurableController;
@ -8,7 +8,7 @@ describe('ConfigurableController', () => {
beforeEach(async () => {
const module: TestingModule = await Test.createTestingModule({
controllers: [ConfigurableController],
providers: [ConfigurableService],
providers: [RoleService],
}).compile();
controller = module.get<ConfigurableController>(ConfigurableController);

View File

@ -7,20 +7,20 @@ import {
Param,
Delete,
ParseUUIDPipe,
HttpStatus,
HttpStatus, Query,
} from '@nestjs/common';
import { ConfigurableService } from './configurable.service';
import { RoleService } from './roles.service';
@Controller({
path: 'config',
version: '1',
})
export class ConfigurableController {
constructor(private readonly usersService: ConfigurableService) {}
constructor(private readonly usersService: RoleService) {}
@Get()
async findAll() {
const [data, count] = await this.usersService.findAll();
async findAll(@Query('page') page: number) {
const [data, count] = await this.usersService.findAllRoles(page);
return {
data,

View File

@ -2,11 +2,12 @@ import { Module } from '@nestjs/common';
import { TypeOrmModule } from '@nestjs/typeorm';
import { Roles } from './entities/roles.entity';
import { ConfigurableController } from './configurable.controller';
import { ConfigurableService } from './configurable.service';
import { RoleService } from './roles.service';
@Module({
imports: [TypeOrmModule.forFeature([Roles])],
controllers: [ConfigurableController],
providers: [ConfigurableService],
providers: [RoleService],
exports: [RoleService]
})
export class ConfigurableModule {}

View File

@ -1,15 +1,15 @@
import { Test, TestingModule } from '@nestjs/testing';
import { ConfigurableService } from './configurable.service';
import { RoleService } from './roles.service';
describe('ConfigurableService', () => {
let service: ConfigurableService;
describe('RoleService', () => {
let service: RoleService;
beforeEach(async () => {
const module: TestingModule = await Test.createTestingModule({
providers: [ConfigurableService],
providers: [RoleService],
}).compile();
service = module.get<ConfigurableService>(ConfigurableService);
service = module.get<RoleService>(RoleService);
});
it('should be defined', () => {

View File

@ -4,14 +4,20 @@ import { Roles } from './entities/roles.entity';
import { InjectRepository } from '@nestjs/typeorm';
@Injectable()
export class ConfigurableService {
export class RoleService {
constructor(
@InjectRepository(Roles)
private rolesRepository: Repository<Roles>,
) {}
findAll() {
return this.rolesRepository.findAndCount();
findAllRoles(page) {
return this.rolesRepository.findAndCount({
skip: page * 10,
take: 10,
order: {
version: 'DESC',
},
});
}
async findOne(id: string) {

33
src/helper/enum-list.ts Normal file
View File

@ -0,0 +1,33 @@
export enum statusTransaction {
PENDING,
SUCCESS,
FAILED,
}
export enum typeTransaction {
DISTRIBUTION,
ORDER,
DEPOSIT_IRS,
}
export enum productType {
NORMAL,
PROMO,
}
export enum coaType {
WALLET,
INCOME,
INVENTORY,
COST_OF_SALES,
SALES,
BANK,
EXPENSE,
ACCOUNT_RECEIVABLE,
ACCOUNT_PAYABLE
}
export enum balanceType {
DEBIT,
CREDIT,
}

View File

@ -0,0 +1,13 @@
import * as crypto from 'crypto';
export function hashPassword(password, salt): Promise<string> {
return new Promise((resolve, reject) => {
crypto.pbkdf2(password, salt, 50, 100, 'sha512', (err, values) => {
if (err) {
return reject(err);
}
resolve(values.toString('hex'));
});
});
}

25
src/helper/irs-service.ts Normal file
View File

@ -0,0 +1,25 @@
import * as axios from 'axios';
export const createTransaction = async (kode, tujuan) => {
const codeTransaksi = generateRequestId();
return axios.default
.get(
`http://h2h.elangpangarep.com/api/h2h?id=PT0005&pin=04JFGC&user=D10BD0&pass=6251F3&kodeproduk=${kode}&tujuan=${tujuan}&counter=1&idtrx=${codeTransaksi}`,
)
.then((response) => {
return response.data;
});
};
export const generateRequestId = () => {
return `${new Date()
.toLocaleString('en-us', {
year: '2-digit',
month: '2-digit',
day: '2-digit',
})
.replace(/(\d+)\/(\d+)\/(\d+)/, '$3$1$2')}${Math.random()
.toPrecision(3)
.replace('0.', '')}`;
};

View File

@ -0,0 +1,22 @@
// import { PassportStrategy } from '@nestjs/passport';
// import { ExtractJwt, Strategy } from 'passport-jwt';
// import { Injectable } from '@nestjs/common';
// import { AuthService } from '../auth/auth.service';
// @Injectable()
// export class JwtStrategy extends PassportStrategy(Strategy) {
// constructor(private readonly authService: AuthService) {
// super({
// jwtFromRequest: ExtractJwt.fromAuthHeaderAsBearerToken(),
// secretOrKey: process.env.SECRETKEY,
// });
// }
// async validate(payload: JwtPayload): Promise<UserDto> {
// const user = await this.authService.validateUser(payload);
// if (!user) {
// throw new HttpException('Invalid token', HttpStatus.UNAUTHORIZED);
// }
// return user;
// }
// }

View File

@ -1,44 +0,0 @@
import {
Entity,
Column,
PrimaryGeneratedColumn,
UpdateDateColumn,
DeleteDateColumn,
VersionColumn,
CreateDateColumn, ManyToOne, ManyToMany, JoinTable,
} from 'typeorm';
import { Product } from '../../product/entities/product.entity';
import { User } from '../../users/entities/user.entity';
import { BaseModel } from '../../config/basemodel.entity';
enum type {
SYSTEM_BANk,
INCOME,
}
enum balanceType {
DEBIT,
CREDIT,
}
@Entity()
export class Roles extends BaseModel{
@PrimaryGeneratedColumn('uuid')
id: string;
@Column()
name: string;
@Column('text')
type: type;
@Column('text')
balanceType: balanceType;
@Column()
amount: number;
@ManyToMany(() => User)
@JoinTable()
user: User[];
}

View File

@ -4,7 +4,7 @@ import {
NestFastifyApplication,
} from '@nestjs/platform-fastify';
import { AppModule } from './app.module';
import {ValidationPipe, VersioningType} from '@nestjs/common';
import { ValidationPipe, VersioningType} from '@nestjs/common';
import { ConfigService } from '@nestjs/config';
import { Logger } from 'nestjs-pino';

View File

@ -10,6 +10,12 @@ export class CreateProductDto {
@IsNotEmpty()
status: string;
@IsNotEmpty()
price: number;
@IsNotEmpty()
markUpPrice: number;
@IsUUID()
subCategoriesId: string;
}

View File

@ -0,0 +1,17 @@
import { IsNotEmpty } from 'class-validator';
import { productType } from '../../../helper/enum-list';
export class UpdatePriceProductDto {
@IsNotEmpty()
price: number;
@IsNotEmpty()
markUpPrice: number;
@IsNotEmpty()
type: productType;
startDate: Date;
endDate: Date;
}

View File

@ -0,0 +1,6 @@
import { OmitType, PartialType } from '@nestjs/mapped-types';
import { CreateProductDto } from './create-product.dto';
export class UpdateProductDto extends PartialType(
OmitType(CreateProductDto, ['price'] as const),
) {}

View File

@ -2,6 +2,9 @@ import { IsNotEmpty, IsUUID } from 'class-validator';
import { CreateCategoriesProductDto } from '../categories/create-categories-product.dto';
export class CreateSubCategoriesProductDto extends CreateCategoriesProductDto {
@IsNotEmpty()
name: string;
@IsUUID()
categoryId: string;
}

View File

@ -2,20 +2,11 @@ import {
Entity,
Column,
PrimaryGeneratedColumn,
UpdateDateColumn,
DeleteDateColumn,
VersionColumn,
CreateDateColumn,
OneToMany,
ManyToOne,
} from 'typeorm';
import { Product } from './product.entity';
import { BaseModel } from '../../config/basemodel.entity';
enum Type {
NORMAL,
PROMO,
}
import { productType } from '../../helper/enum-list';
@Entity()
export class ProductHistoryPrice extends BaseModel {
@ -23,14 +14,23 @@ export class ProductHistoryPrice extends BaseModel {
id: string;
@ManyToOne(() => Product, (product) => product.id)
productId: string;
product: Product;
@Column()
price: number;
@Column()
markUpPrice: number;
@Column({ type: 'date' })
startDate: string;
startDate: Date;
@Column({ type: 'date' })
endDate: string;
@Column({
type: 'date',
nullable:true
})
endDate: Date;
@Column('text')
type: Type;
type: productType;
}

View File

@ -26,6 +26,14 @@ export class Product extends BaseModel{
@Column()
status: string;
@Column()
price: number;
@Column({
nullable:true
})
basePrice: number;
@ManyToOne(
() => ProductSubCategories,
(subCategories) => subCategories.category,

View File

@ -4,19 +4,26 @@ import { ProductSubCategories } from './entities/product-sub-category.entity';
import { InjectRepository } from '@nestjs/typeorm';
import { CreateSubCategoriesProductDto } from './dto/sub-categories/create-sub-categories-product.dto';
import { UpdateSubCategoriesProductDto } from './dto/sub-categories/update-sub-categories-product.dto';
import { ProductCategoriesService } from './product-categories.service';
@Injectable()
export class ProductSubCategoriesService {
constructor(
@InjectRepository(ProductSubCategories)
private productSubCategoriesRepository: Repository<ProductSubCategories>,
private productCategoriesService: ProductCategoriesService,
) {}
async create(CreateCategoriesProductDto: CreateSubCategoriesProductDto) {
const result = await this.productSubCategoriesRepository.insert(
CreateCategoriesProductDto,
async create(createSubCategoriesProductDto: CreateSubCategoriesProductDto) {
const categories = await this.productCategoriesService.findOne(
createSubCategoriesProductDto.categoryId,
);
const result = await this.productSubCategoriesRepository.insert({
name: createSubCategoriesProductDto.name,
category: categories,
});
return this.productSubCategoriesRepository.findOneOrFail(
result.identifiers[0].id,
);
@ -70,11 +77,15 @@ export class ProductSubCategoriesService {
}
}
const result = await this.productSubCategoriesRepository.update(
id,
updateCategoriesProductDto,
const categories = await this.productCategoriesService.findOne(
updateCategoriesProductDto.categoryId,
);
const result = await this.productSubCategoriesRepository.update(id, {
name: updateCategoriesProductDto.name,
category: categories,
});
return this.productSubCategoriesRepository.findOneOrFail(id);
}

View File

@ -7,11 +7,18 @@ import {
Param,
Delete,
ParseUUIDPipe,
HttpStatus, Query,
HttpStatus,
Query,
} from '@nestjs/common';
import { ProductService } from './product.service';
import { ProductCategoriesService } from './product-categories.service';
import { CreateCategoriesProductDto } from './dto/categories/create-categories-product.dto';
import { UpdateCategoriesProductDto } from '../product/dto/categories/update-categories-product.dto';
import { UpdateSubCategoriesProductDto } from '../product/dto/sub-categories/update-sub-categories-product.dto';
import { ProductSubCategoriesService } from './product-sub-categories.service';
import { CreateSubCategoriesProductDto } from './dto/sub-categories/create-sub-categories-product.dto';
import { CreateProductDto } from './dto/product/create-product.dto';
import { UpdateProductDto } from './dto/product/update-product.dto';
@Controller({
path: 'product',
@ -21,8 +28,18 @@ export class ProductController {
constructor(
private readonly productService: ProductService,
private readonly productCategoriesService: ProductCategoriesService,
private readonly productSubCategoriesService: ProductSubCategoriesService,
) {}
@Post()
async create(@Body() createProductDto: CreateProductDto) {
return {
data: await this.productService.create(createProductDto),
statusCode: HttpStatus.CREATED,
message: 'success',
};
}
@Post('categories')
async createCategories(
@Body() createCategoriesProductDto: CreateCategoriesProductDto,
@ -36,6 +53,19 @@ export class ProductController {
};
}
@Post('sub-categories')
async createSubCategories(
@Body() createSubCategoriesProductDto: CreateSubCategoriesProductDto,
) {
return {
data: await this.productSubCategoriesService.create(
createSubCategoriesProductDto,
),
statusCode: HttpStatus.CREATED,
message: 'success',
};
}
@Get()
async findAll(@Query('page') page: number) {
const [data, count] = await this.productService.findAll(page);
@ -48,6 +78,30 @@ export class ProductController {
};
}
@Get('categories')
async findAllCategories(@Query('page') page: number) {
const [data, count] = await this.productCategoriesService.findAll(page);
return {
data,
count,
statusCode: HttpStatus.OK,
message: 'success',
};
}
@Get('sub-categories')
async findAllSubCategories(@Query('page') page: number) {
const [data, count] = await this.productSubCategoriesService.findAll(page);
return {
data,
count,
statusCode: HttpStatus.OK,
message: 'success',
};
}
@Get(':id')
async findOne(@Param('id', ParseUUIDPipe) id: string) {
return {
@ -57,4 +111,90 @@ export class ProductController {
};
}
@Get('categories/:id')
async findOneCategories(@Param('id', ParseUUIDPipe) id: string) {
return {
data: await this.productCategoriesService.findOne(id),
statusCode: HttpStatus.OK,
message: 'success',
};
}
@Get('sub-categories/:id')
async findOneSubCategories(@Param('id', ParseUUIDPipe) id: string) {
return {
data: await this.productSubCategoriesService.findOne(id),
statusCode: HttpStatus.OK,
message: 'success',
};
}
@Put(':id')
async update(
@Param('id', ParseUUIDPipe) id: string,
@Body() updateProductDto: UpdateProductDto,
) {
return {
data: await this.productService.update(id, updateProductDto),
statusCode: HttpStatus.OK,
message: 'success',
};
}
@Put('categories/:id')
async updateCategories(
@Param('id', ParseUUIDPipe) id: string,
@Body() updateCategoriesDto: UpdateCategoriesProductDto,
) {
return {
data: await this.productCategoriesService.update(id, updateCategoriesDto),
statusCode: HttpStatus.OK,
message: 'success',
};
}
@Put('sub-categories/:id')
async updateSubCategories(
@Param('id', ParseUUIDPipe) id: string,
@Body() updateSubCategoriesDto: UpdateSubCategoriesProductDto,
) {
return {
data: await this.productSubCategoriesService.update(
id,
updateSubCategoriesDto,
),
statusCode: HttpStatus.OK,
message: 'success',
};
}
@Delete(':id')
async remove(@Param('id', ParseUUIDPipe) id: string) {
await this.productService.remove(id);
return {
statusCode: HttpStatus.OK,
message: 'success',
};
}
@Delete('categories/:id')
async removeCategories(@Param('id', ParseUUIDPipe) id: string) {
await this.productCategoriesService.remove(id);
return {
statusCode: HttpStatus.OK,
message: 'success',
};
}
@Delete('sub-categories/:id')
async removeSubCategories(@Param('id', ParseUUIDPipe) id: string) {
await this.productSubCategoriesService.remove(id);
return {
statusCode: HttpStatus.OK,
message: 'success',
};
}
}

View File

@ -2,9 +2,28 @@ import { Module } from '@nestjs/common';
import { ProductService } from './product.service';
import { ProductController } from './product.controller';
import { ProductCategoriesService } from './product-categories.service';
import { TypeOrmModule } from '@nestjs/typeorm';
import { Product } from './entities/product.entity';
import { ProductCategories } from './entities/product-category.entity';
import { ProductHistoryPrice } from './entities/product-history-price.entity';
import { ProductSubCategories } from './entities/product-sub-category.entity';
import { ProductSubCategoriesService } from './product-sub-categories.service';
@Module({
imports: [
TypeOrmModule.forFeature([
Product,
ProductCategories,
ProductHistoryPrice,
ProductSubCategories,
]),
],
controllers: [ProductController],
providers: [ProductService, ProductCategoriesService],
providers: [
ProductService,
ProductCategoriesService,
ProductSubCategoriesService,
],
exports: [ProductService],
})
export class ProductModule {}

View File

@ -1,22 +1,44 @@
import { HttpException, HttpStatus, Injectable } from '@nestjs/common';
import { EntityNotFoundError, Repository } from 'typeorm';
import { Product } from './entities/product.entity';
import { ProductCategories } from './entities/product-category.entity';
import { ProductSubCategories } from './entities/product-sub-category.entity';
import { InjectRepository } from '@nestjs/typeorm';
import { CreateProductDto } from '../product/dto/create-product.dto';
import { CreateCategoriesProductDto } from './dto/categories/create-categories-product.dto';
import { CreateSubCategoriesProductDto } from './dto/sub-categories/create-sub-categories-product.dto';
import { CreateProductDto } from './dto/product/create-product.dto';
import { ProductSubCategoriesService } from './product-sub-categories.service';
import { UpdateProductDto } from './dto/product/update-product.dto';
import { ProductHistoryPrice } from './entities/product-history-price.entity';
import { productType } from '../helper/enum-list';
import { UpdatePriceProductDto } from './dto/product/update-price-product.dto';
@Injectable()
export class ProductService {
constructor(
@InjectRepository(Product)
private productRepository: Repository<Product>,
@InjectRepository(ProductHistoryPrice)
private productHistoryPrice: Repository<ProductHistoryPrice>,
private productSubCategoriesService: ProductSubCategoriesService,
) {}
async create(createProductDto: CreateProductDto) {
const result = await this.productRepository.insert(createProductDto);
const subCategories = await this.productSubCategoriesService.findOne(
createProductDto.subCategoriesId,
);
const result = await this.productRepository.insert({
name: createProductDto.name,
code: createProductDto.code,
status: createProductDto.status,
subCategories: subCategories,
price: createProductDto.price,
});
await this.productHistoryPrice.insert({
product: result.identifiers[0],
type: productType.NORMAL,
price: createProductDto.price,
markUpPrice: createProductDto.markUpPrice,
startDate: new Date(),
endDate: null,
});
return this.productRepository.findOneOrFail(result.identifiers[0].id);
}
@ -31,9 +53,9 @@ export class ProductService {
});
}
async findOne(id: string) {
async findOne(code: string) {
try {
return await this.productRepository.findOneOrFail(id);
return await this.productRepository.findOneOrFail({ code: code });
} catch (e) {
if (e instanceof EntityNotFoundError) {
throw new HttpException(
@ -48,4 +70,73 @@ export class ProductService {
}
}
}
async update(id: string, updateProductDto: UpdateProductDto) {
try {
await this.productRepository.findOneOrFail(id);
} catch (e) {
if (e instanceof EntityNotFoundError) {
throw new HttpException(
{
statusCode: HttpStatus.NOT_FOUND,
error: 'Data not found',
},
HttpStatus.NOT_FOUND,
);
} else {
throw e;
}
}
const subCategories = await this.productSubCategoriesService.findOne(
updateProductDto.subCategoriesId,
);
const result = await this.productRepository.update(id, {
name: updateProductDto.name,
code: updateProductDto.code,
status: updateProductDto.status,
subCategories: subCategories,
});
return this.productRepository.findOneOrFail(id);
}
async updatePrice(
code: string,
updatePriceProductDto: UpdatePriceProductDto,
) {
const product = await this.findOne(code);
await this.productHistoryPrice.insert({
product: product,
type: updatePriceProductDto.type,
price: updatePriceProductDto.price,
markUpPrice: updatePriceProductDto.markUpPrice,
startDate: updatePriceProductDto.startDate,
endDate: updatePriceProductDto.endDate,
});
return
}
async remove(id: string) {
try {
await this.productRepository.findOneOrFail(id);
} catch (e) {
if (e instanceof EntityNotFoundError) {
throw new HttpException(
{
statusCode: HttpStatus.NOT_FOUND,
error: 'Data not found',
},
HttpStatus.NOT_FOUND,
);
} else {
throw e;
}
}
await this.productRepository.delete(id);
}
}

View File

@ -0,0 +1,89 @@
import { forwardRef, HttpException, HttpStatus, Inject, Injectable } from '@nestjs/common';
import { EntityNotFoundError, Repository } from 'typeorm';
import { InjectRepository } from '@nestjs/typeorm';
import { COA } from './entities/coa.entity';
import { balanceType, coaType } from '../helper/enum-list';
import { InputCoaDto } from './dto/input-coa.dto';
import { UsersService } from 'src/users/users.service';
export class CoaService {
constructor(
@InjectRepository(COA)
private coaRepository: Repository<COA>,
@Inject(forwardRef(() => UsersService))
private userService: UsersService,
) {}
async create(inputCoaDto: InputCoaDto) {
const user = inputCoaDto.user
let coaData = new COA();
coaData.user = user.id;
coaData.name = coaType[inputCoaDto.type] + '-' + user.username;
coaData.balanceType = inputCoaDto.balanceType;
coaData.type = inputCoaDto.type;
coaData.amount = 0;
const result = await inputCoaDto.coaEntityManager.insert(COA,coaData);
if(inputCoaDto.type == coaType.ACCOUNT_RECEIVABLE || inputCoaDto.type == coaType.ACCOUNT_PAYABLE){
coaData.relatedUser = inputCoaDto.relatedUserId;
await inputCoaDto.coaEntityManager.save(coaData)
}
return coaData;
}
async findByUser(id: string, typeOfCoa: coaType) {
try {
return await this.coaRepository.findOneOrFail({ user: id, type: typeOfCoa });
} catch (e) {
if (e instanceof EntityNotFoundError) {
throw new HttpException(
{
statusCode: HttpStatus.NOT_FOUND,
error: 'Data not found',
},
HttpStatus.NOT_FOUND,
);
} else {
throw e;
}
}
}
async findByUserWithRelated(id: string, relatedId: string, typeOfCoa: coaType) {
try {
return await this.coaRepository.findOneOrFail({ user: id, type: typeOfCoa, relatedUser:relatedId });
} catch (e) {
if (e instanceof EntityNotFoundError) {
throw new HttpException(
{
statusCode: HttpStatus.NOT_FOUND,
error: 'Coa Data not found',
},
HttpStatus.NOT_FOUND,
);
} else {
throw e;
}
}
}
async findByName(name: string) {
try {
return await this.coaRepository.findOneOrFail({ name: name });
} catch (e) {
if (e instanceof EntityNotFoundError) {
throw new HttpException(
{
statusCode: HttpStatus.NOT_FOUND,
error: 'COA Data not found',
},
HttpStatus.NOT_FOUND,
);
} else {
throw e;
}
}
}
}

View File

@ -0,0 +1,12 @@
import { IsNotEmpty, IsUUID } from 'class-validator';
import { balanceType, coaType, statusTransaction, typeTransaction } from 'src/helper/enum-list';
import { EntityManager } from 'typeorm';
export class AddSaldoSupplier {
@IsNotEmpty()
supplier?: string;
@IsNotEmpty()
amount?: number;
}

View File

@ -0,0 +1,36 @@
import { IsNotEmpty, IsUUID } from 'class-validator';
import { balanceType, coaType, statusTransaction, typeTransaction } from 'src/helper/enum-list';
import { EntityManager } from 'typeorm';
import { Transactions } from '../entities/transactions.entity';
interface JournalEntry {
coa_id: string;
debit?: number;
credit?: number;
}
export class CreateJournalDto {
@IsNotEmpty()
transactionalEntityManager: EntityManager;
@IsNotEmpty()
createTransaction?: boolean;
@IsNotEmpty()
userId?: string;
@IsNotEmpty()
transaction?: Transactions;
@IsNotEmpty()
type?: typeTransaction;
@IsNotEmpty()
amount?: number;
@IsNotEmpty()
transactionStatus?: statusTransaction;
@IsNotEmpty()
journals: JournalEntry[]
}

View File

@ -1 +0,0 @@
export class CreateTransactionDto {}

View File

@ -0,0 +1,9 @@
import { IsNotEmpty, IsUUID } from 'class-validator';
export class DistributeTransactionDto {
@IsNotEmpty()
amount: number;
@IsNotEmpty()
destination: string;
}

View File

@ -0,0 +1,21 @@
import { IsNotEmpty, IsUUID } from 'class-validator';
import { balanceType, coaType } from 'src/helper/enum-list';
import { User } from 'src/users/entities/user.entity';
import { EntityManager } from 'typeorm';
export class InputCoaDto {
@IsUUID()
user: User;
@IsNotEmpty()
type: coaType;
@IsNotEmpty()
balanceType: balanceType;
@IsUUID()
relatedUserId: string;
@IsNotEmpty()
coaEntityManager: EntityManager;
}

View File

@ -0,0 +1,6 @@
import { IsNotEmpty, IsUUID } from 'class-validator';
export class OrderTransactionDto {
@IsNotEmpty()
productCode: string;
}

View File

@ -1,4 +1,4 @@
import { PartialType } from '@nestjs/mapped-types';
import { CreateTransactionDto } from './create-transaction.dto';
import { DistributeTransactionDto } from './distribute-transaction.dto';
export class UpdateTransactionDto extends PartialType(CreateTransactionDto) {}
export class UpdateTransactionDto extends PartialType(DistributeTransactionDto) {}

View File

@ -0,0 +1,29 @@
import {
Entity,
Column,
} from 'typeorm';
import { BaseModel } from '../../config/basemodel.entity';
import { coaType, balanceType } from '../../helper/enum-list';
@Entity()
export class COA extends BaseModel {
@Column()
name: string;
@Column('text')
type: coaType;
@Column('text')
balanceType: balanceType;
@Column()
amount: number;
@Column()
user: string;
@Column({
nullable:true
})
relatedUser: string;
}

View File

@ -0,0 +1,42 @@
import {
Entity,
Column,
ManyToOne,
ManyToMany,
JoinTable,
OneToOne,
} from 'typeorm';
import { BaseModel } from '../../config/basemodel.entity';
import { COA } from './coa.entity';
import { Transactions } from './transactions.entity';
import { TransactionType } from './transaction-type.entity';
import { balanceType } from '../../helper/enum-list';
@Entity()
export class TransactionJournal extends BaseModel {
@Column('text')
type: balanceType;
@Column()
amount: number;
@OneToOne(
() => {
return Transactions;
},
(trans) => {
return trans.id;
},
)
transaction: Transactions;
@ManyToOne(
() => {
return COA;
},
(coa) => {
return coa.id;
},
)
coa: COA;
}

View File

@ -0,0 +1,11 @@
import {
Entity,
Column,
} from 'typeorm';
import { BaseModel } from '../../config/basemodel.entity';
@Entity()
export class TransactionType extends BaseModel {
@Column()
name: string;
}

View File

@ -1 +0,0 @@
export class Transaction {}

View File

@ -0,0 +1,34 @@
import {
Entity,
Column,
PrimaryGeneratedColumn,
UpdateDateColumn,
DeleteDateColumn,
VersionColumn,
CreateDateColumn,
ManyToOne,
ManyToMany,
JoinTable,
} from 'typeorm';
import { BaseModel } from '../../config/basemodel.entity';
import { statusTransaction, typeTransaction } from '../../helper/enum-list';
@Entity()
export class Transactions extends BaseModel {
@Column()
amount: number;
@Column()
status: statusTransaction;
@Column()
type: typeTransaction;
@Column()
user: string;
@Column({
nullable: true,
})
userDestination: string;
}

View File

@ -11,7 +11,7 @@ import {
Req,
} from '@nestjs/common';
import { TransactionService } from './transaction.service';
import { CreateTransactionDto } from './dto/create-transaction.dto';
import { DistributeTransactionDto } from './dto/distribute-transaction.dto';
import { FastifyRequest } from 'fastify';
@Controller({

View File

@ -6,10 +6,14 @@ import {
Patch,
Param,
Delete,
Request,
HttpStatus
} from '@nestjs/common';
import { TransactionService } from './transaction.service';
import { CreateTransactionDto } from './dto/create-transaction.dto';
import { DistributeTransactionDto } from './dto/distribute-transaction.dto';
import { OrderTransactionDto } from './dto/order-transaction.dto';
import { UpdateTransactionDto } from './dto/update-transaction.dto';
import { AddSaldoSupplier } from './dto/add-saldo-supplier.dto';
@Controller({
path: 'transaction',
@ -18,31 +22,45 @@ import { UpdateTransactionDto } from './dto/update-transaction.dto';
export class TransactionController {
constructor(private readonly transactionService: TransactionService) {}
@Post()
create(@Body() createTransactionDto: CreateTransactionDto) {
return this.transactionService.create(createTransactionDto);
}
@Get()
findAll() {
return this.transactionService.findAll();
}
@Get(':id')
findOne(@Param('id') id: string) {
return this.transactionService.findOne(+id);
}
@Patch(':id')
update(
@Param('id') id: string,
@Body() updateTransactionDto: UpdateTransactionDto,
@Post('distribute')
create(
@Body() createTransactionDto: DistributeTransactionDto,
@Request() req
) {
return this.transactionService.update(+id, updateTransactionDto);
return this.transactionService.distributeDeposit(createTransactionDto,req.user);
}
@Delete(':id')
remove(@Param('id') id: string) {
return this.transactionService.remove(+id);
@Post('distribute-admin')
distributeAdmin(
@Body() createTransactionDto: DistributeTransactionDto,
@Request() req
) {
return {
data: this.transactionService.distributeFromAdmin(createTransactionDto,req.user),
statusCode: HttpStatus.CREATED,
message: 'success',
};
}
@Post('add-saldo-supplier')
async addSaldoSupplier(
@Body() addSaldoSupplier: AddSaldoSupplier,
@Request() req
) {
return {
data: await this.transactionService.addIRSWallet(addSaldoSupplier,req.user),
statusCode: HttpStatus.CREATED,
message: 'success',
};
}
@Post('order')
orderTransaction(
@Body() orderTransactionDto: OrderTransactionDto,
@Request() req
) {
return this.transactionService.orderTransaction(orderTransactionDto,req.user);
}
}

View File

@ -1,10 +1,29 @@
import { Module } from '@nestjs/common';
import { forwardRef, Module } from '@nestjs/common';
import { TransactionService } from './transaction.service';
import { TransactionController } from './transaction.controller';
import { PpobCallbackController } from './ppob_callback.controller';
import { TypeOrmModule } from '@nestjs/typeorm';
import { COA } from './entities/coa.entity';
import { TransactionType } from './entities/transaction-type.entity';
import { TransactionJournal } from './entities/transaction-journal.entity';
import { Transactions } from './entities/transactions.entity';
import { CoaService } from './coa.service';
import { ProductModule } from '../product/product.module';
import { UsersModule } from 'src/users/users.module';
@Module({
imports: [
TypeOrmModule.forFeature([
TransactionType,
COA,
TransactionJournal,
Transactions,
]),
ProductModule,
forwardRef(() => UsersModule),
],
controllers: [TransactionController, PpobCallbackController],
providers: [TransactionService],
providers: [TransactionService, CoaService],
exports:[CoaService]
})
export class TransactionModule {}

View File

@ -1,26 +1,362 @@
import { Injectable } from '@nestjs/common';
import { CreateTransactionDto } from './dto/create-transaction.dto';
import { UpdateTransactionDto } from './dto/update-transaction.dto';
import { HttpException, HttpStatus, Injectable } from '@nestjs/common';
import { DistributeTransactionDto } from './dto/distribute-transaction.dto';
import { OrderTransactionDto } from './dto/order-transaction.dto';
import { InjectRepository } from '@nestjs/typeorm';
import { Transactions } from './entities/transactions.entity';
import { Connection, EntityManager, Repository } from 'typeorm';
import { COA } from './entities/coa.entity';
import { TransactionType } from './entities/transaction-type.entity';
import { TransactionJournal } from './entities/transaction-journal.entity';
import { CoaService } from './coa.service';
import * as uuid from "uuid";
import { uniq } from "lodash";
import {Decimal} from 'decimal.js';
import {
balanceType,
coaType,
statusTransaction,
typeTransaction,
} from '../helper/enum-list';
import { ProductService } from '../product/product.service';
import * as irsService from '../helper/irs-service';
import { CreateJournalDto } from './dto/create-journal.dto';
import { UsersService } from 'src/users/users.service';
import { AddSaldoSupplier } from './dto/add-saldo-supplier.dto';
interface JournalEntry {
coa_id: string;
debit?: string;
credit?: string;
}
@Injectable()
export class TransactionService {
create(createTransactionDto: CreateTransactionDto) {
return 'This action adds a new transaction';
constructor(
@InjectRepository(Transactions)
private transactionRepository: Repository<Transactions>,
@InjectRepository(TransactionType)
private transactionTypeRepository: Repository<TransactionType>,
@InjectRepository(TransactionJournal)
private transactionJournalRepository: Repository<TransactionJournal>,
@InjectRepository(COA)
private coaRepository: Repository<COA>,
private coaService: CoaService,
private productService: ProductService,
private userService: UsersService,
private connection: Connection,
) {}
async addIRSWallet(addSaldoSupplier: AddSaldoSupplier,currentUser:any) {
// GET COA
const coaBank = await this.coaService.findByName(
coaType[coaType.BANK]+'-SYSTEM',
);
const coaInventory = await this.coaService.findByName(
coaType[coaType.INVENTORY]+'-'+addSaldoSupplier.supplier,
);
//GET USER
const userData = await this.userService.findByUsername(currentUser.username);
await this.connection.transaction(async (manager) => {
//INSERT TRANSACTION
let transactionData = new Transactions();
transactionData.id = uuid.v4();
transactionData.amount = addSaldoSupplier.amount,
transactionData.user = userData.id,
transactionData.status = statusTransaction.SUCCESS,
transactionData.type = typeTransaction.DEPOSIT_IRS,
await manager.insert(Transactions, transactionData);
await this.accountingTransaction({
createTransaction: false,
transactionalEntityManager:manager,
transaction: transactionData,
amount: transactionData.amount,
journals: [{
coa_id: coaBank.id,
debit: transactionData.amount
}, {
coa_id: coaInventory.id,
credit: transactionData.amount
}]
});
});
return true;
}
findAll() {
return `This action returns all transaction`;
async distributeFromAdmin(distributeTransactionDto: DistributeTransactionDto,currentUser:any) {
// GET COA
const coaAR = await this.coaService.findByUser(
distributeTransactionDto.destination,
coaType.ACCOUNT_RECEIVABLE,
);
const coaWallet = await this.coaService.findByUser(
distributeTransactionDto.destination,
coaType.WALLET,
);
//GET USER
const userData = await this.userService.findByUsername(currentUser.username);
await this.connection.transaction(async (manager) => {
//INSERT TRANSACTION
let transactionData = new Transactions();
transactionData.id = uuid.v4();
transactionData.amount = distributeTransactionDto.amount,
transactionData.user = userData.id,
transactionData.status = statusTransaction.SUCCESS,
transactionData.type = typeTransaction.DISTRIBUTION,
await manager.insert(Transactions, transactionData);
await this.accountingTransaction({
createTransaction: false,
transactionalEntityManager:manager,
transaction: transactionData,
amount: transactionData.amount,
journals: [{
coa_id: coaAR.id,
debit: transactionData.amount
}, {
coa_id: coaWallet.id,
credit: transactionData.amount
}]
});
});
return true;
}
findOne(id: number) {
return `This action returns a #${id} transaction`;
async distributeDeposit(distributeTransactionDto: DistributeTransactionDto,currentUser:any) {
//GET USER
const userData = await this.userService.findByUsername(currentUser.username);
// GET COA
const coaSenderWallet = await this.coaService.findByUser(
userData.id,
coaType.WALLET,
);
const coaAP = await this.coaService.findByUserWithRelated(
distributeTransactionDto.destination,
userData.id,
coaType.ACCOUNT_PAYABLE,
);
const coaReceiverWallet = await this.coaService.findByUser(
distributeTransactionDto.destination,
coaType.WALLET,
);
const coaAR = await this.coaService.findByUserWithRelated(
distributeTransactionDto.destination,
userData.id,
coaType.ACCOUNT_RECEIVABLE,
);
await this.connection.transaction(async (manager) => {
let transactionData = new Transactions();
transactionData.id = uuid.v4();
transactionData.amount = distributeTransactionDto.amount,
transactionData.user = userData.id,
transactionData.status = statusTransaction.SUCCESS,
transactionData.type = typeTransaction.DISTRIBUTION,
await manager.insert(Transactions, transactionData);
await this.accountingTransaction({
createTransaction: false,
transactionalEntityManager:manager,
transaction: transactionData,
amount: transactionData.amount,
journals: [{
coa_id: coaSenderWallet.id,
debit: transactionData.amount
}, {
coa_id: coaReceiverWallet.id,
credit: transactionData.amount
}, {
coa_id: coaAR.id,
debit: transactionData.amount
}, {
coa_id: coaAP.id,
credit: transactionData.amount
}]
});
});
return true;
}
update(id: number, updateTransactionDto: UpdateTransactionDto) {
return `This action updates a #${id} transaction`;
async orderTransaction(orderTransactionDto: OrderTransactionDto,currentUser:any) {
//GET PRODUCT
const product = await this.productService.findOne(
orderTransactionDto.productCode,
);
//GET USER
const userData = await this.userService.findByUsername(currentUser.username);
let supervisorData = [];
if(userData.superior != null){
supervisorData.push(await this.userService.findByUsername(currentUser.username));
if(supervisorData[0].superior != null){
supervisorData.push(await this.userService.findByUsername(currentUser.username));
if(supervisorData[0].superior != null){
supervisorData.push(await this.userService.findByUsername(currentUser.username));
}
}
}
//GET COA
const coaAccount = await this.coaService.findByUser(
userData.id,
coaType.WALLET,
);
const coaInventory = await this.coaService.findByName(
coaType[coaType.INVENTORY]+'-IRS',
);
const coaCostOfSales = await this.coaService.findByName(
coaType[coaType.COST_OF_SALES]+'-SYSTEM',
);
const coaSales = await this.coaService.findByName(
coaType[coaType.SALES]+'-SYSTEM',
);
const coaExpense = await this.coaService.findByName(
coaType[coaType.EXPENSE]+'-SYSTEM',
);
supervisorData = supervisorData.map(async it =>{
const coaAccount = await this.coaService.findByUser(
it.id,
coaType.WALLET,
);
return {
coa_id: coaAccount.id,
credit: 0
}
})
if (coaAccount.amount <= product.price) {
throw new HttpException(
{
statusCode: HttpStatus.INTERNAL_SERVER_ERROR,
error: `Transaction Failed because saldo not enough`,
},
HttpStatus.INTERNAL_SERVER_ERROR,
);
}
try {
await this.connection.transaction(async (manager) => {
let transactionData = new Transactions();
transactionData.id = uuid.v4();
transactionData.amount = product.price,
transactionData.user = userData.id,
transactionData.status = statusTransaction.SUCCESS,
transactionData.type = typeTransaction.DISTRIBUTION,
await manager.insert(Transactions, transactionData);
await this.accountingTransaction({
createTransaction: false,
transactionalEntityManager:manager,
transaction: transactionData,
amount: transactionData.amount,
journals: [{
coa_id: coaInventory.id,
credit: product.basePrice
}, {
coa_id: coaCostOfSales.id,
debit: product.basePrice
}, {
coa_id: coaAccount.id,
debit: product.price
}, {
coa_id: coaSales.id,
credit: product.price
},{
coa_id: coaExpense.id,
credit: 0
}].concat(supervisorData)
});
});
} catch (e) {
throw e;
}
return true;
}
remove(id: number) {
return `This action removes a #${id} transaction`;
async accountingTransaction(createJournalDto: CreateJournalDto ) {
let creditSum = createJournalDto.journals.map(it => it.credit).filter(it => it).reduce((a, b) => a.plus(b), new Decimal(0));
let debitSum = createJournalDto.journals.map(it => it.debit).filter(it => it).reduce((a, b) => a.plus(b), new Decimal(0));
let coaIds = uniq(createJournalDto.journals.map(it => it.coa_id));
if (!creditSum.equals(debitSum)) {
throw new Error(`credit and debit doesn't match`);
}
const coas = await this.coaRepository.findByIds(coaIds);
console.log("berhasil")
const transaction = createJournalDto.transaction
await Promise.all(createJournalDto.journals.map(journal => {
const coa = coas.find(it => it.id === journal.coa_id);
if (!coa) {
throw new Error(`coa ${journal.coa_id} not found`);
}
const journalEntry = new TransactionJournal();
journalEntry.coa = coa;
journalEntry.type = journal.debit ? balanceType.DEBIT : balanceType.CREDIT;
journalEntry.amount = (journal.debit) ? journal.debit : journal.credit;
journalEntry.transaction = transaction;
return this.transactionJournalRepository.save(journalEntry);
}));
await Promise.all(coaIds.map(coaId => {
let journalPerCoa = createJournalDto.journals.filter(journal => journal.coa_id == coaId);
let creditSum = journalPerCoa.map(it => it.credit).filter(it => it).reduce((a, b) => a.plus(b), new Decimal(0));
let debitSum = journalPerCoa.map(it => it.debit).filter(it => it).reduce((a, b) => a.plus(b), new Decimal(0));
let coa = coas.find(it => it.id.toLowerCase() === coaId.toLowerCase());
let balance = new Decimal(coa.amount);
if (coa.balanceType === balanceType.DEBIT) {
balance = balance.plus(debitSum.minus(creditSum));
} else if (coa.balanceType === balanceType.CREDIT) {
balance = balance.plus(creditSum.minus(debitSum));
}
const diff = balance.minus(new Decimal(coa.amount));
return createJournalDto.transactionalEntityManager
.createQueryBuilder()
.update(COA)
.set({
amount: () => "amount + " + diff.toString()
})
.where("id = :id", { id: coa.id })
.execute();
}));
return transaction;
}
}

View File

@ -1,9 +1,21 @@
import { IsNotEmpty } from 'class-validator';
import { IsNotEmpty, IsOptional, IsUUID, ValidateIf } from 'class-validator';
export class CreateUserDto {
@IsNotEmpty()
firstName: string;
username: string;
@IsNotEmpty()
lastName: string;
password: string;
@IsUUID()
roleId: string;
@IsNotEmpty()
superior: boolean;
// @ValidateIf((o) => {
// return !!o.superior;
// })
// @IsUUID()
// superior: string;
}

View File

@ -1,45 +1,46 @@
import { Roles } from 'src/configurable/entities/roles.entity';
import {
Entity,
Column,
PrimaryGeneratedColumn,
UpdateDateColumn,
DeleteDateColumn,
VersionColumn,
CreateDateColumn,
PrimaryGeneratedColumn, ManyToOne,
} from 'typeorm';
import { BaseModel } from '../../config/basemodel.entity';
import { hashPassword } from '../../helper/hash_password';
@Entity()
export class User {
export class User extends BaseModel {
@PrimaryGeneratedColumn('uuid')
id: string;
@Column()
firstName: string;
username: string;
@Column()
lastName: string;
password: string;
@Column()
salt: string;
@Column({ default: true })
isActive: boolean;
@CreateDateColumn({
type: 'timestamp with time zone',
nullable: false,
})
createdAt: Date;
@UpdateDateColumn({
type: 'timestamp with time zone',
nullable: false,
})
updatedAt: Date;
@DeleteDateColumn({
type: 'timestamp with time zone',
nullable: true,
})
deletedAt: Date;
@VersionColumn()
version: number;
@ManyToOne(
() => {
return User;
},
(user) => {
return user.id;
},
)
superior: User;
@ManyToOne(
() => {
return Roles;
},
(roles) => {
return roles.id;
},
)
roles: Roles;
}

View File

@ -8,10 +8,13 @@ import {
Delete,
ParseUUIDPipe,
HttpStatus,
Query,
Request
} from '@nestjs/common';
import { UsersService } from './users.service';
import { CreateUserDto } from './dto/create-user.dto';
import { UpdateUserDto } from './dto/update-user.dto';
import {Public} from "../auth/public.decorator";
@Controller({
path: 'users',
@ -21,17 +24,21 @@ export class UsersController {
constructor(private readonly usersService: UsersService) {}
@Post()
async create(@Body() createUserDto: CreateUserDto) {
async create(
@Request() req,
@Body() createUserDto: CreateUserDto
) {
return {
data: await this.usersService.create(createUserDto),
data: await this.usersService.create(createUserDto,req.user),
statusCode: HttpStatus.CREATED,
message: 'success',
};
}
@Public()
@Get()
async findAll() {
const [data, count] = await this.usersService.findAll();
async findAll(@Query('page') page: number) {
const [data, count] = await this.usersService.findAll(page);
return {
data,
@ -41,6 +48,18 @@ export class UsersController {
};
}
@Get('find-by-roles/:id')
async findByRoles(
@Param('id', ParseUUIDPipe) id: string,
@Query('page') page: number
) {
return {
data: await this.usersService.findByRoles(id,page),
statusCode: HttpStatus.OK,
message: 'success',
};
}
@Get(':id')
async findOne(@Param('id', ParseUUIDPipe) id: string) {
return {

View File

@ -1,12 +1,15 @@
import { Module } from '@nestjs/common';
import { forwardRef, Module } from '@nestjs/common';
import { TypeOrmModule } from '@nestjs/typeorm';
import { UsersService } from './users.service';
import { UsersController } from './users.controller';
import { User } from './entities/user.entity';
import { TransactionModule } from 'src/transaction/transaction.module';
import { ConfigurableModule } from 'src/configurable/configurable.module';
@Module({
imports: [TypeOrmModule.forFeature([User])],
imports: [TypeOrmModule.forFeature([User]), forwardRef(() => TransactionModule), ConfigurableModule],
controllers: [UsersController],
providers: [UsersService],
exports: [UsersService],
})
export class UsersModule {}

View File

@ -1,30 +1,150 @@
import { HttpException, HttpStatus, Injectable } from '@nestjs/common';
import { forwardRef, HttpException, HttpStatus, Inject, Injectable } from '@nestjs/common';
import { CreateUserDto } from './dto/create-user.dto';
import { UpdateUserDto } from './dto/update-user.dto';
import { EntityNotFoundError, Repository } from 'typeorm';
import { Connection, EntityNotFoundError, Repository } from 'typeorm';
import { User } from './entities/user.entity';
import { InjectRepository } from '@nestjs/typeorm';
import { randomStringGenerator } from '@nestjs/common/utils/random-string-generator.util';
import { hashPassword } from '../helper/hash_password';
import { CoaService } from 'src/transaction/coa.service';
import { balanceType, coaType } from 'src/helper/enum-list';
import { RoleService } from 'src/configurable/roles.service';
import { InputCoaDto } from 'src/transaction/dto/input-coa.dto';
import * as uuid from "uuid";
@Injectable()
export class UsersService {
constructor(
@InjectRepository(User)
private usersRepository: Repository<User>,
@Inject(forwardRef(() => CoaService))
private coaService: CoaService,
private roleService: RoleService,
private connection: Connection,
) {}
async create(createUserDto: CreateUserDto) {
const result = await this.usersRepository.insert(createUserDto);
async create(createUserDto: CreateUserDto, currentUser: any) {
const roles = await this.roleService.findOne(createUserDto.roleId);
const superior = await this.findByUsername(currentUser.username);
const salt = randomStringGenerator();
let userData = new User();
userData.id = uuid.v4();
userData.username = createUserDto.username,
userData.password = await hashPassword(createUserDto.password, salt),
userData.salt = salt,
userData.superior = superior,
userData.roles = roles
return this.usersRepository.findOneOrFail(result.identifiers[0].id);
await this.connection.transaction(async (manager) => {
const result = await manager.insert(User,userData);
let dataCoaWallet = new InputCoaDto();
dataCoaWallet.user = userData;
dataCoaWallet.balanceType = balanceType.CREDIT;
dataCoaWallet.type = coaType.WALLET;
dataCoaWallet.coaEntityManager = manager;
if(createUserDto.superior){
let dataCoaAP = new InputCoaDto();
dataCoaAP.user = userData;
dataCoaAP.balanceType = balanceType.CREDIT;
dataCoaAP.relatedUserId = superior.id;
dataCoaAP.type = coaType.ACCOUNT_PAYABLE;
dataCoaAP.coaEntityManager = manager;
let dataCoaAR = new InputCoaDto();
dataCoaAR.user = userData;
dataCoaAR.balanceType = balanceType.DEBIT;
dataCoaAR.relatedUserId = superior.id;
dataCoaAR.type = coaType.ACCOUNT_RECEIVABLE;
dataCoaAR.coaEntityManager = manager;
await this.coaService.create(dataCoaAP);
await this.coaService.create(dataCoaAR);
}
await this.coaService.create(dataCoaWallet);
})
return userData;
}
findAll() {
return this.usersRepository.findAndCount();
findAll(page:number) {
return this.usersRepository.findAndCount({
skip: page * 10,
take: 10,
order: {
version: 'DESC',
},
});
}
findByRoles(relationId:string,page:number) {
return this.usersRepository.findAndCount({
skip: page * 10,
take: 10,
where:{
roles:relationId
},
order: {
updatedAt: 'DESC',
},
});
}
async findExist(id: string) {
try {
return await this.usersRepository.findOneOrFail(id);
} catch (e) {
if (e instanceof EntityNotFoundError) {
throw new HttpException(
{
statusCode: HttpStatus.NOT_FOUND,
error: 'Data not found',
},
HttpStatus.NOT_FOUND,
);
} else {
throw e;
}
}
}
async findByUsername(username: string) {
try {
return await this.usersRepository.findOneOrFail({
username:username
});
} catch (e) {
if (e instanceof EntityNotFoundError) {
throw new HttpException(
{
statusCode: HttpStatus.NOT_FOUND,
error: 'Data not found',
},
HttpStatus.NOT_FOUND,
);
} else {
throw e;
}
}
}
async findOne(id: string) {
const coa = await this.coaService.findByUser(id,coaType.WALLET);
try {
return await this.usersRepository.findOneOrFail(id);
const userData = await this.usersRepository.findOneOrFail({
where: {
id: id
},
relations:["roles","superior"]
});
return {
...userData,
wallet:coa.amount,
};
} catch (e) {
if (e instanceof EntityNotFoundError) {
throw new HttpException(
@ -57,7 +177,7 @@ export class UsersService {
}
}
const result = await this.usersRepository.update(id, updateUserDto);
// const result = await this.usersRepository.update(id, updateUserDto);
return this.usersRepository.findOneOrFail(id);
}
@ -81,4 +201,12 @@ export class UsersService {
await this.usersRepository.delete(id);
}
async findOneByUsername(username: string) {
return this.usersRepository.findOneOrFail({
where: {
username,
},
});
}
}

192
yarn.lock
View File

@ -675,11 +675,24 @@
tslib "2.3.1"
uuid "8.3.2"
"@nestjs/jwt@^8.0.0":
version "8.0.0"
resolved "https://registry.yarnpkg.com/@nestjs/jwt/-/jwt-8.0.0.tgz#6c811c17634252dd1dcd5dabf409dbd692b812da"
integrity sha512-fz2LQgYY2zmuD8S+8UE215anwKyXlnB/1FwJQLVR47clNfMeFMK8WCxmn6xdPhF5JKuV1crO6FVabb1qWzDxqQ==
dependencies:
"@types/jsonwebtoken" "8.5.4"
jsonwebtoken "8.5.1"
"@nestjs/mapped-types@*":
version "1.0.0"
resolved "https://registry.npmjs.org/@nestjs/mapped-types/-/mapped-types-1.0.0.tgz"
integrity sha512-26AW5jHadLXtvHs+M+Agd9KZ92dDlBrmD0rORlBlvn2KvsWs4JRaKl2mUsrW7YsdZeAu3Hc4ukqyYyDdyCmMWQ==
"@nestjs/passport@^8.0.1":
version "8.0.1"
resolved "https://registry.yarnpkg.com/@nestjs/passport/-/passport-8.0.1.tgz#f1ed39a19489f794d1fe3fef592b4523bc48da68"
integrity sha512-vn/ZJLXQKvSf9D0BvEoNFJLfzl9AVqfGtDyQMfWDLbaNpoEB2FyeaHGxdiX6H71oLSrQV78c/yuhfantzwdjdg==
"@nestjs/platform-express@^8.0.0":
version "8.2.3"
resolved "https://registry.npmjs.org/@nestjs/platform-express/-/platform-express-8.2.3.tgz"
@ -904,7 +917,7 @@
"@types/qs" "*"
"@types/range-parser" "*"
"@types/express@^4.17.13":
"@types/express@*", "@types/express@^4.17.13":
version "4.17.13"
resolved "https://registry.yarnpkg.com/@types/express/-/express-4.17.13.tgz#a76e2995728999bab51a33fabce1d705a3709034"
integrity sha512-6bSZTPaTIACxn48l50SR+axgrqm6qXFIxrdAKaG6PaJk3+zuUr35hBlgT7vOmJcum+OEaIBLtHV/qloEAFITeA==
@ -958,6 +971,20 @@
resolved "https://registry.yarnpkg.com/@types/json5/-/json5-0.0.29.tgz#ee28707ae94e11d2b827bcbe5270bcea7f3e71ee"
integrity sha1-7ihweulOEdK4J7y+UnC86n8+ce4=
"@types/jsonwebtoken@*":
version "8.5.6"
resolved "https://registry.yarnpkg.com/@types/jsonwebtoken/-/jsonwebtoken-8.5.6.tgz#1913e5a61e70a192c5a444623da4901a7b1a9d42"
integrity sha512-+P3O/xC7nzVizIi5VbF34YtqSonFsdnbXBnWUCYRiKOi1f9gA4sEFvXkrGr/QVV23IbMYvcoerI7nnhDUiWXRQ==
dependencies:
"@types/node" "*"
"@types/jsonwebtoken@8.5.4":
version "8.5.4"
resolved "https://registry.yarnpkg.com/@types/jsonwebtoken/-/jsonwebtoken-8.5.4.tgz#50ccaf0aa6f5d7b9956e70fe323b76e582991913"
integrity sha512-4L8msWK31oXwdtC81RmRBAULd0ShnAHjBuKT9MRQpjP0piNrZdXyTRcKY9/UIfhGeKIT4PvF5amOOUbbT/9Wpg==
dependencies:
"@types/node" "*"
"@types/mime@^1":
version "1.3.2"
resolved "https://registry.yarnpkg.com/@types/mime/-/mime-1.3.2.tgz#93e25bf9ee75fe0fd80b594bc4feb0e862111b5a"
@ -973,6 +1000,39 @@
resolved "https://registry.yarnpkg.com/@types/parse-json/-/parse-json-4.0.0.tgz#2f8bb441434d163b35fb8ffdccd7138927ffb8c0"
integrity sha512-//oorEZjL6sbPcKUaCdIGlIUeH26mgzimjBB77G6XRgnDl/L5wOnpyBGRe/Mmf5CVW3PwEBE1NjiMZ/ssFh4wA==
"@types/passport-jwt@^3.0.6":
version "3.0.6"
resolved "https://registry.yarnpkg.com/@types/passport-jwt/-/passport-jwt-3.0.6.tgz#41cc8b5803d5f5f06eb33e19c453b42716def4f1"
integrity sha512-cmAAMIRTaEwpqxlrZyiEY9kdibk94gP5KTF8AT1Ra4rWNZYHNMreqhKUEeC5WJtuN5SJZjPQmV+XO2P5PlnvNQ==
dependencies:
"@types/express" "*"
"@types/jsonwebtoken" "*"
"@types/passport-strategy" "*"
"@types/passport-local@^1.0.34":
version "1.0.34"
resolved "https://registry.yarnpkg.com/@types/passport-local/-/passport-local-1.0.34.tgz#84d3b35b2fd4d36295039ded17fe5f3eaa62f4f6"
integrity sha512-PSc07UdYx+jhadySxxIYWuv6sAnY5e+gesn/5lkPKfBeGuIYn9OPR+AAEDq73VRUh6NBTpvE/iPE62rzZUslog==
dependencies:
"@types/express" "*"
"@types/passport" "*"
"@types/passport-strategy" "*"
"@types/passport-strategy@*":
version "0.2.35"
resolved "https://registry.yarnpkg.com/@types/passport-strategy/-/passport-strategy-0.2.35.tgz#e52f5212279ea73f02d9b06af67efe9cefce2d0c"
integrity sha512-o5D19Jy2XPFoX2rKApykY15et3Apgax00RRLf0RUotPDUsYrQa7x4howLYr9El2mlUApHmCMv5CZ1IXqKFQ2+g==
dependencies:
"@types/express" "*"
"@types/passport" "*"
"@types/passport@*":
version "1.0.7"
resolved "https://registry.yarnpkg.com/@types/passport/-/passport-1.0.7.tgz#85892f14932168158c86aecafd06b12f5439467a"
integrity sha512-JtswU8N3kxBYgo+n9of7C97YQBT+AYPP2aBfNGTzABqPAZnK/WOAaKfh3XesUYMZRrXFuoPc2Hv0/G/nQFveHw==
dependencies:
"@types/express" "*"
"@types/prettier@^2.1.5":
version "2.4.2"
resolved "https://registry.yarnpkg.com/@types/prettier/-/prettier-2.4.2.tgz#4c62fae93eb479660c3bd93f9d24d561597a8281"
@ -1475,7 +1535,7 @@ avvio@^7.1.2:
fastq "^1.6.1"
queue-microtask "^1.1.2"
axios@0.24.0:
axios@0.24.0, axios@^0.24.0:
version "0.24.0"
resolved "https://registry.npmjs.org/axios/-/axios-0.24.0.tgz"
integrity sha512-Q6cWsys88HoPgAaFAVUb0WpPk0O8iTeisR9IMqy9G8AbO4NlpVknrnQS03zzF9PGAWgO3cgletO3VjV/P7VztA==
@ -1628,6 +1688,11 @@ bser@2.1.1:
dependencies:
node-int64 "^0.4.0"
buffer-equal-constant-time@1.0.1:
version "1.0.1"
resolved "https://registry.yarnpkg.com/buffer-equal-constant-time/-/buffer-equal-constant-time-1.0.1.tgz#f8e71132f7ffe6e01a5c9697a4c6f3e48d5cc819"
integrity sha1-+OcRMvf/5uAaXJaXpMbz5I1cyBk=
buffer-from@^1.0.0:
version "1.1.2"
resolved "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.2.tgz"
@ -1996,6 +2061,11 @@ cross-spawn@^7.0.0, cross-spawn@^7.0.2, cross-spawn@^7.0.3:
shebang-command "^2.0.0"
which "^2.0.1"
crypto@^1.0.1:
version "1.0.1"
resolved "https://registry.yarnpkg.com/crypto/-/crypto-1.0.1.tgz#2af1b7cad8175d24c8a1b0778255794a21803037"
integrity sha512-VxBKmeNcqQdiUQUW2Tzq0t377b54N2bMtXO/qiLa+6eRRmmC4qT3D4OnTGoT/U6O9aklQ/jTwbOtRMTTY8G0Ig==
cssom@^0.4.4:
version "0.4.4"
resolved "https://registry.yarnpkg.com/cssom/-/cssom-0.4.4.tgz#5a66cf93d2d0b661d80bf6a44fb65f5c2e4e0a10"
@ -2147,6 +2217,13 @@ duplexify@^4.1.2:
readable-stream "^3.1.1"
stream-shift "^1.0.0"
ecdsa-sig-formatter@1.0.11:
version "1.0.11"
resolved "https://registry.yarnpkg.com/ecdsa-sig-formatter/-/ecdsa-sig-formatter-1.0.11.tgz#ae0f0fa2d85045ef14a817daa3ce9acd0489e5bf"
integrity sha512-nagl3RYrbNv6kQkeJIpt6NJZy8twLB/2vtz6yN9Z4vRKHN4/QZJIEbqohALSgwKdnksuY3k5Addp5lg8sVoVcQ==
dependencies:
safe-buffer "^5.0.1"
ee-first@1.1.1:
version "1.1.1"
resolved "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz"
@ -3742,6 +3819,39 @@ jsonfile@^6.0.1:
optionalDependencies:
graceful-fs "^4.1.6"
jsonwebtoken@8.5.1, jsonwebtoken@^8.2.0:
version "8.5.1"
resolved "https://registry.yarnpkg.com/jsonwebtoken/-/jsonwebtoken-8.5.1.tgz#00e71e0b8df54c2121a1f26137df2280673bcc0d"
integrity sha512-XjwVfRS6jTMsqYs0EsuJ4LGxXV14zQybNd4L2r0UvbVnSF9Af8x7p5MzbJ90Ioz/9TI41/hTCvznF/loiSzn8w==
dependencies:
jws "^3.2.2"
lodash.includes "^4.3.0"
lodash.isboolean "^3.0.3"
lodash.isinteger "^4.0.4"
lodash.isnumber "^3.0.3"
lodash.isplainobject "^4.0.6"
lodash.isstring "^4.0.1"
lodash.once "^4.0.0"
ms "^2.1.1"
semver "^5.6.0"
jwa@^1.4.1:
version "1.4.1"
resolved "https://registry.yarnpkg.com/jwa/-/jwa-1.4.1.tgz#743c32985cb9e98655530d53641b66c8645b039a"
integrity sha512-qiLX/xhEEFKUAJ6FiBMbes3w9ATzyk5W7Hvzpa/SLYdxNtng+gcurvrI7TbACjIXlsJyr05/S1oUhZrc63evQA==
dependencies:
buffer-equal-constant-time "1.0.1"
ecdsa-sig-formatter "1.0.11"
safe-buffer "^5.0.1"
jws@^3.2.2:
version "3.2.2"
resolved "https://registry.yarnpkg.com/jws/-/jws-3.2.2.tgz#001099f3639468c9414000e99995fa52fb478304"
integrity sha512-YHlZCB6lMTllWDtSPHz/ZXTsi8S00usEV6v1tjq8tOUZzw7DpSDWVXjXDre6ed1w/pd495ODpHZYSdkRTsa0HA==
dependencies:
jwa "^1.4.1"
safe-buffer "^5.0.1"
kleur@^3.0.3:
version "3.0.3"
resolved "https://registry.yarnpkg.com/kleur/-/kleur-3.0.3.tgz#a79c9ecc86ee1ce3fa6206d1216c501f147fc07e"
@ -3820,6 +3930,36 @@ lodash.has@4.5.2:
resolved "https://registry.npmjs.org/lodash.has/-/lodash.has-4.5.2.tgz"
integrity sha1-0Z9NwQlQWMzL4rDN9O4P5Ko3yGI=
lodash.includes@^4.3.0:
version "4.3.0"
resolved "https://registry.yarnpkg.com/lodash.includes/-/lodash.includes-4.3.0.tgz#60bb98a87cb923c68ca1e51325483314849f553f"
integrity sha1-YLuYqHy5I8aMoeUTJUgzFISfVT8=
lodash.isboolean@^3.0.3:
version "3.0.3"
resolved "https://registry.yarnpkg.com/lodash.isboolean/-/lodash.isboolean-3.0.3.tgz#6c2e171db2a257cd96802fd43b01b20d5f5870f6"
integrity sha1-bC4XHbKiV82WgC/UOwGyDV9YcPY=
lodash.isinteger@^4.0.4:
version "4.0.4"
resolved "https://registry.yarnpkg.com/lodash.isinteger/-/lodash.isinteger-4.0.4.tgz#619c0af3d03f8b04c31f5882840b77b11cd68343"
integrity sha1-YZwK89A/iwTDH1iChAt3sRzWg0M=
lodash.isnumber@^3.0.3:
version "3.0.3"
resolved "https://registry.yarnpkg.com/lodash.isnumber/-/lodash.isnumber-3.0.3.tgz#3ce76810c5928d03352301ac287317f11c0b1ffc"
integrity sha1-POdoEMWSjQM1IwGsKHMX8RwLH/w=
lodash.isplainobject@^4.0.6:
version "4.0.6"
resolved "https://registry.yarnpkg.com/lodash.isplainobject/-/lodash.isplainobject-4.0.6.tgz#7c526a52d89b45c45cc690b88163be0497f550cb"
integrity sha1-fFJqUtibRcRcxpC4gWO+BJf1UMs=
lodash.isstring@^4.0.1:
version "4.0.1"
resolved "https://registry.yarnpkg.com/lodash.isstring/-/lodash.isstring-4.0.1.tgz#d527dfb5456eca7cc9bb95d5daeaf88ba54a5451"
integrity sha1-1SfftUVuynzJu5XV2ur4i6VKVFE=
lodash.memoize@4.x:
version "4.1.2"
resolved "https://registry.yarnpkg.com/lodash.memoize/-/lodash.memoize-4.1.2.tgz#bcc6c49a42a2840ed997f323eada5ecd182e0bfe"
@ -3830,6 +3970,11 @@ lodash.merge@^4.6.2:
resolved "https://registry.yarnpkg.com/lodash.merge/-/lodash.merge-4.6.2.tgz#558aa53b43b661e1925a0afdfa36a9a1085fe57a"
integrity sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==
lodash.once@^4.0.0:
version "4.1.1"
resolved "https://registry.yarnpkg.com/lodash.once/-/lodash.once-4.1.1.tgz#0dd3971213c7c56df880977d504c88fb471a97ac"
integrity sha1-DdOXEhPHxW34gJd9UEyI+0cal6w=
lodash.set@4.3.2:
version "4.3.2"
resolved "https://registry.npmjs.org/lodash.set/-/lodash.set-4.3.2.tgz"
@ -4006,6 +4151,11 @@ ms@2.1.2:
resolved "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz"
integrity sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==
ms@^2.1.1:
version "2.1.3"
resolved "https://registry.yarnpkg.com/ms/-/ms-2.1.3.tgz#574c8138ce1d2b5861f0b44579dbadd60c6615b2"
integrity sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==
multer@1.4.3:
version "1.4.3"
resolved "https://registry.npmjs.org/multer/-/multer-1.4.3.tgz"
@ -4266,6 +4416,34 @@ parseurl@~1.3.3:
resolved "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz"
integrity sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==
passport-jwt@^4.0.0:
version "4.0.0"
resolved "https://registry.yarnpkg.com/passport-jwt/-/passport-jwt-4.0.0.tgz#7f0be7ba942e28b9f5d22c2ebbb8ce96ef7cf065"
integrity sha512-BwC0n2GP/1hMVjR4QpnvqA61TxenUMlmfNjYNgK0ZAs0HK4SOQkHcSv4L328blNTLtHq7DbmvyNJiH+bn6C5Mg==
dependencies:
jsonwebtoken "^8.2.0"
passport-strategy "^1.0.0"
passport-local@^1.0.0:
version "1.0.0"
resolved "https://registry.yarnpkg.com/passport-local/-/passport-local-1.0.0.tgz#1fe63268c92e75606626437e3b906662c15ba6ee"
integrity sha1-H+YyaMkudWBmJkN+O5BmYsFbpu4=
dependencies:
passport-strategy "1.x.x"
passport-strategy@1.x.x, passport-strategy@^1.0.0:
version "1.0.0"
resolved "https://registry.yarnpkg.com/passport-strategy/-/passport-strategy-1.0.0.tgz#b5539aa8fc225a3d1ad179476ddf236b440f52e4"
integrity sha1-tVOaqPwiWj0a0XlHbd8ja0QPUuQ=
passport@^0.5.0:
version "0.5.0"
resolved "https://registry.yarnpkg.com/passport/-/passport-0.5.0.tgz#7914aaa55844f9dce8c3aa28f7d6b73647ee0169"
integrity sha512-ln+ue5YaNDS+fes6O5PCzXKSseY5u8MYhX9H5Co4s+HfYI5oqvnHKoOORLYDUPh+8tHvrxugF2GFcUA1Q1Gqfg==
dependencies:
passport-strategy "1.x.x"
pause "0.0.1"
path-exists@^4.0.0:
version "4.0.0"
resolved "https://registry.yarnpkg.com/path-exists/-/path-exists-4.0.0.tgz#513bdbe2d3b95d7762e8c1137efa195c6c61b5b3"
@ -4306,6 +4484,11 @@ path-type@^4.0.0:
resolved "https://registry.yarnpkg.com/path-type/-/path-type-4.0.0.tgz#84ed01c0a7ba380afe09d90a8c180dcd9d03043b"
integrity sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw==
pause@0.0.1:
version "0.0.1"
resolved "https://registry.yarnpkg.com/pause/-/pause-0.0.1.tgz#1d408b3fdb76923b9543d96fb4c9dfd535d9cb5d"
integrity sha1-HUCLP9t2kjuVQ9lvtMnf1TXZy10=
pg-connection-string@^2.5.0:
version "2.5.0"
resolved "https://registry.npmjs.org/pg-connection-string/-/pg-connection-string-2.5.0.tgz"
@ -4832,6 +5015,11 @@ semver@7.x, semver@^7.2.1, semver@^7.3.2, semver@^7.3.4, semver@^7.3.5:
dependencies:
lru-cache "^6.0.0"
semver@^5.6.0:
version "5.7.1"
resolved "https://registry.yarnpkg.com/semver/-/semver-5.7.1.tgz#a954f931aeba508d307bbf069eff0c01c96116f7"
integrity sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==
semver@^6.0.0, semver@^6.3.0:
version "6.3.0"
resolved "https://registry.yarnpkg.com/semver/-/semver-6.3.0.tgz#ee0a64c8af5e8ceea67687b133761e1becbd1d3d"