From 71b041b726a4b15181405fb80f6803b04f21ec32 Mon Sep 17 00:00:00 2001 From: Flatlogic Bot Date: Sat, 5 Jul 2025 20:29:27 +0000 Subject: [PATCH] 1.01 --- .gitignore | 5 + app-shell/src/_schema.json | 7 +- backend/src/db/api/payment.js | 602 ++++++++++++++++++ backend/src/db/migrations/1751746080332.js | 72 +++ backend/src/db/migrations/1751746246913.js | 51 ++ backend/src/db/migrations/1751746291276.js | 47 ++ backend/src/db/migrations/1751746323389.js | 47 ++ backend/src/db/migrations/1751746355023.js | 49 ++ backend/src/db/migrations/1751746389970.js | 47 ++ backend/src/db/migrations/1751746418978.js | 49 ++ backend/src/db/migrations/1751746452878.js | 49 ++ backend/src/db/migrations/1751746485119.js | 49 ++ backend/src/db/migrations/1751746520699.js | 49 ++ backend/src/db/migrations/1751746554451.js | 49 ++ backend/src/db/migrations/1751746599620.js | 49 ++ backend/src/db/migrations/1751746638160.js | 49 ++ backend/src/db/migrations/1751746667725.js | 49 ++ backend/src/db/models/payment.js | 177 +++++ .../db/seeders/20200430130760-user-roles.js | 26 + .../db/seeders/20231127130745-sample-data.js | 523 ++++++++++++++- backend/src/db/seeders/20250705200800.js | 87 +++ backend/src/index.js | 8 + backend/src/routes/payment.js | 486 ++++++++++++++ backend/src/services/payment.js | 114 ++++ backend/src/services/search.js | 26 + frontend/json/runtimeError.json | 1 + .../src/components/Payment/CardPayment.tsx | 274 ++++++++ .../src/components/Payment/ListPayment.tsx | 186 ++++++ .../src/components/Payment/TablePayment.tsx | 481 ++++++++++++++ .../Payment/configurePaymentCols.tsx | 289 +++++++++ frontend/src/menuAside.ts | 8 + frontend/src/pages/dashboard.tsx | 35 + frontend/src/pages/fees/fees-view.tsx | 105 +++ frontend/src/pages/payment/[paymentId].tsx | 278 ++++++++ frontend/src/pages/payment/payment-edit.tsx | 276 ++++++++ frontend/src/pages/payment/payment-list.tsx | 192 ++++++ frontend/src/pages/payment/payment-new.tsx | 228 +++++++ frontend/src/pages/payment/payment-table.tsx | 191 ++++++ frontend/src/pages/payment/payment-view.tsx | 188 ++++++ frontend/src/pages/students/students-view.tsx | 105 +++ frontend/src/stores/payment/paymentSlice.ts | 236 +++++++ frontend/src/stores/store.ts | 2 + 42 files changed, 5823 insertions(+), 18 deletions(-) create mode 100644 backend/src/db/api/payment.js create mode 100644 backend/src/db/migrations/1751746080332.js create mode 100644 backend/src/db/migrations/1751746246913.js create mode 100644 backend/src/db/migrations/1751746291276.js create mode 100644 backend/src/db/migrations/1751746323389.js create mode 100644 backend/src/db/migrations/1751746355023.js create mode 100644 backend/src/db/migrations/1751746389970.js create mode 100644 backend/src/db/migrations/1751746418978.js create mode 100644 backend/src/db/migrations/1751746452878.js create mode 100644 backend/src/db/migrations/1751746485119.js create mode 100644 backend/src/db/migrations/1751746520699.js create mode 100644 backend/src/db/migrations/1751746554451.js create mode 100644 backend/src/db/migrations/1751746599620.js create mode 100644 backend/src/db/migrations/1751746638160.js create mode 100644 backend/src/db/migrations/1751746667725.js create mode 100644 backend/src/db/models/payment.js create mode 100644 backend/src/db/seeders/20250705200800.js create mode 100644 backend/src/routes/payment.js create mode 100644 backend/src/services/payment.js create mode 100644 frontend/json/runtimeError.json create mode 100644 frontend/src/components/Payment/CardPayment.tsx create mode 100644 frontend/src/components/Payment/ListPayment.tsx create mode 100644 frontend/src/components/Payment/TablePayment.tsx create mode 100644 frontend/src/components/Payment/configurePaymentCols.tsx create mode 100644 frontend/src/pages/payment/[paymentId].tsx create mode 100644 frontend/src/pages/payment/payment-edit.tsx create mode 100644 frontend/src/pages/payment/payment-list.tsx create mode 100644 frontend/src/pages/payment/payment-new.tsx create mode 100644 frontend/src/pages/payment/payment-table.tsx create mode 100644 frontend/src/pages/payment/payment-view.tsx create mode 100644 frontend/src/stores/payment/paymentSlice.ts diff --git a/.gitignore b/.gitignore index e427ff3..d0eb167 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,8 @@ node_modules/ */node_modules/ */build/ + +**/node_modules/ +**/build/ +.DS_Store +.env \ No newline at end of file diff --git a/app-shell/src/_schema.json b/app-shell/src/_schema.json index ae32381..0a52a3e 100644 --- a/app-shell/src/_schema.json +++ b/app-shell/src/_schema.json @@ -1,5 +1,4 @@ - - { - "Initial version": "{\"iv\":\"a7UWsdQYvR8hXJqI\",\"encryptedData\":\"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\"}" -} + "Initial version": "{\"iv\":\"a7UWsdQYvR8hXJqI\",\"encryptedData\":\"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\"}", + "1.01": "{\"iv\":\"NNAi2YKFhPimtmc1\",\"encryptedData\":\"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\"}" +} \ No newline at end of file diff --git a/backend/src/db/api/payment.js b/backend/src/db/api/payment.js new file mode 100644 index 0000000..204fbea --- /dev/null +++ b/backend/src/db/api/payment.js @@ -0,0 +1,602 @@ +const db = require('../models'); +const FileDBApi = require('./file'); +const crypto = require('crypto'); +const Utils = require('../utils'); + +const Sequelize = db.Sequelize; +const Op = Sequelize.Op; + +module.exports = class PaymentDBApi { + static async create(data, options) { + const currentUser = (options && options.currentUser) || { id: null }; + const transaction = (options && options.transaction) || undefined; + + const payment = await db.payment.create( + { + id: data.id || undefined, + + amountpaid: data.amountpaid || null, + paymentmode: data.paymentmode || null, + chequeno: data.chequeno || null, + bankname: data.bankname || null, + transactionid: data.transactionid || null, + ddnumber: data.ddnumber || null, + ddissuingbank: data.ddissuingbank || null, + receiptnumber: data.receiptnumber || null, + discountpercentage: data.discountpercentage || null, + discountamount: data.discountamount || null, + discountreason: data.discountreason || null, + ddissuedate: data.ddissuedate || null, + remainingbalance: data.remainingbalance || null, + paymentdate: data.paymentdate || null, + importHash: data.importHash || null, + createdById: currentUser.id, + updatedById: currentUser.id, + }, + { transaction }, + ); + + await payment.setStudent(data.student || null, { + transaction, + }); + + await payment.setInvoice(data.invoice || null, { + transaction, + }); + + return payment; + } + + static async bulkImport(data, options) { + const currentUser = (options && options.currentUser) || { id: null }; + const transaction = (options && options.transaction) || undefined; + + // Prepare data - wrapping individual data transformations in a map() method + const paymentData = data.map((item, index) => ({ + id: item.id || undefined, + + amountpaid: item.amountpaid || null, + paymentmode: item.paymentmode || null, + chequeno: item.chequeno || null, + bankname: item.bankname || null, + transactionid: item.transactionid || null, + ddnumber: item.ddnumber || null, + ddissuingbank: item.ddissuingbank || null, + receiptnumber: item.receiptnumber || null, + discountpercentage: item.discountpercentage || null, + discountamount: item.discountamount || null, + discountreason: item.discountreason || null, + ddissuedate: item.ddissuedate || null, + remainingbalance: item.remainingbalance || null, + paymentdate: item.paymentdate || null, + importHash: item.importHash || null, + createdById: currentUser.id, + updatedById: currentUser.id, + createdAt: new Date(Date.now() + index * 1000), + })); + + // Bulk create items + const payment = await db.payment.bulkCreate(paymentData, { transaction }); + + // For each item created, replace relation files + + return payment; + } + + static async update(id, data, options) { + const currentUser = (options && options.currentUser) || { id: null }; + const transaction = (options && options.transaction) || undefined; + + const payment = await db.payment.findByPk(id, {}, { transaction }); + + const updatePayload = {}; + + if (data.amountpaid !== undefined) + updatePayload.amountpaid = data.amountpaid; + + if (data.paymentmode !== undefined) + updatePayload.paymentmode = data.paymentmode; + + if (data.chequeno !== undefined) updatePayload.chequeno = data.chequeno; + + if (data.bankname !== undefined) updatePayload.bankname = data.bankname; + + if (data.transactionid !== undefined) + updatePayload.transactionid = data.transactionid; + + if (data.ddnumber !== undefined) updatePayload.ddnumber = data.ddnumber; + + if (data.ddissuingbank !== undefined) + updatePayload.ddissuingbank = data.ddissuingbank; + + if (data.receiptnumber !== undefined) + updatePayload.receiptnumber = data.receiptnumber; + + if (data.discountpercentage !== undefined) + updatePayload.discountpercentage = data.discountpercentage; + + if (data.discountamount !== undefined) + updatePayload.discountamount = data.discountamount; + + if (data.discountreason !== undefined) + updatePayload.discountreason = data.discountreason; + + if (data.ddissuedate !== undefined) + updatePayload.ddissuedate = data.ddissuedate; + + if (data.remainingbalance !== undefined) + updatePayload.remainingbalance = data.remainingbalance; + + if (data.paymentdate !== undefined) + updatePayload.paymentdate = data.paymentdate; + + updatePayload.updatedById = currentUser.id; + + await payment.update(updatePayload, { transaction }); + + if (data.student !== undefined) { + await payment.setStudent( + data.student, + + { transaction }, + ); + } + + if (data.invoice !== undefined) { + await payment.setInvoice( + data.invoice, + + { transaction }, + ); + } + + return payment; + } + + static async deleteByIds(ids, options) { + const currentUser = (options && options.currentUser) || { id: null }; + const transaction = (options && options.transaction) || undefined; + + const payment = await db.payment.findAll({ + where: { + id: { + [Op.in]: ids, + }, + }, + transaction, + }); + + await db.sequelize.transaction(async (transaction) => { + for (const record of payment) { + await record.update({ deletedBy: currentUser.id }, { transaction }); + } + for (const record of payment) { + await record.destroy({ transaction }); + } + }); + + return payment; + } + + static async remove(id, options) { + const currentUser = (options && options.currentUser) || { id: null }; + const transaction = (options && options.transaction) || undefined; + + const payment = await db.payment.findByPk(id, options); + + await payment.update( + { + deletedBy: currentUser.id, + }, + { + transaction, + }, + ); + + await payment.destroy({ + transaction, + }); + + return payment; + } + + static async findBy(where, options) { + const transaction = (options && options.transaction) || undefined; + + const payment = await db.payment.findOne({ where }, { transaction }); + + if (!payment) { + return payment; + } + + const output = payment.get({ plain: true }); + + output.student = await payment.getStudent({ + transaction, + }); + + output.invoice = await payment.getInvoice({ + transaction, + }); + + return output; + } + + static async findAll(filter, options) { + const limit = filter.limit || 0; + let offset = 0; + let where = {}; + const currentPage = +filter.page; + + offset = currentPage * limit; + + const orderBy = null; + + const transaction = (options && options.transaction) || undefined; + + let include = [ + { + model: db.students, + as: 'student', + + where: filter.student + ? { + [Op.or]: [ + { + id: { + [Op.in]: filter.student + .split('|') + .map((term) => Utils.uuid(term)), + }, + }, + { + first_name: { + [Op.or]: filter.student + .split('|') + .map((term) => ({ [Op.iLike]: `%${term}%` })), + }, + }, + ], + } + : {}, + }, + + { + model: db.fees, + as: 'invoice', + + where: filter.invoice + ? { + [Op.or]: [ + { + id: { + [Op.in]: filter.invoice + .split('|') + .map((term) => Utils.uuid(term)), + }, + }, + { + amount: { + [Op.or]: filter.invoice + .split('|') + .map((term) => ({ [Op.iLike]: `%${term}%` })), + }, + }, + ], + } + : {}, + }, + ]; + + if (filter) { + if (filter.id) { + where = { + ...where, + ['id']: Utils.uuid(filter.id), + }; + } + + if (filter.chequeno) { + where = { + ...where, + [Op.and]: Utils.ilike('payment', 'chequeno', filter.chequeno), + }; + } + + if (filter.bankname) { + where = { + ...where, + [Op.and]: Utils.ilike('payment', 'bankname', filter.bankname), + }; + } + + if (filter.transactionid) { + where = { + ...where, + [Op.and]: Utils.ilike( + 'payment', + 'transactionid', + filter.transactionid, + ), + }; + } + + if (filter.ddnumber) { + where = { + ...where, + [Op.and]: Utils.ilike('payment', 'ddnumber', filter.ddnumber), + }; + } + + if (filter.ddissuingbank) { + where = { + ...where, + [Op.and]: Utils.ilike( + 'payment', + 'ddissuingbank', + filter.ddissuingbank, + ), + }; + } + + if (filter.receiptnumber) { + where = { + ...where, + [Op.and]: Utils.ilike( + 'payment', + 'receiptnumber', + filter.receiptnumber, + ), + }; + } + + if (filter.discountreason) { + where = { + ...where, + [Op.and]: Utils.ilike( + 'payment', + 'discountreason', + filter.discountreason, + ), + }; + } + + if (filter.amountpaidRange) { + const [start, end] = filter.amountpaidRange; + + if (start !== undefined && start !== null && start !== '') { + where = { + ...where, + amountpaid: { + ...where.amountpaid, + [Op.gte]: start, + }, + }; + } + + if (end !== undefined && end !== null && end !== '') { + where = { + ...where, + amountpaid: { + ...where.amountpaid, + [Op.lte]: end, + }, + }; + } + } + + if (filter.discountpercentageRange) { + const [start, end] = filter.discountpercentageRange; + + if (start !== undefined && start !== null && start !== '') { + where = { + ...where, + discountpercentage: { + ...where.discountpercentage, + [Op.gte]: start, + }, + }; + } + + if (end !== undefined && end !== null && end !== '') { + where = { + ...where, + discountpercentage: { + ...where.discountpercentage, + [Op.lte]: end, + }, + }; + } + } + + if (filter.discountamountRange) { + const [start, end] = filter.discountamountRange; + + if (start !== undefined && start !== null && start !== '') { + where = { + ...where, + discountamount: { + ...where.discountamount, + [Op.gte]: start, + }, + }; + } + + if (end !== undefined && end !== null && end !== '') { + where = { + ...where, + discountamount: { + ...where.discountamount, + [Op.lte]: end, + }, + }; + } + } + + if (filter.ddissuedateRange) { + const [start, end] = filter.ddissuedateRange; + + if (start !== undefined && start !== null && start !== '') { + where = { + ...where, + ddissuedate: { + ...where.ddissuedate, + [Op.gte]: start, + }, + }; + } + + if (end !== undefined && end !== null && end !== '') { + where = { + ...where, + ddissuedate: { + ...where.ddissuedate, + [Op.lte]: end, + }, + }; + } + } + + if (filter.remainingbalanceRange) { + const [start, end] = filter.remainingbalanceRange; + + if (start !== undefined && start !== null && start !== '') { + where = { + ...where, + remainingbalance: { + ...where.remainingbalance, + [Op.gte]: start, + }, + }; + } + + if (end !== undefined && end !== null && end !== '') { + where = { + ...where, + remainingbalance: { + ...where.remainingbalance, + [Op.lte]: end, + }, + }; + } + } + + if (filter.paymentdateRange) { + const [start, end] = filter.paymentdateRange; + + if (start !== undefined && start !== null && start !== '') { + where = { + ...where, + paymentdate: { + ...where.paymentdate, + [Op.gte]: start, + }, + }; + } + + if (end !== undefined && end !== null && end !== '') { + where = { + ...where, + paymentdate: { + ...where.paymentdate, + [Op.lte]: end, + }, + }; + } + } + + if (filter.active !== undefined) { + where = { + ...where, + active: filter.active === true || filter.active === 'true', + }; + } + + if (filter.paymentmode) { + where = { + ...where, + paymentmode: filter.paymentmode, + }; + } + + if (filter.createdAtRange) { + const [start, end] = filter.createdAtRange; + + if (start !== undefined && start !== null && start !== '') { + where = { + ...where, + ['createdAt']: { + ...where.createdAt, + [Op.gte]: start, + }, + }; + } + + if (end !== undefined && end !== null && end !== '') { + where = { + ...where, + ['createdAt']: { + ...where.createdAt, + [Op.lte]: end, + }, + }; + } + } + } + + const queryOptions = { + where, + include, + distinct: true, + order: + filter.field && filter.sort + ? [[filter.field, filter.sort]] + : [['createdAt', 'desc']], + transaction: options?.transaction, + logging: console.log, + }; + + if (!options?.countOnly) { + queryOptions.limit = limit ? Number(limit) : undefined; + queryOptions.offset = offset ? Number(offset) : undefined; + } + + try { + const { rows, count } = await db.payment.findAndCountAll(queryOptions); + + return { + rows: options?.countOnly ? [] : rows, + count: count, + }; + } catch (error) { + console.error('Error executing query:', error); + throw error; + } + } + + static async findAllAutocomplete(query, limit, offset) { + let where = {}; + + if (query) { + where = { + [Op.or]: [ + { ['id']: Utils.uuid(query) }, + Utils.ilike('payment', 'amountpaid', query), + ], + }; + } + + const records = await db.payment.findAll({ + attributes: ['id', 'amountpaid'], + where, + limit: limit ? Number(limit) : undefined, + offset: offset ? Number(offset) : undefined, + orderBy: [['amountpaid', 'ASC']], + }); + + return records.map((record) => ({ + id: record.id, + label: record.amountpaid, + })); + } +}; diff --git a/backend/src/db/migrations/1751746080332.js b/backend/src/db/migrations/1751746080332.js new file mode 100644 index 0000000..2f39b4e --- /dev/null +++ b/backend/src/db/migrations/1751746080332.js @@ -0,0 +1,72 @@ +module.exports = { + /** + * @param {QueryInterface} queryInterface + * @param {Sequelize} Sequelize + * @returns {Promise} + */ + async up(queryInterface, Sequelize) { + /** + * @type {Transaction} + */ + const transaction = await queryInterface.sequelize.transaction(); + try { + await queryInterface.createTable( + 'payment', + { + id: { + type: Sequelize.DataTypes.UUID, + defaultValue: Sequelize.DataTypes.UUIDV4, + primaryKey: true, + }, + createdById: { + type: Sequelize.DataTypes.UUID, + references: { + key: 'id', + model: 'users', + }, + }, + updatedById: { + type: Sequelize.DataTypes.UUID, + references: { + key: 'id', + model: 'users', + }, + }, + createdAt: { type: Sequelize.DataTypes.DATE }, + updatedAt: { type: Sequelize.DataTypes.DATE }, + deletedAt: { type: Sequelize.DataTypes.DATE }, + importHash: { + type: Sequelize.DataTypes.STRING(255), + allowNull: true, + unique: true, + }, + }, + { transaction }, + ); + + await transaction.commit(); + } catch (err) { + await transaction.rollback(); + throw err; + } + }, + /** + * @param {QueryInterface} queryInterface + * @param {Sequelize} Sequelize + * @returns {Promise} + */ + async down(queryInterface, Sequelize) { + /** + * @type {Transaction} + */ + const transaction = await queryInterface.sequelize.transaction(); + try { + await queryInterface.dropTable('payment', { transaction }); + + await transaction.commit(); + } catch (err) { + await transaction.rollback(); + throw err; + } + }, +}; diff --git a/backend/src/db/migrations/1751746246913.js b/backend/src/db/migrations/1751746246913.js new file mode 100644 index 0000000..2f961c7 --- /dev/null +++ b/backend/src/db/migrations/1751746246913.js @@ -0,0 +1,51 @@ +module.exports = { + /** + * @param {QueryInterface} queryInterface + * @param {Sequelize} Sequelize + * @returns {Promise} + */ + async up(queryInterface, Sequelize) { + /** + * @type {Transaction} + */ + const transaction = await queryInterface.sequelize.transaction(); + try { + await queryInterface.addColumn( + 'payment', + 'paymentmode', + { + type: Sequelize.DataTypes.ENUM, + + values: ['value'], + }, + { transaction }, + ); + + await transaction.commit(); + } catch (err) { + await transaction.rollback(); + throw err; + } + }, + /** + * @param {QueryInterface} queryInterface + * @param {Sequelize} Sequelize + * @returns {Promise} + */ + async down(queryInterface, Sequelize) { + /** + * @type {Transaction} + */ + const transaction = await queryInterface.sequelize.transaction(); + try { + await queryInterface.removeColumn('payment', 'paymentmode', { + transaction, + }); + + await transaction.commit(); + } catch (err) { + await transaction.rollback(); + throw err; + } + }, +}; diff --git a/backend/src/db/migrations/1751746291276.js b/backend/src/db/migrations/1751746291276.js new file mode 100644 index 0000000..5dbda41 --- /dev/null +++ b/backend/src/db/migrations/1751746291276.js @@ -0,0 +1,47 @@ +module.exports = { + /** + * @param {QueryInterface} queryInterface + * @param {Sequelize} Sequelize + * @returns {Promise} + */ + async up(queryInterface, Sequelize) { + /** + * @type {Transaction} + */ + const transaction = await queryInterface.sequelize.transaction(); + try { + await queryInterface.addColumn( + 'payment', + 'chequeno', + { + type: Sequelize.DataTypes.TEXT, + }, + { transaction }, + ); + + await transaction.commit(); + } catch (err) { + await transaction.rollback(); + throw err; + } + }, + /** + * @param {QueryInterface} queryInterface + * @param {Sequelize} Sequelize + * @returns {Promise} + */ + async down(queryInterface, Sequelize) { + /** + * @type {Transaction} + */ + const transaction = await queryInterface.sequelize.transaction(); + try { + await queryInterface.removeColumn('payment', 'chequeno', { transaction }); + + await transaction.commit(); + } catch (err) { + await transaction.rollback(); + throw err; + } + }, +}; diff --git a/backend/src/db/migrations/1751746323389.js b/backend/src/db/migrations/1751746323389.js new file mode 100644 index 0000000..6063729 --- /dev/null +++ b/backend/src/db/migrations/1751746323389.js @@ -0,0 +1,47 @@ +module.exports = { + /** + * @param {QueryInterface} queryInterface + * @param {Sequelize} Sequelize + * @returns {Promise} + */ + async up(queryInterface, Sequelize) { + /** + * @type {Transaction} + */ + const transaction = await queryInterface.sequelize.transaction(); + try { + await queryInterface.addColumn( + 'payment', + 'bankname', + { + type: Sequelize.DataTypes.TEXT, + }, + { transaction }, + ); + + await transaction.commit(); + } catch (err) { + await transaction.rollback(); + throw err; + } + }, + /** + * @param {QueryInterface} queryInterface + * @param {Sequelize} Sequelize + * @returns {Promise} + */ + async down(queryInterface, Sequelize) { + /** + * @type {Transaction} + */ + const transaction = await queryInterface.sequelize.transaction(); + try { + await queryInterface.removeColumn('payment', 'bankname', { transaction }); + + await transaction.commit(); + } catch (err) { + await transaction.rollback(); + throw err; + } + }, +}; diff --git a/backend/src/db/migrations/1751746355023.js b/backend/src/db/migrations/1751746355023.js new file mode 100644 index 0000000..b6b01d3 --- /dev/null +++ b/backend/src/db/migrations/1751746355023.js @@ -0,0 +1,49 @@ +module.exports = { + /** + * @param {QueryInterface} queryInterface + * @param {Sequelize} Sequelize + * @returns {Promise} + */ + async up(queryInterface, Sequelize) { + /** + * @type {Transaction} + */ + const transaction = await queryInterface.sequelize.transaction(); + try { + await queryInterface.addColumn( + 'payment', + 'transactionid', + { + type: Sequelize.DataTypes.TEXT, + }, + { transaction }, + ); + + await transaction.commit(); + } catch (err) { + await transaction.rollback(); + throw err; + } + }, + /** + * @param {QueryInterface} queryInterface + * @param {Sequelize} Sequelize + * @returns {Promise} + */ + async down(queryInterface, Sequelize) { + /** + * @type {Transaction} + */ + const transaction = await queryInterface.sequelize.transaction(); + try { + await queryInterface.removeColumn('payment', 'transactionid', { + transaction, + }); + + await transaction.commit(); + } catch (err) { + await transaction.rollback(); + throw err; + } + }, +}; diff --git a/backend/src/db/migrations/1751746389970.js b/backend/src/db/migrations/1751746389970.js new file mode 100644 index 0000000..4cd6878 --- /dev/null +++ b/backend/src/db/migrations/1751746389970.js @@ -0,0 +1,47 @@ +module.exports = { + /** + * @param {QueryInterface} queryInterface + * @param {Sequelize} Sequelize + * @returns {Promise} + */ + async up(queryInterface, Sequelize) { + /** + * @type {Transaction} + */ + const transaction = await queryInterface.sequelize.transaction(); + try { + await queryInterface.addColumn( + 'payment', + 'ddnumber', + { + type: Sequelize.DataTypes.TEXT, + }, + { transaction }, + ); + + await transaction.commit(); + } catch (err) { + await transaction.rollback(); + throw err; + } + }, + /** + * @param {QueryInterface} queryInterface + * @param {Sequelize} Sequelize + * @returns {Promise} + */ + async down(queryInterface, Sequelize) { + /** + * @type {Transaction} + */ + const transaction = await queryInterface.sequelize.transaction(); + try { + await queryInterface.removeColumn('payment', 'ddnumber', { transaction }); + + await transaction.commit(); + } catch (err) { + await transaction.rollback(); + throw err; + } + }, +}; diff --git a/backend/src/db/migrations/1751746418978.js b/backend/src/db/migrations/1751746418978.js new file mode 100644 index 0000000..a0dc1ec --- /dev/null +++ b/backend/src/db/migrations/1751746418978.js @@ -0,0 +1,49 @@ +module.exports = { + /** + * @param {QueryInterface} queryInterface + * @param {Sequelize} Sequelize + * @returns {Promise} + */ + async up(queryInterface, Sequelize) { + /** + * @type {Transaction} + */ + const transaction = await queryInterface.sequelize.transaction(); + try { + await queryInterface.addColumn( + 'payment', + 'ddissuingbank', + { + type: Sequelize.DataTypes.TEXT, + }, + { transaction }, + ); + + await transaction.commit(); + } catch (err) { + await transaction.rollback(); + throw err; + } + }, + /** + * @param {QueryInterface} queryInterface + * @param {Sequelize} Sequelize + * @returns {Promise} + */ + async down(queryInterface, Sequelize) { + /** + * @type {Transaction} + */ + const transaction = await queryInterface.sequelize.transaction(); + try { + await queryInterface.removeColumn('payment', 'ddissuingbank', { + transaction, + }); + + await transaction.commit(); + } catch (err) { + await transaction.rollback(); + throw err; + } + }, +}; diff --git a/backend/src/db/migrations/1751746452878.js b/backend/src/db/migrations/1751746452878.js new file mode 100644 index 0000000..fa52d8a --- /dev/null +++ b/backend/src/db/migrations/1751746452878.js @@ -0,0 +1,49 @@ +module.exports = { + /** + * @param {QueryInterface} queryInterface + * @param {Sequelize} Sequelize + * @returns {Promise} + */ + async up(queryInterface, Sequelize) { + /** + * @type {Transaction} + */ + const transaction = await queryInterface.sequelize.transaction(); + try { + await queryInterface.addColumn( + 'payment', + 'receiptnumber', + { + type: Sequelize.DataTypes.TEXT, + }, + { transaction }, + ); + + await transaction.commit(); + } catch (err) { + await transaction.rollback(); + throw err; + } + }, + /** + * @param {QueryInterface} queryInterface + * @param {Sequelize} Sequelize + * @returns {Promise} + */ + async down(queryInterface, Sequelize) { + /** + * @type {Transaction} + */ + const transaction = await queryInterface.sequelize.transaction(); + try { + await queryInterface.removeColumn('payment', 'receiptnumber', { + transaction, + }); + + await transaction.commit(); + } catch (err) { + await transaction.rollback(); + throw err; + } + }, +}; diff --git a/backend/src/db/migrations/1751746485119.js b/backend/src/db/migrations/1751746485119.js new file mode 100644 index 0000000..30bc910 --- /dev/null +++ b/backend/src/db/migrations/1751746485119.js @@ -0,0 +1,49 @@ +module.exports = { + /** + * @param {QueryInterface} queryInterface + * @param {Sequelize} Sequelize + * @returns {Promise} + */ + async up(queryInterface, Sequelize) { + /** + * @type {Transaction} + */ + const transaction = await queryInterface.sequelize.transaction(); + try { + await queryInterface.addColumn( + 'payment', + 'discountpercentage', + { + type: Sequelize.DataTypes.DECIMAL, + }, + { transaction }, + ); + + await transaction.commit(); + } catch (err) { + await transaction.rollback(); + throw err; + } + }, + /** + * @param {QueryInterface} queryInterface + * @param {Sequelize} Sequelize + * @returns {Promise} + */ + async down(queryInterface, Sequelize) { + /** + * @type {Transaction} + */ + const transaction = await queryInterface.sequelize.transaction(); + try { + await queryInterface.removeColumn('payment', 'discountpercentage', { + transaction, + }); + + await transaction.commit(); + } catch (err) { + await transaction.rollback(); + throw err; + } + }, +}; diff --git a/backend/src/db/migrations/1751746520699.js b/backend/src/db/migrations/1751746520699.js new file mode 100644 index 0000000..6685d4e --- /dev/null +++ b/backend/src/db/migrations/1751746520699.js @@ -0,0 +1,49 @@ +module.exports = { + /** + * @param {QueryInterface} queryInterface + * @param {Sequelize} Sequelize + * @returns {Promise} + */ + async up(queryInterface, Sequelize) { + /** + * @type {Transaction} + */ + const transaction = await queryInterface.sequelize.transaction(); + try { + await queryInterface.addColumn( + 'payment', + 'discountamount', + { + type: Sequelize.DataTypes.DECIMAL, + }, + { transaction }, + ); + + await transaction.commit(); + } catch (err) { + await transaction.rollback(); + throw err; + } + }, + /** + * @param {QueryInterface} queryInterface + * @param {Sequelize} Sequelize + * @returns {Promise} + */ + async down(queryInterface, Sequelize) { + /** + * @type {Transaction} + */ + const transaction = await queryInterface.sequelize.transaction(); + try { + await queryInterface.removeColumn('payment', 'discountamount', { + transaction, + }); + + await transaction.commit(); + } catch (err) { + await transaction.rollback(); + throw err; + } + }, +}; diff --git a/backend/src/db/migrations/1751746554451.js b/backend/src/db/migrations/1751746554451.js new file mode 100644 index 0000000..ebe694e --- /dev/null +++ b/backend/src/db/migrations/1751746554451.js @@ -0,0 +1,49 @@ +module.exports = { + /** + * @param {QueryInterface} queryInterface + * @param {Sequelize} Sequelize + * @returns {Promise} + */ + async up(queryInterface, Sequelize) { + /** + * @type {Transaction} + */ + const transaction = await queryInterface.sequelize.transaction(); + try { + await queryInterface.addColumn( + 'payment', + 'discountreason', + { + type: Sequelize.DataTypes.TEXT, + }, + { transaction }, + ); + + await transaction.commit(); + } catch (err) { + await transaction.rollback(); + throw err; + } + }, + /** + * @param {QueryInterface} queryInterface + * @param {Sequelize} Sequelize + * @returns {Promise} + */ + async down(queryInterface, Sequelize) { + /** + * @type {Transaction} + */ + const transaction = await queryInterface.sequelize.transaction(); + try { + await queryInterface.removeColumn('payment', 'discountreason', { + transaction, + }); + + await transaction.commit(); + } catch (err) { + await transaction.rollback(); + throw err; + } + }, +}; diff --git a/backend/src/db/migrations/1751746599620.js b/backend/src/db/migrations/1751746599620.js new file mode 100644 index 0000000..72c2e0d --- /dev/null +++ b/backend/src/db/migrations/1751746599620.js @@ -0,0 +1,49 @@ +module.exports = { + /** + * @param {QueryInterface} queryInterface + * @param {Sequelize} Sequelize + * @returns {Promise} + */ + async up(queryInterface, Sequelize) { + /** + * @type {Transaction} + */ + const transaction = await queryInterface.sequelize.transaction(); + try { + await queryInterface.addColumn( + 'payment', + 'ddissuedate', + { + type: Sequelize.DataTypes.DATE, + }, + { transaction }, + ); + + await transaction.commit(); + } catch (err) { + await transaction.rollback(); + throw err; + } + }, + /** + * @param {QueryInterface} queryInterface + * @param {Sequelize} Sequelize + * @returns {Promise} + */ + async down(queryInterface, Sequelize) { + /** + * @type {Transaction} + */ + const transaction = await queryInterface.sequelize.transaction(); + try { + await queryInterface.removeColumn('payment', 'ddissuedate', { + transaction, + }); + + await transaction.commit(); + } catch (err) { + await transaction.rollback(); + throw err; + } + }, +}; diff --git a/backend/src/db/migrations/1751746638160.js b/backend/src/db/migrations/1751746638160.js new file mode 100644 index 0000000..6c5a591 --- /dev/null +++ b/backend/src/db/migrations/1751746638160.js @@ -0,0 +1,49 @@ +module.exports = { + /** + * @param {QueryInterface} queryInterface + * @param {Sequelize} Sequelize + * @returns {Promise} + */ + async up(queryInterface, Sequelize) { + /** + * @type {Transaction} + */ + const transaction = await queryInterface.sequelize.transaction(); + try { + await queryInterface.addColumn( + 'payment', + 'remainingbalance', + { + type: Sequelize.DataTypes.DECIMAL, + }, + { transaction }, + ); + + await transaction.commit(); + } catch (err) { + await transaction.rollback(); + throw err; + } + }, + /** + * @param {QueryInterface} queryInterface + * @param {Sequelize} Sequelize + * @returns {Promise} + */ + async down(queryInterface, Sequelize) { + /** + * @type {Transaction} + */ + const transaction = await queryInterface.sequelize.transaction(); + try { + await queryInterface.removeColumn('payment', 'remainingbalance', { + transaction, + }); + + await transaction.commit(); + } catch (err) { + await transaction.rollback(); + throw err; + } + }, +}; diff --git a/backend/src/db/migrations/1751746667725.js b/backend/src/db/migrations/1751746667725.js new file mode 100644 index 0000000..40e0a58 --- /dev/null +++ b/backend/src/db/migrations/1751746667725.js @@ -0,0 +1,49 @@ +module.exports = { + /** + * @param {QueryInterface} queryInterface + * @param {Sequelize} Sequelize + * @returns {Promise} + */ + async up(queryInterface, Sequelize) { + /** + * @type {Transaction} + */ + const transaction = await queryInterface.sequelize.transaction(); + try { + await queryInterface.addColumn( + 'payment', + 'paymentdate', + { + type: Sequelize.DataTypes.DATE, + }, + { transaction }, + ); + + await transaction.commit(); + } catch (err) { + await transaction.rollback(); + throw err; + } + }, + /** + * @param {QueryInterface} queryInterface + * @param {Sequelize} Sequelize + * @returns {Promise} + */ + async down(queryInterface, Sequelize) { + /** + * @type {Transaction} + */ + const transaction = await queryInterface.sequelize.transaction(); + try { + await queryInterface.removeColumn('payment', 'paymentdate', { + transaction, + }); + + await transaction.commit(); + } catch (err) { + await transaction.rollback(); + throw err; + } + }, +}; diff --git a/backend/src/db/models/payment.js b/backend/src/db/models/payment.js new file mode 100644 index 0000000..7cf33a3 --- /dev/null +++ b/backend/src/db/models/payment.js @@ -0,0 +1,177 @@ +const config = require('../../config'); +const providers = config.providers; +const crypto = require('crypto'); +const bcrypt = require('bcrypt'); +const moment = require('moment'); +const Sequelize = require('sequelize'); + +module.exports = function (sequelize, DataTypes) { + const payment = sequelize.define( + 'payment', + { + id: { + type: DataTypes.UUID, + defaultValue: DataTypes.UUIDV4, + primaryKey: true, + }, + + amountpaid: { + type: DataTypes.DECIMAL, + }, + + paymentmode: { + type: DataTypes.ENUM, + + values: ['value'], + }, + + chequeno: { + type: DataTypes.TEXT, + }, + + bankname: { + type: DataTypes.TEXT, + }, + + transactionid: { + type: DataTypes.TEXT, + }, + + ddnumber: { + type: DataTypes.TEXT, + }, + + ddissuingbank: { + type: DataTypes.TEXT, + }, + + receiptnumber: { + type: DataTypes.TEXT, + }, + + discountpercentage: { + type: DataTypes.DECIMAL, + }, + + discountamount: { + type: DataTypes.DECIMAL, + }, + + discountreason: { + type: DataTypes.TEXT, + }, + + ddissuedate: { + type: DataTypes.DATE, + }, + + remainingbalance: { + type: DataTypes.DECIMAL, + }, + + paymentdate: { + type: DataTypes.DATE, + }, + + importHash: { + type: DataTypes.STRING(255), + allowNull: true, + unique: true, + }, + }, + { + timestamps: true, + paranoid: true, + freezeTableName: true, + }, + ); + // Hooks for receipt number generation and balance calculations + payment.addHook('beforeCreate', async (paymentInstance, options) => { + // Generate receipt number: YYYY-MM-sequence + const datePart = moment().format('YYYY-MM'); + // Count existing payments this month + const count = await sequelize.models.payment.count({ + where: Sequelize.where( + Sequelize.fn('to_char', Sequelize.col('createdAt'), 'YYYY-MM'), + datePart + ), + transaction: options.transaction, + }); + paymentInstance.receiptnumber = `${datePart}-${count + 1}`; + // Default payment date + if (!paymentInstance.paymentdate) { + paymentInstance.paymentdate = new Date(); + } + }); + + payment.addHook('afterCreate', async (paymentInstance, options) => { + const transaction = options.transaction; + // Update invoice outstanding if linked + if (paymentInstance.invoiceId) { + const feeRecord = await sequelize.models.fees.findByPk( + paymentInstance.invoiceId, + { transaction } + ); + if (feeRecord) { + const newOutstanding = parseFloat(feeRecord.outstanding || 0) - parseFloat(paymentInstance.amountpaid || 0); + feeRecord.outstanding = newOutstanding; + if (newOutstanding <= 0) { + feeRecord.status = 'Paid'; + } + await feeRecord.save({ transaction }); + } + } + // Update student overall outstanding balance + if (paymentInstance.studentId) { + const studentRecord = await sequelize.models.students.findByPk( + paymentInstance.studentId, + { transaction } + ); + if (studentRecord) { + const newBal = parseFloat(studentRecord.outstanding || 0) - parseFloat(paymentInstance.amountpaid || 0); + studentRecord.outstanding = newBal; + await studentRecord.save({ transaction }); + } + } + // Trigger notifications + try { + const notifyHelper = require('../../services/notifications/helpers'); + await notifyHelper.sendPaymentNotification(paymentInstance); + } catch (err) { + console.error('Payment notification error:', err); + } + }); + + + payment.associate = (db) => { + /// loop through entities and it's fields, and if ref === current e[name] and create relation has many on parent entity + + //end loop + + db.payment.belongsTo(db.students, { + as: 'student', + foreignKey: { + name: 'studentId', + }, + constraints: false, + }); + + db.payment.belongsTo(db.fees, { + as: 'invoice', + foreignKey: { + name: 'invoiceId', + }, + constraints: false, + }); + + db.payment.belongsTo(db.users, { + as: 'createdBy', + }); + + db.payment.belongsTo(db.users, { + as: 'updatedBy', + }); + }; + + return payment; +}; diff --git a/backend/src/db/seeders/20200430130760-user-roles.js b/backend/src/db/seeders/20200430130760-user-roles.js index f25da51..90d46b8 100644 --- a/backend/src/db/seeders/20200430130760-user-roles.js +++ b/backend/src/db/seeders/20200430130760-user-roles.js @@ -118,6 +118,7 @@ module.exports = { 'students', 'roles', 'permissions', + 'payment', , ]; await queryInterface.bulkInsert( @@ -1573,6 +1574,31 @@ primary key ("roles_permissionsId", "permissionId") permissionId: getId('DELETE_PERMISSIONS'), }, + { + createdAt, + updatedAt, + roles_permissionsId: getId('Administrator'), + permissionId: getId('CREATE_PAYMENT'), + }, + { + createdAt, + updatedAt, + roles_permissionsId: getId('Administrator'), + permissionId: getId('READ_PAYMENT'), + }, + { + createdAt, + updatedAt, + roles_permissionsId: getId('Administrator'), + permissionId: getId('UPDATE_PAYMENT'), + }, + { + createdAt, + updatedAt, + roles_permissionsId: getId('Administrator'), + permissionId: getId('DELETE_PAYMENT'), + }, + { createdAt, updatedAt, diff --git a/backend/src/db/seeders/20231127130745-sample-data.js b/backend/src/db/seeders/20231127130745-sample-data.js index 59bf0da..492a034 100644 --- a/backend/src/db/seeders/20231127130745-sample-data.js +++ b/backend/src/db/seeders/20231127130745-sample-data.js @@ -23,6 +23,8 @@ const Library = db.library; const Students = db.students; +const Payment = db.payment; + const AdmissionsData = [ { application_id: 'A001', @@ -49,7 +51,7 @@ const AdmissionsData = [ // type code here for "relation_one" field - status: 'Rejected', + status: 'Accepted', application_date: new Date('2023-09-03'), }, @@ -59,10 +61,20 @@ const AdmissionsData = [ // type code here for "relation_one" field - status: 'Rejected', + status: 'Pending', application_date: new Date('2023-09-04'), }, + + { + application_id: 'A005', + + // type code here for "relation_one" field + + status: 'Pending', + + application_date: new Date('2023-09-05'), + }, ]; const AttendanceData = [ @@ -105,6 +117,16 @@ const AttendanceData = [ present: true, }, + + { + // type code here for "relation_one" field + + // type code here for "relation_one" field + + date: new Date('2023-09-10'), + + present: true, + }, ]; const CoursesData = [ @@ -139,6 +161,14 @@ const CoursesData = [ // type code here for "relation_one" field }, + + { + course_name: 'Biology Basics', + + course_code: 'BIO101', + + // type code here for "relation_one" field + }, ]; const DepartmentsData = [ @@ -165,6 +195,12 @@ const DepartmentsData = [ head_of_department: 'Dr. Marie Curie', }, + + { + name: 'Biology', + + head_of_department: 'Dr. Charles Darwin', + }, ]; const EventsData = [ @@ -199,6 +235,14 @@ const EventsData = [ end_date: new Date('2023-12-05'), }, + + { + event_name: 'Alumni Meet', + + start_date: new Date('2023-12-15'), + + end_date: new Date('2023-12-15'), + }, ]; const ExamsData = [ @@ -233,6 +277,14 @@ const ExamsData = [ exam_date: new Date('2023-11-05'), }, + + { + exam_name: 'Project Presentation', + + // type code here for "relation_one" field + + exam_date: new Date('2023-11-30'), + }, ]; const FacultyData = [ @@ -275,19 +327,19 @@ const FacultyData = [ // type code here for "relation_many" field }, + + { + first_name: 'Angela', + + last_name: 'Martin', + + email: 'angela.martin@example.com', + + // type code here for "relation_many" field + }, ]; const FeesData = [ - { - // type code here for "relation_one" field - - amount: 1500, - - due_date: new Date('2023-10-01'), - - status: 'Paid', - }, - { // type code here for "relation_one" field @@ -317,6 +369,26 @@ const FeesData = [ status: 'Paid', }, + + { + // type code here for "relation_one" field + + amount: 1500, + + due_date: new Date('2023-10-01'), + + status: 'Unpaid', + }, + + { + // type code here for "relation_one" field + + amount: 1500, + + due_date: new Date('2023-10-01'), + + status: 'Unpaid', + }, ]; const GradesData = [ @@ -351,6 +423,14 @@ const GradesData = [ score: 88.5, }, + + { + // type code here for "relation_one" field + + // type code here for "relation_one" field + + score: 92, + }, ]; const LibraryData = [ @@ -359,7 +439,7 @@ const LibraryData = [ author: 'Thomas H. Cormen', - status: 'CheckedOut', + status: 'Available', }, { @@ -367,7 +447,7 @@ const LibraryData = [ author: 'Donald E. Knuth', - status: 'CheckedOut', + status: 'Available', }, { @@ -385,6 +465,14 @@ const LibraryData = [ status: 'Available', }, + + { + book_title: 'Artificial Intelligence: A Modern Approach', + + author: 'Stuart Russell', + + status: 'CheckedOut', + }, ]; const StudentsData = [ @@ -443,6 +531,192 @@ const StudentsData = [ // type code here for "relation_many" field }, + + { + first_name: 'Emily', + + last_name: 'Davis', + + email: 'emily.davis@example.com', + + date_of_birth: new Date('1998-11-05'), + + // type code here for "relation_one" field + + // type code here for "relation_many" field + }, +]; + +const PaymentData = [ + { + // type code here for "relation_one" field + + // type code here for "relation_one" field + + amountpaid: 91.53, + + paymentmode: 'value', + + chequeno: 'Alfred Kinsey', + + bankname: 'Frederick Sanger', + + transactionid: 'Konrad Lorenz', + + ddnumber: 'Theodosius Dobzhansky', + + ddissuingbank: 'Francis Crick', + + receiptnumber: 'Pierre Simon de Laplace', + + discountpercentage: 50.21, + + discountamount: 24.93, + + discountreason: 'Jean Baptiste Lamarck', + + ddissuedate: new Date(Date.now()), + + remainingbalance: 76.33, + + paymentdate: new Date(Date.now()), + }, + + { + // type code here for "relation_one" field + + // type code here for "relation_one" field + + amountpaid: 19.91, + + paymentmode: 'value', + + chequeno: 'Sigmund Freud', + + bankname: 'Franz Boas', + + transactionid: 'Max Delbruck', + + ddnumber: 'Richard Feynman', + + ddissuingbank: 'Joseph J. Thomson', + + receiptnumber: 'August Kekule', + + discountpercentage: 50.34, + + discountamount: 84.54, + + discountreason: 'Carl Gauss (Karl Friedrich Gauss)', + + ddissuedate: new Date(Date.now()), + + remainingbalance: 81.15, + + paymentdate: new Date(Date.now()), + }, + + { + // type code here for "relation_one" field + + // type code here for "relation_one" field + + amountpaid: 12.58, + + paymentmode: 'value', + + chequeno: 'Edwin Hubble', + + bankname: 'B. F. Skinner', + + transactionid: 'John Dalton', + + ddnumber: 'Werner Heisenberg', + + ddissuingbank: 'Nicolaus Copernicus', + + receiptnumber: 'Paul Ehrlich', + + discountpercentage: 35.76, + + discountamount: 67.75, + + discountreason: 'Gregor Mendel', + + ddissuedate: new Date(Date.now()), + + remainingbalance: 49.69, + + paymentdate: new Date(Date.now()), + }, + + { + // type code here for "relation_one" field + + // type code here for "relation_one" field + + amountpaid: 13.29, + + paymentmode: 'value', + + chequeno: 'Galileo Galilei', + + bankname: 'Thomas Hunt Morgan', + + transactionid: 'Tycho Brahe', + + ddnumber: 'Gertrude Belle Elion', + + ddissuingbank: 'Joseph J. Thomson', + + receiptnumber: 'Stephen Hawking', + + discountpercentage: 92.82, + + discountamount: 52.86, + + discountreason: 'Erwin Schrodinger', + + ddissuedate: new Date(Date.now()), + + remainingbalance: 58.85, + + paymentdate: new Date(Date.now()), + }, + + { + // type code here for "relation_one" field + + // type code here for "relation_one" field + + amountpaid: 95.62, + + paymentmode: 'value', + + chequeno: 'Heike Kamerlingh Onnes', + + bankname: 'Hans Bethe', + + transactionid: 'Louis Victor de Broglie', + + ddnumber: 'Isaac Newton', + + ddissuingbank: 'Edward O. Wilson', + + receiptnumber: 'Max Planck', + + discountpercentage: 34.86, + + discountamount: 23.48, + + discountreason: 'Louis Victor de Broglie', + + ddissuedate: new Date(Date.now()), + + remainingbalance: 37.63, + + paymentdate: new Date(Date.now()), + }, ]; // Similar logic for "relation_many" @@ -491,6 +765,17 @@ async function associateAdmissionWithStudent() { if (Admission3?.setStudent) { await Admission3.setStudent(relatedStudent3); } + + const relatedStudent4 = await Students.findOne({ + offset: Math.floor(Math.random() * (await Students.count())), + }); + const Admission4 = await Admissions.findOne({ + order: [['id', 'ASC']], + offset: 4, + }); + if (Admission4?.setStudent) { + await Admission4.setStudent(relatedStudent4); + } } async function associateAttendanceWithStudent() { @@ -537,6 +822,17 @@ async function associateAttendanceWithStudent() { if (Attendance3?.setStudent) { await Attendance3.setStudent(relatedStudent3); } + + const relatedStudent4 = await Students.findOne({ + offset: Math.floor(Math.random() * (await Students.count())), + }); + const Attendance4 = await Attendance.findOne({ + order: [['id', 'ASC']], + offset: 4, + }); + if (Attendance4?.setStudent) { + await Attendance4.setStudent(relatedStudent4); + } } async function associateAttendanceWithCourse() { @@ -583,6 +879,17 @@ async function associateAttendanceWithCourse() { if (Attendance3?.setCourse) { await Attendance3.setCourse(relatedCourse3); } + + const relatedCourse4 = await Courses.findOne({ + offset: Math.floor(Math.random() * (await Courses.count())), + }); + const Attendance4 = await Attendance.findOne({ + order: [['id', 'ASC']], + offset: 4, + }); + if (Attendance4?.setCourse) { + await Attendance4.setCourse(relatedCourse4); + } } async function associateCourseWithDepartment() { @@ -629,6 +936,17 @@ async function associateCourseWithDepartment() { if (Course3?.setDepartment) { await Course3.setDepartment(relatedDepartment3); } + + const relatedDepartment4 = await Departments.findOne({ + offset: Math.floor(Math.random() * (await Departments.count())), + }); + const Course4 = await Courses.findOne({ + order: [['id', 'ASC']], + offset: 4, + }); + if (Course4?.setDepartment) { + await Course4.setDepartment(relatedDepartment4); + } } async function associateExamWithCourse() { @@ -675,6 +993,17 @@ async function associateExamWithCourse() { if (Exam3?.setCourse) { await Exam3.setCourse(relatedCourse3); } + + const relatedCourse4 = await Courses.findOne({ + offset: Math.floor(Math.random() * (await Courses.count())), + }); + const Exam4 = await Exams.findOne({ + order: [['id', 'ASC']], + offset: 4, + }); + if (Exam4?.setCourse) { + await Exam4.setCourse(relatedCourse4); + } } // Similar logic for "relation_many" @@ -723,6 +1052,17 @@ async function associateFeeWithStudent() { if (Fee3?.setStudent) { await Fee3.setStudent(relatedStudent3); } + + const relatedStudent4 = await Students.findOne({ + offset: Math.floor(Math.random() * (await Students.count())), + }); + const Fee4 = await Fees.findOne({ + order: [['id', 'ASC']], + offset: 4, + }); + if (Fee4?.setStudent) { + await Fee4.setStudent(relatedStudent4); + } } async function associateGradeWithStudent() { @@ -769,6 +1109,17 @@ async function associateGradeWithStudent() { if (Grade3?.setStudent) { await Grade3.setStudent(relatedStudent3); } + + const relatedStudent4 = await Students.findOne({ + offset: Math.floor(Math.random() * (await Students.count())), + }); + const Grade4 = await Grades.findOne({ + order: [['id', 'ASC']], + offset: 4, + }); + if (Grade4?.setStudent) { + await Grade4.setStudent(relatedStudent4); + } } async function associateGradeWithExam() { @@ -815,6 +1166,17 @@ async function associateGradeWithExam() { if (Grade3?.setExam) { await Grade3.setExam(relatedExam3); } + + const relatedExam4 = await Exams.findOne({ + offset: Math.floor(Math.random() * (await Exams.count())), + }); + const Grade4 = await Grades.findOne({ + order: [['id', 'ASC']], + offset: 4, + }); + if (Grade4?.setExam) { + await Grade4.setExam(relatedExam4); + } } async function associateStudentWithDepartment() { @@ -861,10 +1223,135 @@ async function associateStudentWithDepartment() { if (Student3?.setDepartment) { await Student3.setDepartment(relatedDepartment3); } + + const relatedDepartment4 = await Departments.findOne({ + offset: Math.floor(Math.random() * (await Departments.count())), + }); + const Student4 = await Students.findOne({ + order: [['id', 'ASC']], + offset: 4, + }); + if (Student4?.setDepartment) { + await Student4.setDepartment(relatedDepartment4); + } } // Similar logic for "relation_many" +async function associatePaymentWithStudent() { + const relatedStudent0 = await Students.findOne({ + offset: Math.floor(Math.random() * (await Students.count())), + }); + const Payment0 = await Payment.findOne({ + order: [['id', 'ASC']], + offset: 0, + }); + if (Payment0?.setStudent) { + await Payment0.setStudent(relatedStudent0); + } + + const relatedStudent1 = await Students.findOne({ + offset: Math.floor(Math.random() * (await Students.count())), + }); + const Payment1 = await Payment.findOne({ + order: [['id', 'ASC']], + offset: 1, + }); + if (Payment1?.setStudent) { + await Payment1.setStudent(relatedStudent1); + } + + const relatedStudent2 = await Students.findOne({ + offset: Math.floor(Math.random() * (await Students.count())), + }); + const Payment2 = await Payment.findOne({ + order: [['id', 'ASC']], + offset: 2, + }); + if (Payment2?.setStudent) { + await Payment2.setStudent(relatedStudent2); + } + + const relatedStudent3 = await Students.findOne({ + offset: Math.floor(Math.random() * (await Students.count())), + }); + const Payment3 = await Payment.findOne({ + order: [['id', 'ASC']], + offset: 3, + }); + if (Payment3?.setStudent) { + await Payment3.setStudent(relatedStudent3); + } + + const relatedStudent4 = await Students.findOne({ + offset: Math.floor(Math.random() * (await Students.count())), + }); + const Payment4 = await Payment.findOne({ + order: [['id', 'ASC']], + offset: 4, + }); + if (Payment4?.setStudent) { + await Payment4.setStudent(relatedStudent4); + } +} + +async function associatePaymentWithInvoice() { + const relatedInvoice0 = await Fees.findOne({ + offset: Math.floor(Math.random() * (await Fees.count())), + }); + const Payment0 = await Payment.findOne({ + order: [['id', 'ASC']], + offset: 0, + }); + if (Payment0?.setInvoice) { + await Payment0.setInvoice(relatedInvoice0); + } + + const relatedInvoice1 = await Fees.findOne({ + offset: Math.floor(Math.random() * (await Fees.count())), + }); + const Payment1 = await Payment.findOne({ + order: [['id', 'ASC']], + offset: 1, + }); + if (Payment1?.setInvoice) { + await Payment1.setInvoice(relatedInvoice1); + } + + const relatedInvoice2 = await Fees.findOne({ + offset: Math.floor(Math.random() * (await Fees.count())), + }); + const Payment2 = await Payment.findOne({ + order: [['id', 'ASC']], + offset: 2, + }); + if (Payment2?.setInvoice) { + await Payment2.setInvoice(relatedInvoice2); + } + + const relatedInvoice3 = await Fees.findOne({ + offset: Math.floor(Math.random() * (await Fees.count())), + }); + const Payment3 = await Payment.findOne({ + order: [['id', 'ASC']], + offset: 3, + }); + if (Payment3?.setInvoice) { + await Payment3.setInvoice(relatedInvoice3); + } + + const relatedInvoice4 = await Fees.findOne({ + offset: Math.floor(Math.random() * (await Fees.count())), + }); + const Payment4 = await Payment.findOne({ + order: [['id', 'ASC']], + offset: 4, + }); + if (Payment4?.setInvoice) { + await Payment4.setInvoice(relatedInvoice4); + } +} + module.exports = { up: async (queryInterface, Sequelize) => { await Admissions.bulkCreate(AdmissionsData); @@ -889,6 +1376,8 @@ module.exports = { await Students.bulkCreate(StudentsData); + await Payment.bulkCreate(PaymentData); + await Promise.all([ // Similar logic for "relation_many" @@ -913,6 +1402,10 @@ module.exports = { await associateStudentWithDepartment(), // Similar logic for "relation_many" + + await associatePaymentWithStudent(), + + await associatePaymentWithInvoice(), ]); }, @@ -938,5 +1431,7 @@ module.exports = { await queryInterface.bulkDelete('library', null, {}); await queryInterface.bulkDelete('students', null, {}); + + await queryInterface.bulkDelete('payment', null, {}); }, }; diff --git a/backend/src/db/seeders/20250705200800.js b/backend/src/db/seeders/20250705200800.js new file mode 100644 index 0000000..c4bb8ba --- /dev/null +++ b/backend/src/db/seeders/20250705200800.js @@ -0,0 +1,87 @@ +const { v4: uuid } = require('uuid'); +const db = require('../models'); +const Sequelize = require('sequelize'); +const config = require('../../config'); + +module.exports = { + /** + * @param{import("sequelize").QueryInterface} queryInterface + * @return {Promise} + */ + async up(queryInterface) { + const createdAt = new Date(); + const updatedAt = new Date(); + + /** @type {Map} */ + const idMap = new Map(); + + /** + * @param {string} key + * @return {string} + */ + function getId(key) { + if (idMap.has(key)) { + return idMap.get(key); + } + const id = uuid(); + idMap.set(key, id); + return id; + } + + /** + * @param {string} name + */ + function createPermissions(name) { + return [ + { + id: getId(`CREATE_${name.toUpperCase()}`), + createdAt, + updatedAt, + name: `CREATE_${name.toUpperCase()}`, + }, + { + id: getId(`READ_${name.toUpperCase()}`), + createdAt, + updatedAt, + name: `READ_${name.toUpperCase()}`, + }, + { + id: getId(`UPDATE_${name.toUpperCase()}`), + createdAt, + updatedAt, + name: `UPDATE_${name.toUpperCase()}`, + }, + { + id: getId(`DELETE_${name.toUpperCase()}`), + createdAt, + updatedAt, + name: `DELETE_${name.toUpperCase()}`, + }, + ]; + } + + const entities = ['payment']; + + const createdPermissions = entities.flatMap(createPermissions); + + // Add permissions to database + await queryInterface.bulkInsert('permissions', createdPermissions); + // Get permissions ids + const permissionsIds = createdPermissions.map((p) => p.id); + // Get admin role + const adminRole = await db.roles.findOne({ + where: { name: config.roles.admin }, + }); + + if (adminRole) { + // Add permissions to admin role if it exists + await adminRole.addPermissions(permissionsIds); + } + }, + down: async (queryInterface, Sequelize) => { + await queryInterface.bulkDelete( + 'permissions', + entities.flatMap(createPermissions), + ); + }, +}; diff --git a/backend/src/index.js b/backend/src/index.js index 71c7cd7..4e1cfd0 100644 --- a/backend/src/index.js +++ b/backend/src/index.js @@ -45,6 +45,8 @@ const rolesRoutes = require('./routes/roles'); const permissionsRoutes = require('./routes/permissions'); +const paymentRoutes = require('./routes/payment'); + const getBaseUrl = (url) => { if (!url) return ''; return url.endsWith('/api') ? url.slice(0, -4) : url; @@ -194,6 +196,12 @@ app.use( permissionsRoutes, ); +app.use( + '/api/payment', + passport.authenticate('jwt', { session: false }), + paymentRoutes, +); + app.use( '/api/openai', passport.authenticate('jwt', { session: false }), diff --git a/backend/src/routes/payment.js b/backend/src/routes/payment.js new file mode 100644 index 0000000..223e595 --- /dev/null +++ b/backend/src/routes/payment.js @@ -0,0 +1,486 @@ +const express = require('express'); + +const PaymentService = require('../services/payment'); +const PaymentDBApi = require('../db/api/payment'); +const wrapAsync = require('../helpers').wrapAsync; + +const router = express.Router(); + +const { parse } = require('json2csv'); + +const { checkCrudPermissions } = require('../middlewares/check-permissions'); + +router.use(checkCrudPermissions('payment')); + +/** + * @swagger + * components: + * schemas: + * Payment: + * type: object + * properties: + + * chequeno: + * type: string + * default: chequeno + * bankname: + * type: string + * default: bankname + * transactionid: + * type: string + * default: transactionid + * ddnumber: + * type: string + * default: ddnumber + * ddissuingbank: + * type: string + * default: ddissuingbank + * receiptnumber: + * type: string + * default: receiptnumber + * discountreason: + * type: string + * default: discountreason + + * amountpaid: + * type: integer + * format: int64 + * discountpercentage: + * type: integer + * format: int64 + * discountamount: + * type: integer + * format: int64 + * remainingbalance: + * type: integer + * format: int64 + + * + */ + +/** + * @swagger + * tags: + * name: Payment + * description: The Payment managing API + */ + +/** + * @swagger + * /api/payment: + * post: + * security: + * - bearerAuth: [] + * tags: [Payment] + * summary: Add new item + * description: Add new item + * requestBody: + * required: true + * content: + * application/json: + * schema: + * properties: + * data: + * description: Data of the updated item + * type: object + * $ref: "#/components/schemas/Payment" + * responses: + * 200: + * description: The item was successfully added + * content: + * application/json: + * schema: + * $ref: "#/components/schemas/Payment" + * 401: + * $ref: "#/components/responses/UnauthorizedError" + * 405: + * description: Invalid input data + * 500: + * description: Some server error + */ +router.post( + '/', + wrapAsync(async (req, res) => { + const referer = + req.headers.referer || + `${req.protocol}://${req.hostname}${req.originalUrl}`; + const link = new URL(referer); + await PaymentService.create( + req.body.data, + req.currentUser, + true, + link.host, + ); + const payload = true; + res.status(200).send(payload); + }), +); + +/** + * @swagger + * /api/budgets/bulk-import: + * post: + * security: + * - bearerAuth: [] + * tags: [Payment] + * summary: Bulk import items + * description: Bulk import items + * requestBody: + * required: true + * content: + * application/json: + * schema: + * properties: + * data: + * description: Data of the updated items + * type: array + * items: + * $ref: "#/components/schemas/Payment" + * responses: + * 200: + * description: The items were successfully imported + * content: + * application/json: + * schema: + * $ref: "#/components/schemas/Payment" + * 401: + * $ref: "#/components/responses/UnauthorizedError" + * 405: + * description: Invalid input data + * 500: + * description: Some server error + * + */ +router.post( + '/bulk-import', + wrapAsync(async (req, res) => { + const referer = + req.headers.referer || + `${req.protocol}://${req.hostname}${req.originalUrl}`; + const link = new URL(referer); + await PaymentService.bulkImport(req, res, true, link.host); + const payload = true; + res.status(200).send(payload); + }), +); + +/** + * @swagger + * /api/payment/{id}: + * put: + * security: + * - bearerAuth: [] + * tags: [Payment] + * summary: Update the data of the selected item + * description: Update the data of the selected item + * parameters: + * - in: path + * name: id + * description: Item ID to update + * required: true + * schema: + * type: string + * requestBody: + * description: Set new item data + * required: true + * content: + * application/json: + * schema: + * properties: + * id: + * description: ID of the updated item + * type: string + * data: + * description: Data of the updated item + * type: object + * $ref: "#/components/schemas/Payment" + * required: + * - id + * responses: + * 200: + * description: The item data was successfully updated + * content: + * application/json: + * schema: + * $ref: "#/components/schemas/Payment" + * 400: + * description: Invalid ID supplied + * 401: + * $ref: "#/components/responses/UnauthorizedError" + * 404: + * description: Item not found + * 500: + * description: Some server error + */ +router.put( + '/:id', + wrapAsync(async (req, res) => { + await PaymentService.update(req.body.data, req.body.id, req.currentUser); + const payload = true; + res.status(200).send(payload); + }), +); + +/** + * @swagger + * /api/payment/{id}: + * delete: + * security: + * - bearerAuth: [] + * tags: [Payment] + * summary: Delete the selected item + * description: Delete the selected item + * parameters: + * - in: path + * name: id + * description: Item ID to delete + * required: true + * schema: + * type: string + * responses: + * 200: + * description: The item was successfully deleted + * content: + * application/json: + * schema: + * $ref: "#/components/schemas/Payment" + * 400: + * description: Invalid ID supplied + * 401: + * $ref: "#/components/responses/UnauthorizedError" + * 404: + * description: Item not found + * 500: + * description: Some server error + */ +router.delete( + '/:id', + wrapAsync(async (req, res) => { + await PaymentService.remove(req.params.id, req.currentUser); + const payload = true; + res.status(200).send(payload); + }), +); + +/** + * @swagger + * /api/payment/deleteByIds: + * post: + * security: + * - bearerAuth: [] + * tags: [Payment] + * summary: Delete the selected item list + * description: Delete the selected item list + * requestBody: + * required: true + * content: + * application/json: + * schema: + * properties: + * ids: + * description: IDs of the updated items + * type: array + * responses: + * 200: + * description: The items was successfully deleted + * content: + * application/json: + * schema: + * $ref: "#/components/schemas/Payment" + * 401: + * $ref: "#/components/responses/UnauthorizedError" + * 404: + * description: Items not found + * 500: + * description: Some server error + */ +router.post( + '/deleteByIds', + wrapAsync(async (req, res) => { + await PaymentService.deleteByIds(req.body.data, req.currentUser); + const payload = true; + res.status(200).send(payload); + }), +); + +/** + * @swagger + * /api/payment: + * get: + * security: + * - bearerAuth: [] + * tags: [Payment] + * summary: Get all payment + * description: Get all payment + * responses: + * 200: + * description: Payment list successfully received + * content: + * application/json: + * schema: + * type: array + * items: + * $ref: "#/components/schemas/Payment" + * 401: + * $ref: "#/components/responses/UnauthorizedError" + * 404: + * description: Data not found + * 500: + * description: Some server error + */ +router.get( + '/', + wrapAsync(async (req, res) => { + const filetype = req.query.filetype; + + const currentUser = req.currentUser; + const payload = await PaymentDBApi.findAll(req.query, { currentUser }); + if (filetype && filetype === 'csv') { + const fields = [ + 'id', + 'chequeno', + 'bankname', + 'transactionid', + 'ddnumber', + 'ddissuingbank', + 'receiptnumber', + 'discountreason', + + 'amountpaid', + 'discountpercentage', + 'discountamount', + 'remainingbalance', + 'ddissuedate', + 'paymentdate', + ]; + const opts = { fields }; + try { + const csv = parse(payload.rows, opts); + res.status(200).attachment(csv); + res.send(csv); + } catch (err) { + console.error(err); + } + } else { + res.status(200).send(payload); + } + }), +); + +/** + * @swagger + * /api/payment/count: + * get: + * security: + * - bearerAuth: [] + * tags: [Payment] + * summary: Count all payment + * description: Count all payment + * responses: + * 200: + * description: Payment count successfully received + * content: + * application/json: + * schema: + * type: array + * items: + * $ref: "#/components/schemas/Payment" + * 401: + * $ref: "#/components/responses/UnauthorizedError" + * 404: + * description: Data not found + * 500: + * description: Some server error + */ +router.get( + '/count', + wrapAsync(async (req, res) => { + const currentUser = req.currentUser; + const payload = await PaymentDBApi.findAll(req.query, null, { + countOnly: true, + currentUser, + }); + + res.status(200).send(payload); + }), +); + +/** + * @swagger + * /api/payment/autocomplete: + * get: + * security: + * - bearerAuth: [] + * tags: [Payment] + * summary: Find all payment that match search criteria + * description: Find all payment that match search criteria + * responses: + * 200: + * description: Payment list successfully received + * content: + * application/json: + * schema: + * type: array + * items: + * $ref: "#/components/schemas/Payment" + * 401: + * $ref: "#/components/responses/UnauthorizedError" + * 404: + * description: Data not found + * 500: + * description: Some server error + */ +router.get('/autocomplete', async (req, res) => { + const payload = await PaymentDBApi.findAllAutocomplete( + req.query.query, + req.query.limit, + req.query.offset, + ); + + res.status(200).send(payload); +}); + +/** + * @swagger + * /api/payment/{id}: + * get: + * security: + * - bearerAuth: [] + * tags: [Payment] + * summary: Get selected item + * description: Get selected item + * parameters: + * - in: path + * name: id + * description: ID of item to get + * required: true + * schema: + * type: string + * responses: + * 200: + * description: Selected item successfully received + * content: + * application/json: + * schema: + * $ref: "#/components/schemas/Payment" + * 400: + * description: Invalid ID supplied + * 401: + * $ref: "#/components/responses/UnauthorizedError" + * 404: + * description: Item not found + * 500: + * description: Some server error + */ +router.get( + '/:id', + wrapAsync(async (req, res) => { + const payload = await PaymentDBApi.findBy({ id: req.params.id }); + + res.status(200).send(payload); + }), +); + +router.use('/', require('../helpers').commonErrorHandler); + +module.exports = router; diff --git a/backend/src/services/payment.js b/backend/src/services/payment.js new file mode 100644 index 0000000..6fd96bf --- /dev/null +++ b/backend/src/services/payment.js @@ -0,0 +1,114 @@ +const db = require('../db/models'); +const PaymentDBApi = require('../db/api/payment'); +const processFile = require('../middlewares/upload'); +const ValidationError = require('./notifications/errors/validation'); +const csv = require('csv-parser'); +const axios = require('axios'); +const config = require('../config'); +const stream = require('stream'); + +module.exports = class PaymentService { + static async create(data, currentUser) { + const transaction = await db.sequelize.transaction(); + try { + await PaymentDBApi.create(data, { + currentUser, + transaction, + }); + + await transaction.commit(); + } catch (error) { + await transaction.rollback(); + throw error; + } + } + + static async bulkImport(req, res, sendInvitationEmails = true, host) { + const transaction = await db.sequelize.transaction(); + + try { + await processFile(req, res); + const bufferStream = new stream.PassThrough(); + const results = []; + + await bufferStream.end(Buffer.from(req.file.buffer, 'utf-8')); // convert Buffer to Stream + + await new Promise((resolve, reject) => { + bufferStream + .pipe(csv()) + .on('data', (data) => results.push(data)) + .on('end', async () => { + console.log('CSV results', results); + resolve(); + }) + .on('error', (error) => reject(error)); + }); + + await PaymentDBApi.bulkImport(results, { + transaction, + ignoreDuplicates: true, + validate: true, + currentUser: req.currentUser, + }); + + await transaction.commit(); + } catch (error) { + await transaction.rollback(); + throw error; + } + } + + static async update(data, id, currentUser) { + const transaction = await db.sequelize.transaction(); + try { + let payment = await PaymentDBApi.findBy({ id }, { transaction }); + + if (!payment) { + throw new ValidationError('paymentNotFound'); + } + + const updatedPayment = await PaymentDBApi.update(id, data, { + currentUser, + transaction, + }); + + await transaction.commit(); + return updatedPayment; + } catch (error) { + await transaction.rollback(); + throw error; + } + } + + static async deleteByIds(ids, currentUser) { + const transaction = await db.sequelize.transaction(); + + try { + await PaymentDBApi.deleteByIds(ids, { + currentUser, + transaction, + }); + + await transaction.commit(); + } catch (error) { + await transaction.rollback(); + throw error; + } + } + + static async remove(id, currentUser) { + const transaction = await db.sequelize.transaction(); + + try { + await PaymentDBApi.remove(id, { + currentUser, + transaction, + }); + + await transaction.commit(); + } catch (error) { + await transaction.rollback(); + throw error; + } + } +}; diff --git a/backend/src/services/search.js b/backend/src/services/search.js index 9cd1672..e75e458 100644 --- a/backend/src/services/search.js +++ b/backend/src/services/search.js @@ -58,11 +58,37 @@ module.exports = class SearchService { library: ['book_title', 'author'], students: ['first_name', 'last_name', 'email'], + + payment: [ + 'chequeno', + + 'bankname', + + 'transactionid', + + 'ddnumber', + + 'ddissuingbank', + + 'receiptnumber', + + 'discountreason', + ], }; const columnsInt = { fees: ['amount'], grades: ['score'], + + payment: [ + 'amountpaid', + + 'discountpercentage', + + 'discountamount', + + 'remainingbalance', + ], }; let allFoundRecords = []; diff --git a/frontend/json/runtimeError.json b/frontend/json/runtimeError.json new file mode 100644 index 0000000..9e26dfe --- /dev/null +++ b/frontend/json/runtimeError.json @@ -0,0 +1 @@ +{} \ No newline at end of file diff --git a/frontend/src/components/Payment/CardPayment.tsx b/frontend/src/components/Payment/CardPayment.tsx new file mode 100644 index 0000000..e7ccd75 --- /dev/null +++ b/frontend/src/components/Payment/CardPayment.tsx @@ -0,0 +1,274 @@ +import React from 'react'; +import ImageField from '../ImageField'; +import ListActionsPopover from '../ListActionsPopover'; +import { useAppSelector } from '../../stores/hooks'; +import dataFormatter from '../../helpers/dataFormatter'; +import { Pagination } from '../Pagination'; +import { saveFile } from '../../helpers/fileSaver'; +import LoadingSpinner from '../LoadingSpinner'; +import Link from 'next/link'; + +import { hasPermission } from '../../helpers/userPermissions'; + +type Props = { + payment: any[]; + loading: boolean; + onDelete: (id: string) => void; + currentPage: number; + numPages: number; + onPageChange: (page: number) => void; +}; + +const CardPayment = ({ + payment, + loading, + onDelete, + currentPage, + numPages, + onPageChange, +}: Props) => { + const asideScrollbarsStyle = useAppSelector( + (state) => state.style.asideScrollbarsStyle, + ); + const bgColor = useAppSelector((state) => state.style.cardsColor); + const darkMode = useAppSelector((state) => state.style.darkMode); + const corners = useAppSelector((state) => state.style.corners); + const focusRing = useAppSelector((state) => state.style.focusRingColor); + + const currentUser = useAppSelector((state) => state.auth.currentUser); + const hasUpdatePermission = hasPermission(currentUser, 'UPDATE_PAYMENT'); + + return ( +
+ {loading && } +
    + {!loading && + payment.map((item, index) => ( +
  • +
    + + {item.amountpaid} + + +
    + +
    +
    +
    +
    +
    + Student +
    +
    +
    + {dataFormatter.studentsOneListFormatter(item.student)} +
    +
    +
    + +
    +
    + Invoice +
    +
    +
    + {dataFormatter.feesOneListFormatter(item.invoice)} +
    +
    +
    + +
    +
    + Amountpaid +
    +
    +
    + {item.amountpaid} +
    +
    +
    + +
    +
    + Paymentmode +
    +
    +
    + {item.paymentmode} +
    +
    +
    + +
    +
    + Chequeno +
    +
    +
    + {item.chequeno} +
    +
    +
    + +
    +
    + Bankname +
    +
    +
    + {item.bankname} +
    +
    +
    + +
    +
    + Transactionid +
    +
    +
    + {item.transactionid} +
    +
    +
    + +
    +
    + Ddnumber +
    +
    +
    + {item.ddnumber} +
    +
    +
    + +
    +
    + Ddissuingbank +
    +
    +
    + {item.ddissuingbank} +
    +
    +
    + +
    +
    + Receiptnumber +
    +
    +
    + {item.receiptnumber} +
    +
    +
    + +
    +
    + Discountpercentage +
    +
    +
    + {item.discountpercentage} +
    +
    +
    + +
    +
    + Discountamount +
    +
    +
    + {item.discountamount} +
    +
    +
    + +
    +
    + Discountreason +
    +
    +
    + {item.discountreason} +
    +
    +
    + +
    +
    + Ddissuedate +
    +
    +
    + {dataFormatter.dateTimeFormatter(item.ddissuedate)} +
    +
    +
    + +
    +
    + Remainingbalance +
    +
    +
    + {item.remainingbalance} +
    +
    +
    + +
    +
    + Paymentdate +
    +
    +
    + {dataFormatter.dateTimeFormatter(item.paymentdate)} +
    +
    +
    +
    +
  • + ))} + {!loading && payment.length === 0 && ( +
    +

    No data to display

    +
    + )} +
+
+ +
+
+ ); +}; + +export default CardPayment; diff --git a/frontend/src/components/Payment/ListPayment.tsx b/frontend/src/components/Payment/ListPayment.tsx new file mode 100644 index 0000000..08e871f --- /dev/null +++ b/frontend/src/components/Payment/ListPayment.tsx @@ -0,0 +1,186 @@ +import React from 'react'; +import CardBox from '../CardBox'; +import ImageField from '../ImageField'; +import dataFormatter from '../../helpers/dataFormatter'; +import { saveFile } from '../../helpers/fileSaver'; +import ListActionsPopover from '../ListActionsPopover'; +import { useAppSelector } from '../../stores/hooks'; +import { Pagination } from '../Pagination'; +import LoadingSpinner from '../LoadingSpinner'; +import Link from 'next/link'; + +import { hasPermission } from '../../helpers/userPermissions'; + +type Props = { + payment: any[]; + loading: boolean; + onDelete: (id: string) => void; + currentPage: number; + numPages: number; + onPageChange: (page: number) => void; +}; + +const ListPayment = ({ + payment, + loading, + onDelete, + currentPage, + numPages, + onPageChange, +}: Props) => { + const currentUser = useAppSelector((state) => state.auth.currentUser); + const hasUpdatePermission = hasPermission(currentUser, 'UPDATE_PAYMENT'); + + const corners = useAppSelector((state) => state.style.corners); + const bgColor = useAppSelector((state) => state.style.cardsColor); + + return ( + <> +
+ {loading && } + {!loading && + payment.map((item) => ( +
+ +
+ dark:divide-dark-700 overflow-x-auto' + } + > +
+

Student

+

+ {dataFormatter.studentsOneListFormatter(item.student)} +

+
+ +
+

Invoice

+

+ {dataFormatter.feesOneListFormatter(item.invoice)} +

+
+ +
+

Amountpaid

+

{item.amountpaid}

+
+ +
+

Paymentmode

+

{item.paymentmode}

+
+ +
+

Chequeno

+

{item.chequeno}

+
+ +
+

Bankname

+

{item.bankname}

+
+ +
+

+ Transactionid +

+

{item.transactionid}

+
+ +
+

Ddnumber

+

{item.ddnumber}

+
+ +
+

+ Ddissuingbank +

+

{item.ddissuingbank}

+
+ +
+

+ Receiptnumber +

+

{item.receiptnumber}

+
+ +
+

+ Discountpercentage +

+

+ {item.discountpercentage} +

+
+ +
+

+ Discountamount +

+

{item.discountamount}

+
+ +
+

+ Discountreason +

+

{item.discountreason}

+
+ +
+

Ddissuedate

+

+ {dataFormatter.dateTimeFormatter(item.ddissuedate)} +

+
+ +
+

+ Remainingbalance +

+

{item.remainingbalance}

+
+ +
+

Paymentdate

+

+ {dataFormatter.dateTimeFormatter(item.paymentdate)} +

+
+ + +
+
+
+ ))} + {!loading && payment.length === 0 && ( +
+

No data to display

+
+ )} +
+
+ +
+ + ); +}; + +export default ListPayment; diff --git a/frontend/src/components/Payment/TablePayment.tsx b/frontend/src/components/Payment/TablePayment.tsx new file mode 100644 index 0000000..1ca6458 --- /dev/null +++ b/frontend/src/components/Payment/TablePayment.tsx @@ -0,0 +1,481 @@ +import React, { useEffect, useState, useMemo } from 'react'; +import { createPortal } from 'react-dom'; +import { ToastContainer, toast } from 'react-toastify'; +import BaseButton from '../BaseButton'; +import CardBoxModal from '../CardBoxModal'; +import CardBox from '../CardBox'; +import { + fetch, + update, + deleteItem, + setRefetch, + deleteItemsByIds, +} from '../../stores/payment/paymentSlice'; +import { useAppDispatch, useAppSelector } from '../../stores/hooks'; +import { useRouter } from 'next/router'; +import { Field, Form, Formik } from 'formik'; +import { DataGrid, GridColDef } from '@mui/x-data-grid'; +import { loadColumns } from './configurePaymentCols'; +import _ from 'lodash'; +import dataFormatter from '../../helpers/dataFormatter'; +import { dataGridStyles } from '../../styles'; + +const perPage = 10; + +const TableSamplePayment = ({ + filterItems, + setFilterItems, + filters, + showGrid, +}) => { + const notify = (type, msg) => toast(msg, { type, position: 'bottom-center' }); + + const dispatch = useAppDispatch(); + const router = useRouter(); + + const pagesList = []; + const [id, setId] = useState(null); + const [currentPage, setCurrentPage] = useState(0); + const [filterRequest, setFilterRequest] = React.useState(''); + const [columns, setColumns] = useState([]); + const [selectedRows, setSelectedRows] = useState([]); + const [sortModel, setSortModel] = useState([ + { + field: '', + sort: 'desc', + }, + ]); + + const { + payment, + loading, + count, + notify: paymentNotify, + refetch, + } = useAppSelector((state) => state.payment); + const { currentUser } = useAppSelector((state) => state.auth); + const focusRing = useAppSelector((state) => state.style.focusRingColor); + const bgColor = useAppSelector((state) => state.style.bgLayoutColor); + const corners = useAppSelector((state) => state.style.corners); + const numPages = + Math.floor(count / perPage) === 0 ? 1 : Math.ceil(count / perPage); + for (let i = 0; i < numPages; i++) { + pagesList.push(i); + } + + const loadData = async (page = currentPage, request = filterRequest) => { + if (page !== currentPage) setCurrentPage(page); + if (request !== filterRequest) setFilterRequest(request); + const { sort, field } = sortModel[0]; + + const query = `?page=${page}&limit=${perPage}${request}&sort=${sort}&field=${field}`; + dispatch(fetch({ limit: perPage, page, query })); + }; + + useEffect(() => { + if (paymentNotify.showNotification) { + notify(paymentNotify.typeNotification, paymentNotify.textNotification); + } + }, [paymentNotify.showNotification]); + + useEffect(() => { + if (!currentUser) return; + loadData(); + }, [sortModel, currentUser]); + + useEffect(() => { + if (refetch) { + loadData(0); + dispatch(setRefetch(false)); + } + }, [refetch, dispatch]); + + const [isModalInfoActive, setIsModalInfoActive] = useState(false); + const [isModalTrashActive, setIsModalTrashActive] = useState(false); + + const handleModalAction = () => { + setIsModalInfoActive(false); + setIsModalTrashActive(false); + }; + + const handleDeleteModalAction = (id: string) => { + setId(id); + setIsModalTrashActive(true); + }; + const handleDeleteAction = async () => { + if (id) { + await dispatch(deleteItem(id)); + await loadData(0); + setIsModalTrashActive(false); + } + }; + + const generateFilterRequests = useMemo(() => { + let request = '&'; + filterItems.forEach((item) => { + const isRangeFilter = filters.find( + (filter) => + filter.title === item.fields.selectedField && + (filter.number || filter.date), + ); + + if (isRangeFilter) { + const from = item.fields.filterValueFrom; + const to = item.fields.filterValueTo; + if (from) { + request += `${item.fields.selectedField}Range=${from}&`; + } + if (to) { + request += `${item.fields.selectedField}Range=${to}&`; + } + } else { + const value = item.fields.filterValue; + if (value) { + request += `${item.fields.selectedField}=${value}&`; + } + } + }); + return request; + }, [filterItems, filters]); + + const deleteFilter = (value) => { + const newItems = filterItems.filter((item) => item.id !== value); + + if (newItems.length) { + setFilterItems(newItems); + } else { + loadData(0, ''); + + setFilterItems(newItems); + } + }; + + const handleSubmit = () => { + loadData(0, generateFilterRequests); + }; + + const handleChange = (id) => (e) => { + const value = e.target.value; + const name = e.target.name; + + setFilterItems( + filterItems.map((item) => { + if (item.id !== id) return item; + if (name === 'selectedField') return { id, fields: { [name]: value } }; + + return { id, fields: { ...item.fields, [name]: value } }; + }), + ); + }; + + const handleReset = () => { + setFilterItems([]); + loadData(0, ''); + }; + + const onPageChange = (page: number) => { + loadData(page); + setCurrentPage(page); + }; + + useEffect(() => { + if (!currentUser) return; + + loadColumns(handleDeleteModalAction, `payment`, currentUser).then( + (newCols) => setColumns(newCols), + ); + }, [currentUser]); + + const handleTableSubmit = async (id: string, data) => { + if (!_.isEmpty(data)) { + await dispatch(update({ id, data })) + .unwrap() + .then((res) => res) + .catch((err) => { + throw new Error(err); + }); + } + }; + + const onDeleteRows = async (selectedRows) => { + await dispatch(deleteItemsByIds(selectedRows)); + await loadData(0); + }; + + const controlClasses = + 'w-full py-2 px-2 my-2 rounded dark:placeholder-gray-400 ' + + ` ${bgColor} ${focusRing} ${corners} ` + + 'dark:bg-slate-800 border'; + + const dataGrid = ( +
+ `datagrid--row`} + rows={payment ?? []} + columns={columns} + initialState={{ + pagination: { + paginationModel: { + pageSize: 10, + }, + }, + }} + disableRowSelectionOnClick + onProcessRowUpdateError={(params) => { + console.log('Error', params); + }} + processRowUpdate={async (newRow, oldRow) => { + const data = dataFormatter.dataGridEditFormatter(newRow); + + try { + await handleTableSubmit(newRow.id, data); + return newRow; + } catch { + return oldRow; + } + }} + sortingMode={'server'} + checkboxSelection + onRowSelectionModelChange={(ids) => { + setSelectedRows(ids); + }} + onSortModelChange={(params) => { + params.length + ? setSortModel(params) + : setSortModel([{ field: '', sort: 'desc' }]); + }} + rowCount={count} + pageSizeOptions={[10]} + paginationMode={'server'} + loading={loading} + onPaginationModelChange={(params) => { + onPageChange(params.page); + }} + /> +
+ ); + + return ( + <> + {filterItems && Array.isArray(filterItems) && filterItems.length ? ( + + null} + > +
+ <> + {filterItems && + filterItems.map((filterItem) => { + return ( +
+
+
+ Filter +
+ + {filters.map((selectOption) => ( + + ))} + +
+ {filters.find( + (filter) => + filter.title === filterItem?.fields?.selectedField, + )?.type === 'enum' ? ( +
+
Value
+ + + {filters + .find( + (filter) => + filter.title === + filterItem?.fields?.selectedField, + ) + ?.options?.map((option) => ( + + ))} + +
+ ) : filters.find( + (filter) => + filter.title === + filterItem?.fields?.selectedField, + )?.number ? ( +
+
+
+ From +
+ +
+
+
+ To +
+ +
+
+ ) : filters.find( + (filter) => + filter.title === + filterItem?.fields?.selectedField, + )?.date ? ( +
+
+
+ From +
+ +
+
+
+ To +
+ +
+
+ ) : ( +
+
+ Contains +
+ +
+ )} +
+
+ Action +
+ { + deleteFilter(filterItem.id); + }} + /> +
+
+ ); + })} +
+ + +
+ +
+
+
+ ) : null} + +

