diff --git a/documents/postman-collection/KasirAja API.postman_collection.json b/documents/postman-collection/KasirAja API.postman_collection.json index 2e067a4..1620d23 100644 --- a/documents/postman-collection/KasirAja API.postman_collection.json +++ b/documents/postman-collection/KasirAja API.postman_collection.json @@ -1072,7 +1072,7 @@ " pm.expect(responseJson).to.haveOwnProperty('status');", " pm.expect(responseJson.status).to.equals('success');", "", - " pm.environment.set('unitId', '')", + " pm.environment.set('categoryId', '')", "})" ], "type": "text/javascript" @@ -1107,6 +1107,293 @@ } ] }, + { + "name": "Customers", + "item": [ + { + "name": "add customer", + "event": [ + { + "listen": "test", + "script": { + "exec": [ + "pm.test('test status code', () => {", + " pm.response.to.have.status(201)", + "});", + "", + "pm.test('test data', () => {", + " const responseJson = pm.response.json();", + "", + " pm.expect(responseJson).to.haveOwnProperty('status');", + " pm.expect(responseJson.status).to.equals('success');", + "", + " pm.environment.set('customerId', responseJson.data.customerId)", + "})" + ], + "type": "text/javascript" + } + } + ], + "request": { + "auth": { + "type": "bearer", + "bearer": [ + { + "key": "token", + "value": "{{accessToken}}", + "type": "string" + } + ] + }, + "method": "POST", + "header": [], + "body": { + "mode": "raw", + "raw": "{\n \"name\": \"pelanggan 1\",\n \"phone\": \"083\",\n \"address\": \"klaten\",\n \"description\": \"-\"\n}", + "options": { + "raw": { + "language": "json" + } + } + }, + "url": { + "raw": "{{host}}/customers", + "host": [ + "{{host}}" + ], + "path": [ + "customers" + ] + } + }, + "response": [] + }, + { + "name": "get customer", + "event": [ + { + "listen": "test", + "script": { + "exec": [ + "pm.test('test status code', () => {", + " pm.response.to.have.status(200)", + "});", + "", + "pm.test('test data', () => {", + " const responseJson = pm.response.json();", + "", + " pm.expect(responseJson).to.haveOwnProperty('status');", + " pm.expect(responseJson.status).to.equals('success');", + "})" + ], + "type": "text/javascript" + } + } + ], + "request": { + "auth": { + "type": "bearer", + "bearer": [ + { + "key": "token", + "value": "{{accessToken}}", + "type": "string" + } + ] + }, + "method": "GET", + "header": [], + "url": { + "raw": "{{host}}/customers/{{customerId}}", + "host": [ + "{{host}}" + ], + "path": [ + "customers", + "{{customerId}}" + ] + } + }, + "response": [] + }, + { + "name": "get customers", + "event": [ + { + "listen": "prerequest", + "script": { + "exec": [ + "var moment = require('moment');", + "", + "pm.environment.set('currentdate', moment().format((\"YYYY-MM-DD\")));", + "pm.environment.set('futuredate', moment().add(5, 'days').format((\"YYYY-MM-DD\")))" + ], + "type": "text/javascript" + } + }, + { + "listen": "test", + "script": { + "exec": [ + "pm.test('test status code', () => {", + " pm.response.to.have.status(200)", + "});", + "", + "pm.test('test data', () => {", + " const responseJson = pm.response.json();", + "", + " pm.expect(responseJson).to.haveOwnProperty('status');", + " pm.expect(responseJson.status).to.equals('success');", + "})" + ], + "type": "text/javascript" + } + } + ], + "request": { + "auth": { + "type": "bearer", + "bearer": [ + { + "key": "token", + "value": "{{accessToken}}", + "type": "string" + } + ] + }, + "method": "GET", + "header": [], + "url": { + "raw": "{{host}}/customers?page=1&q=p", + "host": [ + "{{host}}" + ], + "path": [ + "customers" + ], + "query": [ + { + "key": "page", + "value": "1" + }, + { + "key": "q", + "value": "p" + } + ] + } + }, + "response": [] + }, + { + "name": "update customer", + "event": [ + { + "listen": "test", + "script": { + "exec": [ + "pm.test('test status code', () => {", + " pm.response.to.have.status(200)", + "});", + "", + "pm.test('test data', () => {", + " const responseJson = pm.response.json();", + "", + " pm.expect(responseJson).to.haveOwnProperty('status');", + " pm.expect(responseJson.status).to.equals('success');", + "})" + ], + "type": "text/javascript" + } + } + ], + "request": { + "auth": { + "type": "bearer", + "bearer": [ + { + "key": "token", + "value": "{{accessToken}}", + "type": "string" + } + ] + }, + "method": "PUT", + "header": [], + "body": { + "mode": "raw", + "raw": "{\n \"name\": \"pelanggan 1 update\",\n \"phone\": \"083\",\n \"address\": \"klaten\",\n \"description\": \"update\"\n}", + "options": { + "raw": { + "language": "json" + } + } + }, + "url": { + "raw": "{{host}}/customers/{{customerId}}", + "host": [ + "{{host}}" + ], + "path": [ + "customers", + "{{customerId}}" + ] + } + }, + "response": [] + }, + { + "name": "delete customer", + "event": [ + { + "listen": "test", + "script": { + "exec": [ + "pm.test('test status code', () => {", + " pm.response.to.have.status(200)", + "});", + "", + "pm.test('test data', () => {", + " const responseJson = pm.response.json();", + "", + " pm.expect(responseJson).to.haveOwnProperty('status');", + " pm.expect(responseJson.status).to.equals('success');", + "", + " pm.environment.set('customerId', '')", + "})" + ], + "type": "text/javascript" + } + } + ], + "request": { + "auth": { + "type": "bearer", + "bearer": [ + { + "key": "token", + "value": "{{accessToken}}", + "type": "string" + } + ] + }, + "method": "DELETE", + "header": [], + "url": { + "raw": "{{host}}/customers/{{customerId}}", + "host": [ + "{{host}}" + ], + "path": [ + "customers", + "{{customerId}}" + ] + } + }, + "response": [] + } + ] + }, { "name": "Products", "item": [ @@ -1138,7 +1425,7 @@ "script": { "exec": [ "const createProduct1Request = {", - " url: 'http://localhost:5000/categories',", + " url: `${pm.environment.get('host')}/categories`,", " method: 'POST',", " header: {", " 'Content-Type': 'application/json',", @@ -1437,6 +1724,50 @@ { "name": "Transaction", "item": [ + { + "name": "pre runner [required for runner]", + "event": [ + { + "listen": "prerequest", + "script": { + "exec": [ + "const createCustomerRequest = {", + " url: `${pm.environment.get('host')}/categories`,", + " method: 'POST',", + " header: {", + " 'Content-Type': 'application/json',", + " 'Authorization': `Bearer ${pm.environment.get('accessToken')}`", + " },", + " body: {", + " mode: 'raw',", + " raw: JSON.stringify({", + " name: \"category one\",", + " }),", + " },", + "};", + " ", + "pm.sendRequest(createCustomerRequest, (error, response) => {", + " console.log(error ? error : response);", + " const result = response.json()", + " pm.environment.set('categoryId', result.data.categoryId)", + "});" + ], + "type": "text/javascript" + } + } + ], + "request": { + "method": "GET", + "header": [], + "url": { + "raw": "{{host}}", + "host": [ + "{{host}}" + ] + } + }, + "response": [] + }, { "name": "sale", "event": [ @@ -1444,8 +1775,29 @@ "listen": "prerequest", "script": { "exec": [ + "const createCustomerRequest = {", + " url: `${pm.environment.get('host')}/customers`,", + " method: 'POST',", + " header: {", + " 'Content-Type': 'application/json',", + " 'Authorization': `Bearer ${pm.environment.get('accessToken')}`", + " },", + " body: {", + " mode: 'raw',", + " raw: JSON.stringify({", + " name: \"customer one\",", + " }),", + " },", + "};", + " ", + "pm.sendRequest(createCustomerRequest, (error, response) => {", + " console.log(error ? error : response);", + " const result = response.json()", + " pm.environment.set('customerId', result.data.customerId)", + "});", + "", "const createProduct1Request = {", - " url: 'http://localhost:5000/products',", + " url: `${pm.environment.get('host')}/products`,", " method: 'POST',", " header: {", " 'Content-Type': 'application/json',", @@ -1470,7 +1822,7 @@ "});", "", "const createProduct2Request = {", - " url: 'http://localhost:5000/products',", + " url: `${pm.environment.get('host')}/products`,", " method: 'POST',", " header: {", " 'Content-Type': 'application/json',", @@ -1539,7 +1891,7 @@ "header": [], "body": { "mode": "raw", - "raw": "{\n \"officeId\": \"{{officeId}}\",\n \"date\": \"{{currentdate}}\",\n \"invoice\": \"test\",\n \"amount\": 2000,\n \"discount\": 0,\n \"description\": \"test saja\",\n \"items\" : [\n {\n \"productId\": \"{{productId-1}}\",\n \"quantity\": 1,\n \"price\": 2000\n },\n {\n \"productId\": \"{{productId-2}}\",\n \"quantity\": 1,\n \"price\": 4000\n }\n ]\n}", + "raw": "{\n \"officeId\": \"{{officeId}}\",\n \"customerId\": \"{{customerId}}\",\n \"date\": \"{{currentdate}}\",\n \"invoice\": \"test\",\n \"amount\": 2000,\n \"discount\": 0,\n \"description\": \"test saja\",\n \"items\" : [\n {\n \"productId\": \"{{productId-1}}\",\n \"quantity\": 1,\n \"price\": 2000\n },\n {\n \"productId\": \"{{productId-2}}\",\n \"quantity\": 1,\n \"price\": 4000\n }\n ]\n}", "options": { "raw": { "language": "json" @@ -1683,7 +2035,7 @@ "script": { "exec": [ "const createProduct1Request = {", - " url: 'http://localhost:5000/products',", + " url: `${pm.environment.get('host')}/products`,", " method: 'POST',", " header: {", " 'Content-Type': 'application/json',", @@ -1708,7 +2060,7 @@ "});", "", "const createProduct2Request = {", - " url: 'http://localhost:5000/products',", + " url: `${pm.environment.get('host')}/products`,", " method: 'POST',", " header: {", " 'Content-Type': 'application/json',", diff --git a/documents/postman-collection/KasirAja Dev.postman_environment.json b/documents/postman-collection/KasirAja Dev.postman_environment.json index b436859..7310931 100644 --- a/documents/postman-collection/KasirAja Dev.postman_environment.json +++ b/documents/postman-collection/KasirAja Dev.postman_environment.json @@ -91,9 +91,14 @@ "key": "purchaseId", "value": "", "enabled": true + }, + { + "key": "customerId", + "value": "", + "enabled": true } ], "_postman_variable_scope": "environment", - "_postman_exported_at": "2021-08-09T14:04:30.468Z", + "_postman_exported_at": "2021-08-19T14:21:24.373Z", "_postman_exported_using": "Postman/8.10.0" } \ No newline at end of file diff --git a/documents/tables.png b/documents/tables.png index 4f0431b..2ac079f 100755 Binary files a/documents/tables.png and b/documents/tables.png differ diff --git a/migrations/1628181325738_create-company-product.js b/migrations/1628181325738_create-company-product.js index 1d3a637..d3e4ed0 100644 --- a/migrations/1628181325738_create-company-product.js +++ b/migrations/1628181325738_create-company-product.js @@ -248,9 +248,57 @@ exports.up = (pgm) => { ], }, }); + + pgm.createTable('customers', { + id: { + type: 'uuid', + primaryKey: true, + }, + name: { + type: 'varchar(255)', + notNull: true, + }, + phone: { + type: 'varchar(16)', + notNull: false, + }, + address: { + type: 'text', + notNull: false, + }, + description: { + type: 'text', + notNull: false, + }, + company_id: { + type: 'uuid', + notNull: true, + }, + created_at: { + type: 'timestamp without time zone', + notNull: true, + default: pgm.func('current_timestamp'), + }, + updated_at: { + type: 'timestamp without time zone', + notNull: true, + default: pgm.func('current_timestamp'), + }, + }, { + constraints: { + foreignKeys: [ + { + references: 'companies(id)', + columns: 'company_id', + onDelete: 'CASCADE', + }, + ], + }, + }); }; exports.down = (pgm) => { + pgm.dropTable('customers'); pgm.dropTable('prices'); pgm.dropTable('stocks'); pgm.dropTable('products'); diff --git a/migrations/1628181389101_create-company-transaction.js b/migrations/1628181389101_create-company-transaction.js index e70559a..810b3f1 100644 --- a/migrations/1628181389101_create-company-transaction.js +++ b/migrations/1628181389101_create-company-transaction.js @@ -31,6 +31,10 @@ exports.up = (pgm) => { type: 'numeric(16,2)', notNull: false, }, + customer_id: { + type: 'uuid', + notNull: true, + }, created_by: { type: 'uuid', notNull: true, @@ -58,6 +62,11 @@ exports.up = (pgm) => { columns: 'created_by', onDelete: 'CASCADE', }, + { + references: 'customers(id)', + columns: 'customer_id', + onDelete: 'CASCADE', + }, ], }, }); diff --git a/src/api/categories/handler.js b/src/api/categories/handler.js index 90bd702..6cd2483 100644 --- a/src/api/categories/handler.js +++ b/src/api/categories/handler.js @@ -69,6 +69,7 @@ class CategoriesHandler { async putCategoriesHandler(request) { try { + this._validator.validatePostCategoryPayload(request.payload); const { id: categoryId } = request.params; const { name, description } = request.payload; diff --git a/src/api/customers/handler.js b/src/api/customers/handler.js new file mode 100644 index 0000000..53b4196 --- /dev/null +++ b/src/api/customers/handler.js @@ -0,0 +1,105 @@ +class CustomersHandler { + constructor(service, validator) { + this._service = service; + this._validator = validator; + + this.postCustomerHandler = this.postCustomerHandler.bind(this); + this.getCustomersHandler = this.getCustomersHandler.bind(this); + this.getCustomerByIdHandler = this.getCustomerByIdHandler.bind(this); + this.putCustomerByIdHandler = this.putCustomerByIdHandler.bind(this); + this.deleteCustomerByIdHandler = this.deleteCustomerByIdHandler.bind(this); + } + + async postCustomerHandler(request, h) { + try { + this._validator.validatePostCustomerPayload(request.payload); + + const { companyId } = request.auth.credentials; + const { name, phone, address, description } = request.payload; + + const customerId = await this._service.addCustomer({ + name, phone, address, description, companyId, + }); + + const response = h.response({ + status: 'success', + message: 'Customer berhasil ditambahkan', + data: { + customerId, + name, + }, + }); + response.code(201); + return response; + } catch (error) { + return error; + } + } + + async getCustomersHandler(request) { + try { + const { companyId } = request.auth.credentials; + const { page, q } = request.query; + const { customers, meta } = await this._service.getCustomers(companyId, { page, q }); + + return { + status: 'success', + data: { customers, meta }, + }; + } catch (error) { + return error; + } + } + + async getCustomerByIdHandler(request) { + try { + const { id: customerId } = request.params; + const customer = await this._service.getCustomerById(customerId); + + return { + status: 'success', + data: { + customer, + }, + }; + } catch (error) { + return error; + } + } + + async putCustomerByIdHandler(request) { + try { + this._validator.validatePostCustomerPayload(request.payload); + const { id: customerId } = request.params; + const { name, phone, address, description } = request.payload; + + await this._service.updateCustomerById(customerId, { name, phone, address, description }); + + return { + status: 'success', + data: { + name, + }, + }; + } catch (error) { + return error; + } + } + + async deleteCustomerByIdHandler(request) { + try { + const { id: customerId } = request.params; + + await this._service.deleteCustomerById(customerId); + + return { + status: 'success', + data: {}, + }; + } catch (error) { + return error; + } + } +} + +module.exports = CustomersHandler; diff --git a/src/api/customers/index.js b/src/api/customers/index.js new file mode 100644 index 0000000..a000850 --- /dev/null +++ b/src/api/customers/index.js @@ -0,0 +1,11 @@ +const CustomersHandler = require('./handler'); +const routes = require('./routes'); + +module.exports = { + name: 'customers', + version: '1.0.0', + register: async (server, { service, validator }) => { + const customersHandler = new CustomersHandler(service, validator); + server.route(routes(customersHandler)); + }, +}; diff --git a/src/api/customers/routes.js b/src/api/customers/routes.js new file mode 100644 index 0000000..1ccfbd2 --- /dev/null +++ b/src/api/customers/routes.js @@ -0,0 +1,44 @@ +const routes = (handler) => [ + { + method: 'POST', + path: '/customers', + handler: handler.postCustomerHandler, + options: { + auth: 'kasiraja_jwt', + }, + }, + { + method: 'GET', + path: '/customers', + handler: handler.getCustomersHandler, + options: { + auth: 'kasiraja_jwt', + }, + }, + { + method: 'GET', + path: '/customers/{id}', + handler: handler.getCustomerByIdHandler, + options: { + auth: 'kasiraja_jwt', + }, + }, + { + method: 'PUT', + path: '/customers/{id}', + handler: handler.putCustomerByIdHandler, + options: { + auth: 'kasiraja_jwt', + }, + }, + { + method: 'DELETE', + path: '/customers/{id}', + handler: handler.deleteCustomerByIdHandler, + options: { + auth: 'kasiraja_jwt', + }, + }, +]; + +module.exports = routes; diff --git a/src/api/sales/handler.js b/src/api/sales/handler.js index b1f8599..ff92e08 100644 --- a/src/api/sales/handler.js +++ b/src/api/sales/handler.js @@ -13,11 +13,11 @@ class SalesHandler { this._validator.validatePostSalePayload(request.payload); const { id: userId } = request.auth.credentials; const { - date, invoice, description, amount, discount, items, officeId, + date, invoice, description, amount, discount, items, officeId, customerId } = request.payload; const saleId = await this._service.createTransaction({ - date, invoice, description, amount, discount, items, userId, officeId, + date, invoice, description, amount, discount, items, userId, officeId, customerId }); const response = h.response({ @@ -39,14 +39,19 @@ class SalesHandler { this._validator.validateGetSalesPayload(request.query); const { companyId } = request.auth.credentials; - const { startDate, endDate } = request.query; + const { + startDate, endDate, page, q, customerId, + } = request.query; - const sales = await this._service.getSales(companyId, { startDate, endDate }); + const { sales, meta } = await this._service.getSales(companyId, { + startDate, endDate, page, q, customerId, + }); return { status: 'success', data: { sales, + meta, }, }; } catch (error) { diff --git a/src/server.js b/src/server.js index 9da0ea1..fabb1a0 100644 --- a/src/server.js +++ b/src/server.js @@ -30,6 +30,11 @@ const categories = require('./api/categories'); const CategoriesService = require('./services/postgres/CategoriesService'); const CategoriesValidator = require('./validator/categories'); +// customers +const customers = require('./api/customers'); +const CustomersService = require('./services/postgres/CustomersService'); +const CustomerValidator = require('./validator/customers'); + // products const products = require('./api/products'); const ProductsService = require('./services/postgres/ProductsService'); @@ -52,6 +57,7 @@ const init = async () => { const registrationsService = new RegistrationsService(usersService); const unitsService = new UnitsService(); const categoriesService = new CategoriesService(); + const customersService = new CustomersService(); const productsService = new ProductsService(); const salesService = new SalesService(); const purchasesService = new PurchasesService(); @@ -169,6 +175,13 @@ const init = async () => { service: unitsService, }, }, + { + plugin: customers, + options: { + service: customersService, + validator: CustomerValidator, + }, + }, { plugin: products, options: { diff --git a/src/services/postgres/CustomersService.js b/src/services/postgres/CustomersService.js new file mode 100644 index 0000000..1b87f16 --- /dev/null +++ b/src/services/postgres/CustomersService.js @@ -0,0 +1,100 @@ +const { Pool } = require('pg'); +const uuid = require('uuid-random'); +const NotFoundError = require('../../exceptions/NotFoundError'); +const { validateUuid } = require('../../utils'); + +class CustomersService { + constructor() { + this._pool = new Pool(); + } + + async getCustomers(companyId, { page = 1, limit = 10, q = null }) { + const recordsQuery = await this._pool.query(` + SELECT count(id) as total + FROM customers + WHERE + company_id = '${companyId}' + ${q !== null ? `AND name ILIKE '%${q}%'` : ''} + `); + + const { total } = recordsQuery.rows[0]; + + const totalPages = Math.ceil(total / limit); + const offsets = limit * (page - 1); + + const query = { + text: ` + SELECT id, name, phone, description + FROM customers + WHERE company_id = $1 + ${q !== null ? `AND name ILIKE '%${q}%' OR phone ILIKE '%${q}%'` : ''} + ORDER BY created_at DESC + LIMIT $2 OFFSET $3`, + values: [companyId, limit, offsets], + }; + + const { rows } = await this._pool.query(query); + + return { + customers: rows, + meta: { + totalPages, + total, + page, + }, + }; + } + + async getCustomerById(customerId) { + validateUuid(customerId); + + const query = { + text: 'SELECT name, phone, address, description FROM customers WHERE id = $1', + values: [customerId], + }; + + const results = await this._pool.query(query); + + if (results.rowCount < 1) { + throw new NotFoundError('Customer tidak ditemukan'); + } + + return results.rows[0]; + } + + async addCustomer({ name, phone, address, description, companyId }) { + const id = uuid(); + const query = { + text: 'INSERT INTO customers(id, name, phone, address, description, company_id) VALUES ($1, $2, $3, $4, $5, $6)', + values: [id, name, phone, address, description, companyId], + }; + + await this._pool.query(query); + + return id; + } + + async updateCustomerById(customerId, { name, phone, address, description }) { + validateUuid(customerId); + + const query = { + text: 'UPDATE customers SET name = $1, phone = $2, address = $3, description = $4 WHERE id = $5', + values: [name, phone, address, description, customerId], + }; + + await this._pool.query(query); + } + + async deleteCustomerById(customerId) { + validateUuid(customerId); + + const query = { + text: 'DELETE FROM customers WHERE id = $1', + values: [customerId], + }; + + await this._pool.query(query); + } +} + +module.exports = CustomersService; diff --git a/src/services/postgres/RegistrationService.js b/src/services/postgres/RegistrationService.js index 64b5c6d..84b487a 100644 --- a/src/services/postgres/RegistrationService.js +++ b/src/services/postgres/RegistrationService.js @@ -43,18 +43,25 @@ class RegistrationService { values: [unitId, 'Buah', companyId], }; + const createCustomerQuery = { + text: 'INSERT INTO customers(id, name, phone, address, description, company_id) VALUES ((select uuid_generate_v4()), $1, $2, $3, $4, $5)', + values: ['Pelanggan Umum', '089', 'Klaten', '-', companyId] + } + + const client = await this._pool.connect(); try { - await this._pool.query('BEGIN'); - await this._pool.query(createCompanyQuery); - await this._pool.query(createOfficeQuery); - await this._pool.query(createWarehouseQuery); - await this._pool.query(createUserQuery); - await this._pool.query(createUnitQuery); - await this._pool.query('COMMIT'); + await client.query('BEGIN'); + await client.query(createCompanyQuery); + await client.query(createOfficeQuery); + await client.query(createWarehouseQuery); + await client.query(createUserQuery); + await client.query(createUnitQuery); + await client.query('COMMIT'); } catch (err) { - await this._pool.query('ROLLBACK'); - console.log(err); - throw new InvariantError('Gagal melakukan registrasi'); + await client.query('ROLLBACK'); + throw new InvariantError(`Gagal melakukan registrasi: ${err.message}`); + } finally { + client.release(); } } } diff --git a/src/services/postgres/SalesService.js b/src/services/postgres/SalesService.js index 3eeb7cb..9ca3991 100644 --- a/src/services/postgres/SalesService.js +++ b/src/services/postgres/SalesService.js @@ -10,7 +10,7 @@ class SalesService { } async createTransaction({ - date, invoice, description, amount, discount, items, userId, officeId, + date, invoice, description, amount, discount, items, userId, officeId, customerId, }) { // check stock const stocksQuery = await this._pool.query(` @@ -35,9 +35,9 @@ class SalesService { 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], + sales(id, date, invoice, description, amount, discount, created_by, office_id, customer_id) + VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9) RETURNING id`, + values: [id, date, invoice, description, amount, discount, userId, officeId, customerId], }; const sale = await client.query(saleQuery); @@ -64,22 +64,52 @@ class SalesService { } } - async getSales(companyId, { startDate, endDate }) { + async getSales(companyId, { + startDate, endDate, page = 1, q = null, customerId, limit = 20, + }) { + const recordsQuery = await this._pool.query(` + SELECT count(sales.id) as total + FROM sales + ${customerId ? `LEFT JOIN customers ON customers.id = sales.customer_id` : ''} + WHERE + sales.office_id = (SELECT id FROM offices WHERE company_id = '${companyId}' LIMIT 1) + ${q ? `AND invoice ILIKE '%${q}%'` : ''} + ${customerId ? `AND customer_id = '${customerId}'` : ''} + `); + + const { total } = recordsQuery.rows[0]; + + const totalPages = Math.ceil(total / limit); + const offsets = limit * (page - 1); + const query = { text: `SELECT - sales.id, invoice, date, amount, offices.name as office_name + sales.id, invoice, date, amount, offices.name as office_name, users.name as casier FROM sales LEFT JOIN offices ON offices.id = sales.office_id + LEFT JOIN users ON users.id = sales.created_by + ${customerId ? `LEFT JOIN customers ON customers.id = sales.customer_id` : ''} WHERE - sales.office_id = (SELECT id FROM offices WHERE company_id = $1 LIMIT 1) + sales.office_id = (SELECT id FROM offices WHERE company_id = $1 LIMIT 1) + ${q ? `AND invoice ILIKE '%${q}%'` : ''} + ${customerId ? `AND customer_id = '${customerId}'` : ''} AND date::DATE BETWEEN $2 AND $3 - ORDER BY sales.created_at DESC`, - values: [companyId, startDate, endDate], + ORDER BY sales.created_at DESC + LIMIT $4 OFFSET $5 + `, + values: [companyId, startDate, endDate, limit, offsets], }; - const results = await this._pool.query(query); + const { rows } = await this._pool.query(query); - return results.rows; + return { + sales: rows, + meta: { + page, + total, + totalPages, + }, + }; } async getSaleById(saleId) { @@ -87,10 +117,14 @@ class SalesService { const query = { text: `SELECT - date, invoice, sales.description, amount, discount, users.name as creator, offices.name as office_name + date, invoice, sales.description, amount, discount, + users.name as casier, + offices.name as office_name, + customers.id as customer_id, customers.name as customer_name FROM sales LEFT JOIN offices ON offices.id = sales.office_id LEFT JOIN users ON users.id = sales.created_by + LEFT JOIN customers ON customers.id = sales.customer_id WHERE sales.id = $1`, values: [saleId], }; diff --git a/src/validator/customers/index.js b/src/validator/customers/index.js new file mode 100644 index 0000000..1e853b6 --- /dev/null +++ b/src/validator/customers/index.js @@ -0,0 +1,13 @@ +const PostCustomerPayloadSchema = require('./schema'); +const InvariantError = require('../../exceptions/InvariantError'); + +const CustomerValidator = { + validatePostCustomerPayload: (payload) => { + const validationResult = PostCustomerPayloadSchema.validate(payload); + if (validationResult.error) { + throw new InvariantError(validationResult.error.message); + } + }, +}; + +module.exports = CustomerValidator; diff --git a/src/validator/customers/schema.js b/src/validator/customers/schema.js new file mode 100644 index 0000000..fcd12be --- /dev/null +++ b/src/validator/customers/schema.js @@ -0,0 +1,10 @@ +const Joi = require('joi'); + +const PostCustomerPayloadSchema = Joi.object({ + name: Joi.string().required(), + phone: Joi.string().allow(''), + address: Joi.string().allow(''), + description: Joi.string().allow(''), +}); + +module.exports = PostCustomerPayloadSchema; diff --git a/src/validator/purchases/schema.js b/src/validator/purchases/schema.js index 0bad8d5..b79ae0d 100644 --- a/src/validator/purchases/schema.js +++ b/src/validator/purchases/schema.js @@ -19,6 +19,8 @@ const PostPurchasePayloadSchema = Joi.object({ const GetPurchasesPayloadSchema = Joi.object({ startDate: Joi.date().required(), endDate: Joi.date().required(), + page: Joi.string().allow(''), + q: Joi.string().allow(''), }); module.exports = { PostPurchasePayloadSchema, GetPurchasesPayloadSchema }; diff --git a/src/validator/sales/schema.js b/src/validator/sales/schema.js index 15caa87..2ba29a2 100644 --- a/src/validator/sales/schema.js +++ b/src/validator/sales/schema.js @@ -2,6 +2,7 @@ const Joi = require('joi'); const PostSalePayloadSchema = Joi.object({ officeId: Joi.string().guid().required(), + customerId: Joi.string().guid().required(), date: Joi.date().required(), invoice: Joi.string().required(), amount: Joi.number().required(), @@ -19,6 +20,9 @@ const PostSalePayloadSchema = Joi.object({ const GetSalesPayloadSchema = Joi.object({ startDate: Joi.date().required(), endDate: Joi.date().required(), + page: Joi.string().allow(''), + q: Joi.string().allow(''), + customerId: Joi.string().allow('').guid(), }); module.exports = { PostSalePayloadSchema, GetSalesPayloadSchema };