sales impemented

pull/1/head
Aji Kamaludin 3 years ago
parent cef61a6368
commit 2fd0f7cf6b
No known key found for this signature in database
GPG Key ID: 670E1F26AD5A8099

@ -31,6 +31,10 @@ exports.up = (pgm) => {
type: 'numeric(16,2)',
notNull: false,
},
created_by: {
type: 'uuid',
notNull: true,
},
created_at: {
type: 'timestamp',
notNull: true,
@ -49,6 +53,11 @@ exports.up = (pgm) => {
columns: 'office_id',
onDelete: 'CASCADE',
},
{
references: 'users(id)',
columns: 'created_by',
onDelete: 'CASCADE',
},
],
},
});
@ -57,6 +66,7 @@ exports.up = (pgm) => {
id: {
type: 'uuid',
primaryKey: true,
default: pgm.func('uuid_generate_v4()'),
},
sale_id: {
type: 'uuid',
@ -130,6 +140,10 @@ exports.up = (pgm) => {
type: 'numeric(16,2)',
notNull: false,
},
created_by: {
type: 'uuid',
notNull: true,
},
created_at: {
type: 'timestamp',
notNull: true,
@ -148,6 +162,11 @@ exports.up = (pgm) => {
columns: 'office_id',
onDelete: 'CASCADE',
},
{
references: 'users(id)',
columns: 'created_by',
onDelete: 'CASCADE',
},
],
},
});
@ -156,6 +175,7 @@ exports.up = (pgm) => {
id: {
type: 'uuid',
primaryKey: true,
default: pgm.func('uuid_generate_v4()'),
},
purchase_id: {
type: 'uuid',

@ -21,6 +21,7 @@ class AuthenticationsHandler {
const refreshToken = this._tokenManager.generateRefreshToken({ id, companyId });
await this._authenticationsService.addRefreshToken(refreshToken);
const user = await this._usersService.getMe(id);
const response = h.response({
status: 'success',
@ -28,6 +29,7 @@ class AuthenticationsHandler {
data: {
accessToken,
refreshToken,
user,
},
});
response.code(201);

@ -0,0 +1,74 @@
class SalesHandler {
constructor(service, validator) {
this._service = service;
this._validator = validator;
this.postSaleHandler = this.postSaleHandler.bind(this);
this.getSalesHandler = this.getSalesHandler.bind(this);
this.getSaleByIdHandler = this.getSaleByIdHandler.bind(this);
}
async postSaleHandler(request, h) {
try {
this._validator.validatePostSalePayload(request.payload);
const { id: userId } = request.auth.credentials;
const {
date, invoice, description, amount, discount, items, officeId,
} = request.payload;
const saleId = await this._service.createTransaction({
date, invoice, description, amount, discount, items, userId, officeId,
});
const response = h.response({
status: 'success',
message: 'transaksi ditambahkan',
data: {
saleId,
},
});
response.code(201);
return response;
} catch (error) {
return error;
}
}
async getSalesHandler(request) {
try {
this._validator.validateGetSalesPayload(request.query);
const { companyId } = request.auth.credentials;
const { startDate, endDate } = request.query;
const sales = await this._service.getSales(companyId, { startDate, endDate });
return {
status: 'success',
data: {
sales,
},
};
} catch (error) {
return error;
}
}
async getSaleByIdHandler(request) {
try {
const { id: saleId } = request.params;
const sale = await this._service.getSaleById(saleId);
return {
status: 'success',
data: {
sale,
},
};
} catch (error) {
return error;
}
}
}
module.exports = SalesHandler;

@ -0,0 +1,11 @@
const SalesHandler = require('./handler');
const routes = require('./routes');
module.exports = {
name: 'sales',
version: '1.0.0',
register: async (server, { service, validator }) => {
const salesHandler = new SalesHandler(service, validator);
server.route(routes(salesHandler));
},
};

@ -0,0 +1,28 @@
const routes = (handler) => [
{
method: 'POST',
path: '/sales',
handler: handler.postSaleHandler,
options: {
auth: 'kasiraja_jwt',
},
},
{
method: 'GET',
path: '/sales',
handler: handler.getSalesHandler,
options: {
auth: 'kasiraja_jwt',
},
},
{
method: 'GET',
path: '/sales/{id}',
handler: handler.getSaleByIdHandler,
options: {
auth: 'kasiraja_jwt',
},
},
];
module.exports = routes;

@ -35,6 +35,11 @@ const products = require('./api/products');
const ProductsService = require('./services/postgres/ProductsService');
const ProductsValidator = require('./validator/products');
// sale transaction
const sales = require('./api/sales');
const SalesService = require('./services/postgres/SalesServive');
const SalesValidator = require('./validator/sales');
const init = async () => {
// instances
const usersService = new UsersService();
@ -43,6 +48,7 @@ const init = async () => {
const unitsService = new UnitsService();
const categoriesService = new CategoriesService();
const productsService = new ProductsService();
const salesService = new SalesService();
// server
const server = Hapi.server({
@ -153,6 +159,13 @@ const init = async () => {
validator: CategoriesValidator,
},
},
{
plugin: sales,
options: {
service: salesService,
validator: SalesValidator,
},
},
]);
await server.start();

@ -0,0 +1,121 @@
const { Pool } = require('pg');
const uuid = require('uuid-random');
const InvariantError = require('../../exceptions/InvariantError');
const NotFoundError = require('../../exceptions/NotFoundError');
const { validateUuid } = require('../../utils');
class SalesService {
constructor() {
this._pool = new Pool();
}
async createTransaction({
date, invoice, description, amount, discount, items, userId, officeId,
}) {
// check stock
const stocksQuery = await this._pool.query(`
SELECT product_id, stock, sale FROM stocks
WHERE product_id IN (${items.map((i) => `'${i.productId}'`).join()})`);
const stocks = stocksQuery.rows;
const itemsWithStock = items.map((item) => ({
...item,
stock: stocks.find((sp) => sp.product_id === item.productId).stock,
sale: stocks.find((sp) => sp.product_id === item.productId).sale,
}));
const checkStock = itemsWithStock
.map((iws) => +iws.stock - +iws.quantity).every((i) => i >= 0);
if (!checkStock) {
throw new InvariantError('transaksi gagal: stock tidak cukup');
}
const client = await this._pool.connect();
try {
await client.query('BEGIN'); // transaction
const id = uuid();
const saleQuery = {
text: `INSERT INTO
sales(id, date, invoice, description, amount, discount, created_by, office_id)
VALUES ($1, $2, $3, $4, $5, $6, $7, $8) RETURNING id`,
values: [id, date, invoice, description, amount, discount, userId, officeId],
};
const sale = await client.query(saleQuery);
const saleId = sale.rows[0].id;
await itemsWithStock.map(async (item) => {
await client.query(`UPDATE stocks SET stock = '${+item.stock - +item.quantity}', sale = '${+item.sale + +item.quantity}' WHERE product_id = '${item.productId}'`);
const itemQuery = {
text: `INSERT INTO sale_items(sale_id, product_id, quantity, price) VALUES ('${saleId}', '${item.productId}', '${item.quantity}', '${item.price}')`,
};
await client.query(itemQuery);
});
await client.query('COMMIT');
return saleId;
} catch (error) {
await client.query('ROLLBACK');
throw new InvariantError(`transaksi gagal: ${error.message}`);
} finally {
client.release();
}
}
async getSales(companyId, { startDate, endDate }) {
const query = {
text: `SELECT
invoice, date, amount, offices.name as office_name
FROM sales
LEFT JOIN offices ON offices.id = sales.office_id
WHERE
sales.office_id = (SELECT id FROM offices WHERE company_id = $1 LIMIT 1)
AND date BETWEEN $2 AND $3`,
values: [companyId, startDate, endDate],
};
const results = await this._pool.query(query);
return results.rows;
}
async getSaleById(saleId) {
validateUuid(saleId);
const query = {
text: `SELECT
date, invoice, sales.description, amount, discount, users.name as creator, offices.name as office_name
FROM sales
LEFT JOIN offices ON offices.id = sales.office_id
LEFT JOIN users ON users.id = sales.created_by
WHERE sales.id = $1`,
values: [saleId],
};
const results = await this._pool.query(query);
if (results.rowCount < 1) {
throw new NotFoundError('transaksi tidak ditemukan');
}
const itemsQuery = {
text: `SELECT
products.name, quantity, sale_items.price
FROM sale_items
LEFT JOIN products ON products.id = sale_items.product_id
WHERE sale_id = $1`,
values: [saleId],
};
const items = await this._pool.query(itemsQuery);
return {
...results.rows[0],
items: items.rows,
};
}
}
module.exports = SalesService;

@ -92,6 +92,28 @@ class UsersService {
return result.rows[0];
}
async getMe(userId) {
validateUuid(userId);
const query = {
text: `SELECT
users.name, users.email, offices.id as officeId, companies.id as companyId
FROM users
LEFT JOIN companies ON companies.id = users.company_id
LEFT JOIN offices ON companies.id = offices.company_id
WHERE users.id = $1`,
values: [userId],
};
const result = await this._pool.query(query);
if (result.rowCount < 1) {
throw new NotFoundError('User tidak ditemukan');
}
return result.rows[0];
}
async updateUserById(userId, { name, email, password }) {
validateUuid(userId);

@ -0,0 +1,20 @@
const { PostSalePayloadSchema, GetSalesPayloadSchema } = require('./schema');
const InvariantError = require('../../exceptions/InvariantError');
const SaleValidator = {
validatePostSalePayload: (payload) => {
const validationResult = PostSalePayloadSchema.validate(payload);
if (validationResult.error) {
throw new InvariantError(validationResult.error.message);
}
},
validateGetSalesPayload: (payload) => {
const validationResult = GetSalesPayloadSchema.validate(payload);
if (validationResult.error) {
throw new InvariantError(validationResult.error.message);
}
},
};
module.exports = SaleValidator;

@ -0,0 +1,24 @@
const Joi = require('joi');
const PostSalePayloadSchema = Joi.object({
officeId: Joi.string().guid().required(),
date: Joi.date().required(),
invoice: Joi.string().required(),
amount: Joi.number().required(),
discount: Joi.number().required(),
description: Joi.string(),
items: Joi.array().items(
Joi.object({
productId: Joi.string().guid().required(),
quantity: Joi.number().required(),
price: Joi.number().required(),
}),
),
});
const GetSalesPayloadSchema = Joi.object({
startDate: Joi.date().required(),
endDate: Joi.date().required(),
});
module.exports = { PostSalePayloadSchema, GetSalesPayloadSchema };
Loading…
Cancel
Save