sales impemented
parent
cef61a6368
commit
2fd0f7cf6b
@ -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;
|
@ -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;
|
@ -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…
Reference in New Issue