Are you sure you want to delete this item?

+
+ + {dataGrid} + + {selectedRows.length > 0 && + createPortal( + onDeleteRows(selectedRows)} + />, + document.getElementById('delete-rows-button'), + )} + + + ); +}; + +export default TableSamplePayment; diff --git a/frontend/src/components/Payment/configurePaymentCols.tsx b/frontend/src/components/Payment/configurePaymentCols.tsx new file mode 100644 index 0000000..52924df --- /dev/null +++ b/frontend/src/components/Payment/configurePaymentCols.tsx @@ -0,0 +1,289 @@ +import React from 'react'; +import BaseIcon from '../BaseIcon'; +import { mdiEye, mdiTrashCan, mdiPencilOutline } from '@mdi/js'; +import axios from 'axios'; +import { + GridActionsCellItem, + GridRowParams, + GridValueGetterParams, +} from '@mui/x-data-grid'; +import ImageField from '../ImageField'; +import { saveFile } from '../../helpers/fileSaver'; +import dataFormatter from '../../helpers/dataFormatter'; +import DataGridMultiSelect from '../DataGridMultiSelect'; +import ListActionsPopover from '../ListActionsPopover'; + +import { hasPermission } from '../../helpers/userPermissions'; + +type Params = (id: string) => void; + +export const loadColumns = async ( + onDelete: Params, + entityName: string, + + user, +) => { + async function callOptionsApi(entityName: string) { + if (!hasPermission(user, 'READ_' + entityName.toUpperCase())) return []; + + try { + const data = await axios(`/${entityName}/autocomplete?limit=100`); + return data.data; + } catch (error) { + console.log(error); + return []; + } + } + + const hasUpdatePermission = hasPermission(user, 'UPDATE_PAYMENT'); + + return [ + { + field: 'student', + headerName: 'Student', + flex: 1, + minWidth: 120, + filterable: false, + headerClassName: 'datagrid--header', + cellClassName: 'datagrid--cell', + + editable: hasUpdatePermission, + + sortable: false, + type: 'singleSelect', + getOptionValue: (value: any) => value?.id, + getOptionLabel: (value: any) => value?.label, + valueOptions: await callOptionsApi('students'), + valueGetter: (params: GridValueGetterParams) => + params?.value?.id ?? params?.value, + }, + + { + field: 'invoice', + headerName: 'Invoice', + flex: 1, + minWidth: 120, + filterable: false, + headerClassName: 'datagrid--header', + cellClassName: 'datagrid--cell', + + editable: hasUpdatePermission, + + sortable: false, + type: 'singleSelect', + getOptionValue: (value: any) => value?.id, + getOptionLabel: (value: any) => value?.label, + valueOptions: await callOptionsApi('fees'), + valueGetter: (params: GridValueGetterParams) => + params?.value?.id ?? params?.value, + }, + + { + field: 'amountpaid', + headerName: 'Amountpaid', + flex: 1, + minWidth: 120, + filterable: false, + headerClassName: 'datagrid--header', + cellClassName: 'datagrid--cell', + + editable: hasUpdatePermission, + + type: 'number', + }, + + { + field: 'paymentmode', + headerName: 'Paymentmode', + flex: 1, + minWidth: 120, + filterable: false, + headerClassName: 'datagrid--header', + cellClassName: 'datagrid--cell', + + editable: hasUpdatePermission, + + type: 'singleSelect', + valueOptions: ['value'], + }, + + { + field: 'chequeno', + headerName: 'Chequeno', + flex: 1, + minWidth: 120, + filterable: false, + headerClassName: 'datagrid--header', + cellClassName: 'datagrid--cell', + + editable: hasUpdatePermission, + }, + + { + field: 'bankname', + headerName: 'Bankname', + flex: 1, + minWidth: 120, + filterable: false, + headerClassName: 'datagrid--header', + cellClassName: 'datagrid--cell', + + editable: hasUpdatePermission, + }, + + { + field: 'transactionid', + headerName: 'Transactionid', + flex: 1, + minWidth: 120, + filterable: false, + headerClassName: 'datagrid--header', + cellClassName: 'datagrid--cell', + + editable: hasUpdatePermission, + }, + + { + field: 'ddnumber', + headerName: 'Ddnumber', + flex: 1, + minWidth: 120, + filterable: false, + headerClassName: 'datagrid--header', + cellClassName: 'datagrid--cell', + + editable: hasUpdatePermission, + }, + + { + field: 'ddissuingbank', + headerName: 'Ddissuingbank', + flex: 1, + minWidth: 120, + filterable: false, + headerClassName: 'datagrid--header', + cellClassName: 'datagrid--cell', + + editable: hasUpdatePermission, + }, + + { + field: 'receiptnumber', + headerName: 'Receiptnumber', + flex: 1, + minWidth: 120, + filterable: false, + headerClassName: 'datagrid--header', + cellClassName: 'datagrid--cell', + + editable: hasUpdatePermission, + }, + + { + field: 'discountpercentage', + headerName: 'Discountpercentage', + flex: 1, + minWidth: 120, + filterable: false, + headerClassName: 'datagrid--header', + cellClassName: 'datagrid--cell', + + editable: hasUpdatePermission, + + type: 'number', + }, + + { + field: 'discountamount', + headerName: 'Discountamount', + flex: 1, + minWidth: 120, + filterable: false, + headerClassName: 'datagrid--header', + cellClassName: 'datagrid--cell', + + editable: hasUpdatePermission, + + type: 'number', + }, + + { + field: 'discountreason', + headerName: 'Discountreason', + flex: 1, + minWidth: 120, + filterable: false, + headerClassName: 'datagrid--header', + cellClassName: 'datagrid--cell', + + editable: hasUpdatePermission, + }, + + { + field: 'ddissuedate', + headerName: 'Ddissuedate', + flex: 1, + minWidth: 120, + filterable: false, + headerClassName: 'datagrid--header', + cellClassName: 'datagrid--cell', + + editable: hasUpdatePermission, + + type: 'dateTime', + valueGetter: (params: GridValueGetterParams) => + new Date(params.row.ddissuedate), + }, + + { + field: 'remainingbalance', + headerName: 'Remainingbalance', + flex: 1, + minWidth: 120, + filterable: false, + headerClassName: 'datagrid--header', + cellClassName: 'datagrid--cell', + + editable: hasUpdatePermission, + + type: 'number', + }, + + { + field: 'paymentdate', + headerName: 'Paymentdate', + flex: 1, + minWidth: 120, + filterable: false, + headerClassName: 'datagrid--header', + cellClassName: 'datagrid--cell', + + editable: hasUpdatePermission, + + type: 'dateTime', + valueGetter: (params: GridValueGetterParams) => + new Date(params.row.paymentdate), + }, + + { + field: 'actions', + type: 'actions', + minWidth: 30, + headerClassName: 'datagrid--header', + cellClassName: 'datagrid--cell', + getActions: (params: GridRowParams) => { + return [ +
+ +
, + ]; + }, + }, + ]; +}; diff --git a/frontend/src/menuAside.ts b/frontend/src/menuAside.ts index 35ee4e8..0f354aa 100644 --- a/frontend/src/menuAside.ts +++ b/frontend/src/menuAside.ts @@ -153,6 +153,14 @@ const menuAside: MenuAsideItem[] = [ icon: icon.mdiShieldAccountOutline ?? icon.mdiTable, permissions: 'READ_PERMISSIONS', }, + { + href: '/payment/payment-list', + label: 'Payment', + // eslint-disable-next-line @typescript-eslint/ban-ts-comment + // @ts-ignore + icon: icon.mdiTable ?? icon.mdiTable, + permissions: 'READ_PAYMENT', + }, { href: '/profile', label: 'Profile', diff --git a/frontend/src/pages/dashboard.tsx b/frontend/src/pages/dashboard.tsx index 7472725..857d281 100644 --- a/frontend/src/pages/dashboard.tsx +++ b/frontend/src/pages/dashboard.tsx @@ -42,6 +42,7 @@ const Dashboard = () => { const [students, setStudents] = React.useState(loadingMessage); const [roles, setRoles] = React.useState(loadingMessage); const [permissions, setPermissions] = React.useState(loadingMessage); + const [payment, setPayment] = React.useState(loadingMessage); const [widgetsRole, setWidgetsRole] = React.useState({ role: { value: '', label: '' }, @@ -67,6 +68,7 @@ const Dashboard = () => { 'students', 'roles', 'permissions', + 'payment', ]; const fns = [ setUsers, @@ -83,6 +85,7 @@ const Dashboard = () => { setStudents, setRoles, setPermissions, + setPayment, ]; const requests = entities.map((entity, index) => { @@ -692,6 +695,38 @@ const Dashboard = () => { )} + + {hasPermission(currentUser, 'READ_PAYMENT') && ( + +
+
+
+
+ Payment +
+
+ {payment} +
+
+
+ +
+
+
+ + )} diff --git a/frontend/src/pages/fees/fees-view.tsx b/frontend/src/pages/fees/fees-view.tsx index d8c7bda..a8a615b 100644 --- a/frontend/src/pages/fees/fees-view.tsx +++ b/frontend/src/pages/fees/fees-view.tsx @@ -87,6 +87,111 @@ const FeesView = () => {

{fees?.status ?? 'No data'}

+ <> +

Payment Invoice

+ +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + {fees.payment_invoice && + Array.isArray(fees.payment_invoice) && + fees.payment_invoice.map((item: any) => ( + + router.push(`/payment/payment-view/?id=${item.id}`) + } + > + + + + + + + + + + + + + + + + + + + + + + + + + + + + + ))} + +
AmountpaidPaymentmodeChequenoBanknameTransactionidDdnumberDdissuingbankReceiptnumberDiscountpercentageDiscountamountDiscountreasonDdissuedateRemainingbalancePaymentdate
{item.amountpaid}{item.paymentmode}{item.chequeno}{item.bankname} + {item.transactionid} + {item.ddnumber} + {item.ddissuingbank} + + {item.receiptnumber} + + {item.discountpercentage} + + {item.discountamount} + + {item.discountreason} + + {dataFormatter.dateTimeFormatter(item.ddissuedate)} + + {item.remainingbalance} + + {dataFormatter.dateTimeFormatter(item.paymentdate)} +
+
+ {!fees?.payment_invoice?.length && ( +
No data
+ )} +
+ + { + const router = useRouter(); + const dispatch = useAppDispatch(); + const initVals = { + student: null, + + invoice: null, + + amountpaid: '', + + paymentmode: '', + + chequeno: '', + + bankname: '', + + transactionid: '', + + ddnumber: '', + + ddissuingbank: '', + + receiptnumber: '', + + discountpercentage: '', + + discountamount: '', + + discountreason: '', + + ddissuedate: new Date(), + + remainingbalance: '', + + paymentdate: new Date(), + }; + const [initialValues, setInitialValues] = useState(initVals); + + const { payment } = useAppSelector((state) => state.payment); + + const { paymentId } = router.query; + + useEffect(() => { + dispatch(fetch({ id: paymentId })); + }, [paymentId]); + + useEffect(() => { + if (typeof payment === 'object') { + setInitialValues(payment); + } + }, [payment]); + + useEffect(() => { + if (typeof payment === 'object') { + const newInitialVal = { ...initVals }; + + Object.keys(initVals).forEach((el) => (newInitialVal[el] = payment[el])); + + setInitialValues(newInitialVal); + } + }, [payment]); + + const handleSubmit = async (data) => { + await dispatch(update({ id: paymentId, data })); + await router.push('/payment/payment-list'); + }; + + return ( + <> + + {getPageTitle('Edit payment')} + + + + {''} + + + handleSubmit(values)} + > +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + setInitialValues({ ...initialValues, ddissuedate: date }) + } + /> + + + + + + + + + setInitialValues({ ...initialValues, paymentdate: date }) + } + /> + + + + + + + router.push('/payment/payment-list')} + /> + + +
+
+
+ + ); +}; + +EditPayment.getLayout = function getLayout(page: ReactElement) { + return ( + + {page} + + ); +}; + +export default EditPayment; diff --git a/frontend/src/pages/payment/payment-edit.tsx b/frontend/src/pages/payment/payment-edit.tsx new file mode 100644 index 0000000..bd57dff --- /dev/null +++ b/frontend/src/pages/payment/payment-edit.tsx @@ -0,0 +1,276 @@ +import { mdiChartTimelineVariant, mdiUpload } from '@mdi/js'; +import Head from 'next/head'; +import React, { ReactElement, useEffect, useState } from 'react'; +import DatePicker from 'react-datepicker'; +import 'react-datepicker/dist/react-datepicker.css'; +import dayjs from 'dayjs'; + +import CardBox from '../../components/CardBox'; +import LayoutAuthenticated from '../../layouts/Authenticated'; +import SectionMain from '../../components/SectionMain'; +import SectionTitleLineWithButton from '../../components/SectionTitleLineWithButton'; +import { getPageTitle } from '../../config'; + +import { Field, Form, Formik } from 'formik'; +import FormField from '../../components/FormField'; +import BaseDivider from '../../components/BaseDivider'; +import BaseButtons from '../../components/BaseButtons'; +import BaseButton from '../../components/BaseButton'; +import FormCheckRadio from '../../components/FormCheckRadio'; +import FormCheckRadioGroup from '../../components/FormCheckRadioGroup'; +import FormFilePicker from '../../components/FormFilePicker'; +import FormImagePicker from '../../components/FormImagePicker'; +import { SelectField } from '../../components/SelectField'; +import { SelectFieldMany } from '../../components/SelectFieldMany'; +import { SwitchField } from '../../components/SwitchField'; +import { RichTextField } from '../../components/RichTextField'; + +import { update, fetch } from '../../stores/payment/paymentSlice'; +import { useAppDispatch, useAppSelector } from '../../stores/hooks'; +import { useRouter } from 'next/router'; +import { saveFile } from '../../helpers/fileSaver'; +import dataFormatter from '../../helpers/dataFormatter'; +import ImageField from '../../components/ImageField'; + +const EditPaymentPage = () => { + const router = useRouter(); + const dispatch = useAppDispatch(); + const initVals = { + student: null, + + invoice: null, + + amountpaid: '', + + paymentmode: '', + + chequeno: '', + + bankname: '', + + transactionid: '', + + ddnumber: '', + + ddissuingbank: '', + + receiptnumber: '', + + discountpercentage: '', + + discountamount: '', + + discountreason: '', + + ddissuedate: new Date(), + + remainingbalance: '', + + paymentdate: new Date(), + }; + const [initialValues, setInitialValues] = useState(initVals); + + const { payment } = useAppSelector((state) => state.payment); + + const { id } = router.query; + + useEffect(() => { + dispatch(fetch({ id: id })); + }, [id]); + + useEffect(() => { + if (typeof payment === 'object') { + setInitialValues(payment); + } + }, [payment]); + + useEffect(() => { + if (typeof payment === 'object') { + const newInitialVal = { ...initVals }; + Object.keys(initVals).forEach((el) => (newInitialVal[el] = payment[el])); + setInitialValues(newInitialVal); + } + }, [payment]); + + const handleSubmit = async (data) => { + await dispatch(update({ id: id, data })); + await router.push('/payment/payment-list'); + }; + + return ( + <> + + {getPageTitle('Edit payment')} + + + + {''} + + + handleSubmit(values)} + > +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + setInitialValues({ ...initialValues, ddissuedate: date }) + } + /> + + + + + + + + + setInitialValues({ ...initialValues, paymentdate: date }) + } + /> + + + + + + + router.push('/payment/payment-list')} + /> + + +
+
+
+ + ); +}; + +EditPaymentPage.getLayout = function getLayout(page: ReactElement) { + return ( + + {page} + + ); +}; + +export default EditPaymentPage; diff --git a/frontend/src/pages/payment/payment-list.tsx b/frontend/src/pages/payment/payment-list.tsx new file mode 100644 index 0000000..6c1e40f --- /dev/null +++ b/frontend/src/pages/payment/payment-list.tsx @@ -0,0 +1,192 @@ +import { mdiChartTimelineVariant } from '@mdi/js'; +import Head from 'next/head'; +import { uniqueId } from 'lodash'; +import React, { ReactElement, useState } from 'react'; +import CardBox from '../../components/CardBox'; +import LayoutAuthenticated from '../../layouts/Authenticated'; +import SectionMain from '../../components/SectionMain'; +import SectionTitleLineWithButton from '../../components/SectionTitleLineWithButton'; +import { getPageTitle } from '../../config'; +import TablePayment from '../../components/Payment/TablePayment'; +import BaseButton from '../../components/BaseButton'; +import axios from 'axios'; +import Link from 'next/link'; +import { useAppDispatch, useAppSelector } from '../../stores/hooks'; +import CardBoxModal from '../../components/CardBoxModal'; +import DragDropFilePicker from '../../components/DragDropFilePicker'; +import { setRefetch, uploadCsv } from '../../stores/payment/paymentSlice'; + +import { hasPermission } from '../../helpers/userPermissions'; + +const PaymentTablesPage = () => { + const [filterItems, setFilterItems] = useState([]); + const [csvFile, setCsvFile] = useState(null); + const [isModalActive, setIsModalActive] = useState(false); + const [showTableView, setShowTableView] = useState(false); + + const { currentUser } = useAppSelector((state) => state.auth); + + const dispatch = useAppDispatch(); + + const [filters] = useState([ + { label: 'Chequeno', title: 'chequeno' }, + { label: 'Bankname', title: 'bankname' }, + { label: 'Transactionid', title: 'transactionid' }, + { label: 'Ddnumber', title: 'ddnumber' }, + { label: 'Ddissuingbank', title: 'ddissuingbank' }, + { label: 'Receiptnumber', title: 'receiptnumber' }, + { label: 'Discountreason', title: 'discountreason' }, + + { label: 'Amountpaid', title: 'amountpaid', number: 'true' }, + { + label: 'Discountpercentage', + title: 'discountpercentage', + number: 'true', + }, + { label: 'Discountamount', title: 'discountamount', number: 'true' }, + { label: 'Remainingbalance', title: 'remainingbalance', number: 'true' }, + { label: 'Ddissuedate', title: 'ddissuedate', date: 'true' }, + { label: 'Paymentdate', title: 'paymentdate', date: 'true' }, + + { label: 'Student', title: 'student' }, + + { label: 'Invoice', title: 'invoice' }, + + { + label: 'Paymentmode', + title: 'paymentmode', + type: 'enum', + options: ['value'], + }, + ]); + + const hasCreatePermission = + currentUser && hasPermission(currentUser, 'CREATE_PAYMENT'); + + const addFilter = () => { + const newItem = { + id: uniqueId(), + fields: { + filterValue: '', + filterValueFrom: '', + filterValueTo: '', + selectedField: '', + }, + }; + newItem.fields.selectedField = filters[0].title; + setFilterItems([...filterItems, newItem]); + }; + + const getPaymentCSV = async () => { + const response = await axios({ + url: '/payment?filetype=csv', + method: 'GET', + responseType: 'blob', + }); + const type = response.headers['content-type']; + const blob = new Blob([response.data], { type: type }); + const link = document.createElement('a'); + link.href = window.URL.createObjectURL(blob); + link.download = 'paymentCSV.csv'; + link.click(); + }; + + const onModalConfirm = async () => { + if (!csvFile) return; + await dispatch(uploadCsv(csvFile)); + dispatch(setRefetch(true)); + setCsvFile(null); + setIsModalActive(false); + }; + + const onModalCancel = () => { + setCsvFile(null); + setIsModalActive(false); + }; + + return ( + <> + + {getPageTitle('Payment')} + + + + {''} + + + {hasCreatePermission && ( + + )} + + + + + {hasCreatePermission && ( + setIsModalActive(true)} + /> + )} + +
+
+
+
+ + + + +
+ + + + + ); +}; + +PaymentTablesPage.getLayout = function getLayout(page: ReactElement) { + return ( + + {page} + + ); +}; + +export default PaymentTablesPage; diff --git a/frontend/src/pages/payment/payment-new.tsx b/frontend/src/pages/payment/payment-new.tsx new file mode 100644 index 0000000..fc61152 --- /dev/null +++ b/frontend/src/pages/payment/payment-new.tsx @@ -0,0 +1,228 @@ +import { + mdiAccount, + mdiChartTimelineVariant, + mdiMail, + mdiUpload, +} from '@mdi/js'; +import Head from 'next/head'; +import React, { ReactElement } from 'react'; +import CardBox from '../../components/CardBox'; +import LayoutAuthenticated from '../../layouts/Authenticated'; +import SectionMain from '../../components/SectionMain'; +import SectionTitleLineWithButton from '../../components/SectionTitleLineWithButton'; +import { getPageTitle } from '../../config'; + +import { Field, Form, Formik } from 'formik'; +import FormField from '../../components/FormField'; +import BaseDivider from '../../components/BaseDivider'; +import BaseButtons from '../../components/BaseButtons'; +import BaseButton from '../../components/BaseButton'; +import FormCheckRadio from '../../components/FormCheckRadio'; +import FormCheckRadioGroup from '../../components/FormCheckRadioGroup'; +import FormFilePicker from '../../components/FormFilePicker'; +import FormImagePicker from '../../components/FormImagePicker'; +import { SwitchField } from '../../components/SwitchField'; + +import { SelectField } from '../../components/SelectField'; +import { SelectFieldMany } from '../../components/SelectFieldMany'; +import { RichTextField } from '../../components/RichTextField'; + +import { create } from '../../stores/payment/paymentSlice'; +import { useAppDispatch } from '../../stores/hooks'; +import { useRouter } from 'next/router'; +import moment from 'moment'; + +const initialValues = { + student: '', + + invoice: '', + + amountpaid: '', + + paymentmode: '', + + chequeno: '', + + bankname: '', + + transactionid: '', + + ddnumber: '', + + ddissuingbank: '', + + receiptnumber: '', + + discountpercentage: '', + + discountamount: '', + + discountreason: '', + + ddissuedate: '', + + remainingbalance: '', + + paymentdate: '', +}; + +const PaymentNew = () => { + const router = useRouter(); + const dispatch = useAppDispatch(); + + const handleSubmit = async (data) => { + await dispatch(create(data)); + await router.push('/payment/payment-list'); + }; + return ( + <> + + {getPageTitle('New Item')} + + + + {''} + + + handleSubmit(values)} + > +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + router.push('/payment/payment-list')} + /> + + +
+
+
+ + ); +}; + +PaymentNew.getLayout = function getLayout(page: ReactElement) { + return ( + + {page} + + ); +}; + +export default PaymentNew; diff --git a/frontend/src/pages/payment/payment-table.tsx b/frontend/src/pages/payment/payment-table.tsx new file mode 100644 index 0000000..406ada1 --- /dev/null +++ b/frontend/src/pages/payment/payment-table.tsx @@ -0,0 +1,191 @@ +import { mdiChartTimelineVariant } from '@mdi/js'; +import Head from 'next/head'; +import { uniqueId } from 'lodash'; +import React, { ReactElement, useState } from 'react'; +import CardBox from '../../components/CardBox'; +import LayoutAuthenticated from '../../layouts/Authenticated'; +import SectionMain from '../../components/SectionMain'; +import SectionTitleLineWithButton from '../../components/SectionTitleLineWithButton'; +import { getPageTitle } from '../../config'; +import TablePayment from '../../components/Payment/TablePayment'; +import BaseButton from '../../components/BaseButton'; +import axios from 'axios'; +import Link from 'next/link'; +import { useAppDispatch, useAppSelector } from '../../stores/hooks'; +import CardBoxModal from '../../components/CardBoxModal'; +import DragDropFilePicker from '../../components/DragDropFilePicker'; +import { setRefetch, uploadCsv } from '../../stores/payment/paymentSlice'; + +import { hasPermission } from '../../helpers/userPermissions'; + +const PaymentTablesPage = () => { + const [filterItems, setFilterItems] = useState([]); + const [csvFile, setCsvFile] = useState(null); + const [isModalActive, setIsModalActive] = useState(false); + const [showTableView, setShowTableView] = useState(false); + + const { currentUser } = useAppSelector((state) => state.auth); + + const dispatch = useAppDispatch(); + + const [filters] = useState([ + { label: 'Chequeno', title: 'chequeno' }, + { label: 'Bankname', title: 'bankname' }, + { label: 'Transactionid', title: 'transactionid' }, + { label: 'Ddnumber', title: 'ddnumber' }, + { label: 'Ddissuingbank', title: 'ddissuingbank' }, + { label: 'Receiptnumber', title: 'receiptnumber' }, + { label: 'Discountreason', title: 'discountreason' }, + + { label: 'Amountpaid', title: 'amountpaid', number: 'true' }, + { + label: 'Discountpercentage', + title: 'discountpercentage', + number: 'true', + }, + { label: 'Discountamount', title: 'discountamount', number: 'true' }, + { label: 'Remainingbalance', title: 'remainingbalance', number: 'true' }, + { label: 'Ddissuedate', title: 'ddissuedate', date: 'true' }, + { label: 'Paymentdate', title: 'paymentdate', date: 'true' }, + + { label: 'Student', title: 'student' }, + + { label: 'Invoice', title: 'invoice' }, + + { + label: 'Paymentmode', + title: 'paymentmode', + type: 'enum', + options: ['value'], + }, + ]); + + const hasCreatePermission = + currentUser && hasPermission(currentUser, 'CREATE_PAYMENT'); + + const addFilter = () => { + const newItem = { + id: uniqueId(), + fields: { + filterValue: '', + filterValueFrom: '', + filterValueTo: '', + selectedField: '', + }, + }; + newItem.fields.selectedField = filters[0].title; + setFilterItems([...filterItems, newItem]); + }; + + const getPaymentCSV = async () => { + const response = await axios({ + url: '/payment?filetype=csv', + method: 'GET', + responseType: 'blob', + }); + const type = response.headers['content-type']; + const blob = new Blob([response.data], { type: type }); + const link = document.createElement('a'); + link.href = window.URL.createObjectURL(blob); + link.download = 'paymentCSV.csv'; + link.click(); + }; + + const onModalConfirm = async () => { + if (!csvFile) return; + await dispatch(uploadCsv(csvFile)); + dispatch(setRefetch(true)); + setCsvFile(null); + setIsModalActive(false); + }; + + const onModalCancel = () => { + setCsvFile(null); + setIsModalActive(false); + }; + + return ( + <> + + {getPageTitle('Payment')} + + + + {''} + + + {hasCreatePermission && ( + + )} + + + + + {hasCreatePermission && ( + setIsModalActive(true)} + /> + )} + +
+
+
+
+ + + +
+ + + + + ); +}; + +PaymentTablesPage.getLayout = function getLayout(page: ReactElement) { + return ( + + {page} + + ); +}; + +export default PaymentTablesPage; diff --git a/frontend/src/pages/payment/payment-view.tsx b/frontend/src/pages/payment/payment-view.tsx new file mode 100644 index 0000000..aa98892 --- /dev/null +++ b/frontend/src/pages/payment/payment-view.tsx @@ -0,0 +1,188 @@ +import React, { ReactElement, useEffect } from 'react'; +import Head from 'next/head'; +import DatePicker from 'react-datepicker'; +import 'react-datepicker/dist/react-datepicker.css'; +import dayjs from 'dayjs'; +import { useAppDispatch, useAppSelector } from '../../stores/hooks'; +import { useRouter } from 'next/router'; +import { fetch } from '../../stores/payment/paymentSlice'; +import { saveFile } from '../../helpers/fileSaver'; +import dataFormatter from '../../helpers/dataFormatter'; +import ImageField from '../../components/ImageField'; +import LayoutAuthenticated from '../../layouts/Authenticated'; +import { getPageTitle } from '../../config'; +import SectionTitleLineWithButton from '../../components/SectionTitleLineWithButton'; +import SectionMain from '../../components/SectionMain'; +import CardBox from '../../components/CardBox'; +import BaseButton from '../../components/BaseButton'; +import BaseDivider from '../../components/BaseDivider'; +import { mdiChartTimelineVariant } from '@mdi/js'; +import { SwitchField } from '../../components/SwitchField'; +import FormField from '../../components/FormField'; + +const PaymentView = () => { + const router = useRouter(); + const dispatch = useAppDispatch(); + const { payment } = useAppSelector((state) => state.payment); + + const { id } = router.query; + + function removeLastCharacter(str) { + console.log(str, `str`); + return str.slice(0, -1); + } + + useEffect(() => { + dispatch(fetch({ id })); + }, [dispatch, id]); + + return ( + <> + + {getPageTitle('View payment')} + + + + + + +
+

Student

+ +

{payment?.student?.first_name ?? 'No data'}

+
+ +
+

Invoice

+ +

{payment?.invoice?.amount ?? 'No data'}

+
+ +
+

Amountpaid

+

{payment?.amountpaid || 'No data'}

+
+ +
+

Paymentmode

+

{payment?.paymentmode ?? 'No data'}

+
+ +
+

Chequeno

+

{payment?.chequeno}

+
+ +
+

Bankname

+

{payment?.bankname}

+
+ +
+

Transactionid

+

{payment?.transactionid}

+
+ +
+

Ddnumber

+

{payment?.ddnumber}

+
+ +
+

Ddissuingbank

+

{payment?.ddissuingbank}

+
+ +
+

Receiptnumber

+

{payment?.receiptnumber}

+
+ +
+

Discountpercentage

+

{payment?.discountpercentage || 'No data'}

+
+ +
+

Discountamount

+

{payment?.discountamount || 'No data'}

+
+ +
+

Discountreason

+

{payment?.discountreason}

+
+ + + {payment.ddissuedate ? ( + + ) : ( +

No Ddissuedate

+ )} +
+ +
+

Remainingbalance

+

{payment?.remainingbalance || 'No data'}

+
+ + + {payment.paymentdate ? ( + + ) : ( +

No Paymentdate

+ )} +
+ + + + router.push('/payment/payment-list')} + /> +
+
+ + ); +}; + +PaymentView.getLayout = function getLayout(page: ReactElement) { + return ( + + {page} + + ); +}; + +export default PaymentView; diff --git a/frontend/src/pages/students/students-view.tsx b/frontend/src/pages/students/students-view.tsx index d349de9..e703052 100644 --- a/frontend/src/pages/students/students-view.tsx +++ b/frontend/src/pages/students/students-view.tsx @@ -311,6 +311,111 @@ const StudentsView = () => { + <> +

Payment Student

+ +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + {students.payment_student && + Array.isArray(students.payment_student) && + students.payment_student.map((item: any) => ( + + router.push(`/payment/payment-view/?id=${item.id}`) + } + > + + + + + + + + + + + + + + + + + + + + + + + + + + + + + ))} + +
AmountpaidPaymentmodeChequenoBanknameTransactionidDdnumberDdissuingbankReceiptnumberDiscountpercentageDiscountamountDiscountreasonDdissuedateRemainingbalancePaymentdate
{item.amountpaid}{item.paymentmode}{item.chequeno}{item.bankname} + {item.transactionid} + {item.ddnumber} + {item.ddissuingbank} + + {item.receiptnumber} + + {item.discountpercentage} + + {item.discountamount} + + {item.discountreason} + + {dataFormatter.dateTimeFormatter(item.ddissuedate)} + + {item.remainingbalance} + + {dataFormatter.dateTimeFormatter(item.paymentdate)} +
+
+ {!students?.payment_student?.length && ( +
No data
+ )} +
+ + { + const { id, query } = data; + const result = await axios.get(`payment${query || (id ? `/${id}` : '')}`); + return id + ? result.data + : { rows: result.data.rows, count: result.data.count }; +}); + +export const deleteItemsByIds = createAsyncThunk( + 'payment/deleteByIds', + async (data: any, { rejectWithValue }) => { + try { + await axios.post('payment/deleteByIds', { data }); + } catch (error) { + if (!error.response) { + throw error; + } + + return rejectWithValue(error.response.data); + } + }, +); + +export const deleteItem = createAsyncThunk( + 'payment/deletePayment', + async (id: string, { rejectWithValue }) => { + try { + await axios.delete(`payment/${id}`); + } catch (error) { + if (!error.response) { + throw error; + } + + return rejectWithValue(error.response.data); + } + }, +); + +export const create = createAsyncThunk( + 'payment/createPayment', + async (data: any, { rejectWithValue }) => { + try { + const result = await axios.post('payment', { data }); + return result.data; + } catch (error) { + if (!error.response) { + throw error; + } + + return rejectWithValue(error.response.data); + } + }, +); + +export const uploadCsv = createAsyncThunk( + 'payment/uploadCsv', + async (file: File, { rejectWithValue }) => { + try { + const data = new FormData(); + data.append('file', file); + data.append('filename', file.name); + + const result = await axios.post('payment/bulk-import', data, { + headers: { + 'Content-Type': 'multipart/form-data', + }, + }); + + return result.data; + } catch (error) { + if (!error.response) { + throw error; + } + + return rejectWithValue(error.response.data); + } + }, +); + +export const update = createAsyncThunk( + 'payment/updatePayment', + async (payload: any, { rejectWithValue }) => { + try { + const result = await axios.put(`payment/${payload.id}`, { + id: payload.id, + data: payload.data, + }); + return result.data; + } catch (error) { + if (!error.response) { + throw error; + } + + return rejectWithValue(error.response.data); + } + }, +); + +export const paymentSlice = createSlice({ + name: 'payment', + initialState, + reducers: { + setRefetch: (state, action: PayloadAction) => { + state.refetch = action.payload; + }, + }, + extraReducers: (builder) => { + builder.addCase(fetch.pending, (state) => { + state.loading = true; + resetNotify(state); + }); + builder.addCase(fetch.rejected, (state, action) => { + state.loading = false; + rejectNotify(state, action); + }); + + builder.addCase(fetch.fulfilled, (state, action) => { + if (action.payload.rows && action.payload.count >= 0) { + state.payment = action.payload.rows; + state.count = action.payload.count; + } else { + state.payment = action.payload; + } + state.loading = false; + }); + + builder.addCase(deleteItemsByIds.pending, (state) => { + state.loading = true; + resetNotify(state); + }); + + builder.addCase(deleteItemsByIds.fulfilled, (state) => { + state.loading = false; + fulfilledNotify(state, 'Payment has been deleted'); + }); + + builder.addCase(deleteItemsByIds.rejected, (state, action) => { + state.loading = false; + rejectNotify(state, action); + }); + + builder.addCase(deleteItem.pending, (state) => { + state.loading = true; + resetNotify(state); + }); + + builder.addCase(deleteItem.fulfilled, (state) => { + state.loading = false; + fulfilledNotify(state, `${'Payment'.slice(0, -1)} has been deleted`); + }); + + builder.addCase(deleteItem.rejected, (state, action) => { + state.loading = false; + rejectNotify(state, action); + }); + + builder.addCase(create.pending, (state) => { + state.loading = true; + resetNotify(state); + }); + builder.addCase(create.rejected, (state, action) => { + state.loading = false; + rejectNotify(state, action); + }); + + builder.addCase(create.fulfilled, (state) => { + state.loading = false; + fulfilledNotify(state, `${'Payment'.slice(0, -1)} has been created`); + }); + + builder.addCase(update.pending, (state) => { + state.loading = true; + resetNotify(state); + }); + builder.addCase(update.fulfilled, (state) => { + state.loading = false; + fulfilledNotify(state, `${'Payment'.slice(0, -1)} has been updated`); + }); + builder.addCase(update.rejected, (state, action) => { + state.loading = false; + rejectNotify(state, action); + }); + + builder.addCase(uploadCsv.pending, (state) => { + state.loading = true; + resetNotify(state); + }); + builder.addCase(uploadCsv.fulfilled, (state) => { + state.loading = false; + fulfilledNotify(state, 'Payment has been uploaded'); + }); + builder.addCase(uploadCsv.rejected, (state, action) => { + state.loading = false; + rejectNotify(state, action); + }); + }, +}); + +// Action creators are generated for each case reducer function +export const { setRefetch } = paymentSlice.actions; + +export default paymentSlice.reducer; diff --git a/frontend/src/stores/store.ts b/frontend/src/stores/store.ts index 61756dd..5b1d053 100644 --- a/frontend/src/stores/store.ts +++ b/frontend/src/stores/store.ts @@ -18,6 +18,7 @@ import librarySlice from './library/librarySlice'; import studentsSlice from './students/studentsSlice'; import rolesSlice from './roles/rolesSlice'; import permissionsSlice from './permissions/permissionsSlice'; +import paymentSlice from './payment/paymentSlice'; export const store = configureStore({ reducer: { @@ -40,6 +41,7 @@ export const store = configureStore({ students: studentsSlice, roles: rolesSlice, permissions: permissionsSlice, + payment: paymentSlice, }, });