fix
This commit is contained in:
parent
0e416be400
commit
9d689bdad3
|
@ -1,12 +1,12 @@
|
|||
import { NextFunction, Request, Response } from "express";
|
||||
import { TAPIResponse } from "../../types/core/http";
|
||||
import createHttpError from "http-errors";
|
||||
import { selectAllProductNPrediction } from "../../repository/predictionRepository";
|
||||
import { selectAllProductNPrediction, selectDetailPrediction } from "../../repository/predictionRepository";
|
||||
import { body, matchedData, param } from "express-validator";
|
||||
import { makeAutoPrediction } from "../../services/predictionServices";
|
||||
import expressValidatorErrorHandler from "../../middleware/expressValidatorErrorHandler";
|
||||
import { IPredictionTable } from "../../types/db-model";
|
||||
import { preparePredictionData } from "../../services/prediction/prepareData";
|
||||
import { getPreparedCSVString, preparePredictionData } from "../../services/prediction/prepareData";
|
||||
import { getPredictionModel } from "../../services/prediction/getPredictionModel";
|
||||
import { shouldUseAutoModel } from "../../services/prediction/shouldUseAutoModel";
|
||||
import { buildAutoPrediction } from "../../services/prediction/buildAutoPrediction";
|
||||
|
@ -14,6 +14,8 @@ import { savePredictionModel } from "../../services/prediction/savePredictionMod
|
|||
import { buildManualPrediction } from "../../services/prediction/buildManualPrediction";
|
||||
import { persistPrediction } from "../../services/prediction/persistPrediction";
|
||||
import { isErrorInstanceOfHttpError } from "../../utils/core/httpError";
|
||||
import { sendResponseError } from "../../utils/api/responseError";
|
||||
import { getTotalDaysInNextMonth } from "../../utils/core/date";
|
||||
|
||||
const periodArray = ['daily', 'weekly', 'monthly']
|
||||
|
||||
|
@ -65,6 +67,7 @@ const filePrediction = [
|
|||
value_column,
|
||||
prediction_period,
|
||||
date_regroup: record_period !== prediction_period,
|
||||
future_step: 1
|
||||
})
|
||||
|
||||
if (!pythonResponse?.mape || pythonResponse.mape > 50) {
|
||||
|
@ -86,7 +89,7 @@ const filePrediction = [
|
|||
message: 'Gagal melakukan prediksi. Silakan coba lagi atau cek data input.',
|
||||
});
|
||||
} catch (error) {
|
||||
next(createHttpError(500, "Internal Server Error", { cause: error }));
|
||||
next(sendResponseError(error))
|
||||
}
|
||||
}
|
||||
]
|
||||
|
@ -106,8 +109,7 @@ const getSavedPurchasePredictions = [
|
|||
}
|
||||
res.json(result)
|
||||
} catch (error) {
|
||||
console.log(error)
|
||||
next(createHttpError(500, "Internal Server Error", { error }));
|
||||
next(sendResponseError(error));
|
||||
}
|
||||
}
|
||||
]
|
||||
|
@ -127,8 +129,7 @@ const getSavedSalePredictions = [
|
|||
}
|
||||
res.json(result)
|
||||
} catch (error) {
|
||||
console.log(error)
|
||||
next(createHttpError(500, "Internal Server Error", { error }));
|
||||
next(sendResponseError(error));
|
||||
}
|
||||
}
|
||||
]
|
||||
|
@ -153,7 +154,7 @@ const purchasePrediction = [
|
|||
let predictionResult
|
||||
if (shouldUseAutoModel(model, isExpired)) {
|
||||
predictionResult = await buildAutoPrediction(csv_string, prediction_period)
|
||||
await savePredictionModel(model, predictionResult, product_id, prediction_period, source, req.user!.id)
|
||||
await savePredictionModel(model, predictionResult, product_id, prediction_period, source, req.user!.id, 3)
|
||||
} else {
|
||||
const arimaModel: [number, number, number] = [model!.ar_p, model!.differencing_d, model!.ma_q]
|
||||
predictionResult = await buildManualPrediction(csv_string, prediction_period, arimaModel)
|
||||
|
@ -163,9 +164,7 @@ const purchasePrediction = [
|
|||
|
||||
res.status(200).json({ success: true, data: finalResult })
|
||||
} catch (err) {
|
||||
if (isErrorInstanceOfHttpError(err))
|
||||
return next(err)
|
||||
return next(createHttpError(500, 'Internal Server Error', { cause: err }))
|
||||
next(sendResponseError(err))
|
||||
}
|
||||
}
|
||||
]
|
||||
|
@ -190,7 +189,7 @@ const salesPrediction = [
|
|||
let predictionResult
|
||||
if (shouldUseAutoModel(model, isExpired)) {
|
||||
predictionResult = await buildAutoPrediction(csv_string, prediction_period)
|
||||
await savePredictionModel(model, predictionResult, product_id, prediction_period, source, req.user!.id)
|
||||
await savePredictionModel(model, predictionResult, product_id, prediction_period, source, req.user!.id, 3)
|
||||
} else {
|
||||
const arimaModel: [number, number, number] = [model!.ar_p, model!.differencing_d, model!.ma_q]
|
||||
predictionResult = await buildManualPrediction(csv_string, prediction_period, arimaModel)
|
||||
|
@ -200,9 +199,101 @@ const salesPrediction = [
|
|||
|
||||
res.status(200).json({ success: true, data: finalResult })
|
||||
} catch (err) {
|
||||
if (isErrorInstanceOfHttpError(err))
|
||||
return next(err)
|
||||
return next(createHttpError(500, 'Internal Server Error', { cause: err }))
|
||||
next(sendResponseError(err))
|
||||
}
|
||||
}
|
||||
]
|
||||
|
||||
const smartPrediction = [
|
||||
param('product_id').exists().withMessage('product_id harus disediakan.')
|
||||
.isInt({ gt: 0 }).withMessage('product_id harus berupa angka bulat positif.')
|
||||
.toInt(),
|
||||
|
||||
body('prediction_period')
|
||||
.notEmpty().withMessage('prediction_period tidak boleh kosong.')
|
||||
.isIn(['weekly', 'monthly']).withMessage('prediction_period harus bernilai "weekly" atau "monthly".'),
|
||||
|
||||
body('prediction_source')
|
||||
.notEmpty().withMessage('prediction_source tidak boleh kosong.')
|
||||
.isIn(['sales', 'purchases']).withMessage('prediction_source harus bernilai "sales" atau "purchases".'),
|
||||
|
||||
expressValidatorErrorHandler,
|
||||
async (req: Request, res: Response, next: NextFunction) => {
|
||||
try {
|
||||
const { product_id, prediction_period, prediction_source } = matchedData(req)
|
||||
const prediction_period_days = prediction_period === 'weekly' ? 7 : getTotalDaysInNextMonth(new Date())
|
||||
|
||||
const {
|
||||
csv_string, data_freq
|
||||
} = await getPreparedCSVString(product_id, prediction_period, prediction_source)
|
||||
|
||||
const { model, isExpired } = await getPredictionModel(
|
||||
product_id,
|
||||
prediction_period,
|
||||
prediction_source
|
||||
)
|
||||
|
||||
let predictionResult
|
||||
if (shouldUseAutoModel(model, isExpired)) {
|
||||
predictionResult = await buildAutoPrediction(
|
||||
csv_string,
|
||||
prediction_period,
|
||||
data_freq === 'daily' ? prediction_period_days : 1
|
||||
)
|
||||
await savePredictionModel(
|
||||
model,
|
||||
predictionResult,
|
||||
product_id,
|
||||
prediction_period,
|
||||
prediction_source,
|
||||
req.user!.id,
|
||||
data_freq === 'daily' ? 1 : 3
|
||||
)
|
||||
} else {
|
||||
const arimaModel: [number, number, number] = [model!.ar_p, model!.differencing_d, model!.ma_q]
|
||||
predictionResult = await buildManualPrediction(
|
||||
csv_string,
|
||||
prediction_period,
|
||||
arimaModel,
|
||||
data_freq === 'daily' ? prediction_period_days : 1
|
||||
)
|
||||
}
|
||||
|
||||
const finalResult = await persistPrediction(
|
||||
predictionResult,
|
||||
prediction_period,
|
||||
prediction_source,
|
||||
product_id,
|
||||
req.user!.id
|
||||
)
|
||||
|
||||
res.status(200).json({ success: true, data: finalResult })
|
||||
} catch (error) {
|
||||
next(sendResponseError(error))
|
||||
}
|
||||
}
|
||||
]
|
||||
|
||||
const predictionDetail = [
|
||||
body('product_id').exists().withMessage('product_id harus disediakan.')
|
||||
.isInt({ gt: 0 }).withMessage('product_id harus berupa angka bulat positif.')
|
||||
.toInt(),
|
||||
body('prediction_period')
|
||||
.notEmpty().withMessage('prediction_period tidak boleh kosong.')
|
||||
.isIn(['weekly', 'monthly']).withMessage('prediction_period harus bernilai "weekly" atau "monthly".'),
|
||||
body('source_type')
|
||||
.notEmpty().withMessage('source_type tidak boleh kosong.')
|
||||
.isIn(['sales', 'purchases']).withMessage('source_type harus bernilai "sales" atau "purchases".'),
|
||||
expressValidatorErrorHandler,
|
||||
async (req: Request, res: Response, next: NextFunction) => {
|
||||
try {
|
||||
const { product_id, prediction_period, source_type } = matchedData(req)
|
||||
const detailPrediction = await selectDetailPrediction(
|
||||
product_id, prediction_period, source_type
|
||||
)
|
||||
res.status(200).json({ success: true, data: detailPrediction })
|
||||
} catch (err) {
|
||||
next(sendResponseError(err))
|
||||
}
|
||||
}
|
||||
]
|
||||
|
@ -212,5 +303,7 @@ export default {
|
|||
getSavedPurchasePredictions,
|
||||
getSavedSalePredictions,
|
||||
salesPrediction,
|
||||
purchasePrediction
|
||||
purchasePrediction,
|
||||
predictionDetail,
|
||||
smartPrediction
|
||||
}
|
||||
|
|
|
@ -11,6 +11,7 @@ export function selectLatestPredictions(
|
|||
.join('product_categories as pc', 'pr.product_category_id', 'pc.id')
|
||||
.select(
|
||||
'pr.product_name',
|
||||
'pr.stock',
|
||||
'p.mape',
|
||||
'p.prediction',
|
||||
'pc.category_name',
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
import { db } from "../database/MySQL";
|
||||
import { IPredictionTable, IProductTable, ITransactionTable } from "../types/db-model";
|
||||
import { IPredictionModelTable, IPredictionTable, IProductTable, ITransactionTable } from "../types/db-model";
|
||||
import { TGroupedProductSales } from "../types/db/product-sales";
|
||||
import { selectProductsByUserId } from "./productsRepository";
|
||||
|
||||
|
@ -86,4 +86,31 @@ export async function insertPrediction(data: Partial<IPredictionTable>) {
|
|||
|
||||
export function updatePrediction(id: IPredictionTable['id'], data: Partial<IPredictionTable>) {
|
||||
return db<IPredictionTable>('predictions').where({ id }).update(data)
|
||||
}
|
||||
|
||||
export function selectDetailPrediction(
|
||||
product_id: IPredictionTable['product_id'],
|
||||
period_type: IPredictionTable['period_type'],
|
||||
prediction_source: IPredictionTable['prediction_source'],
|
||||
) {
|
||||
return db<IProductTable>('products')
|
||||
.leftJoin('predictions', function () {
|
||||
this.on('predictions.product_id', '=', 'products.id')
|
||||
.andOn(db.raw('predictions.period_type = ?', [period_type]))
|
||||
.andOn(db.raw('predictions.prediction_source = ?', [prediction_source]))
|
||||
.andOn(db.raw('predictions.expired >= ?', [new Date().toISOString()]));
|
||||
})
|
||||
.where('products.id', product_id)
|
||||
.select(
|
||||
'products.product_code',
|
||||
'products.product_name',
|
||||
'products.stock',
|
||||
'products.selling_price',
|
||||
'products.buying_price',
|
||||
'predictions.prediction',
|
||||
'predictions.lower_bound',
|
||||
'predictions.upper_bound',
|
||||
'predictions.mape',
|
||||
'predictions.rmse'
|
||||
).first();
|
||||
}
|
|
@ -1,6 +1,6 @@
|
|||
import { db } from "../database/MySQL";
|
||||
import { IPurchaseTable } from "../types/db-model";
|
||||
import { getStartOfMonth, getStartOfWeek } from "../utils/core/date";
|
||||
import { db } from "../../database/MySQL";
|
||||
import { IPurchaseTable } from "../../types/db-model";
|
||||
import { getStartOfMonth, getStartOfWeek } from "../../utils/core/date";
|
||||
|
||||
export function insertsDataToRestock(data: Partial<IPurchaseTable>[]) {
|
||||
return db<IPurchaseTable>('restocks').insert(data)
|
||||
|
@ -49,6 +49,27 @@ export const selectLastMonthRestockCount = (user_id: number) => {
|
|||
}>();
|
||||
}
|
||||
|
||||
export function selectGroupedDailyPurchase(
|
||||
productId: IPurchaseTable['product_id'],
|
||||
endOfPeriod: 'last-week' | 'last-month'
|
||||
) {
|
||||
let startOfDay
|
||||
if (endOfPeriod === 'last-month')
|
||||
startOfDay = getStartOfMonth(new Date()).toISOString()
|
||||
else (endOfPeriod === 'last-week')
|
||||
startOfDay = getStartOfWeek(new Date()).toISOString()
|
||||
|
||||
return db<IPurchaseTable>('restocks')
|
||||
.select(
|
||||
db.raw('DATE(buying_date) AS date'),
|
||||
db.raw('SUM(amount) AS total_amount')
|
||||
)
|
||||
.where('product_id', productId)
|
||||
.andWhere('buying_date', '<', startOfDay)
|
||||
.groupByRaw('DATE(buying_date)')
|
||||
.orderBy('date');
|
||||
}
|
||||
|
||||
export function selectGroupedWeeklyPurchase(productId: IPurchaseTable['product_id']) {
|
||||
return db<IPurchaseTable>('restocks')
|
||||
.select(
|
|
@ -1,6 +1,6 @@
|
|||
import { db } from "../database/MySQL";
|
||||
import { ITransactionTable } from "../types/db-model";
|
||||
import { getStartOfMonth, getStartOfWeek } from "../utils/core/date";
|
||||
import { db } from "../../database/MySQL";
|
||||
import { ITransactionTable } from "../../types/db-model";
|
||||
import { getStartOfMonth, getStartOfWeek } from "../../utils/core/date";
|
||||
|
||||
export function insertsDataToTransaction(data: Partial<ITransactionTable>[]) {
|
||||
return db<ITransactionTable>('transactions').insert(data)
|
||||
|
@ -49,6 +49,27 @@ export const selectCurrentMonthSales = async (user_id: number) => {
|
|||
}>();
|
||||
}
|
||||
|
||||
export function selectGroupedDailySales(
|
||||
productId: ITransactionTable['product_id'],
|
||||
endOfPeriod: 'last-week' | 'last-month'
|
||||
) {
|
||||
let startOfDay
|
||||
if (endOfPeriod === 'last-month')
|
||||
startOfDay = getStartOfMonth(new Date()).toISOString()
|
||||
else (endOfPeriod === 'last-week')
|
||||
startOfDay = getStartOfWeek(new Date()).toISOString()
|
||||
|
||||
return db<ITransactionTable>('transactions')
|
||||
.select(
|
||||
db.raw('DATE(transaction_date) AS date'),
|
||||
db.raw('SUM(amount) AS total_amount')
|
||||
)
|
||||
.where('product_id', productId)
|
||||
.andWhere('transaction_date', '<', startOfDay)
|
||||
.groupByRaw('DATE(transaction_date)')
|
||||
.orderBy('date');
|
||||
}
|
||||
|
||||
export function selectGroupedWeeklySales(productId: ITransactionTable['product_id']) {
|
||||
return db('transactions')
|
||||
.select(
|
||||
|
@ -73,6 +94,7 @@ export function selectGroupedMonthlySales(productId: ITransactionTable['product_
|
|||
.where('product_id', productId)
|
||||
.andWhere('transaction_date', '<', getStartOfMonth(new Date()).toISOString())
|
||||
|
||||
|
||||
.groupBy(['year', 'month'])
|
||||
.orderBy(['year', 'month'])
|
||||
}
|
|
@ -1,6 +1,7 @@
|
|||
import express from 'express'
|
||||
var router = express.Router();
|
||||
import authRoute from '../../controller/api/authController';
|
||||
import authenticate from '../../middleware/authMiddleware';
|
||||
|
||||
router.post('/register', authRoute.register);
|
||||
router.post('/login', authRoute.login);
|
||||
|
@ -10,6 +11,6 @@ router.patch('/forgot-password/:token', authRoute.forgotPasswordChangePassword);
|
|||
router.get('/verify/:token', authRoute.verify);
|
||||
router.post('/re-send-email-activation/:token', authRoute.resendEmailVerification);
|
||||
router.get('/refresh-token', authRoute.refreshToken);
|
||||
router.get('/logout', authRoute.logout);
|
||||
router.get('/logout', authenticate, authRoute.logout);
|
||||
|
||||
export default router
|
|
@ -8,4 +8,6 @@ router.get('/saved-predictions/purchases/:period_type', authenticate, prediction
|
|||
router.get('/saved-predictions/sales/:period_type', authenticate, predictionController.getSavedSalePredictions)
|
||||
router.post('/purchase-prediction/:product_id', authenticate, predictionController.purchasePrediction)
|
||||
router.post('/sales-prediction/:product_id', authenticate, predictionController.salesPrediction)
|
||||
router.post('/smart-prediction/:product_id', authenticate, predictionController.smartPrediction)
|
||||
router.post('/detail-prediction', authenticate, predictionController.predictionDetail)
|
||||
export default router
|
|
@ -1,6 +1,6 @@
|
|||
import { countProducts, selectLowStockProductCount } from "../repository/productsRepository"
|
||||
import { selectLastMonthRestockCount, selectMonthlyRestockCount } from "../repository/restockRepository"
|
||||
import { selectLastMonthSales, selectCurrentMonthSales } from "../repository/transactionRepository"
|
||||
import { selectLastMonthRestockCount, selectMonthlyRestockCount } from "../repository/transaction/purchase"
|
||||
import { selectCurrentMonthSales, selectLastMonthSales } from "../repository/transaction/sales"
|
||||
import { calculateGrowth } from '../utils/math/calculateGrowth'
|
||||
|
||||
export async function getDashboardData(user_id: number) {
|
||||
|
|
|
@ -4,12 +4,14 @@ import { IPredictionTable } from "../../types/db-model"
|
|||
|
||||
export async function buildAutoPrediction(
|
||||
csv_string: string,
|
||||
prediction_period: IPredictionTable['period_type']
|
||||
prediction_period: IPredictionTable['period_type'],
|
||||
future_step: number = 1
|
||||
) {
|
||||
const result = await makePrivateAutoPrediction({
|
||||
csv_string,
|
||||
prediction_period,
|
||||
value_column: 'amount',
|
||||
future_step
|
||||
})
|
||||
|
||||
if (!result) throw createHttpError(422, 'Prediksi gagal: model tidak mengembalikan hasil.')
|
||||
|
|
|
@ -5,13 +5,15 @@ import { IPredictionTable } from "../../types/db-model"
|
|||
export async function buildManualPrediction(
|
||||
csv_string: string,
|
||||
prediction_period: IPredictionTable['period_type'],
|
||||
model: [number, number, number]
|
||||
model: [number, number, number],
|
||||
future_step: number = 1
|
||||
) {
|
||||
const result = await makePrivateManualPrediction({
|
||||
csv_string,
|
||||
prediction_period,
|
||||
value_column: 'amount',
|
||||
arima_model: model
|
||||
arima_model: model,
|
||||
future_step
|
||||
})
|
||||
|
||||
if (!result) throw createHttpError(422, 'Prediksi gagal: model tidak mengembalikan hasil.')
|
||||
|
|
|
@ -17,9 +17,9 @@ export async function persistPrediction(
|
|||
|
||||
const record = {
|
||||
...previous,
|
||||
lower_bound: prediction_result.lower.map(v => Math.round(v))[0],
|
||||
prediction: prediction_result.prediction.map(v => Math.round(v))[0],
|
||||
upper_bound: prediction_result.upper.map(v => Math.round(v))[0],
|
||||
lower_bound: prediction_result.lower.map(v => Math.round(v), 0),
|
||||
prediction: prediction_result.prediction.map(v => Math.round(v)),
|
||||
upper_bound: prediction_result.upper.map(v => Math.round(v)),
|
||||
period_type: prediction_period,
|
||||
prediction_source: source,
|
||||
product_id,
|
||||
|
|
|
@ -1,18 +1,27 @@
|
|||
import createHttpError from 'http-errors'
|
||||
import papaparse from 'papaparse'
|
||||
import { selectGroupedMonthlyPurchase, selectGroupedWeeklyPurchase } from '../../repository/restockRepository'
|
||||
import { selectDummy } from '../../repository/dummyRepository'
|
||||
import { selectGroupedDailyPurchase, selectGroupedMonthlyPurchase, selectGroupedWeeklyPurchase } from '../../repository/transaction/purchase'
|
||||
import { selectGroupedDailySales, selectGroupedMonthlySales, selectGroupedWeeklySales } from '../../repository/transaction/sales'
|
||||
|
||||
export async function preparePredictionData(product_id: number, prediction_period: 'weekly' | 'monthly', source: 'purchases' | 'sales') {
|
||||
let groupedData
|
||||
if (prediction_period === 'weekly') {
|
||||
groupedData = await selectGroupedWeeklyPurchase(product_id)
|
||||
} else {
|
||||
groupedData = await selectGroupedMonthlyPurchase(product_id)
|
||||
if (source === 'purchases') {
|
||||
groupedData = await selectGroupedWeeklyPurchase(product_id)
|
||||
} else if (source === 'sales') {
|
||||
groupedData = await selectGroupedWeeklySales(product_id)
|
||||
}
|
||||
} else if (prediction_period === 'monthly') {
|
||||
if (source === 'purchases') {
|
||||
groupedData = await selectGroupedMonthlyPurchase(product_id)
|
||||
} else if (source === 'sales') {
|
||||
groupedData = await selectGroupedMonthlySales(product_id)
|
||||
}
|
||||
}
|
||||
|
||||
if (!groupedData || groupedData.length === 0) {
|
||||
throw createHttpError(404, `Minimal lakukan 1 ${source === 'purchases' ? 'pembelian' : 'penjualan'} product dan lakukan prediksi di bulan berikutnya`)
|
||||
throw createHttpError(404, `Minimal lakukan 1 ${source === 'purchases' ? 'pembelian' : 'penjualan'} product dan lakukan prediksi di ${prediction_period === 'monthly' ? 'bulan' : 'minggu'} berikutnya`)
|
||||
}
|
||||
|
||||
let data = groupedData.map(v => ({ amount: v.amount }))
|
||||
|
@ -34,3 +43,58 @@ export async function preparePredictionData(product_id: number, prediction_perio
|
|||
const csv_string = papaparse.unparse(data)
|
||||
return csv_string
|
||||
}
|
||||
|
||||
type PreparedDataReturnValue = {
|
||||
data_freq: 'daily' | 'weekly' | 'monthly'
|
||||
csv_string: string
|
||||
}
|
||||
export async function getPreparedCSVString(
|
||||
product_id: number,
|
||||
prediction_period: 'weekly' | 'monthly',
|
||||
source: 'purchases' | 'sales'
|
||||
): Promise<PreparedDataReturnValue> {
|
||||
const returnValue: PreparedDataReturnValue = {
|
||||
data_freq: prediction_period,
|
||||
csv_string: ''
|
||||
}
|
||||
|
||||
let groupedData
|
||||
if (prediction_period === 'weekly') {
|
||||
if (source === 'purchases') {
|
||||
groupedData = await selectGroupedWeeklyPurchase(product_id)
|
||||
} else {
|
||||
groupedData = await selectGroupedWeeklySales(product_id)
|
||||
}
|
||||
} else {
|
||||
if (source === 'purchases') {
|
||||
groupedData = await selectGroupedMonthlyPurchase(product_id)
|
||||
} else {
|
||||
groupedData = await selectGroupedMonthlySales(product_id)
|
||||
}
|
||||
}
|
||||
|
||||
if (groupedData.length < 10) {
|
||||
if (source === 'purchases') {
|
||||
groupedData = await selectGroupedDailyPurchase(
|
||||
product_id,
|
||||
prediction_period === 'weekly' ? 'last-week' : 'last-month'
|
||||
)
|
||||
} else {
|
||||
groupedData = await selectGroupedDailySales(
|
||||
product_id,
|
||||
prediction_period === 'weekly' ? 'last-week' : 'last-month'
|
||||
)
|
||||
}
|
||||
returnValue.data_freq = 'daily'
|
||||
}
|
||||
|
||||
if (groupedData.length < 30) {
|
||||
throw createHttpError(422, `Minimal lakukan 30 transaksi ${source === 'sales' ? 'penjualan' : 'pembelian'} harian pada ${prediction_period === 'weekly' ? 'minggu' : 'bulan'} sebelumnya untuk dapat melakukan prediksi`)
|
||||
}
|
||||
|
||||
let data = groupedData.map(v => ({ amount: v.amount }))
|
||||
|
||||
returnValue.csv_string = papaparse.unparse(data)
|
||||
|
||||
return returnValue
|
||||
}
|
|
@ -9,12 +9,13 @@ export async function savePredictionModel(
|
|||
product_id: IPredictionTable['product_id'],
|
||||
prediction_period: IPredictionTable['period_type'],
|
||||
source: IPredictionTable['prediction_source'],
|
||||
user_id: IPredictionTable['user_id']
|
||||
user_id: IPredictionTable['user_id'],
|
||||
expiration_interval: number
|
||||
) {
|
||||
const [ar_p, differencing_d, ma_q] = prediction_result.arima_order
|
||||
const expired = prediction_period === 'monthly' ?
|
||||
getExpiredDateFromMonth(new Date(), 3) :
|
||||
getExpiredDateFromWeek(new Date(), 3)
|
||||
getExpiredDateFromMonth(new Date(), expiration_interval) :
|
||||
getExpiredDateFromWeek(new Date(), expiration_interval)
|
||||
|
||||
if (!model?.id) {
|
||||
await insertPredictionModel({
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
import { countRestocks, insertsDataToRestock, selectAllRestockHistory } from "../repository/restockRepository";
|
||||
import { countRestocks, insertsDataToRestock, selectAllRestockHistory } from "../repository/transaction/purchase";
|
||||
import { IProductTable, IPurchaseTable } from "../types/db-model";
|
||||
import { getProductByProductCodes, updateProductById } from "./productServices";
|
||||
|
||||
|
@ -51,4 +51,4 @@ export async function showRestockHistory(
|
|||
}
|
||||
};
|
||||
return
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
import { countSales, insertsDataToTransaction, selectAllSalesHistory } from "../repository/transactionRepository";
|
||||
import { countSales, insertsDataToTransaction, selectAllSalesHistory } from "../repository/transaction/sales";
|
||||
import { IProductTable, ITransactionTable } from "../types/db-model";
|
||||
import { getProductByProductCodes, updateProductById } from "./productServices";
|
||||
|
||||
|
@ -21,7 +21,7 @@ export async function addTransactionRecords(data: {
|
|||
price: Number(p.selling_price),
|
||||
})
|
||||
|
||||
const stockAfter = (Number(dataMatched?.amount) || 0) - p.stock
|
||||
const stockAfter = p.stock - (Number(dataMatched?.amount) || 0)
|
||||
updateProductById(p.id, {
|
||||
stock: stockAfter < 1 ? 0 : stockAfter
|
||||
})
|
||||
|
|
|
@ -59,9 +59,9 @@ export interface ITransactionTable {
|
|||
|
||||
export interface IPredictionTable {
|
||||
id: number;
|
||||
prediction: number;
|
||||
lower_bound: number;
|
||||
upper_bound: number;
|
||||
prediction: number[];
|
||||
lower_bound: number[];
|
||||
upper_bound: number[];
|
||||
rmse: number;
|
||||
mape: number;
|
||||
product_id: number;
|
||||
|
|
|
@ -4,6 +4,7 @@ type TPythonBasePredictionRequest = {
|
|||
value_column: string
|
||||
date_column?: string
|
||||
date_regroup?: boolean
|
||||
future_step: number
|
||||
}
|
||||
|
||||
type TPythonBasePredictionResponse = {
|
||||
|
|
|
@ -0,0 +1,19 @@
|
|||
import createHttpError, { HttpError } from "http-errors";
|
||||
import { isErrorInstanceOfHttpError } from "../core/httpError";
|
||||
import dotenv from 'dotenv'
|
||||
dotenv.config()
|
||||
|
||||
export function sendResponseError(error: unknown): HttpError {
|
||||
let newError: HttpError;
|
||||
if (isErrorInstanceOfHttpError(error)) {
|
||||
newError = error;
|
||||
} else {
|
||||
newError = createHttpError(500, "Internal Server Error", { cause: error });
|
||||
}
|
||||
|
||||
if (process.env.NODE_ENV === 'development') {
|
||||
console.error(newError);
|
||||
}
|
||||
|
||||
return newError;
|
||||
}
|
|
@ -25,4 +25,8 @@ export function getExpiredDateFromMonth(dateInput: string | Date, period = 1) {
|
|||
|
||||
export function getExpiredDateFromWeek(dateInput: string | Date, period = 1) {
|
||||
return getStartOfWeek(dateInput).add(period, 'week').endOf('day').format('YYYY-MM-DD HH:mm:ss')
|
||||
}
|
||||
|
||||
export function getTotalDaysInNextMonth(dateInput: string | Date = new Date()) {
|
||||
return getStartOfMonth(dateInput).add(1, 'month').daysInMonth()
|
||||
}
|
Loading…
Reference in New Issue