Fees and structures added
This commit is contained in:
@@ -1215,6 +1215,11 @@ Cache-Control: no-store
|
||||
| GET | `/api/student/results?semester=N` | Yes |
|
||||
| POST | `/api/student/results` | Yes |
|
||||
| GET | `/api/student/fees` | Yes |
|
||||
| GET | `/api/student/exam-fee` | Yes |
|
||||
| GET | `/api/student/exam-fee/receipt?history_id=X` | Yes |
|
||||
| GET | `/api/student/fees/history` | Yes |
|
||||
| GET | `/api/student/fees/download-receipt?history_id=X` | Yes |
|
||||
| GET | `/api/student/fees/download-consolidated-receipt?id=X` | Yes |
|
||||
|
||||
---
|
||||
|
||||
|
||||
Binary file not shown.
@@ -1279,3 +1279,339 @@ Caused by: org.h2.mvstore.MVStoreException: The file is locked: /opt/projects/RI
|
||||
at org.h2.mvstore.SingleFileStore.open(SingleFileStore.java:81)
|
||||
at org.h2.mvstore.MVStore.<init>(MVStore.java:291)
|
||||
... 151 more
|
||||
2026-08-20 05:07:04.230651Z database: flush
|
||||
org.h2.message.DbException: General error: "org.h2.mvstore.MVStoreException: The file is locked: /opt/projects/RITz-whatsapp-bot/db/whatsappbot.mv.db [2.4.240/7]" [50000-240]
|
||||
at org.h2.message.DbException.get(DbException.java:212)
|
||||
at org.h2.message.DbException.convert(DbException.java:407)
|
||||
at org.h2.mvstore.db.Store.lambda$new$0(Store.java:122)
|
||||
at org.h2.mvstore.MVStore.handleException(MVStore.java:1675)
|
||||
at org.h2.mvstore.MVStore.panic(MVStore.java:502)
|
||||
at org.h2.mvstore.MVStore.<init>(MVStore.java:296)
|
||||
at org.h2.mvstore.MVStore$Builder.open(MVStore.java:2164)
|
||||
at org.h2.mvstore.db.Store.<init>(Store.java:133)
|
||||
at org.h2.engine.Database.<init>(Database.java:328)
|
||||
at org.h2.engine.Engine.openSession(Engine.java:92)
|
||||
at org.h2.engine.Engine.openSession(Engine.java:222)
|
||||
at org.h2.engine.Engine.createSession(Engine.java:201)
|
||||
at org.h2.engine.SessionRemote.connectEmbeddedOrServer(SessionRemote.java:350)
|
||||
at org.h2.jdbc.JdbcConnection.<init>(JdbcConnection.java:124)
|
||||
at org.h2.Driver.connect(Driver.java:59)
|
||||
at com.zaxxer.hikari.util.DriverDataSource.getConnection(DriverDataSource.java:144)
|
||||
at com.zaxxer.hikari.pool.PoolBase.newConnection(PoolBase.java:373)
|
||||
at com.zaxxer.hikari.pool.PoolBase.newPoolEntry(PoolBase.java:210)
|
||||
at com.zaxxer.hikari.pool.HikariPool.createPoolEntry(HikariPool.java:488)
|
||||
at com.zaxxer.hikari.pool.HikariPool.checkFailFast(HikariPool.java:576)
|
||||
at com.zaxxer.hikari.pool.HikariPool.<init>(HikariPool.java:97)
|
||||
at com.zaxxer.hikari.HikariDataSource.getConnection(HikariDataSource.java:111)
|
||||
at org.hibernate.engine.jdbc.connections.internal.DataSourceConnectionProvider.getConnection(DataSourceConnectionProvider.java:149)
|
||||
at org.hibernate.engine.jdbc.env.internal.JdbcEnvironmentInitiator$ConnectionProviderJdbcConnectionAccess.obtainConnection(JdbcEnvironmentInitiator.java:508)
|
||||
at org.hibernate.resource.transaction.backend.jdbc.internal.JdbcIsolationDelegate.delegateWork(JdbcIsolationDelegate.java:48)
|
||||
at org.hibernate.engine.jdbc.env.internal.JdbcEnvironmentInitiator.getJdbcEnvironmentUsingJdbcMetadata(JdbcEnvironmentInitiator.java:366)
|
||||
at org.hibernate.engine.jdbc.env.internal.JdbcEnvironmentInitiator.getJdbcEnvironment(JdbcEnvironmentInitiator.java:143)
|
||||
at org.hibernate.engine.jdbc.env.internal.JdbcEnvironmentInitiator.initiateService(JdbcEnvironmentInitiator.java:120)
|
||||
at org.hibernate.engine.jdbc.env.internal.JdbcEnvironmentInitiator.initiateService(JdbcEnvironmentInitiator.java:80)
|
||||
at org.hibernate.boot.registry.internal.StandardServiceRegistryImpl.initiateService(StandardServiceRegistryImpl.java:133)
|
||||
at org.hibernate.service.internal.AbstractServiceRegistryImpl.createService(AbstractServiceRegistryImpl.java:260)
|
||||
at org.hibernate.service.internal.AbstractServiceRegistryImpl.initializeService(AbstractServiceRegistryImpl.java:235)
|
||||
at org.hibernate.service.internal.AbstractServiceRegistryImpl.getService(AbstractServiceRegistryImpl.java:212)
|
||||
at org.hibernate.boot.model.relational.Database.<init>(Database.java:44)
|
||||
at org.hibernate.boot.internal.InFlightMetadataCollectorImpl.getDatabase(InFlightMetadataCollectorImpl.java:251)
|
||||
at org.hibernate.boot.internal.InFlightMetadataCollectorImpl.<init>(InFlightMetadataCollectorImpl.java:203)
|
||||
at org.hibernate.boot.model.process.spi.MetadataBuildingProcess.complete(MetadataBuildingProcess.java:180)
|
||||
at org.hibernate.jpa.boot.internal.EntityManagerFactoryBuilderImpl.metadata(EntityManagerFactoryBuilderImpl.java:1388)
|
||||
at org.hibernate.jpa.boot.internal.EntityManagerFactoryBuilderImpl.populateSessionFactoryBuilder(EntityManagerFactoryBuilderImpl.java:1468)
|
||||
at org.hibernate.jpa.boot.internal.EntityManagerFactoryBuilderImpl.build(EntityManagerFactoryBuilderImpl.java:1450)
|
||||
at org.springframework.orm.jpa.vendor.SpringHibernateJpaPersistenceProvider.createContainerEntityManagerFactory(SpringHibernateJpaPersistenceProvider.java:93)
|
||||
at org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean.createNativeEntityManagerFactory(LocalContainerEntityManagerFactoryBean.java:443)
|
||||
at org.springframework.orm.jpa.AbstractEntityManagerFactoryBean.buildNativeEntityManagerFactory(AbstractEntityManagerFactoryBean.java:436)
|
||||
at org.springframework.orm.jpa.AbstractEntityManagerFactoryBean.afterPropertiesSet(AbstractEntityManagerFactoryBean.java:411)
|
||||
at org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean.afterPropertiesSet(LocalContainerEntityManagerFactoryBean.java:419)
|
||||
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.invokeInitMethods(AbstractAutowireCapableBeanFactory.java:1862)
|
||||
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.initializeBean(AbstractAutowireCapableBeanFactory.java:1811)
|
||||
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.doCreateBean(AbstractAutowireCapableBeanFactory.java:603)
|
||||
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.createBean(AbstractAutowireCapableBeanFactory.java:525)
|
||||
at org.springframework.beans.factory.support.AbstractBeanFactory.lambda$doGetBean$0(AbstractBeanFactory.java:333)
|
||||
at org.springframework.beans.factory.support.DefaultSingletonBeanRegistry.getSingleton(DefaultSingletonBeanRegistry.java:371)
|
||||
at org.springframework.beans.factory.support.AbstractBeanFactory.doGetBean(AbstractBeanFactory.java:331)
|
||||
at org.springframework.beans.factory.support.AbstractBeanFactory.getBean(AbstractBeanFactory.java:201)
|
||||
at org.springframework.context.support.AbstractApplicationContext.finishBeanFactoryInitialization(AbstractApplicationContext.java:977)
|
||||
at org.springframework.context.support.AbstractApplicationContext.refresh(AbstractApplicationContext.java:621)
|
||||
at org.springframework.boot.SpringApplication.refresh(SpringApplication.java:756)
|
||||
at org.springframework.boot.SpringApplication.refreshContext(SpringApplication.java:445)
|
||||
at org.springframework.boot.SpringApplication.run(SpringApplication.java:321)
|
||||
at org.springframework.boot.test.context.SpringBootContextLoader.lambda$loadContext$2(SpringBootContextLoader.java:155)
|
||||
at org.springframework.util.function.ThrowingSupplier.get(ThrowingSupplier.java:58)
|
||||
at org.springframework.util.function.ThrowingSupplier.get(ThrowingSupplier.java:46)
|
||||
at org.springframework.boot.SpringApplication.withHook(SpringApplication.java:1465)
|
||||
at org.springframework.boot.test.context.SpringBootContextLoader$ContextLoaderHook.run(SpringBootContextLoader.java:600)
|
||||
at org.springframework.boot.test.context.SpringBootContextLoader.loadContext(SpringBootContextLoader.java:155)
|
||||
at org.springframework.boot.test.context.SpringBootContextLoader.loadContext(SpringBootContextLoader.java:114)
|
||||
at org.springframework.test.context.cache.DefaultCacheAwareContextLoaderDelegate.loadContextInternal(DefaultCacheAwareContextLoaderDelegate.java:247)
|
||||
at org.springframework.test.context.cache.DefaultCacheAwareContextLoaderDelegate.lambda$loadContext$0(DefaultCacheAwareContextLoaderDelegate.java:167)
|
||||
at org.springframework.test.context.cache.DefaultContextCache.put(DefaultContextCache.java:214)
|
||||
at org.springframework.test.context.cache.DefaultCacheAwareContextLoaderDelegate.loadContext(DefaultCacheAwareContextLoaderDelegate.java:160)
|
||||
at org.springframework.test.context.support.DefaultTestContext.getApplicationContext(DefaultTestContext.java:128)
|
||||
at org.springframework.test.context.web.ServletTestExecutionListener.setUpRequestContextIfNecessary(ServletTestExecutionListener.java:200)
|
||||
at org.springframework.test.context.web.ServletTestExecutionListener.prepareTestInstance(ServletTestExecutionListener.java:139)
|
||||
at org.springframework.test.context.TestContextManager.prepareTestInstance(TestContextManager.java:260)
|
||||
at org.springframework.test.context.junit.jupiter.SpringExtension.postProcessTestInstance(SpringExtension.java:242)
|
||||
at org.junit.jupiter.engine.descriptor.ClassBasedTestDescriptor.lambda$invokeTestInstancePostProcessors$1(ClassBasedTestDescriptor.java:423)
|
||||
at org.junit.jupiter.engine.descriptor.ClassBasedTestDescriptor.executeAndMaskThrowable(ClassBasedTestDescriptor.java:428)
|
||||
at org.junit.jupiter.engine.descriptor.ClassBasedTestDescriptor.lambda$invokeTestInstancePostProcessors$0(ClassBasedTestDescriptor.java:422)
|
||||
at java.base/java.util.stream.ForEachOps$ForEachOp$OfRef.accept(ForEachOps.java:184)
|
||||
at java.base/java.util.stream.ReferencePipeline$3$1.accept(ReferencePipeline.java:197)
|
||||
at java.base/java.util.stream.ReferencePipeline$2$1.accept(ReferencePipeline.java:179)
|
||||
at java.base/java.util.stream.ReferencePipeline$3$1.accept(ReferencePipeline.java:197)
|
||||
at java.base/java.util.ArrayList$ArrayListSpliterator.forEachRemaining(ArrayList.java:1708)
|
||||
at java.base/java.util.stream.AbstractPipeline.copyInto(AbstractPipeline.java:509)
|
||||
at java.base/java.util.stream.AbstractPipeline.wrapAndCopyInto(AbstractPipeline.java:499)
|
||||
at java.base/java.util.stream.ForEachOps$ForEachOp.evaluateSequential(ForEachOps.java:151)
|
||||
at java.base/java.util.stream.ForEachOps$ForEachOp$OfRef.evaluateSequential(ForEachOps.java:174)
|
||||
at java.base/java.util.stream.AbstractPipeline.evaluate(AbstractPipeline.java:234)
|
||||
at java.base/java.util.stream.ReferencePipeline.forEach(ReferencePipeline.java:596)
|
||||
at org.junit.jupiter.engine.descriptor.ClassBasedTestDescriptor.invokeTestInstancePostProcessors(ClassBasedTestDescriptor.java:422)
|
||||
at org.junit.jupiter.engine.descriptor.ClassBasedTestDescriptor.lambda$instantiateAndPostProcessTestInstance$0(ClassBasedTestDescriptor.java:334)
|
||||
at org.junit.platform.engine.support.hierarchical.ThrowableCollector.execute(ThrowableCollector.java:74)
|
||||
at org.junit.jupiter.engine.descriptor.ClassBasedTestDescriptor.instantiateAndPostProcessTestInstance(ClassBasedTestDescriptor.java:333)
|
||||
at org.junit.jupiter.engine.descriptor.ClassBasedTestDescriptor.lambda$testInstancesProvider$1(ClassBasedTestDescriptor.java:322)
|
||||
at java.base/java.util.Optional.orElseGet(Optional.java:364)
|
||||
at org.junit.jupiter.engine.descriptor.ClassBasedTestDescriptor.lambda$testInstancesProvider$0(ClassBasedTestDescriptor.java:321)
|
||||
at org.junit.jupiter.engine.execution.TestInstancesProvider.getTestInstances(TestInstancesProvider.java:27)
|
||||
at org.junit.jupiter.engine.descriptor.TestMethodTestDescriptor.lambda$prepare$0(TestMethodTestDescriptor.java:127)
|
||||
at org.junit.platform.engine.support.hierarchical.ThrowableCollector.execute(ThrowableCollector.java:74)
|
||||
at org.junit.jupiter.engine.descriptor.TestMethodTestDescriptor.prepare(TestMethodTestDescriptor.java:126)
|
||||
at org.junit.jupiter.engine.descriptor.TestMethodTestDescriptor.prepare(TestMethodTestDescriptor.java:70)
|
||||
at org.junit.platform.engine.support.hierarchical.NodeTestTask.lambda$prepare$0(NodeTestTask.java:144)
|
||||
at org.junit.platform.engine.support.hierarchical.ThrowableCollector.execute(ThrowableCollector.java:74)
|
||||
at org.junit.platform.engine.support.hierarchical.NodeTestTask.prepare(NodeTestTask.java:144)
|
||||
at org.junit.platform.engine.support.hierarchical.NodeTestTask.execute(NodeTestTask.java:110)
|
||||
at java.base/java.util.ArrayList.forEach(ArrayList.java:1596)
|
||||
at org.junit.platform.engine.support.hierarchical.SameThreadHierarchicalTestExecutorService.invokeAll(SameThreadHierarchicalTestExecutorService.java:42)
|
||||
at org.junit.platform.engine.support.hierarchical.NodeTestTask.lambda$executeRecursively$2(NodeTestTask.java:180)
|
||||
at org.junit.platform.engine.support.hierarchical.ThrowableCollector.execute(ThrowableCollector.java:74)
|
||||
at org.junit.platform.engine.support.hierarchical.NodeTestTask.lambda$executeRecursively$1(NodeTestTask.java:166)
|
||||
at org.junit.platform.engine.support.hierarchical.Node.around(Node.java:138)
|
||||
at org.junit.platform.engine.support.hierarchical.NodeTestTask.lambda$executeRecursively$0(NodeTestTask.java:164)
|
||||
at org.junit.platform.engine.support.hierarchical.ThrowableCollector.execute(ThrowableCollector.java:74)
|
||||
at org.junit.platform.engine.support.hierarchical.NodeTestTask.executeRecursively(NodeTestTask.java:163)
|
||||
at org.junit.platform.engine.support.hierarchical.NodeTestTask.execute(NodeTestTask.java:116)
|
||||
at java.base/java.util.ArrayList.forEach(ArrayList.java:1596)
|
||||
at org.junit.platform.engine.support.hierarchical.SameThreadHierarchicalTestExecutorService.invokeAll(SameThreadHierarchicalTestExecutorService.java:42)
|
||||
at org.junit.platform.engine.support.hierarchical.NodeTestTask.lambda$executeRecursively$2(NodeTestTask.java:180)
|
||||
at org.junit.platform.engine.support.hierarchical.ThrowableCollector.execute(ThrowableCollector.java:74)
|
||||
at org.junit.platform.engine.support.hierarchical.NodeTestTask.lambda$executeRecursively$1(NodeTestTask.java:166)
|
||||
at org.junit.platform.engine.support.hierarchical.Node.around(Node.java:138)
|
||||
at org.junit.platform.engine.support.hierarchical.NodeTestTask.lambda$executeRecursively$0(NodeTestTask.java:164)
|
||||
at org.junit.platform.engine.support.hierarchical.ThrowableCollector.execute(ThrowableCollector.java:74)
|
||||
at org.junit.platform.engine.support.hierarchical.NodeTestTask.executeRecursively(NodeTestTask.java:163)
|
||||
at org.junit.platform.engine.support.hierarchical.NodeTestTask.execute(NodeTestTask.java:116)
|
||||
at org.junit.platform.engine.support.hierarchical.SameThreadHierarchicalTestExecutorService.submit(SameThreadHierarchicalTestExecutorService.java:36)
|
||||
at org.junit.platform.engine.support.hierarchical.HierarchicalTestExecutor.execute(HierarchicalTestExecutor.java:52)
|
||||
at org.junit.platform.engine.support.hierarchical.HierarchicalTestEngine.execute(HierarchicalTestEngine.java:58)
|
||||
at org.junit.platform.launcher.core.EngineExecutionOrchestrator.executeEngine(EngineExecutionOrchestrator.java:246)
|
||||
at org.junit.platform.launcher.core.EngineExecutionOrchestrator.failOrExecuteEngine(EngineExecutionOrchestrator.java:218)
|
||||
at org.junit.platform.launcher.core.EngineExecutionOrchestrator.execute(EngineExecutionOrchestrator.java:179)
|
||||
at org.junit.platform.launcher.core.EngineExecutionOrchestrator.execute(EngineExecutionOrchestrator.java:108)
|
||||
at org.junit.platform.launcher.core.EngineExecutionOrchestrator.lambda$execute$0(EngineExecutionOrchestrator.java:66)
|
||||
at org.junit.platform.launcher.core.EngineExecutionOrchestrator.withInterceptedStreams(EngineExecutionOrchestrator.java:157)
|
||||
at org.junit.platform.launcher.core.EngineExecutionOrchestrator.execute(EngineExecutionOrchestrator.java:65)
|
||||
at org.junit.platform.launcher.core.DefaultLauncher.execute(DefaultLauncher.java:125)
|
||||
at org.junit.platform.launcher.core.DefaultLauncher.execute(DefaultLauncher.java:114)
|
||||
at org.junit.platform.launcher.core.DelegatingLauncher.execute(DelegatingLauncher.java:58)
|
||||
at org.junit.platform.launcher.core.InterceptingLauncher.lambda$execute$2(InterceptingLauncher.java:57)
|
||||
at org.junit.platform.launcher.core.ClasspathAlignmentCheckingLauncherInterceptor.intercept(ClasspathAlignmentCheckingLauncherInterceptor.java:25)
|
||||
at org.junit.platform.launcher.core.InterceptingLauncher.execute(InterceptingLauncher.java:56)
|
||||
at org.junit.platform.launcher.core.DelegatingLauncher.execute(DelegatingLauncher.java:58)
|
||||
at java.base/jdk.internal.reflect.DirectMethodHandleAccessor.invoke(DirectMethodHandleAccessor.java:103)
|
||||
at java.base/java.lang.reflect.Method.invoke(Method.java:580)
|
||||
at org.apache.maven.surefire.api.util.ReflectionUtils.invokeMethodWithArray(ReflectionUtils.java:125)
|
||||
at org.apache.maven.surefire.junitplatform.LauncherAdapter.executeWithCancellationToken(LauncherAdapter.java:68)
|
||||
at org.apache.maven.surefire.junitplatform.LauncherAdapter.execute(LauncherAdapter.java:54)
|
||||
at org.apache.maven.surefire.junitplatform.JUnitPlatformProvider.execute(JUnitPlatformProvider.java:203)
|
||||
at org.apache.maven.surefire.junitplatform.JUnitPlatformProvider.invokeAllTests(JUnitPlatformProvider.java:168)
|
||||
at org.apache.maven.surefire.junitplatform.JUnitPlatformProvider.invoke(JUnitPlatformProvider.java:136)
|
||||
at org.apache.maven.surefire.booter.ForkedBooter.runSuitesInProcess(ForkedBooter.java:385)
|
||||
at org.apache.maven.surefire.booter.ForkedBooter.execute(ForkedBooter.java:162)
|
||||
at org.apache.maven.surefire.booter.ForkedBooter.run(ForkedBooter.java:507)
|
||||
at org.apache.maven.surefire.booter.ForkedBooter.main(ForkedBooter.java:495)
|
||||
Caused by: org.h2.jdbc.JdbcSQLNonTransientException: General error: "org.h2.mvstore.MVStoreException: The file is locked: /opt/projects/RITz-whatsapp-bot/db/whatsappbot.mv.db [2.4.240/7]" [50000-240]
|
||||
at org.h2.message.DbException.getJdbcSQLException(DbException.java:566)
|
||||
at org.h2.message.DbException.getJdbcSQLException(DbException.java:489)
|
||||
... 153 more
|
||||
Caused by: org.h2.mvstore.MVStoreException: The file is locked: /opt/projects/RITz-whatsapp-bot/db/whatsappbot.mv.db [2.4.240/7]
|
||||
at org.h2.mvstore.DataUtils.newMVStoreException(DataUtils.java:996)
|
||||
at org.h2.mvstore.SingleFileStore.lockFileChannel(SingleFileStore.java:143)
|
||||
at org.h2.mvstore.SingleFileStore.open(SingleFileStore.java:117)
|
||||
at org.h2.mvstore.SingleFileStore.open(SingleFileStore.java:81)
|
||||
at org.h2.mvstore.MVStore.<init>(MVStore.java:291)
|
||||
... 147 more
|
||||
2026-08-20 05:07:05.824364Z database: flush
|
||||
org.h2.message.DbException: General error: "org.h2.mvstore.MVStoreException: The file is locked: /opt/projects/RITz-whatsapp-bot/db/whatsappbot.mv.db [2.4.240/7]" [50000-240]
|
||||
at org.h2.message.DbException.get(DbException.java:212)
|
||||
at org.h2.message.DbException.convert(DbException.java:407)
|
||||
at org.h2.mvstore.db.Store.lambda$new$0(Store.java:122)
|
||||
at org.h2.mvstore.MVStore.handleException(MVStore.java:1675)
|
||||
at org.h2.mvstore.MVStore.panic(MVStore.java:502)
|
||||
at org.h2.mvstore.MVStore.<init>(MVStore.java:296)
|
||||
at org.h2.mvstore.MVStore$Builder.open(MVStore.java:2164)
|
||||
at org.h2.mvstore.db.Store.<init>(Store.java:133)
|
||||
at org.h2.engine.Database.<init>(Database.java:328)
|
||||
at org.h2.engine.Engine.openSession(Engine.java:92)
|
||||
at org.h2.engine.Engine.openSession(Engine.java:222)
|
||||
at org.h2.engine.Engine.createSession(Engine.java:201)
|
||||
at org.h2.engine.SessionRemote.connectEmbeddedOrServer(SessionRemote.java:350)
|
||||
at org.h2.jdbc.JdbcConnection.<init>(JdbcConnection.java:124)
|
||||
at org.h2.Driver.connect(Driver.java:59)
|
||||
at com.zaxxer.hikari.util.DriverDataSource.getConnection(DriverDataSource.java:144)
|
||||
at com.zaxxer.hikari.pool.PoolBase.newConnection(PoolBase.java:373)
|
||||
at com.zaxxer.hikari.pool.PoolBase.newPoolEntry(PoolBase.java:210)
|
||||
at com.zaxxer.hikari.pool.HikariPool.createPoolEntry(HikariPool.java:488)
|
||||
at com.zaxxer.hikari.pool.HikariPool.checkFailFast(HikariPool.java:576)
|
||||
at com.zaxxer.hikari.pool.HikariPool.<init>(HikariPool.java:97)
|
||||
at com.zaxxer.hikari.HikariDataSource.getConnection(HikariDataSource.java:111)
|
||||
at org.hibernate.engine.jdbc.connections.internal.DataSourceConnectionProvider.getConnection(DataSourceConnectionProvider.java:149)
|
||||
at org.hibernate.engine.jdbc.env.internal.JdbcEnvironmentInitiator$ConnectionProviderJdbcConnectionAccess.obtainConnection(JdbcEnvironmentInitiator.java:508)
|
||||
at org.hibernate.resource.transaction.backend.jdbc.internal.DdlTransactionIsolatorNonJtaImpl.getIsolatedConnection(DdlTransactionIsolatorNonJtaImpl.java:44)
|
||||
at org.hibernate.resource.transaction.backend.jdbc.internal.DdlTransactionIsolatorNonJtaImpl.getIsolatedConnection(DdlTransactionIsolatorNonJtaImpl.java:37)
|
||||
at org.hibernate.tool.schema.internal.exec.ImprovedExtractionContextImpl.getJdbcConnection(ImprovedExtractionContextImpl.java:61)
|
||||
at org.hibernate.tool.schema.extract.spi.ExtractionContext.getQueryResults(ExtractionContext.java:41)
|
||||
at org.hibernate.tool.schema.extract.internal.SequenceInformationExtractorLegacyImpl.extractMetadata(SequenceInformationExtractorLegacyImpl.java:36)
|
||||
at org.hibernate.tool.schema.extract.internal.DatabaseInformationImpl.initializeSequences(DatabaseInformationImpl.java:73)
|
||||
at org.hibernate.tool.schema.extract.internal.DatabaseInformationImpl.<init>(DatabaseInformationImpl.java:63)
|
||||
at org.hibernate.tool.schema.extract.internal.CachingDatabaseInformationImpl.<init>(CachingDatabaseInformationImpl.java:43)
|
||||
at org.hibernate.tool.schema.internal.GroupedSchemaMigratorImpl.buildDatabaseInformation(GroupedSchemaMigratorImpl.java:112)
|
||||
at org.hibernate.tool.schema.internal.AbstractSchemaMigrator.doMigration(AbstractSchemaMigrator.java:83)
|
||||
at org.hibernate.tool.schema.spi.SchemaManagementToolCoordinator.performDatabaseAction(SchemaManagementToolCoordinator.java:269)
|
||||
at org.hibernate.tool.schema.spi.SchemaManagementToolCoordinator.lambda$process$1(SchemaManagementToolCoordinator.java:101)
|
||||
at java.base/java.util.HashMap.forEach(HashMap.java:1429)
|
||||
at org.hibernate.tool.schema.spi.SchemaManagementToolCoordinator.process(SchemaManagementToolCoordinator.java:100)
|
||||
at org.hibernate.boot.internal.SessionFactoryObserverForSchemaExport.sessionFactoryCreated(SessionFactoryObserverForSchemaExport.java:35)
|
||||
at org.hibernate.internal.SessionFactoryObserverChain.sessionFactoryCreated(SessionFactoryObserverChain.java:33)
|
||||
at org.hibernate.internal.SessionFactoryImpl.<init>(SessionFactoryImpl.java:327)
|
||||
at org.hibernate.internal.SessionFactoryRegistry.instantiateSessionFactory(SessionFactoryRegistry.java:64)
|
||||
at org.hibernate.boot.internal.SessionFactoryBuilderImpl.build(SessionFactoryBuilderImpl.java:458)
|
||||
at org.hibernate.jpa.boot.internal.EntityManagerFactoryBuilderImpl.build(EntityManagerFactoryBuilderImpl.java:1452)
|
||||
at org.springframework.orm.jpa.vendor.SpringHibernateJpaPersistenceProvider.createContainerEntityManagerFactory(SpringHibernateJpaPersistenceProvider.java:93)
|
||||
at org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean.createNativeEntityManagerFactory(LocalContainerEntityManagerFactoryBean.java:443)
|
||||
at org.springframework.orm.jpa.AbstractEntityManagerFactoryBean.buildNativeEntityManagerFactory(AbstractEntityManagerFactoryBean.java:436)
|
||||
at org.springframework.orm.jpa.AbstractEntityManagerFactoryBean.afterPropertiesSet(AbstractEntityManagerFactoryBean.java:411)
|
||||
at org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean.afterPropertiesSet(LocalContainerEntityManagerFactoryBean.java:419)
|
||||
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.invokeInitMethods(AbstractAutowireCapableBeanFactory.java:1862)
|
||||
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.initializeBean(AbstractAutowireCapableBeanFactory.java:1811)
|
||||
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.doCreateBean(AbstractAutowireCapableBeanFactory.java:603)
|
||||
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.createBean(AbstractAutowireCapableBeanFactory.java:525)
|
||||
at org.springframework.beans.factory.support.AbstractBeanFactory.lambda$doGetBean$0(AbstractBeanFactory.java:333)
|
||||
at org.springframework.beans.factory.support.DefaultSingletonBeanRegistry.getSingleton(DefaultSingletonBeanRegistry.java:371)
|
||||
at org.springframework.beans.factory.support.AbstractBeanFactory.doGetBean(AbstractBeanFactory.java:331)
|
||||
at org.springframework.beans.factory.support.AbstractBeanFactory.getBean(AbstractBeanFactory.java:201)
|
||||
at org.springframework.context.support.AbstractApplicationContext.finishBeanFactoryInitialization(AbstractApplicationContext.java:977)
|
||||
at org.springframework.context.support.AbstractApplicationContext.refresh(AbstractApplicationContext.java:621)
|
||||
at org.springframework.boot.SpringApplication.refresh(SpringApplication.java:756)
|
||||
at org.springframework.boot.SpringApplication.refreshContext(SpringApplication.java:445)
|
||||
at org.springframework.boot.SpringApplication.run(SpringApplication.java:321)
|
||||
at org.springframework.boot.test.context.SpringBootContextLoader.lambda$loadContext$2(SpringBootContextLoader.java:155)
|
||||
at org.springframework.util.function.ThrowingSupplier.get(ThrowingSupplier.java:58)
|
||||
at org.springframework.util.function.ThrowingSupplier.get(ThrowingSupplier.java:46)
|
||||
at org.springframework.boot.SpringApplication.withHook(SpringApplication.java:1465)
|
||||
at org.springframework.boot.test.context.SpringBootContextLoader$ContextLoaderHook.run(SpringBootContextLoader.java:600)
|
||||
at org.springframework.boot.test.context.SpringBootContextLoader.loadContext(SpringBootContextLoader.java:155)
|
||||
at org.springframework.boot.test.context.SpringBootContextLoader.loadContext(SpringBootContextLoader.java:114)
|
||||
at org.springframework.test.context.cache.DefaultCacheAwareContextLoaderDelegate.loadContextInternal(DefaultCacheAwareContextLoaderDelegate.java:247)
|
||||
at org.springframework.test.context.cache.DefaultCacheAwareContextLoaderDelegate.lambda$loadContext$0(DefaultCacheAwareContextLoaderDelegate.java:167)
|
||||
at org.springframework.test.context.cache.DefaultContextCache.put(DefaultContextCache.java:214)
|
||||
at org.springframework.test.context.cache.DefaultCacheAwareContextLoaderDelegate.loadContext(DefaultCacheAwareContextLoaderDelegate.java:160)
|
||||
at org.springframework.test.context.support.DefaultTestContext.getApplicationContext(DefaultTestContext.java:128)
|
||||
at org.springframework.test.context.web.ServletTestExecutionListener.setUpRequestContextIfNecessary(ServletTestExecutionListener.java:200)
|
||||
at org.springframework.test.context.web.ServletTestExecutionListener.prepareTestInstance(ServletTestExecutionListener.java:139)
|
||||
at org.springframework.test.context.TestContextManager.prepareTestInstance(TestContextManager.java:260)
|
||||
at org.springframework.test.context.junit.jupiter.SpringExtension.postProcessTestInstance(SpringExtension.java:242)
|
||||
at org.junit.jupiter.engine.descriptor.ClassBasedTestDescriptor.lambda$invokeTestInstancePostProcessors$1(ClassBasedTestDescriptor.java:423)
|
||||
at org.junit.jupiter.engine.descriptor.ClassBasedTestDescriptor.executeAndMaskThrowable(ClassBasedTestDescriptor.java:428)
|
||||
at org.junit.jupiter.engine.descriptor.ClassBasedTestDescriptor.lambda$invokeTestInstancePostProcessors$0(ClassBasedTestDescriptor.java:422)
|
||||
at java.base/java.util.stream.ForEachOps$ForEachOp$OfRef.accept(ForEachOps.java:184)
|
||||
at java.base/java.util.stream.ReferencePipeline$3$1.accept(ReferencePipeline.java:197)
|
||||
at java.base/java.util.stream.ReferencePipeline$2$1.accept(ReferencePipeline.java:179)
|
||||
at java.base/java.util.stream.ReferencePipeline$3$1.accept(ReferencePipeline.java:197)
|
||||
at java.base/java.util.ArrayList$ArrayListSpliterator.forEachRemaining(ArrayList.java:1708)
|
||||
at java.base/java.util.stream.AbstractPipeline.copyInto(AbstractPipeline.java:509)
|
||||
at java.base/java.util.stream.AbstractPipeline.wrapAndCopyInto(AbstractPipeline.java:499)
|
||||
at java.base/java.util.stream.ForEachOps$ForEachOp.evaluateSequential(ForEachOps.java:151)
|
||||
at java.base/java.util.stream.ForEachOps$ForEachOp$OfRef.evaluateSequential(ForEachOps.java:174)
|
||||
at java.base/java.util.stream.AbstractPipeline.evaluate(AbstractPipeline.java:234)
|
||||
at java.base/java.util.stream.ReferencePipeline.forEach(ReferencePipeline.java:596)
|
||||
at org.junit.jupiter.engine.descriptor.ClassBasedTestDescriptor.invokeTestInstancePostProcessors(ClassBasedTestDescriptor.java:422)
|
||||
at org.junit.jupiter.engine.descriptor.ClassBasedTestDescriptor.lambda$instantiateAndPostProcessTestInstance$0(ClassBasedTestDescriptor.java:334)
|
||||
at org.junit.platform.engine.support.hierarchical.ThrowableCollector.execute(ThrowableCollector.java:74)
|
||||
at org.junit.jupiter.engine.descriptor.ClassBasedTestDescriptor.instantiateAndPostProcessTestInstance(ClassBasedTestDescriptor.java:333)
|
||||
at org.junit.jupiter.engine.descriptor.ClassBasedTestDescriptor.lambda$testInstancesProvider$1(ClassBasedTestDescriptor.java:322)
|
||||
at java.base/java.util.Optional.orElseGet(Optional.java:364)
|
||||
at org.junit.jupiter.engine.descriptor.ClassBasedTestDescriptor.lambda$testInstancesProvider$0(ClassBasedTestDescriptor.java:321)
|
||||
at org.junit.jupiter.engine.execution.TestInstancesProvider.getTestInstances(TestInstancesProvider.java:27)
|
||||
at org.junit.jupiter.engine.descriptor.TestMethodTestDescriptor.lambda$prepare$0(TestMethodTestDescriptor.java:127)
|
||||
at org.junit.platform.engine.support.hierarchical.ThrowableCollector.execute(ThrowableCollector.java:74)
|
||||
at org.junit.jupiter.engine.descriptor.TestMethodTestDescriptor.prepare(TestMethodTestDescriptor.java:126)
|
||||
at org.junit.jupiter.engine.descriptor.TestMethodTestDescriptor.prepare(TestMethodTestDescriptor.java:70)
|
||||
at org.junit.platform.engine.support.hierarchical.NodeTestTask.lambda$prepare$0(NodeTestTask.java:144)
|
||||
at org.junit.platform.engine.support.hierarchical.ThrowableCollector.execute(ThrowableCollector.java:74)
|
||||
at org.junit.platform.engine.support.hierarchical.NodeTestTask.prepare(NodeTestTask.java:144)
|
||||
at org.junit.platform.engine.support.hierarchical.NodeTestTask.execute(NodeTestTask.java:110)
|
||||
at java.base/java.util.ArrayList.forEach(ArrayList.java:1596)
|
||||
at org.junit.platform.engine.support.hierarchical.SameThreadHierarchicalTestExecutorService.invokeAll(SameThreadHierarchicalTestExecutorService.java:42)
|
||||
at org.junit.platform.engine.support.hierarchical.NodeTestTask.lambda$executeRecursively$2(NodeTestTask.java:180)
|
||||
at org.junit.platform.engine.support.hierarchical.ThrowableCollector.execute(ThrowableCollector.java:74)
|
||||
at org.junit.platform.engine.support.hierarchical.NodeTestTask.lambda$executeRecursively$1(NodeTestTask.java:166)
|
||||
at org.junit.platform.engine.support.hierarchical.Node.around(Node.java:138)
|
||||
at org.junit.platform.engine.support.hierarchical.NodeTestTask.lambda$executeRecursively$0(NodeTestTask.java:164)
|
||||
at org.junit.platform.engine.support.hierarchical.ThrowableCollector.execute(ThrowableCollector.java:74)
|
||||
at org.junit.platform.engine.support.hierarchical.NodeTestTask.executeRecursively(NodeTestTask.java:163)
|
||||
at org.junit.platform.engine.support.hierarchical.NodeTestTask.execute(NodeTestTask.java:116)
|
||||
at java.base/java.util.ArrayList.forEach(ArrayList.java:1596)
|
||||
at org.junit.platform.engine.support.hierarchical.SameThreadHierarchicalTestExecutorService.invokeAll(SameThreadHierarchicalTestExecutorService.java:42)
|
||||
at org.junit.platform.engine.support.hierarchical.NodeTestTask.lambda$executeRecursively$2(NodeTestTask.java:180)
|
||||
at org.junit.platform.engine.support.hierarchical.ThrowableCollector.execute(ThrowableCollector.java:74)
|
||||
at org.junit.platform.engine.support.hierarchical.NodeTestTask.lambda$executeRecursively$1(NodeTestTask.java:166)
|
||||
at org.junit.platform.engine.support.hierarchical.Node.around(Node.java:138)
|
||||
at org.junit.platform.engine.support.hierarchical.NodeTestTask.lambda$executeRecursively$0(NodeTestTask.java:164)
|
||||
at org.junit.platform.engine.support.hierarchical.ThrowableCollector.execute(ThrowableCollector.java:74)
|
||||
at org.junit.platform.engine.support.hierarchical.NodeTestTask.executeRecursively(NodeTestTask.java:163)
|
||||
at org.junit.platform.engine.support.hierarchical.NodeTestTask.execute(NodeTestTask.java:116)
|
||||
at org.junit.platform.engine.support.hierarchical.SameThreadHierarchicalTestExecutorService.submit(SameThreadHierarchicalTestExecutorService.java:36)
|
||||
at org.junit.platform.engine.support.hierarchical.HierarchicalTestExecutor.execute(HierarchicalTestExecutor.java:52)
|
||||
at org.junit.platform.engine.support.hierarchical.HierarchicalTestEngine.execute(HierarchicalTestEngine.java:58)
|
||||
at org.junit.platform.launcher.core.EngineExecutionOrchestrator.executeEngine(EngineExecutionOrchestrator.java:246)
|
||||
at org.junit.platform.launcher.core.EngineExecutionOrchestrator.failOrExecuteEngine(EngineExecutionOrchestrator.java:218)
|
||||
at org.junit.platform.launcher.core.EngineExecutionOrchestrator.execute(EngineExecutionOrchestrator.java:179)
|
||||
at org.junit.platform.launcher.core.EngineExecutionOrchestrator.execute(EngineExecutionOrchestrator.java:108)
|
||||
at org.junit.platform.launcher.core.EngineExecutionOrchestrator.lambda$execute$0(EngineExecutionOrchestrator.java:66)
|
||||
at org.junit.platform.launcher.core.EngineExecutionOrchestrator.withInterceptedStreams(EngineExecutionOrchestrator.java:157)
|
||||
at org.junit.platform.launcher.core.EngineExecutionOrchestrator.execute(EngineExecutionOrchestrator.java:65)
|
||||
at org.junit.platform.launcher.core.DefaultLauncher.execute(DefaultLauncher.java:125)
|
||||
at org.junit.platform.launcher.core.DefaultLauncher.execute(DefaultLauncher.java:114)
|
||||
at org.junit.platform.launcher.core.DelegatingLauncher.execute(DelegatingLauncher.java:58)
|
||||
at org.junit.platform.launcher.core.InterceptingLauncher.lambda$execute$2(InterceptingLauncher.java:57)
|
||||
at org.junit.platform.launcher.core.ClasspathAlignmentCheckingLauncherInterceptor.intercept(ClasspathAlignmentCheckingLauncherInterceptor.java:25)
|
||||
at org.junit.platform.launcher.core.InterceptingLauncher.execute(InterceptingLauncher.java:56)
|
||||
at org.junit.platform.launcher.core.DelegatingLauncher.execute(DelegatingLauncher.java:58)
|
||||
at java.base/jdk.internal.reflect.DirectMethodHandleAccessor.invoke(DirectMethodHandleAccessor.java:103)
|
||||
at java.base/java.lang.reflect.Method.invoke(Method.java:580)
|
||||
at org.apache.maven.surefire.api.util.ReflectionUtils.invokeMethodWithArray(ReflectionUtils.java:125)
|
||||
at org.apache.maven.surefire.junitplatform.LauncherAdapter.executeWithCancellationToken(LauncherAdapter.java:68)
|
||||
at org.apache.maven.surefire.junitplatform.LauncherAdapter.execute(LauncherAdapter.java:54)
|
||||
at org.apache.maven.surefire.junitplatform.JUnitPlatformProvider.execute(JUnitPlatformProvider.java:203)
|
||||
at org.apache.maven.surefire.junitplatform.JUnitPlatformProvider.invokeAllTests(JUnitPlatformProvider.java:168)
|
||||
at org.apache.maven.surefire.junitplatform.JUnitPlatformProvider.invoke(JUnitPlatformProvider.java:136)
|
||||
at org.apache.maven.surefire.booter.ForkedBooter.runSuitesInProcess(ForkedBooter.java:385)
|
||||
at org.apache.maven.surefire.booter.ForkedBooter.execute(ForkedBooter.java:162)
|
||||
at org.apache.maven.surefire.booter.ForkedBooter.run(ForkedBooter.java:507)
|
||||
at org.apache.maven.surefire.booter.ForkedBooter.main(ForkedBooter.java:495)
|
||||
Caused by: org.h2.jdbc.JdbcSQLNonTransientException: General error: "org.h2.mvstore.MVStoreException: The file is locked: /opt/projects/RITz-whatsapp-bot/db/whatsappbot.mv.db [2.4.240/7]" [50000-240]
|
||||
at org.h2.message.DbException.getJdbcSQLException(DbException.java:566)
|
||||
at org.h2.message.DbException.getJdbcSQLException(DbException.java:489)
|
||||
... 157 more
|
||||
Caused by: org.h2.mvstore.MVStoreException: The file is locked: /opt/projects/RITz-whatsapp-bot/db/whatsappbot.mv.db [2.4.240/7]
|
||||
at org.h2.mvstore.DataUtils.newMVStoreException(DataUtils.java:996)
|
||||
at org.h2.mvstore.SingleFileStore.lockFileChannel(SingleFileStore.java:143)
|
||||
at org.h2.mvstore.SingleFileStore.open(SingleFileStore.java:117)
|
||||
at org.h2.mvstore.SingleFileStore.open(SingleFileStore.java:81)
|
||||
at org.h2.mvstore.MVStore.<init>(MVStore.java:291)
|
||||
... 151 more
|
||||
|
||||
@@ -75,6 +75,13 @@ public class WebhookController {
|
||||
String messageType = messageNode.get("type").asText();
|
||||
|
||||
UserState currentState = sessionService.getUserState(fromPhoneNumber);
|
||||
|
||||
boolean isInMemory = sessionService.isSessionCached(fromPhoneNumber);
|
||||
boolean hasDbRecord = sessionService.hasSavedCredentials(fromPhoneNumber);
|
||||
if (!isInMemory && hasDbRecord) {
|
||||
messageService.sendMessage(fromPhoneNumber, "Authenticating User with IMS... ⏳");
|
||||
}
|
||||
|
||||
boolean isAuthenticated = sessionService.isAuthenticated(fromPhoneNumber);
|
||||
|
||||
// Global reset command
|
||||
@@ -166,7 +173,7 @@ public class WebhookController {
|
||||
messageService.sendMessage(phoneNumber, "This menu selection is no longer active.");
|
||||
studentMenuHandler.sendMainMenu(phoneNumber);
|
||||
}
|
||||
} else if (selectedListId.startsWith("receipt_")) {
|
||||
} else if (selectedListId.startsWith("receipt_") || selectedListId.startsWith("exam_") || selectedListId.startsWith("acad_") || selectedListId.startsWith("cons_")) {
|
||||
// They clicked a receipt download option
|
||||
if (currentState == UserState.AWAITING_RECEIPT_SELECTION) {
|
||||
studentMenuHandler.handleReceiptSelection(phoneNumber, selectedListId);
|
||||
@@ -191,6 +198,16 @@ public class WebhookController {
|
||||
return;
|
||||
}
|
||||
|
||||
if ("student_exam_fees".equals(selectedButtonId)) {
|
||||
studentMenuHandler.handleExamFees(phoneNumber);
|
||||
return;
|
||||
}
|
||||
|
||||
if ("student_payment_history".equals(selectedButtonId)) {
|
||||
studentMenuHandler.handlePaymentHistory(phoneNumber);
|
||||
return;
|
||||
}
|
||||
|
||||
if (currentState == UserState.AWAITING_GRIEVANCE_CATEGORY) {
|
||||
grievanceHandler.handleCategorySelection(phoneNumber, selectedButtonId);
|
||||
} else {
|
||||
|
||||
@@ -97,30 +97,29 @@ public class StudentMenuHandler {
|
||||
sendSemesterSelection(phoneNumber, token);
|
||||
break;
|
||||
|
||||
case "student_fee_details":
|
||||
case "student_fee_details": {
|
||||
String rawFees = imsApiService.getFees(token);
|
||||
String rawExamFees = imsApiService.getExamFees(token);
|
||||
String bodyText = MessageFormatter.formatFeesAndExamFees(rawFees, rawExamFees);
|
||||
String bodyText = MessageFormatter.formatAcademicFeesOnly(rawFees);
|
||||
|
||||
List<Map<String, String>> receiptRows = MessageFormatter.extractReceiptRows(rawExamFees);
|
||||
List<Map<String, String>> feeButtons = new ArrayList<>();
|
||||
Map<String, String> examBtn = new HashMap<>();
|
||||
examBtn.put("id", "student_exam_fees");
|
||||
examBtn.put("title", "Exam Fees");
|
||||
feeButtons.add(examBtn);
|
||||
|
||||
if (!receiptRows.isEmpty()) {
|
||||
// Max 10 items in a list. Limit to 10 if necessary.
|
||||
if (receiptRows.size() > 10) receiptRows = receiptRows.subList(0, 10);
|
||||
Map<String, String> historyBtn = new HashMap<>();
|
||||
historyBtn.put("id", "student_payment_history");
|
||||
historyBtn.put("title", "Payment History");
|
||||
feeButtons.add(historyBtn);
|
||||
|
||||
sessionService.setUserState(phoneNumber, UserState.AWAITING_RECEIPT_SELECTION);
|
||||
|
||||
// Meta API interactive list body limit is 1024 characters
|
||||
if (bodyText.length() > 1024) bodyText = bodyText.substring(0, 1021) + "...";
|
||||
|
||||
Map<String, Object> listMessage = WhatsAppMessageBuilder.buildListMessage(
|
||||
"Receipts", bodyText, "Download Receipt", receiptRows
|
||||
);
|
||||
messageService.sendInteractiveMessage(phoneNumber, listMessage);
|
||||
} else {
|
||||
sendResponseWithMenu(phoneNumber, bodyText);
|
||||
if (bodyText.length() > 1020) {
|
||||
bodyText = bodyText.substring(0, 1017) + "...";
|
||||
}
|
||||
|
||||
Map<String, Object> feeButtonMessage = WhatsAppMessageBuilder.buildButtonMessage(bodyText, feeButtons);
|
||||
messageService.sendInteractiveMessage(phoneNumber, feeButtonMessage);
|
||||
break;
|
||||
}
|
||||
|
||||
case "student_raise_grievance":
|
||||
grievanceHandler.startGrievanceFlow(phoneNumber);
|
||||
@@ -209,15 +208,31 @@ public class StudentMenuHandler {
|
||||
}
|
||||
|
||||
try {
|
||||
String historyId = selectedOptionId.replace("receipt_", "");
|
||||
byte[] pdfBytes = imsApiService.getExamFeeReceiptPdf(token, historyId);
|
||||
byte[] pdfBytes = null;
|
||||
String fileName = "Receipt.pdf";
|
||||
String docMessage = "Here is your Fee Receipt!";
|
||||
|
||||
if (selectedOptionId.startsWith("exam_")) {
|
||||
String longId = sessionService.getReceiptIdMapping(phoneNumber, selectedOptionId);
|
||||
pdfBytes = imsApiService.getExamFeeReceiptPdf(token, longId);
|
||||
fileName = "Exam_Receipt.pdf";
|
||||
docMessage = "Here is your Exam Fee Receipt!";
|
||||
} else if (selectedOptionId.startsWith("acad_")) {
|
||||
String longId = sessionService.getReceiptIdMapping(phoneNumber, selectedOptionId);
|
||||
pdfBytes = imsApiService.getAcademicFeeReceiptPdf(token, longId);
|
||||
fileName = "Academic_Receipt.pdf";
|
||||
docMessage = "Here is your Academic Fee Receipt!";
|
||||
} else if (selectedOptionId.startsWith("cons_")) {
|
||||
String longId = sessionService.getReceiptIdMapping(phoneNumber, selectedOptionId);
|
||||
pdfBytes = imsApiService.getConsolidatedAcademicFeeReceiptPdf(token, longId);
|
||||
fileName = "Consolidated_Receipt.pdf";
|
||||
docMessage = "Here is your Consolidated Academic Fee Receipt!";
|
||||
}
|
||||
|
||||
if (pdfBytes != null) {
|
||||
String fileName = "Receipt_" + historyId + ".pdf";
|
||||
String mediaId = messageService.uploadMedia(pdfBytes, fileName, "application/pdf");
|
||||
|
||||
if (mediaId != null) {
|
||||
messageService.sendDocumentMessage(phoneNumber, mediaId, "Here is your Exam Fee Receipt!", fileName);
|
||||
messageService.sendDocumentMessage(phoneNumber, mediaId, docMessage, fileName);
|
||||
sendResponseWithMenu(phoneNumber, "Select an option from the menu below.");
|
||||
} else {
|
||||
sendResponseWithMenu(phoneNumber, "Failed to upload the receipt document to WhatsApp.");
|
||||
@@ -249,6 +264,59 @@ public class StudentMenuHandler {
|
||||
sendResponseWithMenu(phoneNumber, "Select an option from the menu below.");
|
||||
}
|
||||
|
||||
public void handleExamFees(String phoneNumber) {
|
||||
String token = sessionService.getImsToken(phoneNumber);
|
||||
if (token == null) {
|
||||
messageService.sendMessage(phoneNumber, "Your session has expired. Please type 'Reset' to login again.");
|
||||
return;
|
||||
}
|
||||
|
||||
String rawExamFees = imsApiService.getExamFees(token);
|
||||
String formattedExamFees = MessageFormatter.formatExamFees(rawExamFees);
|
||||
sendResponseWithMenu(phoneNumber, formattedExamFees);
|
||||
}
|
||||
|
||||
public void handlePaymentHistory(String phoneNumber) {
|
||||
String token = sessionService.getImsToken(phoneNumber);
|
||||
if (token == null) {
|
||||
messageService.sendMessage(phoneNumber, "Your session has expired. Please type 'Reset' to login again.");
|
||||
return;
|
||||
}
|
||||
|
||||
String rawExamFees = imsApiService.getExamFees(token);
|
||||
String rawFeesHistory = imsApiService.getAcademicFeesHistory(token);
|
||||
|
||||
String formattedHistory = MessageFormatter.formatCombinedPaymentHistory(rawExamFees, rawFeesHistory);
|
||||
|
||||
Map<String, String> idMappings = new HashMap<>();
|
||||
List<Map<String, String>> examRows = MessageFormatter.extractReceiptRows(rawExamFees, idMappings);
|
||||
List<Map<String, String>> acadRows = MessageFormatter.extractAcademicReceiptRows(rawFeesHistory, idMappings);
|
||||
List<Map<String, String>> consRows = MessageFormatter.extractConsolidatedReceiptRows(rawFeesHistory, idMappings);
|
||||
|
||||
sessionService.storeReceiptIdMappings(phoneNumber, idMappings);
|
||||
|
||||
List<Map<String, String>> receiptRows = new ArrayList<>();
|
||||
receiptRows.addAll(examRows);
|
||||
receiptRows.addAll(acadRows);
|
||||
receiptRows.addAll(consRows);
|
||||
|
||||
if (!receiptRows.isEmpty()) {
|
||||
if (receiptRows.size() > 10) receiptRows = receiptRows.subList(0, 10);
|
||||
sessionService.setUserState(phoneNumber, UserState.AWAITING_RECEIPT_SELECTION);
|
||||
|
||||
if (formattedHistory.length() > 1024) {
|
||||
formattedHistory = formattedHistory.substring(0, 1021) + "...";
|
||||
}
|
||||
|
||||
Map<String, Object> listMessage = WhatsAppMessageBuilder.buildListMessage(
|
||||
"Receipts", formattedHistory, "Download Receipt", receiptRows
|
||||
);
|
||||
messageService.sendInteractiveMessage(phoneNumber, listMessage);
|
||||
} else {
|
||||
sendResponseWithMenu(phoneNumber, formattedHistory);
|
||||
}
|
||||
}
|
||||
|
||||
private void sendResponseWithMenu(String phoneNumber, String bodyText) {
|
||||
// Meta API interactive list body limit is 1024 characters
|
||||
if (bodyText.length() > 1024) {
|
||||
|
||||
@@ -134,7 +134,15 @@ public class ImsApiService {
|
||||
}
|
||||
|
||||
public byte[] getExamFeeReceiptPdf(String token, String historyId) {
|
||||
String url = BASE_URL + "/api/student/exam-fee/receipt?history_id=" + historyId;
|
||||
String cleanId = historyId;
|
||||
if (cleanId.startsWith("?history_id=")) {
|
||||
cleanId = cleanId.substring(12);
|
||||
} else if (cleanId.startsWith("?id=")) {
|
||||
cleanId = cleanId.substring(4);
|
||||
} else if (cleanId.startsWith("?")) {
|
||||
cleanId = cleanId.substring(1);
|
||||
}
|
||||
String url = BASE_URL + "/api/student/exam-fee/receipt?history_id=" + cleanId;
|
||||
HttpHeaders headers = new HttpHeaders();
|
||||
headers.setBearerAuth(token);
|
||||
|
||||
@@ -186,4 +194,60 @@ public class ImsApiService {
|
||||
}
|
||||
return "Student";
|
||||
}
|
||||
|
||||
public String getAcademicFeesHistory(String token) {
|
||||
return fetchFromIms("/api/student/fees/history", token);
|
||||
}
|
||||
|
||||
public byte[] getAcademicFeeReceiptPdf(String token, String historyId) {
|
||||
String cleanId = historyId;
|
||||
if (cleanId.startsWith("?history_id=")) {
|
||||
cleanId = cleanId.substring(12);
|
||||
} else if (cleanId.startsWith("?id=")) {
|
||||
cleanId = cleanId.substring(4);
|
||||
} else if (cleanId.startsWith("?")) {
|
||||
cleanId = cleanId.substring(1);
|
||||
}
|
||||
String url = BASE_URL + "/api/student/fees/download-receipt?history_id=" + cleanId;
|
||||
HttpHeaders headers = new HttpHeaders();
|
||||
headers.setBearerAuth(token);
|
||||
|
||||
HttpEntity<String> request = new HttpEntity<>(headers);
|
||||
|
||||
try {
|
||||
ResponseEntity<byte[]> response = restTemplate.exchange(url, org.springframework.http.HttpMethod.GET, request, byte[].class);
|
||||
if (response.getStatusCode().is2xxSuccessful()) {
|
||||
return response.getBody();
|
||||
}
|
||||
} catch (Exception e) {
|
||||
System.err.println("Failed to download Academic Fee receipt PDF: " + e.getMessage());
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
public byte[] getConsolidatedAcademicFeeReceiptPdf(String token, String id) {
|
||||
String cleanId = id;
|
||||
if (cleanId.startsWith("?id=")) {
|
||||
cleanId = cleanId.substring(4);
|
||||
} else if (cleanId.startsWith("?history_id=")) {
|
||||
cleanId = cleanId.substring(12);
|
||||
} else if (cleanId.startsWith("?")) {
|
||||
cleanId = cleanId.substring(1);
|
||||
}
|
||||
String url = BASE_URL + "/api/student/fees/download-consolidated-receipt?id=" + cleanId;
|
||||
HttpHeaders headers = new HttpHeaders();
|
||||
headers.setBearerAuth(token);
|
||||
|
||||
HttpEntity<String> request = new HttpEntity<>(headers);
|
||||
|
||||
try {
|
||||
ResponseEntity<byte[]> response = restTemplate.exchange(url, org.springframework.http.HttpMethod.GET, request, byte[].class);
|
||||
if (response.getStatusCode().is2xxSuccessful()) {
|
||||
return response.getBody();
|
||||
}
|
||||
} catch (Exception e) {
|
||||
System.err.println("Failed to download Consolidated Academic Fee receipt PDF: " + e.getMessage());
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -42,6 +42,14 @@ public class SessionService {
|
||||
userStates.put(phoneNumber, state);
|
||||
}
|
||||
|
||||
public boolean isSessionCached(String phoneNumber) {
|
||||
return sessionTokens.containsKey(phoneNumber);
|
||||
}
|
||||
|
||||
public boolean hasSavedCredentials(String phoneNumber) {
|
||||
return userRepository.existsById(phoneNumber);
|
||||
}
|
||||
|
||||
public void resetState(String phoneNumber) {
|
||||
userStates.remove(phoneNumber);
|
||||
}
|
||||
@@ -104,6 +112,20 @@ public class SessionService {
|
||||
return name;
|
||||
}
|
||||
|
||||
private final Map<String, Map<String, String>> receiptIdMappings = new ConcurrentHashMap<>();
|
||||
|
||||
public void storeReceiptIdMappings(String phoneNumber, Map<String, String> mappings) {
|
||||
receiptIdMappings.put(phoneNumber, mappings);
|
||||
}
|
||||
|
||||
public String getReceiptIdMapping(String phoneNumber, String shortId) {
|
||||
Map<String, String> mappings = receiptIdMappings.get(phoneNumber);
|
||||
if (mappings != null) {
|
||||
return mappings.get(shortId);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
public void setTempLoginId(String phoneNumber, String tempId) {
|
||||
temporaryLoginIds.put(phoneNumber, tempId);
|
||||
}
|
||||
|
||||
@@ -51,10 +51,9 @@ public class MessageFormatter {
|
||||
|
||||
if (!foundData) {
|
||||
sb.append("Details not available in this format.\n");
|
||||
sb.append("\n*DEBUG RAW ATTENDANCE JSON:*\n").append(rawAttendance).append("\n");
|
||||
}
|
||||
} catch (Exception e) {
|
||||
sb.append("⚠️ Error formatting attendance data.\nRAW JSON: ").append(rawAttendance).append("\n");
|
||||
sb.append("⚠️ Error formatting attendance data.\n");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -85,10 +84,9 @@ public class MessageFormatter {
|
||||
|
||||
if (!foundData) {
|
||||
sb.append("Details not available in this format.\n");
|
||||
sb.append("\n*DEBUG RAW LEAVES JSON:*\n").append(rawLeaves).append("\n");
|
||||
}
|
||||
} catch (Exception e) {
|
||||
sb.append("⚠️ Error formatting leaves data.\nRAW JSON: ").append(rawLeaves).append("\n");
|
||||
sb.append("⚠️ Error formatting leaves data.\n");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -482,30 +480,385 @@ public class MessageFormatter {
|
||||
return sb.toString();
|
||||
}
|
||||
|
||||
public static java.util.List<java.util.Map<String, String>> extractReceiptRows(String rawExamFees) {
|
||||
public static String formatAcademicFeesOnly(String rawFees) {
|
||||
StringBuilder sb = new StringBuilder();
|
||||
sb.append("💵 *Academic Dues / Fees*\n\n");
|
||||
if (rawFees == null) {
|
||||
sb.append("❌ Failed to retrieve academic fee details.\n");
|
||||
} else {
|
||||
try {
|
||||
JsonNode root = mapper.readTree(rawFees);
|
||||
boolean foundData = false;
|
||||
|
||||
if (root.has("data")) {
|
||||
JsonNode data = root.get("data");
|
||||
if (data.isArray() && data.size() > 0) {
|
||||
for (JsonNode fee : data) {
|
||||
String type = fee.has("type") ? fee.get("type").asText() :
|
||||
(fee.has("description") ? fee.get("description").asText() : "Fee");
|
||||
String amount = fee.has("amount") ? fee.get("amount").asText() : "0";
|
||||
String status = fee.has("status") ? fee.get("status").asText() : "N/A";
|
||||
|
||||
sb.append("- *").append(type).append("*: ₹").append(amount).append(" (").append(status).append(")\n");
|
||||
foundData = true;
|
||||
}
|
||||
} else if (data.isObject()) {
|
||||
java.util.Iterator<java.util.Map.Entry<String, JsonNode>> fields = data.fields();
|
||||
while (fields.hasNext()) {
|
||||
java.util.Map.Entry<String, JsonNode> field = fields.next();
|
||||
sb.append("- *").append(field.getKey().replace("_", " ")).append("*: ").append(field.getValue().asText()).append("\n");
|
||||
foundData = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!foundData) {
|
||||
sb.append("Details not available in this format.\n");
|
||||
}
|
||||
} catch (Exception e) {
|
||||
sb.append("⚠️ Error formatting academic fee data.\n");
|
||||
}
|
||||
}
|
||||
return sb.toString();
|
||||
}
|
||||
|
||||
public static String formatExamFees(String rawExamFees) {
|
||||
StringBuilder sb = new StringBuilder();
|
||||
sb.append("📝 *Exam Fees*\n\n");
|
||||
if (rawExamFees == null) {
|
||||
sb.append("❌ Failed to retrieve exam fee details.\n");
|
||||
} else {
|
||||
try {
|
||||
JsonNode root = mapper.readTree(rawExamFees);
|
||||
boolean foundData = false;
|
||||
|
||||
if (root.has("data")) {
|
||||
JsonNode data = root.get("data");
|
||||
if (data.isArray() && data.size() > 0) {
|
||||
for (JsonNode fee : data) {
|
||||
String status = fee.has("status") ? fee.get("status").asText() : "";
|
||||
if (!status.equalsIgnoreCase("Paid") && !status.equalsIgnoreCase("Success")) {
|
||||
String semester = fee.has("semester") ? fee.get("semester").asText() : "Unknown Sem";
|
||||
String amount = fee.has("amount") ? fee.get("amount").asText() : "0";
|
||||
String displayStatus = status.isEmpty() ? "Unpaid" : status;
|
||||
sb.append("- *Sem ").append(semester).append("*: ₹").append(amount).append(" (").append(displayStatus).append(")\n");
|
||||
foundData = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!foundData) {
|
||||
sb.append("No outstanding exam fees found.\n");
|
||||
}
|
||||
} catch (Exception e) {
|
||||
sb.append("⚠️ Error formatting exam fee data.\n");
|
||||
}
|
||||
}
|
||||
return sb.toString();
|
||||
}
|
||||
|
||||
public static String formatPaymentHistory(String rawExamFees) {
|
||||
StringBuilder sb = new StringBuilder();
|
||||
sb.append("📜 *Payment History*\n\n");
|
||||
if (rawExamFees == null) {
|
||||
sb.append("❌ Failed to retrieve payment history.\n");
|
||||
} else {
|
||||
try {
|
||||
JsonNode root = mapper.readTree(rawExamFees);
|
||||
boolean foundData = false;
|
||||
|
||||
if (root.has("data")) {
|
||||
JsonNode data = root.get("data");
|
||||
if (data.isArray() && data.size() > 0) {
|
||||
for (JsonNode fee : data) {
|
||||
String status = fee.has("status") ? fee.get("status").asText() : "";
|
||||
if (status.equalsIgnoreCase("Paid") || status.equalsIgnoreCase("Success") || fee.has("history_id")) {
|
||||
String semester = fee.has("semester") ? fee.get("semester").asText() : "Unknown Sem";
|
||||
String amount = fee.has("amount") ? fee.get("amount").asText() : "0";
|
||||
String date = fee.has("date") ? " on " + fee.get("date").asText() : "";
|
||||
sb.append("- *Sem ").append(semester).append("*: ₹").append(amount).append(" (Paid").append(date).append(")\n");
|
||||
foundData = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!foundData) {
|
||||
sb.append("No payment history found.\n");
|
||||
}
|
||||
} catch (Exception e) {
|
||||
sb.append("⚠️ Error formatting payment history.\n");
|
||||
}
|
||||
}
|
||||
return sb.toString();
|
||||
}
|
||||
|
||||
public static java.util.List<java.util.Map<String, String>> extractReceiptRows(String rawExamFees, java.util.Map<String, String> idMappings) {
|
||||
java.util.List<java.util.Map<String, String>> rows = new java.util.ArrayList<>();
|
||||
if (rawExamFees == null) return rows;
|
||||
try {
|
||||
JsonNode root = mapper.readTree(rawExamFees);
|
||||
if (root.has("data") && root.get("data").isArray()) {
|
||||
for (JsonNode fee : root.get("data")) {
|
||||
if (root.has("data")) {
|
||||
JsonNode data = root.get("data");
|
||||
JsonNode historyNode = null;
|
||||
if (data.isObject() && data.has("history")) {
|
||||
historyNode = data.get("history");
|
||||
}
|
||||
|
||||
if (historyNode != null && historyNode.isObject()) {
|
||||
java.util.Iterator<java.util.Map.Entry<String, JsonNode>> fields = historyNode.fields();
|
||||
while (fields.hasNext()) {
|
||||
java.util.Map.Entry<String, JsonNode> field = fields.next();
|
||||
JsonNode arrayNode = field.getValue();
|
||||
if (arrayNode.isArray()) {
|
||||
for (JsonNode fee : arrayNode) {
|
||||
if (fee.has("history_id") || fee.has("id")) {
|
||||
String id = fee.has("history_id") ? fee.get("history_id").asText() : fee.get("id").asText();
|
||||
String amount = fee.has("amount") ? fee.get("amount").asText() : "";
|
||||
String date = fee.has("date") ? fee.get("date").asText() : "";
|
||||
String amount = fee.has("paid_amt") ? fee.get("paid_amt").asText() : (fee.has("amount") ? fee.get("amount").asText() : "");
|
||||
String date = fee.has("paid_date") ? fee.get("paid_date").asText() : (fee.has("date") ? fee.get("date").asText() : "");
|
||||
|
||||
java.util.Map<String, String> row = new java.util.HashMap<>();
|
||||
row.put("id", "receipt_" + id);
|
||||
row.put("title", "Receipt " + id);
|
||||
String shortId = "exam_" + (rows.size() + 1);
|
||||
idMappings.put(shortId, id);
|
||||
row.put("id", shortId);
|
||||
|
||||
String title = "Receipt " + date;
|
||||
if (title.length() > 24 || date.isEmpty()) {
|
||||
title = "Exam Receipt #" + (rows.size() + 1);
|
||||
}
|
||||
row.put("title", title);
|
||||
row.put("description", "₹" + amount + (date.isEmpty() ? "" : " on " + date));
|
||||
rows.add(row);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (Exception e) {}
|
||||
return rows;
|
||||
}
|
||||
|
||||
public static java.util.List<java.util.Map<String, String>> extractAcademicReceiptRows(String rawFeesHistory, java.util.Map<String, String> idMappings) {
|
||||
java.util.List<java.util.Map<String, String>> rows = new java.util.ArrayList<>();
|
||||
if (rawFeesHistory == null) return rows;
|
||||
try {
|
||||
JsonNode root = mapper.readTree(rawFeesHistory);
|
||||
if (root.has("data")) {
|
||||
JsonNode data = root.get("data");
|
||||
JsonNode historyNode = null;
|
||||
|
||||
if (data.isObject()) {
|
||||
if (data.has("history")) historyNode = data.get("history");
|
||||
else if (data.has("payment_history")) historyNode = data.get("payment_history");
|
||||
else if (data.has("paymentHistory")) historyNode = data.get("paymentHistory");
|
||||
else if (data.has("logs")) historyNode = data.get("logs");
|
||||
}
|
||||
|
||||
if (historyNode != null) {
|
||||
if (historyNode.isObject()) {
|
||||
java.util.Iterator<java.util.Map.Entry<String, JsonNode>> fields = historyNode.fields();
|
||||
while (fields.hasNext()) {
|
||||
java.util.Map.Entry<String, JsonNode> field = fields.next();
|
||||
JsonNode arrayNode = field.getValue();
|
||||
if (arrayNode.isArray()) {
|
||||
for (JsonNode fee : arrayNode) {
|
||||
if (fee.has("history_id") || fee.has("id")) {
|
||||
String id = fee.has("history_id") ? fee.get("history_id").asText() : fee.get("id").asText();
|
||||
String amount = fee.has("paid_amt") ? fee.get("paid_amt").asText() : (fee.has("amount") ? fee.get("amount").asText() : "");
|
||||
String date = fee.has("paid_date") ? fee.get("paid_date").asText() : (fee.has("date") ? fee.get("date").asText() : "");
|
||||
String type = fee.has("type") ? fee.get("type").asText() :
|
||||
(fee.has("description") ? fee.get("description").asText() : (fee.has("name") ? fee.get("name").asText() : "Academic Fee"));
|
||||
|
||||
java.util.Map<String, String> row = new java.util.HashMap<>();
|
||||
String shortId = "acad_" + (rows.size() + 1);
|
||||
idMappings.put(shortId, id);
|
||||
row.put("id", shortId);
|
||||
|
||||
String title = "Receipt " + date;
|
||||
if (title.length() > 24 || date.isEmpty()) {
|
||||
title = "Acad Receipt #" + (rows.size() + 1);
|
||||
}
|
||||
row.put("title", title);
|
||||
row.put("description", type + ": ₹" + amount + (date.isEmpty() ? "" : " on " + date));
|
||||
rows.add(row);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
} else if (historyNode.isArray()) {
|
||||
for (JsonNode fee : historyNode) {
|
||||
if (fee.has("history_id") || fee.has("id")) {
|
||||
String id = fee.has("history_id") ? fee.get("history_id").asText() : fee.get("id").asText();
|
||||
String amount = fee.has("paid_amt") ? fee.get("paid_amt").asText() : (fee.has("amount") ? fee.get("amount").asText() : "");
|
||||
String date = fee.has("paid_date") ? fee.get("paid_date").asText() : (fee.has("date") ? fee.get("date").asText() : "");
|
||||
String type = fee.has("type") ? fee.get("type").asText() :
|
||||
(fee.has("description") ? fee.get("description").asText() : "Academic Fee");
|
||||
|
||||
java.util.Map<String, String> row = new java.util.HashMap<>();
|
||||
String shortId = "acad_" + (rows.size() + 1);
|
||||
idMappings.put(shortId, id);
|
||||
row.put("id", shortId);
|
||||
|
||||
String title = "Receipt " + date;
|
||||
if (title.length() > 24 || date.isEmpty()) {
|
||||
title = "Acad Receipt #" + (rows.size() + 1);
|
||||
}
|
||||
row.put("title", title);
|
||||
row.put("description", type + ": ₹" + amount + (date.isEmpty() ? "" : " on " + date));
|
||||
rows.add(row);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (Exception e) {}
|
||||
return rows;
|
||||
}
|
||||
|
||||
public static java.util.List<java.util.Map<String, String>> extractConsolidatedReceiptRows(String rawFeesHistory, java.util.Map<String, String> idMappings) {
|
||||
java.util.List<java.util.Map<String, String>> rows = new java.util.ArrayList<>();
|
||||
if (rawFeesHistory == null) return rows;
|
||||
try {
|
||||
JsonNode root = mapper.readTree(rawFeesHistory);
|
||||
if (root.has("data") && root.get("data").isObject()) {
|
||||
JsonNode data = root.get("data");
|
||||
JsonNode consolidatedNode = null;
|
||||
|
||||
if (data.has("consolidated")) consolidatedNode = data.get("consolidated");
|
||||
else if (data.has("consolidated_receipts")) consolidatedNode = data.get("consolidated_receipts");
|
||||
else if (data.has("consolidatedReceipts")) consolidatedNode = data.get("consolidatedReceipts");
|
||||
|
||||
if (consolidatedNode != null && consolidatedNode.isArray()) {
|
||||
for (JsonNode rec : consolidatedNode) {
|
||||
if (rec.has("id")) {
|
||||
String id = rec.get("id").asText();
|
||||
String academicYear = rec.has("academic_year") ? rec.get("academic_year").asText() : "";
|
||||
String totalFee = rec.has("total_fee") ? rec.get("total_fee").asText() : "";
|
||||
String paidFee = rec.has("paid_fee") ? rec.get("paid_fee").asText() : "";
|
||||
|
||||
java.util.Map<String, String> row = new java.util.HashMap<>();
|
||||
String shortId = "cons_" + (rows.size() + 1);
|
||||
idMappings.put(shortId, id);
|
||||
row.put("id", shortId);
|
||||
|
||||
// WhatsApp interactive list row titles must be <= 24 characters
|
||||
String title = "Consolidated " + academicYear;
|
||||
if (title.length() > 24 || academicYear.isEmpty()) {
|
||||
title = "Consolidated #" + (rows.size() + 1);
|
||||
}
|
||||
row.put("title", title);
|
||||
|
||||
String description = "Paid: ₹" + paidFee + " / ₹" + totalFee;
|
||||
if (description.length() > 72) {
|
||||
description = description.substring(0, 69) + "...";
|
||||
}
|
||||
row.put("description", description);
|
||||
rows.add(row);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (Exception e) {}
|
||||
return rows;
|
||||
}
|
||||
|
||||
public static String formatCombinedPaymentHistory(String rawExamFees, String rawFeesHistory) {
|
||||
StringBuilder sb = new StringBuilder();
|
||||
|
||||
// --- Parse Exam Fee History ---
|
||||
sb.append("📝 *Exam Fee Payment History*\n\n");
|
||||
boolean foundExam = false;
|
||||
if (rawExamFees != null) {
|
||||
try {
|
||||
JsonNode root = mapper.readTree(rawExamFees);
|
||||
if (root.has("data")) {
|
||||
JsonNode data = root.get("data");
|
||||
JsonNode historyNode = null;
|
||||
if (data.isObject() && data.has("history")) {
|
||||
historyNode = data.get("history");
|
||||
}
|
||||
|
||||
if (historyNode != null && historyNode.isObject()) {
|
||||
java.util.Iterator<java.util.Map.Entry<String, JsonNode>> fields = historyNode.fields();
|
||||
while (fields.hasNext()) {
|
||||
java.util.Map.Entry<String, JsonNode> field = fields.next();
|
||||
JsonNode arrayNode = field.getValue();
|
||||
if (arrayNode.isArray()) {
|
||||
for (JsonNode fee : arrayNode) {
|
||||
String semester = fee.has("semester") ? fee.get("semester").asText() : "Unknown Sem";
|
||||
String amount = fee.has("paid_amt") ? fee.get("paid_amt").asText() : (fee.has("amount") ? fee.get("amount").asText() : "0");
|
||||
String date = fee.has("paid_date") ? " on " + fee.get("paid_date").asText() : (fee.has("date") ? " on " + fee.get("date").asText() : "");
|
||||
sb.append("- *Sem ").append(semester).append("*: ₹").append(amount).append(" (Paid").append(date).append(")\n");
|
||||
foundExam = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (Exception e) {}
|
||||
}
|
||||
if (!foundExam) {
|
||||
sb.append("No exam fee payment history found.\n");
|
||||
}
|
||||
|
||||
sb.append("\n");
|
||||
|
||||
// --- Parse Academic Fee History ---
|
||||
sb.append("💵 *Academic Fee Payment History*\n\n");
|
||||
boolean foundAcad = false;
|
||||
if (rawFeesHistory != null) {
|
||||
try {
|
||||
JsonNode root = mapper.readTree(rawFeesHistory);
|
||||
if (root.has("data")) {
|
||||
JsonNode data = root.get("data");
|
||||
JsonNode historyNode = null;
|
||||
if (data.isObject()) {
|
||||
if (data.has("history")) historyNode = data.get("history");
|
||||
else if (data.has("payment_history")) historyNode = data.get("payment_history");
|
||||
else if (data.has("paymentHistory")) historyNode = data.get("paymentHistory");
|
||||
else if (data.has("logs")) historyNode = data.get("logs");
|
||||
}
|
||||
|
||||
if (historyNode != null) {
|
||||
if (historyNode.isObject()) {
|
||||
java.util.Iterator<java.util.Map.Entry<String, JsonNode>> fields = historyNode.fields();
|
||||
while (fields.hasNext()) {
|
||||
java.util.Map.Entry<String, JsonNode> field = fields.next();
|
||||
JsonNode arrayNode = field.getValue();
|
||||
if (arrayNode.isArray()) {
|
||||
for (JsonNode fee : arrayNode) {
|
||||
String amount = fee.has("paid_amt") ? fee.get("paid_amt").asText() : (fee.has("amount") ? fee.get("amount").asText() : "");
|
||||
String date = fee.has("paid_date") ? " on " + fee.get("paid_date").asText() : (fee.has("date") ? " on " + fee.get("date").asText() : "");
|
||||
String type = fee.has("type") ? fee.get("type").asText() :
|
||||
(fee.has("description") ? fee.get("description").asText() : (fee.has("name") ? fee.get("name").asText() : "Academic Fee"));
|
||||
sb.append("- *").append(type).append("*: ₹").append(amount).append(" (Paid").append(date).append(")\n");
|
||||
foundAcad = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
} else if (historyNode.isArray()) {
|
||||
for (JsonNode fee : historyNode) {
|
||||
String amount = fee.has("paid_amt") ? fee.get("paid_amt").asText() : (fee.has("amount") ? fee.get("amount").asText() : "");
|
||||
String date = fee.has("paid_date") ? " on " + fee.get("paid_date").asText() : (fee.has("date") ? " on " + fee.get("date").asText() : "");
|
||||
String type = fee.has("type") ? fee.get("type").asText() :
|
||||
(fee.has("description") ? fee.get("description").asText() : "Academic Fee");
|
||||
sb.append("- *").append(type).append("*: ₹").append(amount).append(" (Paid").append(date).append(")\n");
|
||||
foundAcad = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (Exception e) {}
|
||||
}
|
||||
if (!foundAcad) {
|
||||
sb.append("No academic fee payment history found.\n");
|
||||
}
|
||||
|
||||
return sb.toString();
|
||||
}
|
||||
|
||||
private static String getNumberEmoji(int num) {
|
||||
switch (num) {
|
||||
case 1: return "1️⃣";
|
||||
|
||||
@@ -19,4 +19,4 @@ spring.h2.console.enabled=true
|
||||
|
||||
# Encryption Secret for Passcodes (Must be 16, 24, or 32 bytes)
|
||||
bot.encryption.secret=12345678901234567890123456789012
|
||||
whatsapp.api-token=EAAc3Wjql00oBSUjYhQ1TZBiNJNmwNlpktCtJpqSLXycDVjWjPiP5dBWHvvFX2Deq1tGXQO2jgVOGChnJk9jsMxLVdxvUs4z7g7xlfcQtAPYiHwvQbPVSbGTTCBvjv2IvHf7Ql2iNBKL72GZCrxAJrQpmjHUG5sbQkVWmMpNfwgI52iDBw0BifU1QhQHFEOnQfiPu19KUp3hJOg6XkUbbXyfBKvbSkZB57xj0XHMv6wgpqDrQwAACOjSlSPrNEC3eZB9DbZAqWYnPkJCsX99Pz3GhA
|
||||
whatsapp.api-token=EAAc3Wjql00oBSfSg58drijkqefLn47aI2vYcl8r6GxuwC5vJRt2G8Std3ynf3KnKp9SyuwKXAQfr2Q7hYrNOE66t3dRcS3GAWFbDbikrmvXkbjpIh5V9QNVGI2T4V1VeJAj9d6HMsiHZCAJujaIVLdLleIPti4ok9brnNf1BPwBZA8SZAQ06OUcbSba3SZAQhcoFsh1Utz1g9PLCqsl8Fh9H8ynVJagAxMYqF86sV7fEHKpG6eCp2EkRtfOoE7XbL5AurGkiZA5ZBHiSZCOOujplm3cmgZDZD
|
||||
|
||||
Reference in New Issue
Block a user