cash-report-system/backend/routes/reports.js

234 lines
6.9 KiB
JavaScript

// POST / - create report (validate body, use req.user)
// GET / - list reports (filters: by user, store, date; admin sees all)
// PUT /:id - update report (owner or admin)
// DELETE /:id - delete (admin only)
const express = require("express");
const { body, param, query, validationResult } = require("express-validator");
const { db } = require("../database/init");
const verifyToken = require("../middleware/auth");
const router = express.Router();
// POST /api/reports - create a report
router.post(
"/",
verifyToken,
[
body("storeId").isInt({ min: 1 }).withMessage("Valid storeId required"),
body("reportDate").isISO8601().withMessage("Valid reportDate required"),
body("income").isFloat().withMessage("Valid income required"),
body("initialCash").isFloat().withMessage("Valid initialCash required"),
body("totalIncome").isFloat().withMessage("Valid totalIncome required"),
body("wages").optional().isString(),
body("expenses").optional().isString(),
body("totalWages").isFloat(),
body("totalExpenses").isFloat(),
body("envelope").isFloat(),
body("finalCash").isFloat(),
],
async (req, res) => {
const errors = validationResult(req);
if (!errors.isEmpty())
return res.status(400).json({ errors: errors.array() });
const {
storeId,
reportDate,
income,
initialCash,
totalIncome,
wages,
expenses,
totalWages,
totalExpenses,
envelope,
finalCash,
} = req.body;
const userId = req.user.userId;
db.run(
`INSERT INTO reports
(userId, storeId, reportDate, income, initialCash, totalIncome, wages, expenses, totalWages, totalExpenses, envelope, finalCash)
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`,
[
userId,
storeId,
reportDate,
income,
initialCash,
totalIncome,
wages,
expenses,
totalWages,
totalExpenses,
envelope,
finalCash,
],
function (err) {
if (err) {
if (
err.message &&
err.message.toLowerCase().includes("unique constraint failed")
) {
return res.status(409).json({
error:
"Отчет за этот магазин и дату уже был отправлен этим пользователем.",
});
}
console.error("DB error:", err);
return res.status(500).json({ error: "Database error" });
}
res.status(201).json({ id: this.lastID });
}
);
}
);
// GET /api/reports - list reports, optional filters
router.get(
"/",
verifyToken,
[
query("userId").optional().isInt(),
query("storeId").optional().isInt(),
query("reportDate").optional().isISO8601(),
],
(req, res) => {
let sql = `
SELECT reports.*, stores.name AS storeName, users.username AS username, users.fullName AS fullName
FROM reports
JOIN stores ON reports.storeId = stores.id
JOIN users ON reports.userId = users.id
WHERE 1=1
`;
const params = [];
if (req.user.role !== "admin") {
sql += " AND reports.userId = ?";
params.push(req.user.userId);
} else {
if (req.query.userId) {
sql += " AND reports.userId = ?";
params.push(req.query.userId);
}
}
if (req.query.storeId) {
sql += " AND reports.storeId = ?";
params.push(req.query.storeId);
}
if (req.query.reportDate) {
sql += " AND reports.reportDate = ?";
params.push(req.query.reportDate);
}
sql += " ORDER BY reports.reportDate DESC";
db.all(sql, params, (err, rows) => {
if (err) return res.status(500).json({ error: "Database error" });
res.json({ reports: rows });
});
}
);
// PUT /api/reports/:id - update a report
router.put(
"/:id",
verifyToken,
[
param("id").isInt(),
body("income").optional().isFloat(),
body("initialCash").optional().isFloat(),
body("totalIncome").optional().isFloat(),
body("wages").optional().isString(),
body("expenses").optional().isString(),
body("totalWages").optional().isFloat(),
body("totalExpenses").optional().isFloat(),
body("envelope").optional().isFloat(),
body("finalCash").optional().isFloat(),
body("isVerified").optional().isInt({ min: 0, max: 1 }),
],
(req, res) => {
const errors = validationResult(req);
if (!errors.isEmpty())
return res.status(400).json({ errors: errors.array() });
const reportId = req.params.id;
// Only owner or admin can update
db.get("SELECT * FROM reports WHERE id = ?", [reportId], (err, report) => {
if (err || !report)
return res.status(404).json({ error: "Report not found" });
if (req.user.role !== "admin" && report.userId !== req.user.userId) {
return res.status(403).json({ error: "Forbidden" });
}
const fields = [];
const values = [];
for (const key of [
"income",
"initialCash",
"totalIncome",
"wages",
"expenses",
"totalWages",
"totalExpenses",
"envelope",
"finalCash",
"isVerified",
]) {
if (req.body[key] !== undefined) {
fields.push(`${key} = ?`);
values.push(req.body[key]);
}
}
if (fields.length === 0)
return res.status(400).json({ error: "No data to update" });
values.push(reportId);
db.run(
`UPDATE reports SET ${fields.join(
", "
)}, updatedAt = CURRENT_TIMESTAMP WHERE id = ?`,
values,
function (err) {
if (err) return res.status(500).json({ error: "Database error" });
res.json({ updated: this.changes });
}
);
});
}
);
// POST /api/reports/:id/verify - admin only
router.post("/:id/verify", verifyToken, [param("id").isInt()], (req, res) => {
if (req.user.role !== "admin")
return res.status(403).json({ error: "Admin only" });
const reportId = req.params.id;
const verifiedBy = req.user.userId;
const verifiedAt = new Date().toISOString();
db.run(
`UPDATE reports SET isVerified = 1, verifiedBy = ?, verifiedAt = ?, updatedAt = CURRENT_TIMESTAMP WHERE id = ?`,
[verifiedBy, verifiedAt, reportId],
function (err) {
if (err) return res.status(500).json({ error: "Database error" });
if (this.changes === 0)
return res.status(404).json({ error: "Report not found" });
res.json({ verified: true, reportId });
}
);
});
// DELETE /api/reports/:id - admin only
router.delete("/:id", verifyToken, [param("id").isInt()], (req, res) => {
if (req.user.role !== "admin")
return res.status(403).json({ error: "Admin only" });
db.run("DELETE FROM reports WHERE id = ?", [req.params.id], function (err) {
if (err) return res.status(500).json({ error: "Database error" });
res.json({ deleted: this.changes });
});
});
module.exports = router;