menambahkan crud suppliers
This commit is contained in:
parent
f5639c6615
commit
cd198e9272
|
@ -23,10 +23,12 @@
|
||||||
"http-errors": "~1.6.3",
|
"http-errors": "~1.6.3",
|
||||||
"jsonwebtoken": "^9.0.0",
|
"jsonwebtoken": "^9.0.0",
|
||||||
"knex": "^2.5.1",
|
"knex": "^2.5.1",
|
||||||
|
"libphonenumber-js": "^1.12.8",
|
||||||
"moment": "^2.29.4",
|
"moment": "^2.29.4",
|
||||||
"mongodb": "^6.14.0",
|
"mongodb": "^6.14.0",
|
||||||
"mongoose": "^8.11.0",
|
"mongoose": "^8.11.0",
|
||||||
"morgan": "~1.9.1",
|
"morgan": "~1.9.1",
|
||||||
|
"multer": "^1.4.5-lts.2",
|
||||||
"mysql": "^2.18.1",
|
"mysql": "^2.18.1",
|
||||||
"mysql2": "^3.14.1",
|
"mysql2": "^3.14.1",
|
||||||
"node-schedule": "^2.1.1",
|
"node-schedule": "^2.1.1",
|
||||||
|
@ -42,6 +44,7 @@
|
||||||
"@types/express": "^5.0.1",
|
"@types/express": "^5.0.1",
|
||||||
"@types/jsonwebtoken": "^9.0.9",
|
"@types/jsonwebtoken": "^9.0.9",
|
||||||
"@types/morgan": "^1.9.9",
|
"@types/morgan": "^1.9.9",
|
||||||
|
"@types/multer": "^1.4.12",
|
||||||
"@types/nodemailer": "^6.4.17",
|
"@types/nodemailer": "^6.4.17",
|
||||||
"ts-node": "^10.9.2",
|
"ts-node": "^10.9.2",
|
||||||
"ts-node-dev": "^2.0.0",
|
"ts-node-dev": "^2.0.0",
|
||||||
|
@ -311,6 +314,16 @@
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT"
|
"license": "MIT"
|
||||||
},
|
},
|
||||||
|
"node_modules/@types/multer": {
|
||||||
|
"version": "1.4.12",
|
||||||
|
"resolved": "https://registry.npmjs.org/@types/multer/-/multer-1.4.12.tgz",
|
||||||
|
"integrity": "sha512-pQ2hoqvXiJt2FP9WQVLPRO+AmiIm/ZYkavPlIQnx282u4ZrVdztx0pkh3jjpQt0Kz+YI0YhSG264y08UJKoUQg==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"@types/express": "*"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/@types/node": {
|
"node_modules/@types/node": {
|
||||||
"version": "22.15.3",
|
"version": "22.15.3",
|
||||||
"resolved": "https://registry.npmjs.org/@types/node/-/node-22.15.3.tgz",
|
"resolved": "https://registry.npmjs.org/@types/node/-/node-22.15.3.tgz",
|
||||||
|
@ -537,6 +550,12 @@
|
||||||
"node": ">= 8"
|
"node": ">= 8"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/append-field": {
|
||||||
|
"version": "1.0.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/append-field/-/append-field-1.0.0.tgz",
|
||||||
|
"integrity": "sha512-klpgFSWLW1ZEs8svjfb7g4qWY0YS5imI82dTg+QahUvJ8YqAY0P10Uk8tTyh9ZGuYEZEMaeJYCF5BFuX552hsw==",
|
||||||
|
"license": "MIT"
|
||||||
|
},
|
||||||
"node_modules/aproba": {
|
"node_modules/aproba": {
|
||||||
"version": "2.0.0",
|
"version": "2.0.0",
|
||||||
"resolved": "https://registry.npmjs.org/aproba/-/aproba-2.0.0.tgz",
|
"resolved": "https://registry.npmjs.org/aproba/-/aproba-2.0.0.tgz",
|
||||||
|
@ -756,7 +775,6 @@
|
||||||
"version": "1.1.2",
|
"version": "1.1.2",
|
||||||
"resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.2.tgz",
|
"resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.2.tgz",
|
||||||
"integrity": "sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==",
|
"integrity": "sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==",
|
||||||
"dev": true,
|
|
||||||
"license": "MIT"
|
"license": "MIT"
|
||||||
},
|
},
|
||||||
"node_modules/buffer-more-ints": {
|
"node_modules/buffer-more-ints": {
|
||||||
|
@ -765,6 +783,17 @@
|
||||||
"integrity": "sha512-EMetuGFz5SLsT0QTnXzINh4Ksr+oo4i+UGTXEshiGCQWnsgSs7ZhJ8fzlwQ+OzEMs0MpDAMr1hxnblp5a4vcHg==",
|
"integrity": "sha512-EMetuGFz5SLsT0QTnXzINh4Ksr+oo4i+UGTXEshiGCQWnsgSs7ZhJ8fzlwQ+OzEMs0MpDAMr1hxnblp5a4vcHg==",
|
||||||
"license": "MIT"
|
"license": "MIT"
|
||||||
},
|
},
|
||||||
|
"node_modules/busboy": {
|
||||||
|
"version": "1.6.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/busboy/-/busboy-1.6.0.tgz",
|
||||||
|
"integrity": "sha512-8SFQbg/0hQ9xy3UNTB0YEnsNBbWfhf7RtnzpL7TkBiTBRfrQ9Fxcnz7VJsleJpyp6rVLvXiuORqjlHi5q+PYuA==",
|
||||||
|
"dependencies": {
|
||||||
|
"streamsearch": "^1.1.0"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=10.16.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/bytes": {
|
"node_modules/bytes": {
|
||||||
"version": "3.1.2",
|
"version": "3.1.2",
|
||||||
"resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.2.tgz",
|
"resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.2.tgz",
|
||||||
|
@ -962,6 +991,21 @@
|
||||||
"integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==",
|
"integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==",
|
||||||
"license": "MIT"
|
"license": "MIT"
|
||||||
},
|
},
|
||||||
|
"node_modules/concat-stream": {
|
||||||
|
"version": "1.6.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/concat-stream/-/concat-stream-1.6.2.tgz",
|
||||||
|
"integrity": "sha512-27HBghJxjiZtIk3Ycvn/4kbJk/1uZuJFfuPEns6LaEvpvG1f0hTea8lilrouyo9mVc2GWdcEZ8OLoGmSADlrCw==",
|
||||||
|
"engines": [
|
||||||
|
"node >= 0.8"
|
||||||
|
],
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"buffer-from": "^1.0.0",
|
||||||
|
"inherits": "^2.0.3",
|
||||||
|
"readable-stream": "^2.2.2",
|
||||||
|
"typedarray": "^0.0.6"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/console-control-strings": {
|
"node_modules/console-control-strings": {
|
||||||
"version": "1.1.0",
|
"version": "1.1.0",
|
||||||
"resolved": "https://registry.npmjs.org/console-control-strings/-/console-control-strings-1.1.0.tgz",
|
"resolved": "https://registry.npmjs.org/console-control-strings/-/console-control-strings-1.1.0.tgz",
|
||||||
|
@ -2127,6 +2171,12 @@
|
||||||
"integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==",
|
"integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==",
|
||||||
"license": "MIT"
|
"license": "MIT"
|
||||||
},
|
},
|
||||||
|
"node_modules/libphonenumber-js": {
|
||||||
|
"version": "1.12.8",
|
||||||
|
"resolved": "https://registry.npmjs.org/libphonenumber-js/-/libphonenumber-js-1.12.8.tgz",
|
||||||
|
"integrity": "sha512-f1KakiQJa9tdc7w1phC2ST+DyxWimy9c3g3yeF+84QtEanJr2K77wAmBPP22riU05xldniHsvXuflnLZ4oysqA==",
|
||||||
|
"license": "MIT"
|
||||||
|
},
|
||||||
"node_modules/lodash": {
|
"node_modules/lodash": {
|
||||||
"version": "4.17.21",
|
"version": "4.17.21",
|
||||||
"resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz",
|
"resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz",
|
||||||
|
@ -2351,7 +2401,6 @@
|
||||||
"version": "1.2.8",
|
"version": "1.2.8",
|
||||||
"resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.8.tgz",
|
"resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.8.tgz",
|
||||||
"integrity": "sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==",
|
"integrity": "sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==",
|
||||||
"dev": true,
|
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"funding": {
|
"funding": {
|
||||||
"url": "https://github.com/sponsors/ljharb"
|
"url": "https://github.com/sponsors/ljharb"
|
||||||
|
@ -2629,6 +2678,36 @@
|
||||||
"integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==",
|
"integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==",
|
||||||
"license": "MIT"
|
"license": "MIT"
|
||||||
},
|
},
|
||||||
|
"node_modules/multer": {
|
||||||
|
"version": "1.4.5-lts.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/multer/-/multer-1.4.5-lts.2.tgz",
|
||||||
|
"integrity": "sha512-VzGiVigcG9zUAoCNU+xShztrlr1auZOlurXynNvO9GiWD1/mTBbUljOKY+qMeazBqXgRnjzeEgJI/wyjJUHg9A==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"append-field": "^1.0.0",
|
||||||
|
"busboy": "^1.0.0",
|
||||||
|
"concat-stream": "^1.5.2",
|
||||||
|
"mkdirp": "^0.5.4",
|
||||||
|
"object-assign": "^4.1.1",
|
||||||
|
"type-is": "^1.6.4",
|
||||||
|
"xtend": "^4.0.0"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">= 6.0.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/multer/node_modules/mkdirp": {
|
||||||
|
"version": "0.5.6",
|
||||||
|
"resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.6.tgz",
|
||||||
|
"integrity": "sha512-FP+p8RB8OWpF3YZBCrP5gtADmtXApB5AMLn+vdyA+PyxCjrCs00mjyUozssO33cwDeT3wNGdLxJ5M//YqtHAJw==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"minimist": "^1.2.6"
|
||||||
|
},
|
||||||
|
"bin": {
|
||||||
|
"mkdirp": "bin/cmd.js"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/mysql": {
|
"node_modules/mysql": {
|
||||||
"version": "2.18.1",
|
"version": "2.18.1",
|
||||||
"resolved": "https://registry.npmjs.org/mysql/-/mysql-2.18.1.tgz",
|
"resolved": "https://registry.npmjs.org/mysql/-/mysql-2.18.1.tgz",
|
||||||
|
@ -3586,6 +3665,14 @@
|
||||||
"node": ">= 0.8"
|
"node": ">= 0.8"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/streamsearch": {
|
||||||
|
"version": "1.1.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/streamsearch/-/streamsearch-1.1.0.tgz",
|
||||||
|
"integrity": "sha512-Mcc5wHehp9aXz1ax6bZUyY5afg9u2rv5cqQI3mRrYkGC8rW2hM02jWuwjtL++LS5qinSyhj2QfLyNsuc+VsExg==",
|
||||||
|
"engines": {
|
||||||
|
"node": ">=10.0.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/string_decoder": {
|
"node_modules/string_decoder": {
|
||||||
"version": "1.1.1",
|
"version": "1.1.1",
|
||||||
"resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz",
|
"resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz",
|
||||||
|
@ -3871,6 +3958,12 @@
|
||||||
"node": ">= 0.6"
|
"node": ">= 0.6"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/typedarray": {
|
||||||
|
"version": "0.0.6",
|
||||||
|
"resolved": "https://registry.npmjs.org/typedarray/-/typedarray-0.0.6.tgz",
|
||||||
|
"integrity": "sha512-/aCDEGatGvZ2BIk+HmLf4ifCJFwvKFNb9/JeZPMulfgFracn9QFcAf5GO8B/mweUjSoblS5In0cWhqpfs/5PQA==",
|
||||||
|
"license": "MIT"
|
||||||
|
},
|
||||||
"node_modules/typescript": {
|
"node_modules/typescript": {
|
||||||
"version": "5.8.3",
|
"version": "5.8.3",
|
||||||
"resolved": "https://registry.npmjs.org/typescript/-/typescript-5.8.3.tgz",
|
"resolved": "https://registry.npmjs.org/typescript/-/typescript-5.8.3.tgz",
|
||||||
|
|
|
@ -24,10 +24,12 @@
|
||||||
"http-errors": "~1.6.3",
|
"http-errors": "~1.6.3",
|
||||||
"jsonwebtoken": "^9.0.0",
|
"jsonwebtoken": "^9.0.0",
|
||||||
"knex": "^2.5.1",
|
"knex": "^2.5.1",
|
||||||
|
"libphonenumber-js": "^1.12.8",
|
||||||
"moment": "^2.29.4",
|
"moment": "^2.29.4",
|
||||||
"mongodb": "^6.14.0",
|
"mongodb": "^6.14.0",
|
||||||
"mongoose": "^8.11.0",
|
"mongoose": "^8.11.0",
|
||||||
"morgan": "~1.9.1",
|
"morgan": "~1.9.1",
|
||||||
|
"multer": "^1.4.5-lts.2",
|
||||||
"mysql": "^2.18.1",
|
"mysql": "^2.18.1",
|
||||||
"mysql2": "^3.14.1",
|
"mysql2": "^3.14.1",
|
||||||
"node-schedule": "^2.1.1",
|
"node-schedule": "^2.1.1",
|
||||||
|
@ -43,6 +45,7 @@
|
||||||
"@types/express": "^5.0.1",
|
"@types/express": "^5.0.1",
|
||||||
"@types/jsonwebtoken": "^9.0.9",
|
"@types/jsonwebtoken": "^9.0.9",
|
||||||
"@types/morgan": "^1.9.9",
|
"@types/morgan": "^1.9.9",
|
||||||
|
"@types/multer": "^1.4.12",
|
||||||
"@types/nodemailer": "^6.4.17",
|
"@types/nodemailer": "^6.4.17",
|
||||||
"ts-node": "^10.9.2",
|
"ts-node": "^10.9.2",
|
||||||
"ts-node-dev": "^2.0.0",
|
"ts-node-dev": "^2.0.0",
|
||||||
|
|
|
@ -1,129 +0,0 @@
|
||||||
// controllers/productsController.ts
|
|
||||||
|
|
||||||
import express, { NextFunction, Request, Response } from "express";
|
|
||||||
import { body, param } from "express-validator";
|
|
||||||
import createHttpError from "http-errors";
|
|
||||||
import { matchedData } from "express-validator";
|
|
||||||
import * as productCategoriesRepository from "../../repository/productCategories"; // Repository untuk Product Categories
|
|
||||||
import * as productsRepository from "../../repository/products"; // Repository untuk Products
|
|
||||||
import validate from "../../middleware/expressValidatorErrorHandler"; // Validation middleware
|
|
||||||
|
|
||||||
// POST: Tambah produk baru
|
|
||||||
const createProduct = [
|
|
||||||
body("product_name").notEmpty().withMessage("Product name is required"),
|
|
||||||
body("description").optional().isString(),
|
|
||||||
body("price").notEmpty().isNumeric().withMessage("Price must be a number"),
|
|
||||||
body("product_category_id").notEmpty().isInt().withMessage("Invalid category ID"), // Ganti isMongoId() ke isInt()
|
|
||||||
validate,
|
|
||||||
async (req: Request, res: Response, next: NextFunction) => {
|
|
||||||
try {
|
|
||||||
const data = matchedData(req);
|
|
||||||
|
|
||||||
// Cek apakah kategori produk ada di database
|
|
||||||
const categoryExists = await productCategoriesRepository.getProductCategoryById(data.product_category_id);
|
|
||||||
if (!categoryExists) return next(createHttpError(404, "Category not found"));
|
|
||||||
|
|
||||||
// Simpan produk
|
|
||||||
const product = await productsRepository.createProduct(data);
|
|
||||||
|
|
||||||
res.status(201).json({ message: "Product created", product });
|
|
||||||
} catch (err) {
|
|
||||||
next(createHttpError(500, "Internal Server Error", { cause: err }));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
];
|
|
||||||
|
|
||||||
// PATCH: Perbarui produk
|
|
||||||
const updateProduct = [
|
|
||||||
param("id").isInt().withMessage("Invalid product ID"), // Ganti isMongoId() ke isInt()
|
|
||||||
body("product_name").optional().isString(),
|
|
||||||
body("description").optional().isString(),
|
|
||||||
body("price").optional().isNumeric().withMessage("Price must be a number"),
|
|
||||||
body("product_category_id").optional().isInt().withMessage("Invalid category ID"), // Ganti isMongoId() ke isInt()
|
|
||||||
validate,
|
|
||||||
async (req: Request, res: Response, next: NextFunction) => {
|
|
||||||
try {
|
|
||||||
const { id } = req.params;
|
|
||||||
const data = matchedData(req);
|
|
||||||
|
|
||||||
// Jika ada perubahan kategori, cek apakah kategori tersebut ada
|
|
||||||
if (data.product_category_id) {
|
|
||||||
const categoryExists = await productCategoriesRepository.getProductCategoryById(data.product_category_id);
|
|
||||||
if (!categoryExists) return next(createHttpError(404, "Category not found"));
|
|
||||||
}
|
|
||||||
|
|
||||||
// Update produk
|
|
||||||
const product = await productsRepository.updateProductById(Number(id), data);
|
|
||||||
if (!product) return next(createHttpError(404, "Product not found"));
|
|
||||||
|
|
||||||
res.json({ message: "Product updated", product });
|
|
||||||
} catch (err) {
|
|
||||||
next(createHttpError(500, "Internal Server Error", { cause: err }));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
];
|
|
||||||
|
|
||||||
// DELETE: Hapus produk
|
|
||||||
const deleteProduct = [
|
|
||||||
param("id").isInt().withMessage("Invalid product ID"), // Ganti isMongoId() ke isInt()
|
|
||||||
validate,
|
|
||||||
async (req: Request, res: Response, next: NextFunction) => {
|
|
||||||
try {
|
|
||||||
const { id } = req.params;
|
|
||||||
|
|
||||||
const product = await productsRepository.deleteProductById(Number(id));
|
|
||||||
if (!product) return next(createHttpError(404, "Product not found"));
|
|
||||||
|
|
||||||
res.json({ message: "Product deleted" });
|
|
||||||
} catch (err) {
|
|
||||||
next(createHttpError(500, "Internal Server Error", { cause: err }));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
];
|
|
||||||
|
|
||||||
// GET: Ambil satu produk berdasarkan ID
|
|
||||||
const getProduct = [
|
|
||||||
param("id").isInt().withMessage("Invalid product ID"), // Ganti isMongoId() ke isInt()
|
|
||||||
validate,
|
|
||||||
async (req: Request, res: Response, next: NextFunction) => {
|
|
||||||
try {
|
|
||||||
const { id } = req.params;
|
|
||||||
const product = await productsRepository.getProductById(Number(id));
|
|
||||||
|
|
||||||
if (!product) return next(createHttpError(404, "Product not found"));
|
|
||||||
|
|
||||||
// Ambil kategori produk terkait (join dengan kategori produk)
|
|
||||||
const productCategory = await productCategoriesRepository.getProductCategoryById(product.product_category_id);
|
|
||||||
product.product_category = productCategory; // Menambahkan informasi kategori ke dalam produk
|
|
||||||
|
|
||||||
res.json(product);
|
|
||||||
} catch (err) {
|
|
||||||
next(createHttpError(500, "Internal Server Error", { cause: err }));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
];
|
|
||||||
|
|
||||||
// GET: Ambil semua produk
|
|
||||||
const getProducts = async (req: Request, res: Response, next: NextFunction) => {
|
|
||||||
try {
|
|
||||||
const allProducts = await productsRepository.getAllProducts();
|
|
||||||
|
|
||||||
// Ambil kategori produk terkait untuk setiap produk
|
|
||||||
for (const product of allProducts) {
|
|
||||||
const category = await productCategoriesRepository.getProductCategoryById(product.product_category_id);
|
|
||||||
product.product_category = category;
|
|
||||||
}
|
|
||||||
|
|
||||||
res.json(allProducts);
|
|
||||||
} catch (err) {
|
|
||||||
next(createHttpError(500, "Internal Server Error", { cause: err }));
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
export default {
|
|
||||||
createProduct,
|
|
||||||
updateProduct,
|
|
||||||
deleteProduct,
|
|
||||||
getProduct,
|
|
||||||
getProducts
|
|
||||||
};
|
|
|
@ -2,71 +2,83 @@ import express, { NextFunction, Request, Response } from "express";
|
||||||
import { body, param, query, matchedData } from "express-validator";
|
import { body, param, query, matchedData } from "express-validator";
|
||||||
import createHttpError from "http-errors";
|
import createHttpError from "http-errors";
|
||||||
import validate from "../../middleware/expressValidatorErrorHandler";
|
import validate from "../../middleware/expressValidatorErrorHandler";
|
||||||
import { predictions, products } from "../../database/models";
|
import { fileValidate, multiFileValidate } from "../../middleware/fileUploader";
|
||||||
|
import axios from "axios";
|
||||||
|
|
||||||
// 🟢 **POST: Tambah Prediksi Stok Baru**
|
// 🟢 **POST: Tambah Prediksi Stok Baru**
|
||||||
const createStockForecasting = [
|
// const createStockForecasting = [
|
||||||
body("stock_sold").notEmpty().isNumeric().withMessage("Stock sold must be a number"),
|
// body("stock_sold").notEmpty().isNumeric().withMessage("Stock sold must be a number"),
|
||||||
body("stock_predicted").notEmpty().isNumeric().withMessage("Stock predicted must be a number"),
|
// body("stock_predicted").notEmpty().isNumeric().withMessage("Stock predicted must be a number"),
|
||||||
body("type").notEmpty().isString().withMessage("Type is required"),
|
// body("type").notEmpty().isString().withMessage("Type is required"),
|
||||||
body("accuracy").optional().isNumeric().withMessage("Accuracy must be a number"),
|
// body("accuracy").optional().isNumeric().withMessage("Accuracy must be a number"),
|
||||||
body("user_id").notEmpty().isMongoId().withMessage("Invalid user ID"),
|
// body("user_id").notEmpty().isMongoId().withMessage("Invalid user ID"),
|
||||||
body("product_id").notEmpty().isMongoId().withMessage("Invalid product ID"),
|
// body("product_id").notEmpty().isMongoId().withMessage("Invalid product ID"),
|
||||||
validate,
|
// validate,
|
||||||
|
// async (req: Request, res: Response, next: NextFunction) => {
|
||||||
|
// try {
|
||||||
|
// const data = matchedData(req);
|
||||||
|
|
||||||
|
// // Cek apakah produk ada
|
||||||
|
// const productExists = await products.findById(data.product_id);
|
||||||
|
// if (!productExists) return next(createHttpError(404, "Product not found"));
|
||||||
|
|
||||||
|
// // Simpan data prediksi
|
||||||
|
// const prediction = new predictions(data);
|
||||||
|
// await prediction.save();
|
||||||
|
// res.status(201).json({ message: "Stock forecasting created", prediction });
|
||||||
|
// } catch (err) {
|
||||||
|
// next(createHttpError(500, "Internal Server Error", { cause: err }));
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
// ];
|
||||||
|
|
||||||
|
// // 🔵 **GET: Ambil Prediksi Berdasarkan Tanggal**
|
||||||
|
// const getStockForecastingByDate = [
|
||||||
|
// query("date").notEmpty().isISO8601().withMessage("Invalid date format"),
|
||||||
|
// validate,
|
||||||
|
// async (req: Request, res: Response, next: NextFunction) => {
|
||||||
|
// try {
|
||||||
|
// const { date } = matchedData(req);
|
||||||
|
// const startDate = new Date(date);
|
||||||
|
// const endDate = new Date(date);
|
||||||
|
// endDate.setDate(endDate.getDate() + 1);
|
||||||
|
|
||||||
|
// const forecasts = await predictions.find({
|
||||||
|
// createdAt: { $gte: startDate, $lt: endDate }
|
||||||
|
// }).populate("product_id user_id");
|
||||||
|
|
||||||
|
// if (forecasts.length === 0) return next(createHttpError(404, "No stock forecasting found for this date"));
|
||||||
|
|
||||||
|
// res.json(forecasts);
|
||||||
|
// } catch (err) {
|
||||||
|
// next(createHttpError(500, "Internal Server Error", { cause: err }));
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
// ];
|
||||||
|
|
||||||
|
// // 🟠 **GET: Ambil Semua Prediksi (History)**
|
||||||
|
// const getStockForecastings = async (req: Request, res: Response, next: NextFunction) => {
|
||||||
|
// try {
|
||||||
|
// const forecasts = await predictions.find().populate("product_id user_id");
|
||||||
|
// res.json(forecasts);
|
||||||
|
// } catch (err) {
|
||||||
|
// next(createHttpError(500, "Internal Server Error", { cause: err }));
|
||||||
|
// }
|
||||||
|
// };
|
||||||
|
|
||||||
|
const makePredictionFromSheet = [
|
||||||
|
fileValidate('sheet'),
|
||||||
async (req: Request, res: Response, next: NextFunction) => {
|
async (req: Request, res: Response, next: NextFunction) => {
|
||||||
try {
|
res.json({
|
||||||
const data = matchedData(req);
|
status: true,
|
||||||
|
message: 'File Saved'
|
||||||
// Cek apakah produk ada
|
})
|
||||||
const productExists = await products.findById(data.product_id);
|
|
||||||
if (!productExists) return next(createHttpError(404, "Product not found"));
|
|
||||||
|
|
||||||
// Simpan data prediksi
|
|
||||||
const prediction = new predictions(data);
|
|
||||||
await prediction.save();
|
|
||||||
res.status(201).json({ message: "Stock forecasting created", prediction });
|
|
||||||
} catch (err) {
|
|
||||||
next(createHttpError(500, "Internal Server Error", { cause: err }));
|
|
||||||
}
|
}
|
||||||
}
|
]
|
||||||
];
|
|
||||||
|
|
||||||
// 🔵 **GET: Ambil Prediksi Berdasarkan Tanggal**
|
|
||||||
const getStockForecastingByDate = [
|
|
||||||
query("date").notEmpty().isISO8601().withMessage("Invalid date format"),
|
|
||||||
validate,
|
|
||||||
async (req: Request, res: Response, next: NextFunction) => {
|
|
||||||
try {
|
|
||||||
const { date } = matchedData(req);
|
|
||||||
const startDate = new Date(date);
|
|
||||||
const endDate = new Date(date);
|
|
||||||
endDate.setDate(endDate.getDate() + 1);
|
|
||||||
|
|
||||||
const forecasts = await predictions.find({
|
|
||||||
createdAt: { $gte: startDate, $lt: endDate }
|
|
||||||
}).populate("product_id user_id");
|
|
||||||
|
|
||||||
if (forecasts.length === 0) return next(createHttpError(404, "No stock forecasting found for this date"));
|
|
||||||
|
|
||||||
res.json(forecasts);
|
|
||||||
} catch (err) {
|
|
||||||
next(createHttpError(500, "Internal Server Error", { cause: err }));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
];
|
|
||||||
|
|
||||||
// 🟠 **GET: Ambil Semua Prediksi (History)**
|
|
||||||
const getStockForecastings = async (req: Request, res: Response, next: NextFunction) => {
|
|
||||||
try {
|
|
||||||
const forecasts = await predictions.find().populate("product_id user_id");
|
|
||||||
res.json(forecasts);
|
|
||||||
} catch (err) {
|
|
||||||
next(createHttpError(500, "Internal Server Error", { cause: err }));
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
createStockForecasting,
|
// createStockForecasting,
|
||||||
getStockForecastingByDate,
|
// getStockForecastingByDate,
|
||||||
getStockForecastings
|
// getStockForecastings
|
||||||
|
makePredictionFromSheet
|
||||||
};
|
};
|
||||||
|
|
|
@ -1,101 +0,0 @@
|
||||||
import express, { NextFunction, Request, Response } from "express";
|
|
||||||
import { body, param, matchedData } from "express-validator";
|
|
||||||
import createHttpError from "http-errors";
|
|
||||||
import { suppliers } from "../../database/models";
|
|
||||||
import validate from "../../middleware/expressValidatorErrorHandler";
|
|
||||||
|
|
||||||
// 🟢 **POST: Tambah Supplier Baru**
|
|
||||||
const createSupplier = [
|
|
||||||
body("supplier_name").notEmpty().withMessage("Supplier name is required"),
|
|
||||||
body("contact").optional().isString().withMessage("Contact must be a string"),
|
|
||||||
body("address").optional().isString().withMessage("Address must be a string"),
|
|
||||||
body("user_id").notEmpty().isMongoId().withMessage("Invalid user ID"),
|
|
||||||
validate,
|
|
||||||
async (req: Request, res: Response, next: NextFunction) => {
|
|
||||||
try {
|
|
||||||
const data = matchedData(req);
|
|
||||||
|
|
||||||
const supplier = new suppliers(data);
|
|
||||||
await supplier.save();
|
|
||||||
|
|
||||||
res.status(201).json({ message: "Supplier created", supplier });
|
|
||||||
} catch (err) {
|
|
||||||
next(createHttpError(500, "Internal Server Error", { cause: err }));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
];
|
|
||||||
|
|
||||||
// 🟠 **PATCH: Perbarui Supplier**
|
|
||||||
const updateSupplier = [
|
|
||||||
param("id").isMongoId().withMessage("Invalid supplier ID"),
|
|
||||||
body("supplier_name").optional().isString(),
|
|
||||||
body("contact").optional().isString(),
|
|
||||||
body("address").optional().isString(),
|
|
||||||
validate,
|
|
||||||
async (req: Request, res: Response, next: NextFunction) => {
|
|
||||||
try {
|
|
||||||
const { id } = req.params;
|
|
||||||
const data = matchedData(req);
|
|
||||||
|
|
||||||
const supplier = await suppliers.findByIdAndUpdate(id, data, { new: true });
|
|
||||||
if (!supplier) return next(createHttpError(404, "Supplier not found"));
|
|
||||||
|
|
||||||
res.json({ message: "Supplier updated", supplier });
|
|
||||||
} catch (err) {
|
|
||||||
next(createHttpError(500, "Internal Server Error", { cause: err }));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
];
|
|
||||||
|
|
||||||
// 🔴 **DELETE: Hapus Supplier**
|
|
||||||
const deleteSupplier = [
|
|
||||||
param("id").isMongoId().withMessage("Invalid supplier ID"),
|
|
||||||
validate,
|
|
||||||
async (req: Request, res: Response, next: NextFunction) => {
|
|
||||||
try {
|
|
||||||
const { id } = req.params;
|
|
||||||
const supplier = await suppliers.findByIdAndDelete(id);
|
|
||||||
if (!supplier) return next(createHttpError(404, "Supplier not found"));
|
|
||||||
|
|
||||||
res.json({ message: "Supplier deleted" });
|
|
||||||
} catch (err) {
|
|
||||||
next(createHttpError(500, "Internal Server Error", { cause: err }));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
];
|
|
||||||
|
|
||||||
// 🔵 **GET: Ambil Satu Supplier Berdasarkan ID**
|
|
||||||
const getSupplier = [
|
|
||||||
param("id").isMongoId().withMessage("Invalid supplier ID"),
|
|
||||||
validate,
|
|
||||||
async (req: Request, res: Response, next: NextFunction) => {
|
|
||||||
try {
|
|
||||||
const { id } = req.params;
|
|
||||||
const supplier = await suppliers.findById(id).populate("user_id");
|
|
||||||
|
|
||||||
if (!supplier) return next(createHttpError(404, "Supplier not found"));
|
|
||||||
|
|
||||||
res.json(supplier);
|
|
||||||
} catch (err) {
|
|
||||||
next(createHttpError(500, "Internal Server Error", { cause: err }));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
];
|
|
||||||
|
|
||||||
// 🟡 **GET: Ambil Semua Supplier**
|
|
||||||
const getSuppliers = async (req: Request, res: Response, next: NextFunction) => {
|
|
||||||
try {
|
|
||||||
const allSuppliers = await suppliers.find().populate("user_id");
|
|
||||||
res.json(allSuppliers);
|
|
||||||
} catch (err) {
|
|
||||||
next(createHttpError(500, "Internal Server Error", { cause: err }));
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
export default {
|
|
||||||
createSupplier,
|
|
||||||
updateSupplier,
|
|
||||||
deleteSupplier,
|
|
||||||
getSupplier,
|
|
||||||
getSuppliers
|
|
||||||
};
|
|
|
@ -0,0 +1,121 @@
|
||||||
|
import { body, matchedData, param, query } from "express-validator"
|
||||||
|
import expressValidatorErrorHandler from "../../middleware/expressValidatorErrorHandler"
|
||||||
|
import { NextFunction, Request, Response } from "express"
|
||||||
|
import { createSupplier, deleteSupplier, getSupplier, getSuppliers, updateSupplier } from "../../services/supplierServices"
|
||||||
|
import { TAPIResponse } from "../../types/core/http"
|
||||||
|
import { ISupplierTable } from "../../types/db-model"
|
||||||
|
import createHttpError from "http-errors"
|
||||||
|
import parsePhoneNumberFromString from "libphonenumber-js"
|
||||||
|
|
||||||
|
const addSupplier = [
|
||||||
|
body('supplier_name').notEmpty().isString(),
|
||||||
|
body('contact')
|
||||||
|
.optional({ nullable: true })
|
||||||
|
.isNumeric({ no_symbols: true }).withMessage('Contact must contain only digits (0-9), no symbols allowed'),
|
||||||
|
body('address').optional().isString(),
|
||||||
|
expressValidatorErrorHandler,
|
||||||
|
async (req: Request, res: Response, next: NextFunction) => {
|
||||||
|
const reqData = matchedData<ISupplierTable>(req)
|
||||||
|
const phone = parsePhoneNumberFromString(`+${reqData.contact}`);
|
||||||
|
if (!phone?.isValid())
|
||||||
|
next(createHttpError(400, 'Invalid phone number format. Please match international format.'))
|
||||||
|
try {
|
||||||
|
const newSupplierId = await createSupplier({
|
||||||
|
...reqData,
|
||||||
|
user_id: req.user!.id
|
||||||
|
})
|
||||||
|
const result: TAPIResponse = {
|
||||||
|
success: true,
|
||||||
|
message: 'New supplier successfully created.',
|
||||||
|
data: {
|
||||||
|
supplierId: newSupplierId
|
||||||
|
}
|
||||||
|
}
|
||||||
|
res.json(result)
|
||||||
|
} catch (error) {
|
||||||
|
next(createHttpError(500, error as Error))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
|
||||||
|
const showAllSupplier = [
|
||||||
|
query('page').optional().isInt(),
|
||||||
|
query('limit').optional().isInt(),
|
||||||
|
async (req: Request, res: Response, next: NextFunction) => {
|
||||||
|
try {
|
||||||
|
const { page = 1, limit = 10 } = matchedData(req)
|
||||||
|
const suppliers = await getSuppliers(req.user!.id, page, limit);
|
||||||
|
const result: TAPIResponse = {
|
||||||
|
success: true,
|
||||||
|
data: suppliers
|
||||||
|
}
|
||||||
|
res.json(result)
|
||||||
|
} catch (error) {
|
||||||
|
next(createHttpError(500, error as Error))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
|
||||||
|
const getSupplierDetail = [
|
||||||
|
param('id'),
|
||||||
|
expressValidatorErrorHandler,
|
||||||
|
async (req: Request, res: Response, next: NextFunction) => {
|
||||||
|
try {
|
||||||
|
const { id } = matchedData(req)
|
||||||
|
const supplier = await getSupplier(id)
|
||||||
|
const result: TAPIResponse = {
|
||||||
|
success: true,
|
||||||
|
data: supplier
|
||||||
|
}
|
||||||
|
res.json(result)
|
||||||
|
} catch (error) {
|
||||||
|
next(createHttpError(500, error as Error))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
|
||||||
|
const updateSupplierRoute = [
|
||||||
|
param('id'),
|
||||||
|
body('supplier_name').notEmpty().isString(),
|
||||||
|
body('contact')
|
||||||
|
.optional({ nullable: true })
|
||||||
|
.isNumeric({ no_symbols: true }).withMessage('Contact must contain only digits (0-9), no symbols allowed'),
|
||||||
|
body('address').optional().isString(),
|
||||||
|
expressValidatorErrorHandler,
|
||||||
|
async (req: Request, res: Response, next: NextFunction) => {
|
||||||
|
try {
|
||||||
|
const { id } = matchedData(req, { locations: ['params'] })
|
||||||
|
const reqBody = matchedData<ISupplierTable>(req, { locations: ['body'] })
|
||||||
|
const phone = parsePhoneNumberFromString(`+${reqBody.contact}`);
|
||||||
|
if (!phone?.isValid())
|
||||||
|
next(createHttpError(400, 'Invalid phone number format. Please match international format.'))
|
||||||
|
await updateSupplier(id, reqBody)
|
||||||
|
const result: TAPIResponse = {
|
||||||
|
success: true,
|
||||||
|
message: 'Supplier data successfully updated'
|
||||||
|
}
|
||||||
|
res.json(result)
|
||||||
|
} catch (error) {
|
||||||
|
next(createHttpError(500, error as Error))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
|
||||||
|
const deleteSupplierRoute = [
|
||||||
|
param('id'),
|
||||||
|
expressValidatorErrorHandler,
|
||||||
|
async (req: Request, res: Response, next: NextFunction) => {
|
||||||
|
try {
|
||||||
|
const { id } = matchedData(req)
|
||||||
|
await deleteSupplier(id)
|
||||||
|
const result: TAPIResponse = {
|
||||||
|
success: true,
|
||||||
|
message: 'Supplier successfully deleted'
|
||||||
|
}
|
||||||
|
res.json(result)
|
||||||
|
} catch (error) {
|
||||||
|
next(createHttpError(500, error as Error))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
export default { addSupplier, showAllSupplier, getSupplierDetail, updateSupplierRoute, deleteSupplierRoute }
|
|
@ -0,0 +1,62 @@
|
||||||
|
import multer from 'multer';
|
||||||
|
import path from 'path';
|
||||||
|
import fs from 'fs';
|
||||||
|
import { root_path } from '../utils/core/storage';
|
||||||
|
|
||||||
|
const allowedExtensions = ['.xlsx', '.xls', '.csv', '.tsv', '.ods', '.json', '.txt', '.html', '.slk', '.dbf', '.prn', '.dif'];
|
||||||
|
const allowedMimeTypes = [
|
||||||
|
'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet',
|
||||||
|
'application/vnd.ms-excel',
|
||||||
|
'text/csv',
|
||||||
|
'application/vnd.oasis.opendocument.spreadsheet'
|
||||||
|
];
|
||||||
|
|
||||||
|
const storage = multer.diskStorage({
|
||||||
|
destination: function (req, file, cb) {
|
||||||
|
const uploadDir = root_path('src/temp');
|
||||||
|
if (!fs.existsSync(uploadDir)) fs.mkdirSync(uploadDir, { recursive: true });
|
||||||
|
cb(null, uploadDir);
|
||||||
|
},
|
||||||
|
filename: function (req, file, cb) {
|
||||||
|
const ext = path.extname(file.originalname);
|
||||||
|
const base = path.basename(file.originalname, ext);
|
||||||
|
const timestamp = Date.now();
|
||||||
|
cb(null, `${base}-${timestamp}${ext}`);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
export function fileValidate(fieldName: string) {
|
||||||
|
return multer({
|
||||||
|
storage,
|
||||||
|
limits: { fileSize: 10 * 1024 * 1024 },
|
||||||
|
fileFilter: (req, file, cb) => {
|
||||||
|
const ext = path.extname(file.originalname).toLowerCase();
|
||||||
|
const isValidMime = allowedMimeTypes.includes(file.mimetype);
|
||||||
|
const isValidExt = allowedExtensions.includes(ext);
|
||||||
|
|
||||||
|
if (!isValidMime || !isValidExt) {
|
||||||
|
return cb(new Error('Unsupported file format for SheetJS') as any, false);
|
||||||
|
}
|
||||||
|
|
||||||
|
cb(null, true);
|
||||||
|
}
|
||||||
|
}).single(fieldName);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function multiFileValidate(fields: multer.Field[]) {
|
||||||
|
return multer({
|
||||||
|
storage,
|
||||||
|
limits: { fileSize: 10 * 1024 * 1024 },
|
||||||
|
fileFilter: (req, file, cb) => {
|
||||||
|
const ext = path.extname(file.originalname).toLowerCase();
|
||||||
|
const isValidMime = allowedMimeTypes.includes(file.mimetype);
|
||||||
|
const isValidExt = allowedExtensions.includes(ext);
|
||||||
|
|
||||||
|
if (!isValidMime || !isValidExt) {
|
||||||
|
return cb(new Error('Unsupported file format for SheetJS') as any, false);
|
||||||
|
}
|
||||||
|
|
||||||
|
cb(null, true);
|
||||||
|
}
|
||||||
|
}).fields(fields);
|
||||||
|
}
|
|
@ -0,0 +1,44 @@
|
||||||
|
import { db } from "../database/MySQL"
|
||||||
|
import { ISupplierTable } from "../types/db-model"
|
||||||
|
|
||||||
|
export const insertSupplier = async ({
|
||||||
|
address, contact, supplier_name, user_id
|
||||||
|
}: ISupplierTable) => {
|
||||||
|
const [id] = await db<ISupplierTable>('suppliers').insert({
|
||||||
|
supplier_name, contact, user_id, address
|
||||||
|
})
|
||||||
|
return id
|
||||||
|
}
|
||||||
|
|
||||||
|
export const getSupplierById = (id: number) => {
|
||||||
|
return db<ISupplierTable>('suppliers').where({ id })
|
||||||
|
}
|
||||||
|
|
||||||
|
export const getAllSupplier = (user_id: number, limit: number, offset: number) => {
|
||||||
|
return db<ISupplierTable>('suppliers')
|
||||||
|
.select('*')
|
||||||
|
.where({ user_id })
|
||||||
|
.limit(limit)
|
||||||
|
.offset(offset);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
export const countSuppliers = async (id_user: number) => {
|
||||||
|
const result = await db('suppliers')
|
||||||
|
.where({ user_id: id_user })
|
||||||
|
.count('id as count')
|
||||||
|
.first<{
|
||||||
|
count: string;
|
||||||
|
}>();
|
||||||
|
|
||||||
|
return parseInt(result?.count || '0');
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
export const updateSupplierById = (id: number, data: ISupplierTable) => {
|
||||||
|
return getSupplierById(id).update(data)
|
||||||
|
}
|
||||||
|
|
||||||
|
export const deleteSupplierById = (id: number) => {
|
||||||
|
return getSupplierById(id).delete()
|
||||||
|
}
|
|
@ -2,6 +2,7 @@ import express from 'express'
|
||||||
var router = express.Router();
|
var router = express.Router();
|
||||||
|
|
||||||
import authEndpoint from './auth'
|
import authEndpoint from './auth'
|
||||||
|
import supplierEndpoint from './supplier'
|
||||||
import authenticate from '../../middleware/authMiddleware';
|
import authenticate from '../../middleware/authMiddleware';
|
||||||
// import productCategory from '../../controller/api/productCategoriesController';
|
// import productCategory from '../../controller/api/productCategoriesController';
|
||||||
// import product from '../../controller/api/productsController';
|
// import product from '../../controller/api/productsController';
|
||||||
|
@ -25,56 +26,6 @@ router.get('/is-authenticated', (req, res) => {
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
// ----- Product Categories
|
router.use('/', supplierEndpoint)
|
||||||
// router.post("/product-category", productCategory.createCategory);
|
|
||||||
// router.get("/product-categories", productCategory.getCategories);
|
|
||||||
// router.get("/product-category/:id", productCategory.getCategory);
|
|
||||||
// router.patch("/product-category/:id", productCategory.updateCategory);
|
|
||||||
// router.delete("/product-category/:id", productCategory.deleteCategory);
|
|
||||||
|
|
||||||
|
|
||||||
// ----- Products
|
|
||||||
// router.get("/product/:id", product.getProduct);
|
|
||||||
// router.post("/product", product.createProduct);
|
|
||||||
// router.patch("/product/:id", product.updateProduct);
|
|
||||||
// router.delete("/product/:id", product.deleteProduct);
|
|
||||||
// router.get("/products", product.getProducts);
|
|
||||||
|
|
||||||
|
|
||||||
// ----- Stock Forecasting
|
|
||||||
// router.post("/stock-forecasting", stockForecastingController.createStockForecasting);
|
|
||||||
// router.get("/stock-forecasting", stockForecastingController.getStockForecastingByDate);
|
|
||||||
// router.get("/stock-forecasting/history", stockForecastingController.getStockForecastings);
|
|
||||||
|
|
||||||
|
|
||||||
// ----- Stock Purchases
|
|
||||||
// router.post("/stock-purchase", stockPurchaseController.createStockPurchase);
|
|
||||||
// router.patch("/stock-purchase/:id", stockPurchaseController.updateStockPurchase);
|
|
||||||
// router.delete("/stock-purchase/:id", stockPurchaseController.deleteStockPurchase);
|
|
||||||
// router.get("/stock-purchase/:id", stockPurchaseController.getStockPurchase);
|
|
||||||
// router.get("/stock-purchase", stockPurchaseController.getStockPurchases);
|
|
||||||
|
|
||||||
|
|
||||||
// ----- Suppliers
|
|
||||||
// router.post("/supplier", supplierController.createSupplier);
|
|
||||||
// router.patch("/supplier/:id", supplierController.updateSupplier);
|
|
||||||
// router.delete("/supplier/:id", supplierController.deleteSupplier);
|
|
||||||
// router.get("/supplier/:id", supplierController.getSupplier);
|
|
||||||
// router.get("/supplier", supplierController.getSuppliers);
|
|
||||||
|
|
||||||
|
|
||||||
// ----- Transactions
|
|
||||||
// router.post("/transaction", transactionController.createTransaction);
|
|
||||||
// router.patch("/transaction/:id", transactionController.updateTransaction);
|
|
||||||
// router.delete("/transaction/:id", transactionController.deleteTransaction);
|
|
||||||
// router.get("/transaction/:id", transactionController.getTransaction);
|
|
||||||
// router.get("/transaction", transactionController.getTransactions);
|
|
||||||
|
|
||||||
// ----- userFile
|
|
||||||
// router.post("/user-files", userFileController.createUserFile);
|
|
||||||
// router.patch("/user-files/:id", userFileController.updateUserFile);
|
|
||||||
// router.delete("/user-files/:id", userFileController.deleteUserFile);
|
|
||||||
// router.get("/user-files/:id", userFileController.getUserFile);
|
|
||||||
// router.get("/user-files", userFileController.getUserFiles);
|
|
||||||
|
|
||||||
export default router;
|
export default router;
|
||||||
|
|
|
@ -0,0 +1,13 @@
|
||||||
|
import express from 'express'
|
||||||
|
var router = express.Router();
|
||||||
|
import supplierController from '../../controller/api/suppliersController';
|
||||||
|
import authenticate from '../../middleware/authMiddleware';
|
||||||
|
|
||||||
|
router.use('/', authenticate)
|
||||||
|
router.post('/supplier', supplierController.addSupplier);
|
||||||
|
router.get('/suppliers', supplierController.showAllSupplier);
|
||||||
|
router.get('/supplier/:id', supplierController.getSupplierDetail);
|
||||||
|
router.patch('/supplier/:id', supplierController.updateSupplierRoute);
|
||||||
|
router.delete('/supplier/:id', supplierController.deleteSupplierRoute);
|
||||||
|
|
||||||
|
export default router
|
|
@ -0,0 +1,31 @@
|
||||||
|
import { countSuppliers, deleteSupplierById, getAllSupplier, getSupplierById, insertSupplier, updateSupplierById } from "../repository/suppliersRepository";
|
||||||
|
import { ISupplierTable } from "../types/db-model";
|
||||||
|
|
||||||
|
export const createSupplier = async (data: ISupplierTable) => insertSupplier(data)
|
||||||
|
export const getSuppliers = async (
|
||||||
|
id_user: number,
|
||||||
|
page = 1,
|
||||||
|
limit = 10
|
||||||
|
) => {
|
||||||
|
const safePage = Math.max(1, Number(page));
|
||||||
|
const safeLimit = Math.max(1, Number(limit));
|
||||||
|
const offset = (safePage - 1) * safeLimit;
|
||||||
|
|
||||||
|
const [data, total] = await Promise.all([
|
||||||
|
getAllSupplier(id_user, safeLimit, offset),
|
||||||
|
countSuppliers(id_user)
|
||||||
|
]);
|
||||||
|
|
||||||
|
return {
|
||||||
|
data,
|
||||||
|
meta: {
|
||||||
|
page: safePage,
|
||||||
|
limit: safeLimit,
|
||||||
|
total,
|
||||||
|
totalPages: Math.ceil(total / safeLimit)
|
||||||
|
}
|
||||||
|
};
|
||||||
|
};
|
||||||
|
export const getSupplier = async (id: number) => getSupplierById(id).first()
|
||||||
|
export const updateSupplier = async (id: number, data: ISupplierTable) => updateSupplierById(id, data)
|
||||||
|
export const deleteSupplier = async (id: number) => deleteSupplierById(id)
|
Binary file not shown.
|
@ -5,6 +5,8 @@ declare namespace NodeJS {
|
||||||
PORT: string;
|
PORT: string;
|
||||||
ALLOWED_ORIGIN: string;
|
ALLOWED_ORIGIN: string;
|
||||||
|
|
||||||
|
PYTHON_API_HOST: string;
|
||||||
|
|
||||||
DB_CONNECTION: 'mysql' | string;
|
DB_CONNECTION: 'mysql' | string;
|
||||||
DB_HOST: string;
|
DB_HOST: string;
|
||||||
DB_PORT: string;
|
DB_PORT: string;
|
||||||
|
|
|
@ -1,6 +1,5 @@
|
||||||
import { IUser } from "../../db-model";
|
import { IUser } from "../../db-model";
|
||||||
import { TAccessToken } from "../../jwt";
|
import { TAccessToken } from "../../jwt";
|
||||||
import { TAPIResponse, TPaginatedResponse } from "../http";
|
|
||||||
|
|
||||||
declare module "express-serve-static-core" {
|
declare module "express-serve-static-core" {
|
||||||
interface Request {
|
interface Request {
|
||||||
|
|
|
@ -5,11 +5,15 @@ export type TAPIResponse<T = Record<string, any>> = {
|
||||||
data?: T;
|
data?: T;
|
||||||
};
|
};
|
||||||
|
|
||||||
export type TPaginatedResponse<T = Record<string, any>> = TAPIResponse<T> & {
|
interface TPaginatedResult<T> {
|
||||||
pagination?: {
|
data: T[];
|
||||||
total: number;
|
meta: {
|
||||||
page: number;
|
page: number;
|
||||||
pageSize: number;
|
limit: number;
|
||||||
|
total: number;
|
||||||
totalPages: number;
|
totalPages: number;
|
||||||
}
|
|
||||||
};
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
export type TPaginatedResponse<T = Record<string, any>> =
|
||||||
|
TAPIResponse<TPaginatedResult<T>>
|
||||||
|
|
Loading…
Reference in New Issue