RMC chatbot version 1 implemented

This commit is contained in:
2026-08-18 09:39:38 +05:30
commit 67a10b9156
31 changed files with 4837 additions and 0 deletions

4
.gitignore vendored Normal file
View File

@@ -0,0 +1,4 @@
/target/
/.idea/
*.iml
.DS_Store

3
.vscode/settings.json vendored Normal file
View File

@@ -0,0 +1,3 @@
{
"java.compile.nullAnalysis.mode": "automatic"
}

310
README.md Normal file
View File

@@ -0,0 +1,310 @@
# WhatsApp Automation
WhatsApp Automation is a Spring Boot backend for WhatsApp Business messaging workflows. It receives webhook events from Meta, runs bot-driven conversation logic, and persists booking/state data in PostgreSQL.
## About The Project
This project provides automation features around WhatsApp messaging:
- Webhook verification and message handling (`/webhook`)
- Chatbot conversation flow
- PostgreSQL persistence for appointments, service bookings, test bookings, and user state
The application is a **Java 21** Maven service built with Spring Boot.
## Tech Stack
- Spring Boot 3.3.2
- Java 21
- Spring Web
- Spring Data JPA
- PostgreSQL
- ngrok (for local webhook tunneling to Meta)
## Requirements
| Tool | Purpose |
|------|---------|
| **Java 21** | Runtime for the Spring Boot app |
| **Maven 3.9+** | Build and run the project |
| **PostgreSQL** | Application database |
| **ngrok** | Expose local backend to the public internet so Meta can call your webhook |
| **Meta / WhatsApp Business API access** | Access token, webhook configuration, and test messaging |
---
## How to Run the Backend (Full Setup)
Follow these steps in order.
### 1. Clone the repository
```bash
git clone <repository-url>
cd "WhatsApp Automation"
```
Replace `<repository-url>` with the actual Git remote URL for this project.
---
### 2. Install ngrok
ngrok creates a public HTTPS URL that tunnels to your local Spring Boot server so Meta can deliver WhatsApp webhooks.
1. Go to [https://ngrok.com](https://ngrok.com)
2. Sign up or sign in
3. Install ngrok for your OS (macOS example with Homebrew):
```bash
brew install ngrok
```
Or download the binary from the ngrok dashboard and follow their install instructions.
---
### 3. Authenticate ngrok with your access token
After signing in to ngrok, copy your **authtoken** from the ngrok dashboard, then run:
```bash
ngrok config add-authtoken <YOUR_NGROK_AUTHTOKEN>
```
Without this step, ngrok will not start a usable tunnel.
---
### 4. Log in to the WhatsApp Business / Meta developer dashboard
1. Open the [WhatsApp Business API / Meta for Developers](https://developers.facebook.com/) dashboard
2. Log in with the account that has access to this app
3. Use the project’s WhatsApp / Meta credentials (app ID, business ID, phone number ID, etc.) as configured for your team
Relevant app links used by this project:
- **API testing / access token:**
[Generate WhatsApp access token](https://developers.facebook.com/apps/1054675710401577/use_cases/customize/api-testing-v2/?product_route=whatsapp-business&business_id=1051413880774512&use_case_enum=WHATSAPP_BUSINESS_MESSAGING&selected_tab=api-testing-v2)
- **Webhooks configuration:**
[Configure webhooks](https://developers.facebook.com/apps/1054675710401577/use_cases/customize/webhooks/?product_route=webhooks&business_id=1051413880774512&use_case_enum=WHATSAPP_BUSINESS_MESSAGING&selected_tab=webhooks)
---
### 5. Generate a WhatsApp access token and configure the app
1. Open the API testing page:
[https://developers.facebook.com/apps/1054675710401577/use_cases/customize/api-testing-v2/?product_route=whatsapp-business&business_id=1051413880774512&use_case_enum=WHATSAPP_BUSINESS_MESSAGING&selected_tab=api-testing-v2](https://developers.facebook.com/apps/1054675710401577/use_cases/customize/api-testing-v2/?product_route=whatsapp-business&business_id=1051413880774512&use_case_enum=WHATSAPP_BUSINESS_MESSAGING&selected_tab=api-testing-v2)
2. Generate a **new Access Token**
3. Open the project config file:
```text
src/main/resources/application.properties
```
4. Paste the token into the access-token property:
```properties
whatsapp.access-token=<YOUR_WHATSAPP_ACCESS_TOKEN>
```
> **Note:** Temporary tokens from Meta expire. If outbound WhatsApp API calls start failing with auth errors, generate a new token and update this property.
---
### 6. Create the PostgreSQL database and set credentials
1. Ensure PostgreSQL is installed and running locally
2. Create the database used by this project (default name: `rmc_bot`):
```bash
createdb rmc_bot
```
Or via `psql`:
```sql
CREATE DATABASE rmc_bot;
```
3. Update database settings in `src/main/resources/application.properties`:
```properties
spring.datasource.url=jdbc:postgresql://localhost:5432/rmc_bot
spring.datasource.username=<YOUR_POSTGRES_USERNAME>
spring.datasource.password=<YOUR_POSTGRES_PASSWORD>
```
Default values currently in the file (change these for your machine):
| Property | Default / example |
|----------|-------------------|
| `spring.datasource.url` | `jdbc:postgresql://localhost:5432/rmc_bot` |
| `spring.datasource.username` | `postgres` |
| `spring.datasource.password` | *(your local password)* |
The app uses:
```properties
spring.jpa.hibernate.ddl-auto=update
```
so tables are created/updated automatically on startup.
---
### 7. Start the Spring Boot backend
The server listens on **port 8085** (`server.port=8085`).
From the project root:
```bash
mvn spring-boot:run
```
Confirm the app started successfully (no datasource or port-binding errors). Leave this process running.
You can also run tests with:
```bash
mvn test
```
---
### 8. Start ngrok and point it at the backend
**Important:** Start the Spring Boot backend *before* configuring Meta webhooks, and keep both the backend and ngrok running while testing.
In a **new terminal**:
```bash
ngrok http 8085
```
ngrok will print a public HTTPS URL, for example:
```text
https://abcd-1234.ngrok-free.app
```
Copy that base URL (the `https://...` forwarding address).
> Free ngrok URLs change every time you restart ngrok. When the URL changes, you must update the Callback URL in Meta again.
---
### 9. Configure the Meta webhook
1. Open the webhooks page:
[https://developers.facebook.com/apps/1054675710401577/use_cases/customize/webhooks/?product_route=webhooks&business_id=1051413880774512&use_case_enum=WHATSAPP_BUSINESS_MESSAGING&selected_tab=webhooks](https://developers.facebook.com/apps/1054675710401577/use_cases/customize/webhooks/?product_route=webhooks&business_id=1051413880774512&use_case_enum=WHATSAPP_BUSINESS_MESSAGING&selected_tab=webhooks)
2. Set **Callback URL** to:
```text
https://<YOUR_NGROK_HOST>/webhook
```
Example:
```text
https://abcd-1234.ngrok-free.app/webhook
```
3. Set **Verify Token** to exactly:
```text
my_whatsapp_verify_token_123
```
This must match `whatsapp.verify-token` in `application.properties`:
```properties
whatsapp.verify-token=my_whatsapp_verify_token_123
```
4. Click **Verify and Save** (or equivalent). Meta will send a `GET` request to `/webhook` with `hub.mode`, `hub.verify_token`, and `hub.challenge`. The backend validates the token and returns the challenge.
5. Subscribe to the webhook fields you need (typically **messages** for inbound WhatsApp traffic).
---
## Configuration reference
All runtime secrets and connection settings live in:
```text
src/main/resources/application.properties
```
| Property | Description |
|----------|-------------|
| `server.port` | Backend HTTP port (default `8085`) |
| `whatsapp.access-token` | Meta WhatsApp Cloud API access token |
| `whatsapp.verify-token` | Token Meta uses to verify your webhook (`my_whatsapp_verify_token_123`) |
| `spring.datasource.url` | JDBC URL for PostgreSQL |
| `spring.datasource.username` | PostgreSQL username |
| `spring.datasource.password` | PostgreSQL password |
| `spring.jpa.hibernate.ddl-auto` | Schema mode (`update`) |
| `spring.jpa.properties.hibernate.dialect` | PostgreSQL dialect |
| `spring.jpa.show-sql` | Log SQL statements |
Webhook endpoint exposed by the app:
| Method | Path | Purpose |
|--------|------|---------|
| `GET` | `/webhook` | Meta webhook verification |
| `POST` | `/webhook` | Inbound WhatsApp events / messages |
---
## Quick checklist
Use this when bringing the stack up on a new machine:
1. [ ] Clone the repo
2. [ ] Install and authenticate ngrok
3. [ ] Log in to Meta / WhatsApp Business dashboard
4. [ ] Generate access token → set `whatsapp.access-token`
5. [ ] Create PostgreSQL DB → set datasource username/password
6. [ ] `mvn spring-boot:run` (port **8085**)
7. [ ] `ngrok http 8085`
8. [ ] Meta Callback URL = `https://<ngrok-host>/webhook`
9. [ ] Verify token = `my_whatsapp_verify_token_123`
10. [ ] Verify webhook + subscribe to message events
---
## Troubleshooting
| Problem | What to check |
|---------|----------------|
| Webhook verification fails | Verify token must be exactly `my_whatsapp_verify_token_123`. Backend must be running. Callback URL must end with `/webhook`. |
| Meta cannot reach the server | ngrok must be running (`ngrok http 8085`). Use the current ngrok HTTPS URL, not an old one. |
| Database connection errors | PostgreSQL is running; database `rmc_bot` exists; username/password in `application.properties` are correct. |
| Cannot send WhatsApp messages | Access token may have expired — generate a new one and update `whatsapp.access-token`. |
| Port already in use | Something else is bound to `8085`, or change `server.port` and point ngrok at the new port. |
| Not receiving messages on webhook | Your WhatsApp Business Account (WABA) might not be subscribed to your developer App. Link them via API: `curl -X POST "https://graph.facebook.com/v20.0/<WABA_ID>/subscribed_apps" -H "Authorization: Bearer <your_access_token>"` |
---
## Project structure (high level)
```text
src/main/java/com/example/whatsappautomation/
WhatsAppAutomationApplication.java
controller/WhatsAppWebhookController.java
service/ChatbotService.java
entity/ # Appointment, ServiceBooking, TestBooking, UserState
repository/ # Spring Data JPA repositories
src/main/resources/
application.properties
```
---
## License
Internal / project use unless otherwise specified.

121
app_test.log Normal file
View File

@@ -0,0 +1,121 @@
[INFO] Scanning for projects...
[INFO]
[INFO] ------------------< com.example:whatsapp-automation >-------------------
[INFO] Building WhatsApp Automation 0.0.1-SNAPSHOT
[INFO] from pom.xml
[INFO] --------------------------------[ jar ]---------------------------------
[INFO]
[INFO] >>> spring-boot:3.3.2:run (default-cli) > test-compile @ whatsapp-automation >>>
[INFO]
[INFO] --- resources:3.3.1:resources (default-resources) @ whatsapp-automation ---
[INFO] Copying 1 resource from src/main/resources to target/classes
[INFO] Copying 3 resources from src/main/resources to target/classes
[INFO]
[INFO] --- compiler:3.13.0:compile (default-compile) @ whatsapp-automation ---
[INFO] Nothing to compile - all classes are up to date.
[INFO]
[INFO] --- resources:3.3.1:testResources (default-testResources) @ whatsapp-automation ---
[INFO] skip non existing resourceDirectory /Users/devprasad/Downloads/RMC-WhatsApp-Bot-main/src/test/resources
[INFO]
[INFO] --- compiler:3.13.0:testCompile (default-testCompile) @ whatsapp-automation ---
[INFO] Nothing to compile - all classes are up to date.
[INFO]
[INFO] <<< spring-boot:3.3.2:run (default-cli) < test-compile @ whatsapp-automation <<<
[INFO]
[INFO]
[INFO] --- spring-boot:3.3.2:run (default-cli) @ whatsapp-automation ---
[INFO] Attaching agents: []
. ____ _ __ _ _
/\\ / ___'_ __ _ _(_)_ __ __ _ \ \ \ \
( ( )\___ | '_ | '_| | '_ \/ _` | \ \ \ \
\\/ ___)| |_)| | | | | || (_| | ) ) ) )
' |____| .__|_| |_|_| |_\__, | / / / /
=========|_|==============|___/=/_/_/_/
:: Spring Boot :: (v3.3.2)
2026-08-17T11:25:46.813+05:30 INFO 98153 --- [whatsapp-automation] [ main] c.e.w.WhatsAppAutomationApplication : Starting WhatsAppAutomationApplication using Java 26.0.1 with PID 98153 (/Users/devprasad/Downloads/RMC-WhatsApp-Bot-main/target/classes started by devprasad in /Users/devprasad/Downloads/RMC-WhatsApp-Bot-main)
2026-08-17T11:25:46.814+05:30 INFO 98153 --- [whatsapp-automation] [ main] c.e.w.WhatsAppAutomationApplication : No active profile set, falling back to 1 default profile: "default"
2026-08-17T11:25:46.992+05:30 INFO 98153 --- [whatsapp-automation] [ main] .s.d.r.c.RepositoryConfigurationDelegate : Bootstrapping Spring Data JPA repositories in DEFAULT mode.
2026-08-17T11:25:47.009+05:30 INFO 98153 --- [whatsapp-automation] [ main] .s.d.r.c.RepositoryConfigurationDelegate : Finished Spring Data repository scanning in 14 ms. Found 4 JPA repository interfaces.
WARNING: A restricted method in java.lang.System has been called
WARNING: java.lang.System::load has been called by org.apache.tomcat.jni.Library in an unnamed module (file:/Users/devprasad/.m2/repository/org/apache/tomcat/embed/tomcat-embed-core/10.1.26/tomcat-embed-core-10.1.26.jar)
WARNING: Use --enable-native-access=ALL-UNNAMED to avoid a warning for callers in this module
WARNING: Restricted methods will be blocked in a future release unless native access is enabled
2026-08-17T11:25:47.150+05:30 INFO 98153 --- [whatsapp-automation] [ main] o.s.b.w.embedded.tomcat.TomcatWebServer : Tomcat initialized with port 8086 (http)
2026-08-17T11:25:47.153+05:30 INFO 98153 --- [whatsapp-automation] [ main] o.apache.catalina.core.StandardService : Starting service [Tomcat]
2026-08-17T11:25:47.154+05:30 INFO 98153 --- [whatsapp-automation] [ main] o.apache.catalina.core.StandardEngine : Starting Servlet engine: [Apache Tomcat/10.1.26]
2026-08-17T11:25:47.167+05:30 INFO 98153 --- [whatsapp-automation] [ main] o.a.c.c.C.[Tomcat].[localhost].[/] : Initializing Spring embedded WebApplicationContext
2026-08-17T11:25:47.168+05:30 INFO 98153 --- [whatsapp-automation] [ main] w.s.c.ServletWebServerApplicationContext : Root WebApplicationContext: initialization completed in 339 ms
2026-08-17T11:25:47.210+05:30 INFO 98153 --- [whatsapp-automation] [ main] o.hibernate.jpa.internal.util.LogHelper : HHH000204: Processing PersistenceUnitInfo [name: default]
2026-08-17T11:25:47.226+05:30 INFO 98153 --- [whatsapp-automation] [ main] org.hibernate.Version : HHH000412: Hibernate ORM core version 6.5.2.Final
2026-08-17T11:25:47.234+05:30 INFO 98153 --- [whatsapp-automation] [ main] o.h.c.internal.RegionFactoryInitiator : HHH000026: Second-level cache disabled
2026-08-17T11:25:47.316+05:30 INFO 98153 --- [whatsapp-automation] [ main] o.s.o.j.p.SpringPersistenceUnitInfo : No LoadTimeWeaver setup: ignoring JPA class transformer
2026-08-17T11:25:47.323+05:30 INFO 98153 --- [whatsapp-automation] [ main] com.zaxxer.hikari.HikariDataSource : HikariPool-1 - Starting...
2026-08-17T11:25:47.373+05:30 INFO 98153 --- [whatsapp-automation] [ main] com.zaxxer.hikari.pool.HikariPool : HikariPool-1 - Added connection org.postgresql.jdbc.PgConnection@10e9a5fe
2026-08-17T11:25:47.373+05:30 INFO 98153 --- [whatsapp-automation] [ main] com.zaxxer.hikari.HikariDataSource : HikariPool-1 - Start completed.
2026-08-17T11:25:47.387+05:30 WARN 98153 --- [whatsapp-automation] [ main] org.hibernate.orm.deprecation : HHH90000025: PostgreSQLDialect does not need to be specified explicitly using 'hibernate.dialect' (remove the property setting and it will be selected by default)
2026-08-17T11:25:47.639+05:30 INFO 98153 --- [whatsapp-automation] [ main] o.h.e.t.j.p.i.JtaPlatformInitiator : HHH000489: No JTA platform available (set 'hibernate.transaction.jta.platform' to enable JTA platform integration)
Hibernate: alter table if exists user_state add column menu_page integer not null
2026-08-17T11:25:47.666+05:30 WARN 98153 --- [whatsapp-automation] [ main] o.h.t.s.i.ExceptionHandlerLoggedImpl : GenerationTarget encountered exception accepting command : Error executing DDL "alter table if exists user_state add column menu_page integer not null" via JDBC [ERROR: column "menu_page" of relation "user_state" contains null values]
org.hibernate.tool.schema.spi.CommandAcceptanceException: Error executing DDL "alter table if exists user_state add column menu_page integer not null" via JDBC [ERROR: column "menu_page" of relation "user_state" contains null values]
at org.hibernate.tool.schema.internal.exec.GenerationTargetToDatabase.accept(GenerationTargetToDatabase.java:94) ~[hibernate-core-6.5.2.Final.jar:6.5.2.Final]
at org.hibernate.tool.schema.internal.AbstractSchemaMigrator.applySqlString(AbstractSchemaMigrator.java:575) ~[hibernate-core-6.5.2.Final.jar:6.5.2.Final]
at org.hibernate.tool.schema.internal.AbstractSchemaMigrator.applySqlStrings(AbstractSchemaMigrator.java:515) ~[hibernate-core-6.5.2.Final.jar:6.5.2.Final]
at org.hibernate.tool.schema.internal.AbstractSchemaMigrator.migrateTable(AbstractSchemaMigrator.java:333) ~[hibernate-core-6.5.2.Final.jar:6.5.2.Final]
at org.hibernate.tool.schema.internal.GroupedSchemaMigratorImpl.performTablesMigration(GroupedSchemaMigratorImpl.java:84) ~[hibernate-core-6.5.2.Final.jar:6.5.2.Final]
at org.hibernate.tool.schema.internal.AbstractSchemaMigrator.performMigration(AbstractSchemaMigrator.java:232) ~[hibernate-core-6.5.2.Final.jar:6.5.2.Final]
at org.hibernate.tool.schema.internal.AbstractSchemaMigrator.doMigration(AbstractSchemaMigrator.java:117) ~[hibernate-core-6.5.2.Final.jar:6.5.2.Final]
at org.hibernate.tool.schema.spi.SchemaManagementToolCoordinator.performDatabaseAction(SchemaManagementToolCoordinator.java:280) ~[hibernate-core-6.5.2.Final.jar:6.5.2.Final]
at org.hibernate.tool.schema.spi.SchemaManagementToolCoordinator.lambda$process$5(SchemaManagementToolCoordinator.java:144) ~[hibernate-core-6.5.2.Final.jar:6.5.2.Final]
at java.base/java.util.HashMap.forEach(HashMap.java:1430) ~[na:na]
at org.hibernate.tool.schema.spi.SchemaManagementToolCoordinator.process(SchemaManagementToolCoordinator.java:141) ~[hibernate-core-6.5.2.Final.jar:6.5.2.Final]
at org.hibernate.boot.internal.SessionFactoryObserverForSchemaExport.sessionFactoryCreated(SessionFactoryObserverForSchemaExport.java:37) ~[hibernate-core-6.5.2.Final.jar:6.5.2.Final]
at org.hibernate.internal.SessionFactoryObserverChain.sessionFactoryCreated(SessionFactoryObserverChain.java:35) ~[hibernate-core-6.5.2.Final.jar:6.5.2.Final]
at org.hibernate.internal.SessionFactoryImpl.<init>(SessionFactoryImpl.java:322) ~[hibernate-core-6.5.2.Final.jar:6.5.2.Final]
at org.hibernate.boot.internal.SessionFactoryBuilderImpl.build(SessionFactoryBuilderImpl.java:457) ~[hibernate-core-6.5.2.Final.jar:6.5.2.Final]
at org.hibernate.jpa.boot.internal.EntityManagerFactoryBuilderImpl.build(EntityManagerFactoryBuilderImpl.java:1506) ~[hibernate-core-6.5.2.Final.jar:6.5.2.Final]
at org.springframework.orm.jpa.vendor.SpringHibernateJpaPersistenceProvider.createContainerEntityManagerFactory(SpringHibernateJpaPersistenceProvider.java:75) ~[spring-orm-6.1.11.jar:6.1.11]
at org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean.createNativeEntityManagerFactory(LocalContainerEntityManagerFactoryBean.java:390) ~[spring-orm-6.1.11.jar:6.1.11]
at org.springframework.orm.jpa.AbstractEntityManagerFactoryBean.buildNativeEntityManagerFactory(AbstractEntityManagerFactoryBean.java:409) ~[spring-orm-6.1.11.jar:6.1.11]
at org.springframework.orm.jpa.AbstractEntityManagerFactoryBean.afterPropertiesSet(AbstractEntityManagerFactoryBean.java:396) ~[spring-orm-6.1.11.jar:6.1.11]
at org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean.afterPropertiesSet(LocalContainerEntityManagerFactoryBean.java:366) ~[spring-orm-6.1.11.jar:6.1.11]
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.invokeInitMethods(AbstractAutowireCapableBeanFactory.java:1853) ~[spring-beans-6.1.11.jar:6.1.11]
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.initializeBean(AbstractAutowireCapableBeanFactory.java:1802) ~[spring-beans-6.1.11.jar:6.1.11]
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.doCreateBean(AbstractAutowireCapableBeanFactory.java:600) ~[spring-beans-6.1.11.jar:6.1.11]
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.createBean(AbstractAutowireCapableBeanFactory.java:522) ~[spring-beans-6.1.11.jar:6.1.11]
at org.springframework.beans.factory.support.AbstractBeanFactory.lambda$doGetBean$0(AbstractBeanFactory.java:337) ~[spring-beans-6.1.11.jar:6.1.11]
at org.springframework.beans.factory.support.DefaultSingletonBeanRegistry.getSingleton(DefaultSingletonBeanRegistry.java:234) ~[spring-beans-6.1.11.jar:6.1.11]
at org.springframework.beans.factory.support.AbstractBeanFactory.doGetBean(AbstractBeanFactory.java:335) ~[spring-beans-6.1.11.jar:6.1.11]
at org.springframework.beans.factory.support.AbstractBeanFactory.getBean(AbstractBeanFactory.java:205) ~[spring-beans-6.1.11.jar:6.1.11]
at org.springframework.context.support.AbstractApplicationContext.finishBeanFactoryInitialization(AbstractApplicationContext.java:954) ~[spring-context-6.1.11.jar:6.1.11]
at org.springframework.context.support.AbstractApplicationContext.refresh(AbstractApplicationContext.java:625) ~[spring-context-6.1.11.jar:6.1.11]
at org.springframework.boot.web.servlet.context.ServletWebServerApplicationContext.refresh(ServletWebServerApplicationContext.java:146) ~[spring-boot-3.3.2.jar:3.3.2]
at org.springframework.boot.SpringApplication.refresh(SpringApplication.java:754) ~[spring-boot-3.3.2.jar:3.3.2]
at org.springframework.boot.SpringApplication.refreshContext(SpringApplication.java:456) ~[spring-boot-3.3.2.jar:3.3.2]
at org.springframework.boot.SpringApplication.run(SpringApplication.java:335) ~[spring-boot-3.3.2.jar:3.3.2]
at org.springframework.boot.SpringApplication.run(SpringApplication.java:1363) ~[spring-boot-3.3.2.jar:3.3.2]
at org.springframework.boot.SpringApplication.run(SpringApplication.java:1352) ~[spring-boot-3.3.2.jar:3.3.2]
at com.example.whatsappautomation.WhatsAppAutomationApplication.main(WhatsAppAutomationApplication.java:10) ~[classes/:na]
Caused by: org.postgresql.util.PSQLException: ERROR: column "menu_page" of relation "user_state" contains null values
at org.postgresql.core.v3.QueryExecutorImpl.receiveErrorResponse(QueryExecutorImpl.java:2725) ~[postgresql-42.7.3.jar:42.7.3]
at org.postgresql.core.v3.QueryExecutorImpl.processResults(QueryExecutorImpl.java:2412) ~[postgresql-42.7.3.jar:42.7.3]
at org.postgresql.core.v3.QueryExecutorImpl.execute(QueryExecutorImpl.java:371) ~[postgresql-42.7.3.jar:42.7.3]
at org.postgresql.jdbc.PgStatement.executeInternal(PgStatement.java:502) ~[postgresql-42.7.3.jar:42.7.3]
at org.postgresql.jdbc.PgStatement.execute(PgStatement.java:419) ~[postgresql-42.7.3.jar:42.7.3]
at org.postgresql.jdbc.PgStatement.executeWithFlags(PgStatement.java:341) ~[postgresql-42.7.3.jar:42.7.3]
at org.postgresql.jdbc.PgStatement.executeCachedSql(PgStatement.java:326) ~[postgresql-42.7.3.jar:42.7.3]
at org.postgresql.jdbc.PgStatement.executeWithFlags(PgStatement.java:302) ~[postgresql-42.7.3.jar:42.7.3]
at org.postgresql.jdbc.PgStatement.execute(PgStatement.java:297) ~[postgresql-42.7.3.jar:42.7.3]
at com.zaxxer.hikari.pool.ProxyStatement.execute(ProxyStatement.java:94) ~[HikariCP-5.1.0.jar:na]
at com.zaxxer.hikari.pool.HikariProxyStatement.execute(HikariProxyStatement.java) ~[HikariCP-5.1.0.jar:na]
at org.hibernate.tool.schema.internal.exec.GenerationTargetToDatabase.accept(GenerationTargetToDatabase.java:80) ~[hibernate-core-6.5.2.Final.jar:6.5.2.Final]
... 37 common frames omitted
2026-08-17T11:25:47.668+05:30 INFO 98153 --- [whatsapp-automation] [ main] j.LocalContainerEntityManagerFactoryBean : Initialized JPA EntityManagerFactory for persistence unit 'default'
2026-08-17T11:25:47.778+05:30 WARN 98153 --- [whatsapp-automation] [ main] JpaBaseConfiguration$JpaWebConfiguration : spring.jpa.open-in-view is enabled by default. Therefore, database queries may be performed during view rendering. Explicitly configure spring.jpa.open-in-view to disable this warning
2026-08-17T11:25:47.866+05:30 INFO 98153 --- [whatsapp-automation] [ main] o.s.b.w.embedded.tomcat.TomcatWebServer : Tomcat started on port 8086 (http) with context path '/'
2026-08-17T11:25:47.869+05:30 INFO 98153 --- [whatsapp-automation] [ main] c.e.w.WhatsAppAutomationApplication : Started WhatsAppAutomationApplication in 1.159 seconds (process running for 1.272)

63
fix_args.py Normal file
View File

@@ -0,0 +1,63 @@
import re
with open('src/main/java/com/example/whatsappautomation/service/ChatbotService.java', 'r') as f:
content = f.read()
# We want to match: sendPaginatedButtons(state, arg2, arg3, new String[]{...}, new String[]{...})
# and replace it with: sendPaginatedButtons(state, arg2, new String[]{...}, new String[]{...})
# Note: arg2 and arg3 might be getMessage(...) or "string".
# It's safest to match: sendPaginatedButtons(state, (whatever), (whatever), new String[]{
def replacer(match):
# match.group(1) is the part from `state, ` up to `, new String[]{`
# this part contains `arg2, arg3`. We need to split by comma from the right, but arg2 or arg3 might have commas!
# Wait, getting the last argument before `new String[]{` is the best way.
# Actually, the button text (arg3) is almost always `getMessage("something.btn", state)` or `"View Options"` or similar.
# Let's just use regex to match the third argument which is always followed by `, new String\[\]\{`
pass
# Let's write a simple python parser for the arguments of sendPaginatedButtons
new_content = ""
idx = 0
while True:
start_idx = content.find('sendPaginatedButtons(state, ', idx)
if start_idx == -1:
new_content += content[idx:]
break
new_content += content[idx:start_idx]
# find the start of `new String[]{`
arrays_start = content.find('new String[]{', start_idx)
# The text between `start_idx + len('sendPaginatedButtons(state, ')` and `arrays_start` is `arg2, arg3, `
# We want to remove `arg3, `.
# `arg3` is either a string literal `"..."` or a `getMessage(...)` call.
args_str = content[start_idx + len('sendPaginatedButtons(state, '):arrays_start].strip()
# args_str ends with `,`. Let's remove the trailing comma.
args_str = args_str[:-1].strip()
# Now we need to split args_str into arg2 and arg3.
# If it ends with `state)`, arg3 is `getMessage(..., state)`.
# If it ends with `"`, arg3 is a string literal.
# Actually, we can just find the LAST comma that is NOT inside parentheses!
# A simple comma split works if we respect parens.
def rsplit_args(s):
depth = 0
for i in range(len(s)-1, -1, -1):
if s[i] == ')': depth += 1
elif s[i] == '(': depth -= 1
elif s[i] == ',' and depth == 0:
return s[:i].strip(), s[i+1:].strip()
return s, ""
arg2, arg3 = rsplit_args(args_str)
new_content += f"sendPaginatedButtons(state, {arg2}, "
idx = arrays_start
with open('src/main/java/com/example/whatsappautomation/service/ChatbotService.java', 'w') as f:
f.write(new_content)
print("Fixed arguments")

101
fix_menu.py Normal file
View File

@@ -0,0 +1,101 @@
with open("src/main/java/com/example/whatsappautomation/service/ChatbotService.java", "r") as f:
content = f.read()
old_paginated = """ private void sendPaginatedButtons(UserState state, String bodyText, String[] ids, String[] titles) {
int totalOptions = ids.length;
// Loop and send chunks of up to 3 buttons
for (int i = 0; i < totalOptions; i += 3) {
int end = Math.min(i + 3, totalOptions);
int count = end - i;
String[] chunkIds = new String[count];
String[] chunkTitles = new String[count];
for (int j = 0; j < count; j++) {
chunkIds[j] = ids[i + j];
chunkTitles[j] = titles[i + j];
}
String chunkBodyText = (i == 0) ? bodyText : ( "en".equals(state.getLanguagePreference()) ? "More options:" : "மேலும் விருப்பங்கள்:" );
sendInteractiveButtons(chunkBodyText, chunkIds, chunkTitles);
}
}"""
new_methods = """ private void sendInteractiveList(String bodyText, String buttonText, String sectionTitle, String[] ids, String[] titles) {
String phoneNumberId = currentPhoneId.get();
String recipientPhoneNumber = currentFrom.get();
if (phoneNumberId == null || recipientPhoneNumber == null) return;
String url = "https://graph.facebook.com/v20.0/" + phoneNumberId + "/messages";
try {
ObjectNode requestBody = objectMapper.createObjectNode();
requestBody.put("messaging_product", "whatsapp");
requestBody.put("recipient_type", "individual");
requestBody.put("to", recipientPhoneNumber);
requestBody.put("type", "interactive");
ObjectNode interactiveNode = objectMapper.createObjectNode();
interactiveNode.put("type", "list");
ObjectNode bodyNode = objectMapper.createObjectNode();
UserState uState = userStateRepository.findById(recipientPhoneNumber).orElse(null);
if (uState != null && !"MAIN_MENU".equals(uState.getState()) && !"LANGUAGE_SELECTION".equals(uState.getState())) {
String restartText = "en".equals(uState.getLanguagePreference())
? "\\n\\n(Press 0 to restart the conversation)"
: "\\n\\n(உரையாடலை மீண்டும் தொடங்க 0 ஐ அழுத்தவும்)";
bodyText += restartText;
}
bodyNode.put("text", bodyText);
interactiveNode.set("body", bodyNode);
ObjectNode actionNode = objectMapper.createObjectNode();
actionNode.put("button", buttonText);
com.fasterxml.jackson.databind.node.ArrayNode sectionsArray = actionNode.putArray("sections");
ObjectNode section = objectMapper.createObjectNode();
section.put("title", sectionTitle);
com.fasterxml.jackson.databind.node.ArrayNode rowsArray = section.putArray("rows");
int limit = Math.min(ids.length, 10);
for (int i = 0; i < limit; i++) {
ObjectNode row = objectMapper.createObjectNode();
row.put("id", ids[i]);
row.put("title", titles[i]);
rowsArray.add(row);
}
sectionsArray.add(section);
interactiveNode.set("action", actionNode);
requestBody.set("interactive", interactiveNode);
HttpHeaders headers = new HttpHeaders();
headers.setContentType(org.springframework.http.MediaType.APPLICATION_JSON);
headers.setBearerAuth(accessToken);
org.springframework.http.HttpEntity<String> entity = new org.springframework.http.HttpEntity<>(requestBody.toString(), headers);
restTemplate.postForEntity(url, entity, String.class);
} catch (org.springframework.web.client.HttpStatusCodeException e) {
logger.error("Error sending interactive list. Status: {}, Response: {}", e.getStatusCode(), e.getResponseBodyAsString(), e);
} catch (Exception e) {
logger.error("Error sending interactive list", e);
}
}
private void sendPaginatedButtons(UserState state, String bodyText, String[] ids, String[] titles) {
if (ids.length <= 3) {
sendInteractiveButtons(bodyText, ids, titles);
} else {
String buttonText = "en".equals(state.getLanguagePreference()) ? "Select Option" : "விருப்பத்தை தேர்ந்தெடுக்கவும்";
String sectionTitle = "en".equals(state.getLanguagePreference()) ? "Options" : "விருப்பங்கள்";
sendInteractiveList(bodyText, buttonText, sectionTitle, ids, titles);
}
}"""
content = content.replace(old_paginated, new_methods)
with open("src/main/java/com/example/whatsappautomation/service/ChatbotService.java", "w") as f:
f.write(content)
print("Replacement complete.")

91
fix_restart.py Normal file
View File

@@ -0,0 +1,91 @@
with open("src/main/java/com/example/whatsappautomation/service/ChatbotService.java", "r") as f:
content = f.read()
# 1. Modify sendInteractiveButtons to fetch state and append restart text
old_method_start = """ private void sendInteractiveButtons(String bodyText, String[] ids, String[] titles) {
String phoneNumberId = currentPhoneId.get();
String recipientPhoneNumber = currentFrom.get();"""
new_method_start = """ private void sendInteractiveButtons(String bodyText, String[] ids, String[] titles) {
String phoneNumberId = currentPhoneId.get();
String recipientPhoneNumber = currentFrom.get();
UserState uState = userStateRepository.findById(recipientPhoneNumber).orElse(null);
if (uState != null && !"MAIN_MENU".equals(uState.getState()) && !"LANGUAGE_SELECTION".equals(uState.getState())) {
String restartText = "en".equals(uState.getLanguagePreference())
? "\\n\\n(Press 0 to restart the conversation)"
: "\\n\\n(உரையாடலை மீண்டும் தொடங்க 0 ஐ அழுத்தவும்)";
bodyText += restartText;
}"""
content = content.replace(old_method_start, new_method_start)
# 2. Modify sendPaginatedButtons to not append restart text itself
old_paginated = """ private void sendPaginatedButtons(UserState state, String bodyText, String[] ids, String[] titles) {
int totalOptions = ids.length;
String restartText = "en".equals(state.getLanguagePreference())
? "\\n\\n(Send 0 to go back to the main menu / Restart the conversation)"
: "\\n\\n(உரையாடலை மீண்டும் தொடங்க 0 ஐ அழுத்தவும்)";
// Loop and send chunks of up to 3 buttons
for (int i = 0; i < totalOptions; i += 3) {
int end = Math.min(i + 3, totalOptions);
int count = end - i;
String[] chunkIds = new String[count];
String[] chunkTitles = new String[count];
for (int j = 0; j < count; j++) {
chunkIds[j] = ids[i + j];
chunkTitles[j] = titles[i + j];
}
String chunkBodyText = (i == 0) ? bodyText : ( "en".equals(state.getLanguagePreference()) ? "More options:" : "மேலும் விருப்பங்கள்:" );
if (end == totalOptions) {
chunkBodyText += restartText;
}
sendInteractiveButtons(chunkBodyText, chunkIds, chunkTitles);
}
}"""
new_paginated = """ private void sendPaginatedButtons(UserState state, String bodyText, String[] ids, String[] titles) {
int totalOptions = ids.length;
// Loop and send chunks of up to 3 buttons
for (int i = 0; i < totalOptions; i += 3) {
int end = Math.min(i + 3, totalOptions);
int count = end - i;
String[] chunkIds = new String[count];
String[] chunkTitles = new String[count];
for (int j = 0; j < count; j++) {
chunkIds[j] = ids[i + j];
chunkTitles[j] = titles[i + j];
}
String chunkBodyText = (i == 0) ? bodyText : ( "en".equals(state.getLanguagePreference()) ? "More options:" : "மேலும் விருப்பங்கள்:" );
sendInteractiveButtons(chunkBodyText, chunkIds, chunkTitles);
}
}"""
content = content.replace(old_paginated, new_paginated)
# 3. Change processMessage to say "Press 0 to restart the conversation" instead of "(Send 0 to go back to the main menu / Restart the conversation)" for text responses
old_process_text = """ if (response != null && !response.isEmpty() && !response.startsWith("INTERACTIVE:") && !userState.getState().equals("MAIN_MENU") && !userState.getState().equals("LANGUAGE_SELECTION")) {
response += "\\n\\n(Send 0 to go back to the main menu / Restart the conversation)";
}"""
new_process_text = """ if (response != null && !response.isEmpty() && !response.startsWith("INTERACTIVE:") && !userState.getState().equals("MAIN_MENU") && !userState.getState().equals("LANGUAGE_SELECTION")) {
String rt = "en".equals(userState.getLanguagePreference())
? "\\n\\n(Press 0 to restart the conversation)"
: "\\n\\n(உரையாடலை மீண்டும் தொடங்க 0 ஐ அழுத்தவும்)";
response += rt;
}"""
content = content.replace(old_process_text, new_process_text)
with open("src/main/java/com/example/whatsappautomation/service/ChatbotService.java", "w") as f:
f.write(content)
print("Replacement complete.")

38
fix_tamil.py Normal file
View File

@@ -0,0 +1,38 @@
replacements = {
"காய்ச்சல் பரிசோதனைகள்": "காய்ச்சல் பரிசோதனை",
"மூட்டு மற்றும் எலும்பு": "மூட்டு & எலும்பு",
"முழுமையான உடல் பரிசோதனை": "முழு உடல் பரிசோதனை",
"காய்ச்சல் அல்ட்ரா பேனல்": "காய்ச்சல் அல்ட்ரா",
"மலம் பரிசோதனை (Stool Occult Blood)": "மலம் பரிசோதனை",
"அமயா இன்ட் கார்டியாக்": "அமயா கார்டியாக்",
"கார்டியாக் பேக்கேஜ் - அமயா": "அமயா பேக்கேஜ்",
"உயர் இரத்த அழுத்தம் தொகுப்பு": "உயர் ரத்த அழுத்தம்",
"மாஸ்டர் ஹெல்த் பேசிக்": "மாஸ்டர் பேசிக்",
"மாஸ்டர் ஹெல்த் எக்ஸிகியூட்டிவ்": "மாஸ்டர் எக்ஸிகியூட்",
"மாஸ்டர் ஹெல்த் பிரீமியம்": "மாஸ்டர் பிரீமியம்",
"கர்ப்ப காலம் அட்வான்ஸ்டு": "கர்ப்ப அட்வான்ஸ்டு",
"சர்க்கரை கட்டுப்பாடு பேசிக்": "சர்க்கரை பேசிக்",
"சர்க்கரை கட்டுப்பாடு பிளஸ்": "சர்க்கரை பிளஸ்",
"பெண்கள் நல்வாழ்வு பேசிக்": "பெண்கள் பேசிக்",
"பெண்கள் நல்வாழ்வு அட்வான்ஸ்டு": "பெண்கள் அட்வான்ஸ்டு",
"புதிய சந்திப்பை முன்பதிவு செய்ய": "புதிய முன்பதிவு",
"சந்திப்புகளைப் பார்க்க": "சந்திப்புகளை பார்",
"சந்திப்பை மாற்றியமைக்க": "மாற்றியமைக்க",
"சந்திப்பை ரத்து செய்ய": "ரத்து செய்ய",
"முன்பதிவை உறுதிப்படுத்தவும்": "உறுதிப்படுத்துக",
"சந்திப்பை ரத்து செய்யவும்": "ரத்து செய்க",
"கட்டு போடுதல் (Dressing)": "கட்டு போடுதல்",
"வடிகுழாய் (Catheterization)": "வடிகுழாய்",
"வீட்டு பராமரிப்பு தொகுப்புகள்": "வீட்டு பராமரிப்பு"
}
with open('src/main/resources/messages_ta.properties', 'r', encoding='utf-8') as f:
content = f.read()
for old, new in replacements.items():
content = content.replace(f"={old}", f"={new}")
with open('src/main/resources/messages_ta.properties', 'w', encoding='utf-8') as f:
f.write(content)
print("Replaced lengths.")

15
length_check.py Normal file
View File

@@ -0,0 +1,15 @@
import re
# Keys that are used for buttons (options)
button_keys = ['opt', 'btn', 'yes', 'no', 'confirm', 'change', 'cancel']
with open('src/main/resources/messages_ta.properties', 'r', encoding='utf-8') as f:
for line in f:
line = line.strip()
if not line or line.startswith('#'):
continue
if '=' in line:
key, val = line.split('=', 1)
is_button = any(bk in key for bk in button_keys)
if is_button and len(val) > 20:
print(f"{key}={val} ({len(val)} chars)")

56
pom.xml Normal file
View File

@@ -0,0 +1,56 @@
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>3.3.2</version>
<relativePath/>
</parent>
<groupId>com.example</groupId>
<artifactId>whatsapp-automation</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>WhatsApp Automation</name>
<description>Spring Boot application for WhatsApp automation</description>
<properties>
<java.version>21</java.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency>
<dependency>
<groupId>org.postgresql</groupId>
<artifactId>postgresql</artifactId>
<scope>runtime</scope>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>

54
replace.py Normal file
View File

@@ -0,0 +1,54 @@
import re
with open('src/main/java/com/example/whatsappautomation/service/ChatbotService.java', 'r') as f:
content = f.read()
# Pattern to match sendInteractiveList(...) up to the null);
# We need to capture bodyText (arg 1), ids (arg 4), titles (arg 5).
# Using a python script to parse the balanced parentheses might be better.
def replace_calls(text):
start = 0
while True:
idx = text.find('sendInteractiveList(', start)
if idx == -1:
break
# find the end of this statement
end_idx = text.find(');', idx)
if end_idx == -1:
break
call_str = text[idx:end_idx+2]
# We need to extract the parts. It's safer to just split by "new String[]{"
# because we know args 4 and 5 are array allocations.
parts = call_str.split('new String[]{')
if len(parts) == 3:
# parts[0] has 'sendInteractiveList(bodyText, buttonText, sectionTitle, \n'
# parts[1] has 'ids...}, \n'
# parts[2] has 'titles...}, null);'
# Extract bodyText from parts[0]
# sendInteractiveList(arg1, arg2, arg3,
# We want just arg1!
p0 = parts[0][len('sendInteractiveList('):].strip()
# It's tricky to parse commas.
pass
print("Doing regex...")
# Actually, let's just use a simple regex if they are formatted consistently.
# Almost all of them are:
# sendInteractiveList( BODY, BTN, TITLE,
# new String[]{IDS},
# new String[]{TITLES}, null);
pattern = r'sendInteractiveList\((.*?),\s*[^,]+,\s*[^,]+,\s*(new String\[\]\{.*?\})\s*,\s*(new String\[\]\{.*?\})\s*,\s*null\);'
replaced = re.sub(pattern, r'sendPaginatedButtons(state, \1, \2, \3);', content, flags=re.DOTALL)
with open('src/main/java/com/example/whatsappautomation/service/ChatbotService.java', 'w') as f:
f.write(replaced)
print("Done")

View File

@@ -0,0 +1,12 @@
package com.example.whatsappautomation;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
@SpringBootApplication
public class WhatsAppAutomationApplication {
public static void main(String[] args) {
SpringApplication.run(WhatsAppAutomationApplication.class, args);
}
}

View File

@@ -0,0 +1,99 @@
package com.example.whatsappautomation.controller;
import com.example.whatsappautomation.service.ChatbotService;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.http.*;
import org.springframework.web.bind.annotation.*;
@RestController
@RequestMapping("/webhook")
public class WhatsAppWebhookController {
private static final Logger logger = LoggerFactory.getLogger(WhatsAppWebhookController.class);
@Value("${whatsapp.verify-token}")
private String verifyToken;
private final ObjectMapper objectMapper = new ObjectMapper();
private final ChatbotService chatbotService;
public WhatsAppWebhookController(ChatbotService chatbotService) {
this.chatbotService = chatbotService;
}
/**
* Webhook verification endpoint for Meta/WhatsApp.
*/
@GetMapping
public ResponseEntity<String> verifyWebhook(
@RequestParam("hub.mode") String mode,
@RequestParam("hub.challenge") String challenge,
@RequestParam("hub.verify_token") String token) {
logger.info("Received verification request: mode={}, token={}", mode, token);
if ("subscribe".equals(mode) && verifyToken.equals(token)) {
logger.info("Webhook verified successfully!");
return ResponseEntity.ok(challenge);
} else {
logger.warn("Webhook verification failed. Provided token: {}", token);
return ResponseEntity.status(HttpStatus.FORBIDDEN).body("Verification failed");
}
}
/**
* Webhook event handler endpoint.
*/
@PostMapping
public ResponseEntity<String> receive(@RequestBody String body) {
System.out.println("Received:");
System.out.println(body);
try {
JsonNode payload = objectMapper.readTree(body);
if (payload.has("entry")) {
JsonNode entryArray = payload.get("entry");
if (entryArray.isArray() && entryArray.size() > 0) {
JsonNode changesArray = entryArray.get(0).get("changes");
if (changesArray != null && changesArray.isArray() && changesArray.size() > 0) {
JsonNode value = changesArray.get(0).get("value");
if (value != null && value.has("messages")) {
JsonNode messagesArray = value.get("messages");
if (messagesArray.isArray() && messagesArray.size() > 0) {
JsonNode message = messagesArray.get(0);
String from = message.get("from").asText();
// Avoid processing status changes/read receipts as incoming messages
String textBody = null;
if (message.has("text")) {
textBody = message.get("text").get("body").asText();
} else if (message.has("interactive")) {
JsonNode interactive = message.get("interactive");
if (interactive.has("button_reply")) {
textBody = interactive.get("button_reply").get("id").asText();
} else if (interactive.has("list_reply")) {
textBody = interactive.get("list_reply").get("id").asText();
}
}
if (textBody != null) {
String phoneNumberId = value.get("metadata").get("phone_number_id").asText();
// Delegate to Chatbot Service
chatbotService.processMessage(from, textBody, phoneNumberId);
}
}
}
}
}
}
} catch (Exception e) {
logger.error("Error processing webhook payload", e);
}
return ResponseEntity.ok("EVENT_RECEIVED");
}
}

View File

@@ -0,0 +1,44 @@
package com.example.whatsappautomation.entity;
import jakarta.persistence.*;
@Entity
@Table(name = "appointment")
public class Appointment {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String phoneNumber;
private String patientId;
private String appointmentId;
private String department;
private String doctor;
private String appointmentDate;
private String appointmentTime;
private String fee;
private String status;
// Getters and Setters
public Long getId() { return id; }
public void setId(Long id) { this.id = id; }
public String getPhoneNumber() { return phoneNumber; }
public void setPhoneNumber(String phoneNumber) { this.phoneNumber = phoneNumber; }
public String getPatientId() { return patientId; }
public void setPatientId(String patientId) { this.patientId = patientId; }
public String getAppointmentId() { return appointmentId; }
public void setAppointmentId(String appointmentId) { this.appointmentId = appointmentId; }
public String getDepartment() { return department; }
public void setDepartment(String department) { this.department = department; }
public String getDoctor() { return doctor; }
public void setDoctor(String doctor) { this.doctor = doctor; }
public String getAppointmentDate() { return appointmentDate; }
public void setAppointmentDate(String appointmentDate) { this.appointmentDate = appointmentDate; }
public String getAppointmentTime() { return appointmentTime; }
public void setAppointmentTime(String appointmentTime) { this.appointmentTime = appointmentTime; }
public String getFee() { return fee; }
public void setFee(String fee) { this.fee = fee; }
public String getStatus() { return status; }
public void setStatus(String status) { this.status = status; }
}

View File

@@ -0,0 +1,44 @@
package com.example.whatsappautomation.entity;
import jakarta.persistence.*;
@Entity
@Table(name = "service_booking")
public class ServiceBooking {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String phoneNumber;
private String patientId;
private String bookingId;
private String serviceName;
private String price;
private String address;
private String bookingDate;
private String bookingTime;
private String status;
// Getters and Setters
public Long getId() { return id; }
public void setId(Long id) { this.id = id; }
public String getPhoneNumber() { return phoneNumber; }
public void setPhoneNumber(String phoneNumber) { this.phoneNumber = phoneNumber; }
public String getPatientId() { return patientId; }
public void setPatientId(String patientId) { this.patientId = patientId; }
public String getBookingId() { return bookingId; }
public void setBookingId(String bookingId) { this.bookingId = bookingId; }
public String getServiceName() { return serviceName; }
public void setServiceName(String serviceName) { this.serviceName = serviceName; }
public String getPrice() { return price; }
public void setPrice(String price) { this.price = price; }
public String getAddress() { return address; }
public void setAddress(String address) { this.address = address; }
public String getBookingDate() { return bookingDate; }
public void setBookingDate(String bookingDate) { this.bookingDate = bookingDate; }
public String getBookingTime() { return bookingTime; }
public void setBookingTime(String bookingTime) { this.bookingTime = bookingTime; }
public String getStatus() { return status; }
public void setStatus(String status) { this.status = status; }
}

View File

@@ -0,0 +1,47 @@
package com.example.whatsappautomation.entity;
import jakarta.persistence.*;
@Entity
@Table(name = "test_booking")
public class TestBooking {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String phoneNumber;
private String bookingId;
private String packageName;
private String price;
private String collectionType;
private String address;
private String bookingDate;
private String bookingTime;
private String patientId;
private String status;
// Getters and Setters
public Long getId() { return id; }
public void setId(Long id) { this.id = id; }
public String getPhoneNumber() { return phoneNumber; }
public void setPhoneNumber(String phoneNumber) { this.phoneNumber = phoneNumber; }
public String getBookingId() { return bookingId; }
public void setBookingId(String bookingId) { this.bookingId = bookingId; }
public String getPackageName() { return packageName; }
public void setPackageName(String packageName) { this.packageName = packageName; }
public String getPrice() { return price; }
public void setPrice(String price) { this.price = price; }
public String getCollectionType() { return collectionType; }
public void setCollectionType(String collectionType) { this.collectionType = collectionType; }
public String getAddress() { return address; }
public void setAddress(String address) { this.address = address; }
public String getBookingDate() { return bookingDate; }
public void setBookingDate(String bookingDate) { this.bookingDate = bookingDate; }
public String getBookingTime() { return bookingTime; }
public void setBookingTime(String bookingTime) { this.bookingTime = bookingTime; }
public String getPatientId() { return patientId; }
public void setPatientId(String patientId) { this.patientId = patientId; }
public String getStatus() { return status; }
public void setStatus(String status) { this.status = status; }
}

View File

@@ -0,0 +1,162 @@
package com.example.whatsappautomation.entity;
import jakarta.persistence.Entity;
import jakarta.persistence.Id;
import jakarta.persistence.Table;
@Entity
@Table(name = "user_state")
public class UserState {
@Id
private String phoneNumber;
private String patientId;
private String state;
private String category;
private String subCategory;
private String selectedItem;
private String price;
private String address;
private String bookingDate;
private String bookingTime;
private String languagePreference = "en";
private Integer menuPage = 0;
private String lastPhoneNumberId;
private Long lastInteractionTime;
public UserState() {}
public UserState(String phoneNumber) {
this.phoneNumber = phoneNumber;
this.state = "LANGUAGE_SELECTION";
this.patientId = "PAT-" + (100000 + new java.util.Random().nextInt(900000));
}
public void reset() {
this.state = "LANGUAGE_SELECTION";
this.category = null;
this.subCategory = null;
this.selectedItem = null;
this.price = null;
this.address = null;
this.bookingDate = null;
this.bookingTime = null;
this.menuPage = 0;
}
public String getPhoneNumber() {
return phoneNumber;
}
public void setPhoneNumber(String phoneNumber) {
this.phoneNumber = phoneNumber;
}
public String getState() {
return state;
}
public void setState(String state) {
if (!state.equals(this.state)) {
this.menuPage = 0;
}
this.state = state;
}
public String getCategory() {
return category;
}
public void setCategory(String category) {
this.category = category;
}
public String getSubCategory() {
return subCategory;
}
public void setSubCategory(String subCategory) {
this.subCategory = subCategory;
}
public String getSelectedItem() {
return selectedItem;
}
public void setSelectedItem(String selectedItem) {
this.selectedItem = selectedItem;
}
public String getPrice() {
return price;
}
public void setPrice(String price) {
this.price = price;
}
public String getAddress() {
return address;
}
public void setAddress(String address) {
this.address = address;
}
public String getBookingDate() {
return bookingDate;
}
public void setBookingDate(String bookingDate) {
this.bookingDate = bookingDate;
}
public String getBookingTime() {
return bookingTime;
}
public void setBookingTime(String bookingTime) {
this.bookingTime = bookingTime;
}
public String getPatientId() {
return patientId;
}
public void setPatientId(String patientId) {
this.patientId = patientId;
}
public String getLanguagePreference() {
return languagePreference;
}
public void setLanguagePreference(String languagePreference) {
this.languagePreference = languagePreference;
}
public Integer getMenuPage() {
return menuPage;
}
public void setMenuPage(Integer menuPage) {
this.menuPage = menuPage;
}
public String getLastPhoneNumberId() {
return lastPhoneNumberId;
}
public void setLastPhoneNumberId(String lastPhoneNumberId) {
this.lastPhoneNumberId = lastPhoneNumberId;
}
public Long getLastInteractionTime() {
return lastInteractionTime;
}
public void setLastInteractionTime(Long lastInteractionTime) {
this.lastInteractionTime = lastInteractionTime;
}
}

View File

@@ -0,0 +1,14 @@
package com.example.whatsappautomation.repository;
import com.example.whatsappautomation.entity.Appointment;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.stereotype.Repository;
import java.util.List;
import java.util.Optional;
@Repository
public interface AppointmentRepository extends JpaRepository<Appointment, Long> {
List<Appointment> findByPhoneNumber(String phoneNumber);
Optional<Appointment> findByAppointmentId(String appointmentId);
}

View File

@@ -0,0 +1,9 @@
package com.example.whatsappautomation.repository;
import com.example.whatsappautomation.entity.ServiceBooking;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.stereotype.Repository;
@Repository
public interface ServiceBookingRepository extends JpaRepository<ServiceBooking, Long> {
}

View File

@@ -0,0 +1,9 @@
package com.example.whatsappautomation.repository;
import com.example.whatsappautomation.entity.TestBooking;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.stereotype.Repository;
@Repository
public interface TestBookingRepository extends JpaRepository<TestBooking, Long> {
}

View File

@@ -0,0 +1,9 @@
package com.example.whatsappautomation.repository;
import com.example.whatsappautomation.entity.UserState;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.stereotype.Repository;
@Repository
public interface UserStateRepository extends JpaRepository<UserState, String> {
}

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,19 @@
spring.application.name=whatsapp-automation
server.port=8085
whatsapp.access-token=EAAOZCONMSXCkBSQhQ1hh9oJ7J9kYvZBvZAQjz3SH0KKbZC709ES9afccFJ95uCh8TuWVgxU6cFjZALkTSfGBy15FU1uwdWDimSMUytSmW7NzSLQ9RCq6RgONcIbKuNRioYwO2uPJu8RqhmoLeyztqzzBpGupN4jn9yOkvCXClL0IvfFdFwyvTb4zl3uR0ZB95bQ6bpFDvH0ZAM8fwYirdM6hgIjhoUitKkGRU6ONbADCD5HvZAUSZB5idETz0C3WR8LYMZBZAbZCrwZA2Iw8L2BcDTIEI
whatsapp.verify-token=my_whatsapp_verify_token_123
spring.datasource.url=jdbc:postgresql://localhost:5432/rmc_bot
spring.datasource.username=postgres
spring.datasource.password=arjun@132006
spring.jpa.hibernate.ddl-auto=update
spring.jpa.properties.hibernate.dialect=org.hibernate.dialect.PostgreSQLDialect
spring.jpa.show-sql=true
conversation.inactivity.timeout=1200
chatbot.new-workflow.enabled=true
spring.messages.encoding=UTF-8

View File

@@ -0,0 +1,92 @@
welcome.body=👋 Welcome to Rajalakshmi Medical College! I'm your virtual healthcare assistant. Please choose one of the following options:
menu.btn.services=My Services
menu.btn.tests=My Tests
menu.btn.booking=Book Appointment
menu.services=🩺 My Services\n\nPlease choose a category:\n1️⃣ Nursing Services\n2️⃣ Attender Services\n3️⃣ Physiotherapy\n4️⃣ Medical Procedures\n5️⃣ Digital Consultation\n6️⃣ Home Care Packages\n\nReply with the option number.
menu.tests=🏥 Rajalakshmi Medical College\nWelcome to Laboratory Services.\n\nPlease choose a Health Package Category.\n1️⃣ Fever Panel Tests\n2️⃣ Heart Health\n3️⃣ Joint & Bone\n4️⃣ Master Health Check Plans\n5️⃣ Pregnancy\n6️⃣ Sugar Control Series\n7️⃣ Women's Wellness Plans\n8️⃣ Search Test\n\nReply with the option number.
menu.doctor=👨‍⚕️ Doctor Appointment\nPlease choose an option\n1️⃣ Book New Appointment\n2️⃣ View My Appointments\n3️⃣ Reschedule Appointment\n4️⃣ Cancel Appointment\n\nReply with the option number.
invalid.option=Invalid option. Please try again.
new.menu.title=How may I help you today?
new.menu.opt1=🚨 Emergency Contact
new.menu.opt2=👨‍⚕️ Doctor Appointment
new.menu.opt3=🏥 Health Packages
new.menu.opt4=🔬 Book Lab Test
new.menu.opt5=💼 Job Enquiry
new.menu.opt6=📞 Assistance
new.menu.btn=Select Option
new.menu.list=Main Menu
new.emergency.text=For emergency assistance, please contact our emergency department immediately.\n📞 Emergency Contact: +91 44 2715 2000
new.btn.main_menu=Main Menu
new.btn.exit=Exit
new.btn.back=⬅️ Back
new.exit.message=Thank you for contacting Rajalakshmi Medical College. Have a healthy day! 👋
new.assistance.text=Certainly. For assistance, you can contact our reception team.\n📞 Reception Contact Number: +91 44 2715 2000
new.job.ask_phone=Please reply with your 10-digit Contact Number.
new.job.invalid_phone=Invalid phone number. Please enter a 10-digit number.
new.job.ask_email=Thank you. Now please reply with your Email ID.
new.job.invalid_email=Invalid email format. Please enter a valid Email ID.
new.job.success=Thank you. Your details have been received successfully.\nOur recruitment team will contact you regarding suitable opportunities.\n📞 HR Contact: +91 44 2715 2000 (hr@rmchospital.com)
new.doc.dept.title=Please select a speciality.
new.doc.dept.btn=View Specialities
new.doc.dept.list=Specialities
new.doc.sel.title=Please select a Doctor.
new.doc.sel.btn=View Doctors
new.doc.sel.list=Doctors
new.doc.ask_date=Please enter your preferred visit date in DD/MM/YYYY format.
new.doc.invalid_date=Invalid date format or date is invalid. Please reply with a valid date in DD/MM/YYYY format.
new.doc.time.title=Please choose an available time slot.
new.doc.time.btn=View Time Slots
new.doc.time.list=Time Slots
new.doc.summary=👨‍⚕️ Doctor: {0}\n🏥 Speciality: {1}\n📅 Date: {2}\n⏰ Time: {3}
new.doc.confirm_btn=Confirm Appointment
new.doc.cancel_btn=Cancel
new.doc.success=✅ Your appointment has been successfully booked.\n\nDoctor: {0}\nSpeciality: {1}\nDate: {2}\nTime: {3}\nAppointment ID: {4}
new.pkg.title=Please select a Healthcare Package.
new.pkg.btn=View Packages
new.pkg.list=Packages
new.pkg.opt1=Gen Health (₹1500)
new.pkg.opt2=Cardiac Pkg (₹3000)
new.pkg.opt3=Maternity (₹2500)
new.pkg.ask_coupon=Do you have a coupon code? Please type the code below, or click 'Skip Coupon' to proceed.
new.pkg.btn_skip=Skip Coupon
new.pkg.invalid_coupon=Invalid coupon code. Please try again or click 'Skip Coupon'.
new.pkg.ask_date=Please enter your preferred date in DD/MM/YYYY format.
new.pkg.ask_time=Please select a preferred time slot.
new.pkg.confirm_title=Please confirm your booking details:
new.pkg.summary=📦 Package: {0}\n📅 Date: {1}\n⏰ Time: {2}\n💵 Rate: {3} (Coupon: {4})
new.pkg.success=✅ Healthcare package scheduled successfully.\n\nPackage: {0}\nDate: {1}\nTime: {2}\nFinal Rate: {3}\nBooking ID: {4}
new.inv.title=Please choose the investigation type.
new.inv.btn=Choose Type
new.inv.list=Types
new.inv.opt1=🩸 Blood / Lab
new.inv.opt2=🩻 Radiology
new.blood.title=Please select a blood test.
new.blood.btn=View Tests
new.blood.list=Tests
new.blood.opt1=CBC Test (₹300)
new.blood.opt2=LFT Test (₹500)
new.blood.opt3=Lipid Profile (₹450)
new.blood.opt4=HbA1c Test (₹350)
new.blood.confirm=You have selected:\n🧪 {0}\nRate: {1}\nWould you like to confirm?
new.blood.btn_confirm=Confirm
new.blood.btn_cancel=Cancel
new.radio.title=Please select a Radiology service.
new.radio.btn=View Services
new.radio.list=Services
new.radio.opt1=X-Ray (₹500)
new.radio.opt2=CT Scan (₹2500)
new.radio.opt3=MRI (₹5000)
new.radio.opt4=Ultrasound (₹1200)
new.btn.confirm=Confirm Booking
new.btn.cancel=Cancel
new.booking.success=✅ Booking scheduled successfully.\n\nService: {0}\nDate: {1}\nTime: {2}\nRate: {3}\nBooking ID: {4}

View File

@@ -0,0 +1,293 @@
welcome.body=👋 Welcome to Rajalakshmi Medical College! I'm your virtual healthcare assistant. Please choose one of the following options:
menu.btn.services=My Services
menu.btn.tests=My Tests
menu.btn.booking=Book Appointment
menu.services=🩺 My Services\n\nPlease choose a category:\n1️⃣ Nursing Services\n2️⃣ Attender Services\n3️⃣ Physiotherapy\n4️⃣ Medical Procedures\n5️⃣ Digital Consultation\n6️⃣ Home Care Packages\n\nReply with the option number.
menu.tests=🏥 Rajalakshmi Medical College\nWelcome to Laboratory Services.\n\nPlease choose a Health Package Category.\n1️⃣ Fever Panel Tests\n2️⃣ Heart Health\n3️⃣ Joint & Bone\n4️⃣ Master Health Check Plans\n5️⃣ Pregnancy\n6️⃣ Sugar Control Series\n7️⃣ Women's Wellness Plans\n8️⃣ Search Test\n\nReply with the option number.
menu.doctor=👨‍⚕️ Doctor Appointment\nPlease choose an option\n1️⃣ Book New Appointment\n2️⃣ View My Appointments\n3️⃣ Reschedule Appointment\n4️⃣ Cancel Appointment\n\nReply with the option number.
invalid.option=Invalid option. Please try again.
# Tests Menu
tests.main.title=🩺 My Tests\n\nPlease choose a category:
tests.main.btn=View Tests
tests.main.list=Test Categories
tests.main.opt1=Fever Panel
tests.main.opt2=Heart Health
tests.main.opt3=Joint & Bone
tests.main.opt4=Master Health
tests.main.opt5=Pregnancy
tests.main.opt6=Sugar Control
tests.main.opt7=Women's Wellness
tests.fever.title=🌡️ Fever Panel Tests
tests.fever.btn=View Tests
tests.fever.list=Tests
tests.fever.opt1=Fever Ultra Panel
tests.fever.opt2=CBC
tests.fever.opt3=IgE Total Serum
tests.fever.opt4=LFT
tests.fever.opt5=Stool Occult Blood
tests.fever.desc1=₹1,999 (11 Tests)
tests.fever.desc2=₹300
tests.fever.desc3=₹400
tests.fever.desc4=₹500
tests.fever.desc5=₹250
tests.heart.title=❤️ Heart Health Packages
tests.heart.btn=View Packages
tests.heart.list=Packages
tests.heart.opt1=Heart Health Plus
tests.heart.opt2=Amaya Int Cardiac
tests.heart.opt3=Cardiac Pkg - Amaya
tests.heart.opt4=Healthy Heart Basic
tests.heart.opt5=Heart Basic
tests.heart.opt6=Heart Master
tests.heart.opt7=Hypertension Comp
tests.joint.title=🦴 Joint & Bone Packages
tests.joint.opt1=Joint Care Plus
tests.joint.opt2=Arthritis Package
tests.master.title=🩺 Master Health Check Plans
tests.master.btn=View Plans
tests.master.list=Plans
tests.master.opt1=Master Health Basic
tests.master.opt2=Master Health Exec
tests.master.opt3=Master Health Prem
tests.preg.title=🤰 Pregnancy Packages
tests.preg.opt1=Pregnancy Basic
tests.preg.opt2=Pregnancy Advanced
tests.sugar.title=🩸 Sugar Control Series
tests.sugar.btn=View Packages
tests.sugar.list=Packages
tests.sugar.opt1=Sugar Control Basic
tests.sugar.opt2=Sugar Control Plus
tests.sugar.opt3=Diabetes Monitoring
tests.sugar.opt4=HbA1c Comprehensive
tests.women.title=👩 Women's Wellness Plans
tests.women.opt1=Wellness Basic
tests.women.opt2=Wellness Advanced
# Doctor Menu
doc.main.title=🩺 Doctor Appointments
doc.main.btn=View Options
doc.main.list=Options
doc.main.opt1=Book New Appointment
doc.main.opt2=View Appointments
doc.main.opt3=Reschedule Appointment
doc.main.opt4=Cancel Appointment
doc.dept.title=Please select a department.
doc.dept.btn=View Departments
doc.dept.list=Departments
doc.dept.opt1=General Medicine
doc.dept.opt2=Cardiology
doc.dept.opt3=Orthopedics
doc.dept.opt4=Neurology
doc.dept.opt5=Pediatrics
doc.dept.opt6=Gynecology
doc.dept.opt7=Dermatology
doc.dept.opt8=ENT
doc.dept.opt9=Ophthalmology
doc.sel.title=Available Doctors
doc.sel.btn=Select Doctor
doc.sel.list=Doctors
doc.date.title=Available Dates
doc.date.btn=Select Date
doc.date.list=Dates
doc.time.title=Available Time Slots
doc.time.btn=Select Time
doc.time.list=Time Slots
doc.summary.title=Appointment Summary
doc.summary.btn=Select Option
doc.summary.list=Options
doc.summary.confirm=Confirm Booking
doc.summary.change=Change Time
doc.summary.cancel=Cancel
doc.reschedule.ask=Please enter your Appointment ID.
doc.cancel.ask=Please enter your Appointment ID.
doc.no_apps=You have no upcoming appointments.
# Confirmations
booking.service.confirm=✅ Booking Confirmed\nService: {0}\nAmount: {1}\n\nPlease share:\n📍 Service Address\nor\n📍 Live Location
booking.ask_address=Please enter your address.
booking.ask_date=Thank you.\nPlease reply with your preferred date.\n\n📅 DD/MM/YYYY
booking.ask_time.title=Please choose the preferred start time.
booking.ask_time.btn=Select Time
booking.ask_time.list=Available Slots
booking.ask_time.opt1=9 AM - 1 PM
booking.ask_time.opt2=1 PM - 5 PM
booking.ask_time.opt3=5 PM - 9 PM
booking.service.success=✅ Service Booked Successfully\nBooking ID:\n{0}\nPatient ID:\n{1}\n\nPlease provide your Patient ID at the hospital billing desk for payment.\n\nOur nursing team will contact you shortly.\nThank you for choosing Rajalakshmi Medical College.
booking.test.confirm_pkg=Selected Package\n\n{0}\nPrice:\n{1}\nWould you like to continue?
booking.test.btn_book=Book Now
booking.test.btn_back=Back
booking.test.ask_coll=How would you like to provide your sample?
booking.test.coll_home=Home Collection
booking.test.coll_visit=Visit Laboratory
booking.test.ask_date=Please reply with your preferred date.\n📅 DD/MM/YYYY
booking.test.ask_time.title=Choose your preferred time slot.
booking.test.ask_time.btn=Select Slot
booking.test.ask_time.list=Time Slots
booking.test.opt1=7 AM - 9 AM
booking.test.opt2=9 AM - 11 AM
booking.test.opt3=11 AM - 1 PM
booking.test.success=✅ Booking Successful\nBooking ID:\n{0}\nPatient ID:\n{1}\n\nPackage:\n{2}\nAmount:\n{3}\n\nPlease provide your Patient ID at the hospital billing desk for payment.\n\nCollection:\n{4}\nTechnician Visit\n{5}\n{6}\n\nThank you for choosing Rajalakshmi Medical College.
# Final Main Menu
menu.final.title=Would you like to go back to the Main Menu?
menu.final.yes=Main Menu
menu.final.no=Exit
doc.booking.success=✅ Appointment Booked Successfully\n\nAppointment ID:\n{0}\n\nPatient ID:\n{1}\n\nPlease provide your Patient ID at the hospital billing desk for payment.\n\nDoctor:\n{2}\n\nDepartment:\n{3}\n\nDate:\n{4}\n\nTime:\n{5}\n\nPlease arrive 15 minutes before your appointment.\nThank you for choosing Rajalakshmi Medical College
doc.view_apps.title=Your Upcoming Appointments
doc.view_apps.item=Appointment ID:\n{0}\nDoctor:\n{1}\nDepartment:\n{2}\nDate:\n{3}\nTime:\n{4}\nStatus:\n✅ {5}
doc.view_apps.btn=Select Option
doc.view_apps.list=Options
doc.view_apps.opt1=Cancel Appointment
doc.view_apps.opt2=Reschedule
doc.view_apps.opt3=Main Menu
doc.invalid_id=Invalid Appointment ID. Please try again.
doc.reschedule.time.title=Available New Time Slots
doc.reschedule.time.btn=Select Time
doc.reschedule.time.list=Time Slots
doc.reschedule.success=✅ Appointment Rescheduled\nDoctor:\n{0}\nNew Time:\n{1}\nDate:\n{2}
error.generic=Error. Please try again.
doc.cancel.confirm_msg=Are you sure you want to cancel?
doc.cancel.yes=Yes
doc.cancel.no=No
doc.cancel.success=✅ Appointment Cancelled Successfully.
pagination.next=Next ➡️
# Services Menu
services.nursing.title=💉 Nursing Services\n\nChoose:
services.nursing.opt1=Male Nurse
services.nursing.opt2=Female Nurse
services.attender.title=🤝 Attender Services
services.attender.opt1=Male Attender
services.attender.opt2=Female Attender
services.physio.title=🏃 Physiotherapy\n\nPrice: ₹700\n\nWould you like to book?
services.physio.yes=Yes
services.physio.no=No
services.medproc.title=🩺 Medical Procedures
services.medproc.btn=View Options
services.medproc.list=Procedures
services.medproc.opt1=IM Injection
services.medproc.opt2=IV Injection
services.medproc.opt3=Nebulization
services.medproc.opt4=Dressing Charges
services.medproc.opt5=Catheterization
services.digital.title=💻 Digital Consultation\n\nConsultation Fee: ₹750\n\nWould you like to schedule?
services.digital.yes=Yes
services.digital.back=Back
services.homecare.title=🏡 Home Care Packages
services.homecare.btn=View Services
services.homecare.list=Services
services.homecare.opt1=Nursing Care
services.homecare.opt2=Attender Care
services.homecare.opt3=Physiotherapy
services.homecare.opt4=Medical Procedures
services.main.title=🩺 My Services\n\nPlease choose a category:
services.main.btn=View Services
services.main.list=Service Categories
services.main.opt1=Nursing Services
services.main.opt2=Attender Services
services.main.opt3=Physiotherapy
services.main.opt4=Medical Procedures
services.main.opt5=Digital Consult
services.main.opt6=Home Care Packages
new.menu.title=How may I help you today?
new.menu.opt1=🚨 Emergency Contact
new.menu.opt2=👨‍⚕️ Doctor Appointment
new.menu.opt3=🏥 Health Packages
new.menu.opt4=🔬 Book Lab Test
new.menu.opt5=💼 Job Enquiry
new.menu.opt6=📞 Assistance
new.menu.btn=Select Option
new.menu.list=Main Menu
new.emergency.text=For emergency assistance, please contact our emergency department immediately.\n📞 Emergency Contact: +91 44 2715 2000
new.btn.main_menu=Main Menu
new.btn.exit=Exit
new.btn.back=⬅️ Back
new.exit.message=Thank you for contacting Rajalakshmi Medical College. Have a healthy day! 👋
new.assistance.text=Certainly. For assistance, you can contact our reception team.\n📞 Reception Contact Number: +91 44 2715 2000
new.job.ask_phone=Please reply with your 10-digit Contact Number.
new.job.invalid_phone=Invalid phone number. Please enter a 10-digit number.
new.job.ask_email=Thank you. Now please reply with your Email ID.
new.job.invalid_email=Invalid email format. Please enter a valid Email ID.
new.job.success=Thank you. Your details have been received successfully.\nOur recruitment team will contact you regarding suitable opportunities.\n📞 HR Contact: +91 44 2715 2000 (hr@rmchospital.com)
new.doc.dept.title=Please select a speciality.
new.doc.dept.btn=View Specialities
new.doc.dept.list=Specialities
new.doc.sel.title=Please select a Doctor.
new.doc.sel.btn=View Doctors
new.doc.sel.list=Doctors
new.doc.ask_date=Please enter your preferred visit date in DD/MM/YYYY format.
new.doc.invalid_date=Invalid date format or date is invalid. Please reply with a valid date in DD/MM/YYYY format.
new.doc.time.title=Please choose an available time slot.
new.doc.time.btn=View Time Slots
new.doc.time.list=Time Slots
new.doc.summary=👨‍⚕️ Doctor: {0}\n🏥 Speciality: {1}\n📅 Date: {2}\n⏰ Time: {3}
new.doc.confirm_btn=Confirm Appointment
new.doc.cancel_btn=Cancel
new.doc.success=✅ Your appointment has been successfully booked.\n\nDoctor: {0}\nSpeciality: {1}\nDate: {2}\nTime: {3}\nAppointment ID: {4}
new.pkg.title=Please select a Healthcare Package.
new.pkg.btn=View Packages
new.pkg.list=Packages
new.pkg.opt1=Gen Health (₹1500)
new.pkg.opt2=Cardiac Pkg (₹3000)
new.pkg.opt3=Maternity (₹2500)
new.pkg.ask_coupon=Do you have a coupon code? Please type the code below, or click 'Skip Coupon' to proceed.
new.pkg.btn_skip=Skip Coupon
new.pkg.invalid_coupon=Invalid coupon code. Please try again or click 'Skip Coupon'.
new.pkg.ask_date=Please enter your preferred date in DD/MM/YYYY format.
new.pkg.ask_time=Please select a preferred time slot.
new.pkg.confirm_title=Please confirm your booking details:
new.pkg.summary=📦 Package: {0}\n📅 Date: {1}\n⏰ Time: {2}\n💵 Rate: {3} (Coupon: {4})
new.pkg.success=✅ Healthcare package scheduled successfully.\n\nPackage: {0}\nDate: {1}\nTime: {2}\nFinal Rate: {3}\nBooking ID: {4}
new.inv.title=Please choose the investigation type.
new.inv.btn=Choose Type
new.inv.list=Types
new.inv.opt1=🩸 Blood / Lab
new.inv.opt2=🩻 Radiology
new.blood.title=Please select a blood test.
new.blood.btn=View Tests
new.blood.list=Tests
new.blood.opt1=CBC Test (₹300)
new.blood.opt2=LFT Test (₹500)
new.blood.opt3=Lipid Profile (₹450)
new.blood.opt4=HbA1c Test (₹350)
new.blood.confirm=You have selected:\n🧪 {0}\nRate: {1}\nWould you like to confirm?
new.blood.btn_confirm=Confirm
new.blood.btn_cancel=Cancel
new.radio.title=Please select a Radiology service.
new.radio.btn=View Services
new.radio.list=Services
new.radio.opt1=X-Ray (₹500)
new.radio.opt2=CT Scan (₹2500)
new.radio.opt3=MRI (₹5000)
new.radio.opt4=Ultrasound (₹1200)
new.btn.confirm=Confirm Booking
new.btn.cancel=Cancel
new.booking.success=✅ Booking scheduled successfully.\n\nService: {0}\nDate: {1}\nTime: {2}\nRate: {3}\nBooking ID: {4}

View File

@@ -0,0 +1,110 @@
welcome.body=आरएमसी अस्पताल में आपका स्वागत है! कृपया अपनी पसंदीदा भाषा चुनें।
menu.btn.services=मेरी सेवाएं
menu.btn.tests=मेरे परीक्षण
menu.btn.booking=अपॉइंटमेंट बुक करें
invalid.option=अमान्य विकल्प। कृपया पुनः प्रयास करें।
new.menu.title=आज मैं आपकी क्या सहायता कर सकता हूँ?
new.menu.opt1=🚨 आपातकालीन संपर्क
new.menu.opt2=👨‍⚕️ डॉक्टर अपॉइंटमेंट
new.menu.opt3=🏥 स्वास्थ्य पैकेज
new.menu.opt4=🔬 जांच अपॉइंटमेंट
new.menu.opt5=💼 नौकरी पूछताछ
new.menu.opt6=📞 सहायता
new.menu.btn=विकल्प चुनें
new.menu.list=मुख्य सूची
new.emergency.text=आपातकालीन सहायता के लिए, कृपया तुरंत हमारे आपातकालीन विभाग से संपर्क करें।\n📞 आपातकालीन संपर्क: +91 44 2715 2000
new.btn.main_menu=मुख्य सूची
new.btn.exit=बाहर निकलें
new.btn.back=⬅️ वापस
new.exit.message=राजलक्ष्मी मेडिकल कॉलेज से संपर्क करने के लिए धन्यवाद। आपका दिन स्वस्थ रहे! 👋
new.assistance.text=बिल्कुल। सहायता के लिए, आप हमारी रिसेप्शन टीम से संपर्क कर सकते हैं।\n📞 रिसेप्शन संपर्क नंबर: +91 44 2715 2000
new.job.ask_phone=कृपया अपना 10 अंकों का संपर्क नंबर दर्ज करें।
new.job.invalid_phone=अमान्य फ़ोन नंबर। कृपया 10 अंकों का नंबर दर्ज करें।
new.job.ask_email=धन्यवाद। अब कृपया अपना ईमेल आईडी दर्ज करें।
new.job.invalid_email=अमान्य ईमेल प्रारूप। कृपया एक मान्य ईमेल आईडी दर्ज करें।
new.job.success=धन्यवाद। आपके विवरण सफलतापूर्वक प्राप्त हो गए हैं।\nहमारी भर्ती टीम उपयुक्त अवसरों के संबंध में आपसे संपर्क करेगी।\n📞 मानव संसाधन (HR) संपर्क: +91 44 2715 2000 (hr@rmchospital.com)
new.doc.dept.title=कृपया एक विशेषता चुनें।
new.doc.dept.btn=विशेषताएं देखें
new.doc.dept.list=विशेषताएं
new.doc.sel.title=कृपया एक डॉक्टर चुनें।
new.doc.sel.btn=डॉक्टर देखें
new.doc.sel.list=डॉक्टर
new.doc.ask_date=कृपया अपनी पसंदीदा यात्रा तिथि DD/MM/YYYY प्रारूप में दर्ज करें।
new.doc.invalid_date=अमान्य तिथि प्रारूप या तिथि अमान्य है। कृपया DD/MM/YYYY प्रारूप में एक मान्य तिथि दर्ज करें।
new.doc.time.title=कृपया एक उपलब्ध समय स्लॉट चुनें।
new.doc.time.btn=समय स्लॉट देखें
new.doc.time.list=समय स्लॉट
new.doc.summary=👨‍⚕️ डॉक्टर: {0}\n🏥 विभाग: {1}\n📅 तिथि: {2}\n⏰ समय: {3}
new.doc.confirm_btn=अपॉइंटमेंट की पुष्टि करें
new.doc.cancel_btn=रद्द करें
new.doc.success=✅ आपका अपॉइंटमेंट सफलतापूर्वक बुक हो गया है।\n\nडॉक्टर: {0}\nविभाग: {1}\nतिथि: {2}\nसमय: {3}\nअपॉइंटमेंट आईडी: {4}
new.pkg.title=कृपया एक स्वास्थ्य पैकेज चुनें।
new.pkg.btn=पैकेज देखें
new.pkg.list=पैकेज
new.pkg.opt1=सामान्य स्वास्थ्य (₹1500)
new.pkg.opt2=हृदय पैकेज (₹3000)
new.pkg.opt3=मातृत्व पैकेज (₹2500)
new.pkg.ask_coupon=क्या आपके पास कूपन कोड है? कृपया कूपन कोड दर्ज करें, या आगे बढ़ने के लिए 'कूपन छोड़ें' पर क्लिक करें।
new.pkg.btn_skip=कूपन छोड़ें
new.pkg.invalid_coupon=अमान्य कूपन कोड। कृपया पुनः प्रयास करें या 'कूपन छोड़ें' पर क्लिक करें।
new.pkg.ask_date=कृपया पसंदीदा तिथि DD/MM/YYYY प्रारूप में दर्ज करें।
new.pkg.ask_time=कृपया पसंदीदा समय स्लॉट चुनें।
new.pkg.confirm_title=कृपया अपने बुकिंग विवरण की पुष्टि करें:
new.pkg.summary=📦 पैकेज: {0}\n📅 तिथि: {1}\n⏰ समय: {2}\n💵 दर: {3} (कूपन: {4})
new.pkg.success=✅ स्वास्थ्य पैकेज सफलतापूर्वक निर्धारित किया गया है।\n\nपैकेज: {0}\nतिथि: {1}\nसमय: {2}\nअंतिम दर: {3}\nबुकिंग आईडी: {4}
new.inv.title=कृपया जांच का प्रकार चुनें।
new.inv.btn=प्रकार चुनें
new.inv.list=प्रकार
new.inv.opt1=🩸 रक्त / लैब
new.inv.opt2=🩻 रेडियोलॉजी
new.blood.title=कृपया रक्त परीक्षण चुनें।
new.blood.btn=परीक्षण देखें
new.blood.list=परीक्षण
new.blood.opt1=CBC रक्त जांच (₹300)
new.blood.opt2=LFT लीवर जांच (₹500)
new.blood.opt3=लिपिड प्रोफाइल (₹450)
new.blood.opt4=HbA1c शुगर जांच (₹350)
new.blood.confirm=आपने चुना है:\n🧪 {0}\nदर: {1}\nक्या आप पुष्टि करना चाहते हैं?
new.blood.btn_confirm=पुष्टि करें
new.blood.btn_cancel=रद्द करें
new.radio.title=कृपया रेडियोलॉजी सेवा चुनें।
new.radio.btn=सेवाएं देखें
new.radio.list=सेवाएं
new.radio.opt1=एक्स-रे (₹500)
new.radio.opt2=सीटी स्कैन (₹2500)
new.radio.opt3=एमआरआई (₹5000)
new.radio.opt4=अल्ट्रासाउंड (₹1200)
new.btn.confirm=बुकिंग की पुष्टि करें
new.btn.cancel=रद्द करें
new.booking.success=✅ बुकिंग सफलतापूर्वक निर्धारित की गई है।\n\nसेवा: {0}\nतिथि: {1}\nसमय: {2}\nदर: {3}\nबुकिंग आईडी: {4}
doc.dept.title=कृपया एक विभाग चुनें।
doc.dept.btn=विभाग देखें
doc.dept.list=विभाग
doc.dept.opt1=सामान्य चिकित्सा
doc.dept.opt2=हृदय रोग विज्ञान
doc.dept.opt3=हड्डी रोग विज्ञान
doc.dept.opt4=न्यूरोलॉजी
doc.dept.opt5=बाल रोग विज्ञान
doc.dept.opt6=स्त्री रोग विज्ञान
doc.dept.opt7=त्वचा विज्ञान
doc.dept.opt8=ईएनटी
doc.dept.opt9=नेत्र विज्ञान
booking.test.ask_date=कृपया अपनी पसंदीदा तिथि दर्ज करें।\n📅 DD/MM/YYYY
booking.test.ask_time.title=अपना पसंदीदा समय स्लॉट चुनें।
booking.test.ask_time.btn=स्लॉट चुनें
booking.test.ask_time.list=समय स्लॉट
booking.test.opt1=सुबह 7 - 9 बजे
booking.test.opt2=सुबह 9 - 11 बजे
booking.test.opt3=सुबह 11 - दोपहर 1 बजे

View File

@@ -0,0 +1,293 @@
welcome.body=👋 ராஜலட்சுமி மருத்துவக் கல்லூரிக்கு வரவேற்கிறோம்! நான் உங்கள் மெய்நிகர் சுகாதார உதவியாளர். தயவுசெய்து கீழ்க்கண்ட விருப்பங்களில் ஒன்றைத் தேர்ந்தெடுக்கவும்:
menu.btn.services=என் சேவைகள்
menu.btn.tests=பரிசோதனைகள்
menu.btn.booking=முன்பதிவு செய்க
menu.services=🩺 எனது சேவைகள்\n\nதயவுசெய்து ஒரு வகையைத் தேர்ந்தெடுக்கவும்:\n1️⃣ செவிலியர் சேவைகள்\n2️⃣ உதவியாளர் சேவைகள்\n3️⃣ பிசியோதெரபி\n4️⃣ மருத்துவ நடைமுறைகள்\n5️⃣ டிஜிட்டல் ஆலோசனை\n6️⃣ வீட்டு பராமரிப்பு தொகுப்புகள்\n\nவிருப்ப எண்ணுடன் பதிலளிக்கவும்.
menu.tests=🏥 ராஜலட்சுமி மருத்துவக் கல்லூரி\nஆய்வக சேவைகளுக்கு வரவேற்கிறோம்.\n\nசுகாதாரத் தொகுப்பு வகையைத் தேர்ந்தெடுக்கவும்.\n1️⃣ காய்ச்சல் பரிசோதனைகள்\n2️⃣ இதய ஆரோக்கியம்\n3️⃣ மூட்டு மற்றும் எலும்பு\n4️⃣ முழுமையான உடல் பரிசோதனை\n5️⃣ கர்ப்ப காலம்\n6️⃣ சர்க்கரை கட்டுப்பாடு\n7️⃣ பெண்கள் நல்வாழ்வு\n8️⃣ பரிசோதனையைத் தேடுங்கள்\n\nவிருப்ப எண்ணுடன் பதிலளிக்கவும்.
menu.doctor=👨‍⚕️ மருத்துவர் சந்திப்பு\nதயவுசெய்து ஒரு விருப்பத்தைத் தேர்ந்தெடுக்கவும்\n1️⃣ புதிய சந்திப்பை முன்பதிவு செய்ய\n2️⃣ எனது சந்திப்புகளைப் பார்க்க\n3️⃣ சந்திப்பை மாற்றியமைக்க\n4️⃣ சந்திப்பை ரத்து செய்ய\n\nவிருப்ப எண்ணுடன் பதிலளிக்கவும்.
invalid.option=தவறான விருப்பம். தயவுசெய்து மீண்டும் முயற்சிக்கவும்.
# Tests Menu
tests.main.title=🩺 எனது பரிசோதனைகள்\n\nதயவுசெய்து ஒரு வகையைத் தேர்ந்தெடுக்கவும்:
tests.main.btn=பரிசோதனைகளை காண்க
tests.main.list=பரிசோதனை பிரிவுகள்
tests.main.opt1=காய்ச்சல் பரிசோதனை
tests.main.opt2=இதய ஆரோக்கியம்
tests.main.opt3=மூட்டு & எலும்பு
tests.main.opt4=முழு உடல் பரிசோதனை
tests.main.opt5=கர்ப்ப காலம்
tests.main.opt6=சர்க்கரை கட்டுப்பாடு
tests.main.opt7=பெண்கள் நல்வாழ்வு
tests.fever.title=🌡️ காய்ச்சல் பரிசோதனைகள்
tests.fever.btn=பரிசோதனைகளை காண்க
tests.fever.list=பரிசோதனைகள்
tests.fever.opt1=காய்ச்சல் அல்ட்ரா
tests.fever.opt2=CBC
tests.fever.opt3=IgE Total Serum
tests.fever.opt4=LFT
tests.fever.opt5=மலம் பரிசோதனை
tests.fever.desc1=₹1,999 (11 பரிசோதனைகள்)
tests.fever.desc2=₹300
tests.fever.desc3=₹400
tests.fever.desc4=₹500
tests.fever.desc5=₹250
tests.heart.title=❤️ இதய ஆரோக்கிய தொகுப்புகள்
tests.heart.btn=தொகுப்புகளை காண்க
tests.heart.list=தொகுப்புகள்
tests.heart.opt1=ஹார்ட் ஹெல்த் பிளஸ்
tests.heart.opt2=அமயா கார்டியாக்
tests.heart.opt3=அமயா பேக்கேஜ்
tests.heart.opt4=ஹெல்தி ஹார்ட் பேசிக்
tests.heart.opt5=ஹார்ட் பேசிக்
tests.heart.opt6=ஹார்ட் மாஸ்டர்
tests.heart.opt7=உயர் ரத்த அழுத்தம்
tests.joint.title=🦴 மூட்டு மற்றும் எலும்பு தொகுப்புகள்
tests.joint.opt1=ஜாயின்ட் கேர் பிளஸ்
tests.joint.opt2=ஆர்த்ரிடிஸ் பேக்கேஜ்
tests.master.title=🩺 முழுமையான உடல் பரிசோதனை திட்டங்கள்
tests.master.btn=திட்டங்களை காண்க
tests.master.list=திட்டங்கள்
tests.master.opt1=மாஸ்டர் பேசிக்
tests.master.opt2=மாஸ்டர் எக்ஸிகியூட்
tests.master.opt3=மாஸ்டர் பிரீமியம்
tests.preg.title=🤰 கர்ப்ப கால தொகுப்புகள்
tests.preg.opt1=கர்ப்ப காலம் பேசிக்
tests.preg.opt2=கர்ப்ப அட்வான்ஸ்டு
tests.sugar.title=🩸 சர்க்கரை கட்டுப்பாடு தொடர்
tests.sugar.btn=தொகுப்புகளை காண்க
tests.sugar.list=தொகுப்புகள்
tests.sugar.opt1=சர்க்கரை பேசிக்
tests.sugar.opt2=சர்க்கரை பிளஸ்
tests.sugar.opt3=நீரிழிவு கண்காணிப்பு
tests.sugar.opt4=HbA1c விரிவானது
tests.women.title=👩 பெண்கள் நல்வாழ்வு திட்டங்கள்
tests.women.opt1=பெண்கள் பேசிக்
tests.women.opt2=பெண்கள் அட்வான்ஸ்டு
# Doctor Menu
doc.main.title=🩺 மருத்துவர் சந்திப்புகள்
doc.main.btn=விருப்பங்களை காண்க
doc.main.list=விருப்பங்கள்
doc.main.opt1=புதிய முன்பதிவு
doc.main.opt2=சந்திப்புகளை பார்
doc.main.opt3=மாற்றியமைக்க
doc.main.opt4=ரத்து செய்ய
doc.dept.title=தயவுசெய்து ஒரு துறையை தேர்ந்தெடுக்கவும்.
doc.dept.btn=துறைகளை காண்க
doc.dept.list=துறைகள்
doc.dept.opt1=பொது மருத்துவம்
doc.dept.opt2=இருதயவியல்
doc.dept.opt3=எலும்பியல்
doc.dept.opt4=நரம்பியல்
doc.dept.opt5=குழந்தை மருத்துவம்
doc.dept.opt6=மகப்பேறியல்
doc.dept.opt7=தோல் மருத்துவம்
doc.dept.opt8=காது மூக்கு தொண்டை
doc.dept.opt9=கண் மருத்துவம்
doc.sel.title=கிடைக்கும் மருத்துவர்கள்
doc.sel.btn=மருத்துவரை தேர்ந்தெடுங்கள்
doc.sel.list=மருத்துவர்கள்
doc.date.title=கிடைக்கும் தேதிகள்
doc.date.btn=தேதியை தேர்ந்தெடுங்கள்
doc.date.list=தேதிகள்
doc.time.title=கிடைக்கும் நேரங்கள்
doc.time.btn=நேரத்தை தேர்ந்தெடுங்கள்
doc.time.list=நேரங்கள்
doc.summary.title=சந்திப்பு சுருக்கம்
doc.summary.btn=விருப்பத்தை தேர்ந்தெடுங்கள்
doc.summary.list=விருப்பங்கள்
doc.summary.confirm=உறுதிப்படுத்துக
doc.summary.change=நேரத்தை மாற்றவும்
doc.summary.cancel=ரத்து செய்யவும்
doc.reschedule.ask=தயவுசெய்து உங்கள் சந்திப்பு ஐடியை உள்ளிடவும்.
doc.cancel.ask=தயவுசெய்து உங்கள் சந்திப்பு ஐடியை உள்ளிடவும்.
doc.no_apps=உங்களுக்கு வரவிருக்கும் சந்திப்புகள் ஏதும் இல்லை.
# Confirmations
booking.service.confirm=✅ முன்பதிவு உறுதி செய்யப்பட்டது\nசேவை: {0}\nகட்டணம்: {1}\n\nதயவுசெய்து பகிரவும்:\n📍 சேவை முகவரி\nஅல்லது\n📍 நேரலை இருப்பிடம் (Live Location)
booking.ask_address=தயவுசெய்து உங்கள் முகவரியை உள்ளிடவும்.
booking.ask_date=நன்றி.\nஉங்கள் விருப்பமான தேதியை உள்ளிடவும்.\n\n📅 DD/MM/YYYY
booking.ask_time.title=விருப்பமான தொடக்க நேரத்தை தேர்வு செய்யவும்.
booking.ask_time.btn=நேரத்தை தேர்ந்தெடுக்கவும்
booking.ask_time.list=கிடைக்கும் நேரங்கள்
booking.ask_time.opt1=காலை 9 - மதியம் 1
booking.ask_time.opt2=மதியம் 1 - மாலை 5
booking.ask_time.opt3=மாலை 5 - இரவு 9
booking.service.success=✅ சேவை வெற்றிகரமாக முன்பதிவு செய்யப்பட்டது\nமுன்பதிவு எண்:\n{0}\nநோயாளி எண்:\n{1}\n\nபணம் செலுத்த உங்கள் நோயாளி எண்ணை மருத்துவமனை பில்லிங் டெஸ்கில் வழங்கவும்.\n\nஎங்கள் செவிலியர் குழு உங்களை விரைவில் தொடர்புகொள்ளும்.\nராஜலட்சுமி மருத்துவக் கல்லூரியை தேர்ந்தெடுத்ததற்கு நன்றி.
booking.test.confirm_pkg=தேர்ந்தெடுக்கப்பட்ட தொகுப்பு\n\n{0}\nவிலை:\n{1}\nநீங்கள் தொடர விரும்புகிறீர்களா?
booking.test.btn_book=முன்பதிவு செய்
booking.test.btn_back=பின்செல்
booking.test.ask_coll=மாதிரியை எவ்வாறு வழங்க விரும்புகிறீர்கள்?
booking.test.coll_home=வீட்டு மாதிரி சேகரிப்பு
booking.test.coll_visit=ஆய்வகத்திற்கு வருகை
booking.test.ask_date=உங்கள் விருப்பமான தேதியை உள்ளிடவும்.\n📅 DD/MM/YYYY
booking.test.ask_time.title=விருப்பமான நேரத்தை தேர்வு செய்யவும்.
booking.test.ask_time.btn=நேரத்தை தேர்ந்தெடுக்கவும்
booking.test.ask_time.list=நேரங்கள்
booking.test.opt1=காலை 7 - காலை 9
booking.test.opt2=காலை 9 - காலை 11
booking.test.opt3=காலை 11 - மதியம் 1
booking.test.success=✅ முன்பதிவு வெற்றிகரமாக முடிந்தது\nமுன்பதிவு எண்:\n{0}\nநோயாளி எண்:\n{1}\n\nதொகுப்பு:\n{2}\nகட்டணம்:\n{3}\n\nபணம் செலுத்த உங்கள் நோயாளி எண்ணை மருத்துவமனை பில்லிங் டெஸ்கில் வழங்கவும்.\n\nசேகரிப்பு:\n{4}\nதொழில்நுட்ப வல்லுநர் வருகை\n{5}\n{6}\n\nராஜலட்சுமி மருத்துவக் கல்லூரியை தேர்ந்தெடுத்ததற்கு நன்றி.
# Final Main Menu
menu.final.title=நீங்கள் பிரதான மெனுவுக்குச் செல்ல விரும்புகிறீர்களா?
menu.final.yes=பிரதான மெனு
menu.final.no=வெளியேறு
doc.booking.success=✅ சந்திப்பு வெற்றிகரமாக முன்பதிவு செய்யப்பட்டது\n\nசந்திப்பு ஐடி:\n{0}\n\nநோயாளி ஐடி:\n{1}\n\nபணம் செலுத்த உங்கள் நோயாளி எண்ணை மருத்துவமனை பில்லிங் டெஸ்கில் வழங்கவும்.\n\nமருத்துவர்:\n{2}\n\nதுறை:\n{3}\n\nதேதி:\n{4}\n\nநேரம்:\n{5}\n\nஉங்கள் சந்திப்புக்கு 15 நிமிடங்களுக்கு முன்பு வரவும்.\nராஜலட்சுமி மருத்துவக் கல்லூரியை தேர்ந்தெடுத்ததற்கு நன்றி
doc.view_apps.title=உங்கள் வரவிருக்கும் சந்திப்புகள்
doc.view_apps.item=சந்திப்பு ஐடி:\n{0}\nமருத்துவர்:\n{1}\nதுறை:\n{2}\nதேதி:\n{3}\nநேரம்:\n{4}\nநிலை:\n✅ {5}
doc.view_apps.btn=விருப்பத்தை தேர்ந்தெடுக்கவும்
doc.view_apps.list=விருப்பங்கள்
doc.view_apps.opt1=ரத்து செய்யவும்
doc.view_apps.opt2=மாற்றியமைக்க
doc.view_apps.opt3=பிரதான மெனு
doc.invalid_id=தவறான சந்திப்பு ஐடி. தயவுசெய்து மீண்டும் முயற்சிக்கவும்.
doc.reschedule.time.title=கிடைக்கும் புதிய நேரங்கள்
doc.reschedule.time.btn=நேரத்தை தேர்ந்தெடுக்கவும்
doc.reschedule.time.list=நேரங்கள்
doc.reschedule.success=✅ சந்திப்பு மாற்றியமைக்கப்பட்டது\nமருத்துவர்:\n{0}\nபுதிய நேரம்:\n{1}\nதேதி:\n{2}
error.generic=பிழை. தயவுசெய்து மீண்டும் முயற்சிக்கவும்.
doc.cancel.confirm_msg=நிச்சயமாக ரத்து செய்ய விரும்புகிறீர்களா?
doc.cancel.yes=ஆம்
doc.cancel.no=இல்லை
doc.cancel.success=✅ சந்திப்பு வெற்றிகரமாக ரத்து செய்யப்பட்டது.
pagination.next=அடுத்து ➡️
# Services Menu
services.nursing.title=💉 செவிலியர் சேவைகள்\n\nதேர்வு செய்க:
services.nursing.opt1=ஆண் செவிலியர்
services.nursing.opt2=பெண் செவிலியர்
services.attender.title=🤝 உதவியாளர் சேவைகள்
services.attender.opt1=ஆண் உதவியாளர்
services.attender.opt2=பெண் உதவியாளர்
services.physio.title=🏃 பிசியோதெரபி\n\nவிலை: ₹700\n\nநீங்கள் முன்பதிவு செய்ய விரும்புகிறீர்களா?
services.physio.yes=ஆம்
services.physio.no=இல்லை
services.medproc.title=🩺 மருத்துவ நடைமுறைகள்
services.medproc.btn=விருப்பங்களை காண்க
services.medproc.list=நடைமுறைகள்
services.medproc.opt1=IM ஊசி
services.medproc.opt2=IV ஊசி
services.medproc.opt3=நெபுலைசேஷன்
services.medproc.opt4=கட்டு போடுதல்
services.medproc.opt5=வடிகுழாய்
services.digital.title=💻 டிஜிட்டல் ஆலோசனை\n\nஆலோசனை கட்டணம்: ₹750\n\nநீங்கள் முன்பதிவு செய்ய விரும்புகிறீர்களா?
services.digital.yes=ஆம்
services.digital.back=பின்செல்
services.homecare.title=🏡 வீட்டு பராமரிப்பு தொகுப்புகள்
services.homecare.btn=சேவைகளை காண்க
services.homecare.list=சேவைகள்
services.homecare.opt1=செவிலியர் பராமரிப்பு
services.homecare.opt2=உதவியாளர் பராமரிப்பு
services.homecare.opt3=பிசியோதெரபி
services.homecare.opt4=மருத்துவ நடைமுறைகள்
services.main.title=🩺 எனது சேவைகள்\n\nதயவுசெய்து ஒரு வகையைத் தேர்ந்தெடுக்கவும்:
services.main.btn=சேவைகளை காண்க
services.main.list=சேவை பிரிவுகள்
services.main.opt1=செவிலியர் சேவைகள்
services.main.opt2=உதவியாளர் சேவைகள்
services.main.opt3=பிசியோதெரபி
services.main.opt4=மருத்துவ நடைமுறைகள்
services.main.opt5=டிஜிட்டல் ஆலோசனை
services.main.opt6=வீட்டு பராமரிப்பு
new.menu.title=இன்று நான் உங்களுக்கு எவ்வாறு உதவ முடியும்?
new.menu.opt1=🚨 அவசர தொடர்பு எண்
new.menu.opt2=👨‍⚕️ மருத்துவர் சந்திப்பு
new.menu.opt3=🏥 சுகாதார தொகுப்புகள்
new.menu.opt4=🔬 பரிசோதனை முன்பதிவு
new.menu.opt5=💼 வேலை வாய்ப்பு
new.menu.opt6=📞 உதவி
new.menu.btn=விருப்பத்தைத் தேர்ந்தெடுக்கவும்
new.menu.list=முதன்மை மெனு
new.emergency.text=அவசर உதவிக்கு, உடனடியாக எங்கள் அவசர சிகிச்சை பிரிவைத் தொடர்பு கொள்ளவும்.\n📞 அவசர தொடர்பு எண்: +91 44 2715 2000
new.btn.main_menu=முதன்மை மெனு
new.btn.exit=வெளியேறு
new.btn.back=⬅️ பின்னால்
new.exit.message=ராஜலட்சுமி மருத்துவக் கல்லூரியைத் தொடர்பு கொண்டதற்கு நன்றி. ஆரோக்கியமான நாளாக அமையட்டும்! 👋
new.assistance.text=நிச்சயமாக. உதவிக்கு, நீங்கள் எங்கள் வரவேற்புக் குழுவைத் தொடர்பு கொள்ளலாம்.\n📞 வரவேற்பு தொடர்பு எண்: +91 44 2715 2000
new.job.ask_phone=தயவுசெய்து உங்கள் 10 இலக்க தொடர்பு எண்ணை உள்ளிடவும்.
new.job.invalid_phone=தவறான தொலைபேசி எண். தயவுசெய்து 10 இலக்க எண்ணை உள்ளிடவும்.
new.job.ask_email=நன்றி. இப்போது தயவுசெய்து உங்கள் மின்னஞ்சல் ஐடியை உள்ளிடவும்.
new.job.invalid_email=தவறான மின்னஞ்சல் வடிவம். தயவுசெய்து சரியான மின்னஞ்சல் ஐடியை உள்ளிடவும்.
new.job.success=நன்றி. உங்கள் विवरणங்கள் வெற்றிகரமாகப் பெறப்பட்டன.\nஎங்கள் வேலைவாய்ப்பு குழு பொருத்தமான வாய்ப்புகள் குறித்து உங்களைத் தொடர்பு கொள்ளும்.\n📞 மனிதவள (HR) தொடர்பு எண்: +91 44 2715 2000 (hr@rmchospital.com)
new.doc.dept.title=தயவுசெய்து ஒரு சிறப்புத் துறையைத் தேர்ந்தெடுக்கவும்.
new.doc.dept.btn=சிறப்புத் துறைகளைக் காண்க
new.doc.dept.list=சிறப்புத் துறைகள்
new.doc.sel.title=தயவுசெய்து ஒரு மருத்துவரைத் தேர்ந்தெடுக்கவும்.
new.doc.sel.btn=மருத்துவர்களைக் காண்க
new.doc.sel.list=மருத்துவர்கள்
new.doc.ask_date=தயவுசெய்து நீங்கள் வர விரும்பும் தேதியை DD/MM/YYYY வடிவத்தில் உள்ளிடவும்.
new.doc.invalid_date=தவறான தேதி வடிவம் அல்லது தேதி செல்லாதது. தயவுசெய்து DD/MM/YYYY வடிவத்தில் சரியான தேதியை உள்ளிடவும்.
new.doc.time.title=தயவுசெய்து கிடைக்கக்கூடிய நேரத்தைத் தேர்ந்தெடுக்கவும்.
new.doc.time.btn=நேरங்களைக் காண்க
new.doc.time.list=நேரங்கள்
new.doc.summary=👨‍⚕️ மருத்துவர்: {0}\n🏥 சிறப்புத் துறை: {1}\n📅 தேதி: {2}\n⏰ நேரம்: {3}
new.doc.confirm_btn=முன்பதிவை உறுதிசெய்
new.doc.cancel_btn=ரத்துசெய்
new.doc.success=✅ உங்கள் மருத்துவ சந்திப்பு வெற்றிகரமாக முன்பதிவு செய்யப்பட்டது.\n\nமருத்துவர்: {0}\nசிறப்புத் துறை: {1}\nதேதி: {2}\nநேரம்: {3}\nசந்திப்பு ஐடி: {4}
new.pkg.title=தயவுசெய்து ஒரு சுகாதார தொகுப்பைத் தேர்ந்தெடுக்கவும்.
new.pkg.btn=தொகுப்புகளைக் காண்க
new.pkg.list=தொகுப்புகள்
new.pkg.opt1=பொது பரிசோதனை (₹1500)
new.pkg.opt2=இதய தொகுப்பு (₹3000)
new.pkg.opt3=மகப்பேறு (₹2500)
new.pkg.ask_coupon=உங்களிடம் ஏதேனும் கூப்பன் குறியீடு உள்ளதா? கூப்பன் குறியீட்டை உள்ளிடவும் அல்லது தொடர 'கூப்பனைத் தவிர்' என்பதைத் தேர்ந்தெடுக்கவும்.
new.pkg.btn_skip=கூப்பனைத் தவிர்
new.pkg.invalid_coupon=தவறான கூப்பன் குறியீடு. தயவுசெய்து மீண்டும் முயற்சிக்கவும் அல்லது 'கூப்பனைத் தவிர்' என்பதைத் தேர்ந்தெடுக்கவும்.
new.pkg.ask_date=தயவுசெய்து உங்களுக்கு விருப்பமான தேதியை DD/MM/YYYY வடிவத்தில் உள்ளிடவும்.
new.pkg.ask_time=தயவுசெய்து உங்களுக்கு விருப்பமான நேரத்தைத் தேர்ந்தெடுக்கவும்.
new.pkg.confirm_title=முன்பதிவு விவரங்களை உறுதிப்படுத்தவும்:
new.pkg.summary=📦 தொகுப்பு: {0}\n📅 தேதி: {1}\n⏰ நேரம்: {2}\n💵 கட்டணம்: {3} (கூப்பன்: {4})
new.pkg.success=✅ சுகாதார தொகுப்பு வெற்றிகரமாக திட்டமிடப்பட்டது.\n\nதொகுப்பு: {0}\nதேதி: {1}\nநேரம்: {2}\nஇறுதி கட்டணம்: {3}\nமுன்பதிவு ஐடி: {4}
new.inv.title=தயவுசெய்து பரிசோதனை வகையைத் தேர்ந்தெடுக்கவும்.
new.inv.btn=வகையைத் தேர்ந்தெடுக்கவும்
new.inv.list=பரிசோதனை வகைகள்
new.inv.opt1=🩸 இரத்தம் / ஆய்வகம்
new.inv.opt2=🩻 ரேடியாலजी
new.blood.title=தயவுசெய்து இரத்தப் பரிசோதனையைத் தேர்ந்தெடுக்கவும்.
new.blood.btn=பரிசோதனைகளைக் காண்க
new.blood.list=இரத்தப் பரிசோதனைகள்
new.blood.opt1=CBC ரத்தத் தேர்வு (₹300)
new.blood.opt2=LFT கல்லீரல் தேர்வு (₹500)
new.blood.opt3=லிப்பிட் தேர்வு (₹450)
new.blood.opt4=HbA1c சர்க்கரை (₹350)
new.blood.confirm=நீங்கள் தேர்ந்தெடுத்துள்ளீர்கள்:\n🧪 {0}\nகட்டணம்: {1}\nஉறுதிப்படுத்த விரும்புகிறீர்களா?
new.blood.btn_confirm=உறுதிசெய்
new.blood.btn_cancel=ரத்துசெய்
new.radio.title=தயவுசெய்து ரேடியாலஜி சேவையைத் தேர்ந்தெடுக்கவும்.
new.radio.btn=சேவைகளைக் காண்க
new.radio.list=சேவைகள்
new.radio.opt1=எக்ஸ்-ரே (₹500)
new.radio.opt2=சிடி ஸ்கேன் (₹2500)
new.radio.opt3=எம்ஆர்ஐ (₹5000)
new.radio.opt4=அல்ட்ராசவுண்ட் (₹1200)
new.btn.confirm=முன்பதிவை உறுதிசெய்
new.btn.cancel=ரத்துசெய்
new.booking.success=✅ முன்பதிவு வெற்றிகரமாக திட்டமிடப்பட்டது.\n\nசேவை: {0}\nதேதி: {1}\nநேரம்: {2}\nகட்டணம்: {3}\nமுன்பதிவு ஐடி: {4}

View File

@@ -0,0 +1,220 @@
package com.example.whatsappautomation;
import com.example.whatsappautomation.entity.UserState;
import com.example.whatsappautomation.repository.AppointmentRepository;
import com.example.whatsappautomation.repository.ServiceBookingRepository;
import com.example.whatsappautomation.repository.TestBookingRepository;
import com.example.whatsappautomation.repository.UserStateRepository;
import com.example.whatsappautomation.service.ChatbotService;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.mockito.ArgumentCaptor;
import org.mockito.Mockito;
import org.springframework.context.MessageSource;
import org.springframework.http.HttpEntity;
import org.springframework.http.ResponseEntity;
import org.springframework.test.util.ReflectionTestUtils;
import org.springframework.web.client.RestTemplate;
import java.util.Optional;
import static org.junit.jupiter.api.Assertions.*;
import static org.mockito.ArgumentMatchers.*;
import static org.mockito.Mockito.*;
class InactivityTimeoutTests {
private UserStateRepository userStateRepository;
private ServiceBookingRepository serviceBookingRepository;
private TestBookingRepository testBookingRepository;
private AppointmentRepository appointmentRepository;
private MessageSource messageSource;
private RestTemplate restTemplate;
private ChatbotService chatbotService;
@BeforeEach
void setUp() {
userStateRepository = mock(UserStateRepository.class);
serviceBookingRepository = mock(ServiceBookingRepository.class);
testBookingRepository = mock(TestBookingRepository.class);
appointmentRepository = mock(AppointmentRepository.class);
messageSource = mock(MessageSource.class);
restTemplate = mock(RestTemplate.class);
chatbotService = new ChatbotService(
userStateRepository,
serviceBookingRepository,
testBookingRepository,
appointmentRepository,
messageSource
);
ReflectionTestUtils.setField(chatbotService, "restTemplate", restTemplate);
ReflectionTestUtils.setField(chatbotService, "accessToken", "fakeToken");
ReflectionTestUtils.setField(chatbotService, "timeoutSeconds", 2); // 2 seconds timeout for quick testing
}
@Test
void testUserInactiveForTwoSeconds_SessionExpires() throws InterruptedException {
String from = "1234567890";
String phoneId = "987654321";
UserState userState = new UserState(from);
userState.setState("MAIN_MENU");
userState.setLanguagePreference("en");
when(userStateRepository.findById(from)).thenReturn(Optional.of(userState));
when(restTemplate.postForEntity(anyString(), any(HttpEntity.class), eq(String.class)))
.thenReturn(ResponseEntity.ok("SUCCESS"));
// Process a message to schedule the timer
chatbotService.processMessage(from, "Some command", phoneId);
// Verify scheduled task was registered
verify(userStateRepository, atLeastOnce()).save(any(UserState.class));
assertNotNull(userState.getLastInteractionTime());
assertEquals("987654321", userState.getLastPhoneNumberId());
// Wait for the timeout of 2 seconds (wait 3 seconds to be safe)
Thread.sleep(3000);
// Verify user state transitions to EXPIRED
ArgumentCaptor<UserState> userStateCaptor = ArgumentCaptor.forClass(UserState.class);
verify(userStateRepository, atLeast(2)).save(userStateCaptor.capture());
UserState finalState = userStateCaptor.getAllValues().get(userStateCaptor.getAllValues().size() - 1);
assertEquals("EXPIRED", finalState.getState());
assertNull(finalState.getCategory());
assertNull(finalState.getSubCategory());
// Verify fallback message was sent in English
ArgumentCaptor<HttpEntity> entityCaptor = ArgumentCaptor.forClass(HttpEntity.class);
verify(restTemplate, atLeastOnce()).postForEntity(anyString(), entityCaptor.capture(), eq(String.class));
boolean foundFallback = false;
for (HttpEntity entity : entityCaptor.getAllValues()) {
String body = entity.getBody().toString();
if (body.contains("It looks like your previous conversation has ended") && body.contains("Hi or Hello")) {
foundFallback = true;
break;
}
}
assertTrue(foundFallback, "Expected English fallback message to be sent");
}
@Test
void testUserSendsMessageBeforeTimeout_TimerResets() throws InterruptedException {
String from = "1234567890";
String phoneId = "987654321";
UserState userState = new UserState(from);
userState.setState("MAIN_MENU");
userState.setLanguagePreference("en");
when(userStateRepository.findById(from)).thenReturn(Optional.of(userState));
when(restTemplate.postForEntity(anyString(), any(HttpEntity.class), eq(String.class)))
.thenReturn(ResponseEntity.ok("SUCCESS"));
// Initial message schedules timer
chatbotService.processMessage(from, "First message", phoneId);
long initialTime = userState.getLastInteractionTime();
// Wait 1 second (less than 2s timeout) and send another message
Thread.sleep(1000);
chatbotService.processMessage(from, "Second message", phoneId);
long resetTime = userState.getLastInteractionTime();
assertTrue(resetTime > initialTime, "Last interaction time should have increased");
// Wait 1.5 seconds (total 2.5s since first message, but only 1.5s since second message)
Thread.sleep(1500);
// Verify state is NOT EXPIRED because the 2-second timer has not yet run out from the second interaction
assertNotEquals("EXPIRED", userState.getState());
// Now wait another 1.5 seconds (timer should now fire for the second interaction)
Thread.sleep(1500);
assertEquals("EXPIRED", userState.getState());
}
@Test
void testExpiredSessionWithGreeting_StartsNewConversation() {
String from = "1234567890";
String phoneId = "987654321";
UserState userState = new UserState(from);
userState.setState("EXPIRED");
userState.setLanguagePreference("en");
when(userStateRepository.findById(from)).thenReturn(Optional.of(userState));
when(restTemplate.postForEntity(anyString(), any(HttpEntity.class), eq(String.class)))
.thenReturn(ResponseEntity.ok("SUCCESS"));
// Sending "Hi" to start new conversation
chatbotService.processMessage(from, "Hi", phoneId);
// Since it's a greeting, reset is triggered and state becomes LANGUAGE_SELECTION,
// which then triggers interactive language selection message.
assertEquals("LANGUAGE_SELECTION", userState.getState());
}
@Test
void testExpiredSessionWithNonGreeting_SendsReminderMessage() {
String from = "1234567890";
String phoneId = "987654321";
UserState userState = new UserState(from);
userState.setState("EXPIRED");
userState.setLanguagePreference("en");
when(userStateRepository.findById(from)).thenReturn(Optional.of(userState));
when(restTemplate.postForEntity(anyString(), any(HttpEntity.class), eq(String.class)))
.thenReturn(ResponseEntity.ok("SUCCESS"));
// Sending a non-greeting
chatbotService.processMessage(from, "Book Appointment", phoneId);
// The state remains EXPIRED
assertEquals("EXPIRED", userState.getState());
// Verify reminder message was sent
ArgumentCaptor<HttpEntity> entityCaptor = ArgumentCaptor.forClass(HttpEntity.class);
verify(restTemplate, times(1)).postForEntity(anyString(), entityCaptor.capture(), eq(String.class));
String body = entityCaptor.getValue().getBody().toString();
assertTrue(body.contains("To start a new conversation, please type Hi or Hello"),
"Should remind the user to start a new conversation");
}
@Test
void testTamilFallbackMessage() throws InterruptedException {
String from = "1234567890";
String phoneId = "987654321";
UserState userState = new UserState(from);
userState.setState("MAIN_MENU");
userState.setLanguagePreference("ta"); // Tamil preference
when(userStateRepository.findById(from)).thenReturn(Optional.of(userState));
when(restTemplate.postForEntity(anyString(), any(HttpEntity.class), eq(String.class)))
.thenReturn(ResponseEntity.ok("SUCCESS"));
// Initial interaction
chatbotService.processMessage(from, "விருப்பம்", phoneId);
// Wait for the timeout
Thread.sleep(3000);
assertEquals("EXPIRED", userState.getState());
// Verify fallback message sent in Tamil
ArgumentCaptor<HttpEntity> entityCaptor = ArgumentCaptor.forClass(HttpEntity.class);
verify(restTemplate, atLeastOnce()).postForEntity(anyString(), entityCaptor.capture(), eq(String.class));
boolean foundTamilFallback = false;
for (HttpEntity entity : entityCaptor.getAllValues()) {
String body = entity.getBody().toString();
if (body.contains("உரையாடல் முடிவடைந்துவிட்டது") && body.contains("Hi அல்லது Hello")) {
foundTamilFallback = true;
break;
}
}
assertTrue(foundTamilFallback, "Expected Tamil fallback message to be sent");
}
}

View File

@@ -0,0 +1,353 @@
package com.example.whatsappautomation;
import com.example.whatsappautomation.entity.Appointment;
import com.example.whatsappautomation.entity.ServiceBooking;
import com.example.whatsappautomation.entity.TestBooking;
import com.example.whatsappautomation.entity.UserState;
import com.example.whatsappautomation.repository.AppointmentRepository;
import com.example.whatsappautomation.repository.ServiceBookingRepository;
import com.example.whatsappautomation.repository.TestBookingRepository;
import com.example.whatsappautomation.repository.UserStateRepository;
import com.example.whatsappautomation.service.ChatbotService;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.mockito.ArgumentCaptor;
import org.springframework.context.MessageSource;
import org.springframework.http.HttpEntity;
import org.springframework.http.ResponseEntity;
import org.springframework.test.util.ReflectionTestUtils;
import org.springframework.web.client.RestTemplate;
import java.util.Locale;
import java.util.Optional;
import static org.junit.jupiter.api.Assertions.*;
import static org.mockito.ArgumentMatchers.*;
import static org.mockito.Mockito.*;
class NewWorkflowTests {
private UserStateRepository userStateRepository;
private ServiceBookingRepository serviceBookingRepository;
private TestBookingRepository testBookingRepository;
private AppointmentRepository appointmentRepository;
private MessageSource messageSource;
private RestTemplate restTemplate;
private ChatbotService chatbotService;
@BeforeEach
void setUp() {
userStateRepository = mock(UserStateRepository.class);
serviceBookingRepository = mock(ServiceBookingRepository.class);
testBookingRepository = mock(TestBookingRepository.class);
appointmentRepository = mock(AppointmentRepository.class);
messageSource = mock(MessageSource.class);
restTemplate = mock(RestTemplate.class);
chatbotService = new ChatbotService(
userStateRepository,
serviceBookingRepository,
testBookingRepository,
appointmentRepository,
messageSource
);
ReflectionTestUtils.setField(chatbotService, "restTemplate", restTemplate);
ReflectionTestUtils.setField(chatbotService, "accessToken", "fakeToken");
ReflectionTestUtils.setField(chatbotService, "newWorkflowEnabled", true);
ReflectionTestUtils.setField(chatbotService, "timeoutSeconds", 120);
// Standard stubbing for messageSource to return keys/formatted messages
when(messageSource.getMessage(anyString(), any(), any(Locale.class)))
.thenAnswer(invocation -> {
String key = invocation.getArgument(0);
Object[] args = invocation.getArgument(1);
if (args != null && args.length > 0) {
StringBuilder sb = new StringBuilder(key);
for (Object arg : args) {
sb.append(" [").append(arg).append("]");
}
return sb.toString();
}
return key;
});
when(restTemplate.postForEntity(anyString(), any(HttpEntity.class), eq(String.class)))
.thenReturn(ResponseEntity.ok("SUCCESS"));
}
@Test
void testFeatureFlagDisabled_FallsBackToOldWorkflow() {
ReflectionTestUtils.setField(chatbotService, "newWorkflowEnabled", false);
String from = "1234567890";
UserState userState = new UserState(from);
userState.setState("LANGUAGE_SELECTION");
when(userStateRepository.findById(from)).thenReturn(Optional.of(userState));
chatbotService.processMessage(from, "LANG_EN", "987654321");
// When old workflow runs LANG_EN, it should route to the old state machine.
// In the old workflow, LANG_EN transitions to MAIN_MENU.
assertEquals("MAIN_MENU", userState.getState());
assertEquals("en", userState.getLanguagePreference());
}
@Test
void testLanguageSelection_English_Tamil_Hindi() {
String from = "1234567890";
// English
UserState userState1 = new UserState(from);
userState1.setState("LANGUAGE_SELECTION");
when(userStateRepository.findById(from)).thenReturn(Optional.of(userState1));
chatbotService.processMessage(from, "LANG_EN", "987654321");
assertEquals("MAIN_MENU", userState1.getState());
assertEquals("en", userState1.getLanguagePreference());
// Tamil
UserState userState2 = new UserState(from);
userState2.setState("LANGUAGE_SELECTION");
when(userStateRepository.findById(from)).thenReturn(Optional.of(userState2));
chatbotService.processMessage(from, "LANG_TA", "987654321");
assertEquals("MAIN_MENU", userState2.getState());
assertEquals("ta", userState2.getLanguagePreference());
// Hindi
UserState userState3 = new UserState(from);
userState3.setState("LANGUAGE_SELECTION");
when(userStateRepository.findById(from)).thenReturn(Optional.of(userState3));
chatbotService.processMessage(from, "LANG_HI", "987654321");
assertEquals("MAIN_MENU", userState3.getState());
assertEquals("hi", userState3.getLanguagePreference());
}
@Test
void testEmergencyFlow() {
String from = "1234567890";
UserState userState = new UserState(from);
userState.setState("MAIN_MENU");
userState.setLanguagePreference("en");
when(userStateRepository.findById(from)).thenReturn(Optional.of(userState));
// Go to Emergency
chatbotService.processMessage(from, "NEW_MENU_EMERGENCY", "987654321");
assertEquals("NEW_EMERGENCY", userState.getState());
// Select Main Menu
chatbotService.processMessage(from, "NEW_EMERGENCY_MAIN_MENU", "987654321");
assertEquals("MAIN_MENU", userState.getState());
// Go back to Emergency
chatbotService.processMessage(from, "1", "987654321"); // Option 1
assertEquals("NEW_EMERGENCY", userState.getState());
// Exit
chatbotService.processMessage(from, "NEW_EMERGENCY_EXIT", "987654321");
assertEquals("EXPIRED", userState.getState());
}
@Test
void testAssistanceFlow() {
String from = "1234567890";
UserState userState = new UserState(from);
userState.setState("MAIN_MENU");
userState.setLanguagePreference("en");
when(userStateRepository.findById(from)).thenReturn(Optional.of(userState));
// Go to Assistance
chatbotService.processMessage(from, "NEW_MENU_ASSISTANCE", "987654321");
assertEquals("NEW_ASSISTANCE", userState.getState());
// Select Main Menu
chatbotService.processMessage(from, "NEW_ASSISTANCE_MAIN_MENU", "987654321");
assertEquals("MAIN_MENU", userState.getState());
}
@Test
void testJobEnquiryFlow() {
String from = "1234567890";
UserState userState = new UserState(from);
userState.setState("MAIN_MENU");
userState.setLanguagePreference("en");
when(userStateRepository.findById(from)).thenReturn(Optional.of(userState));
// Start Job Enquiry
chatbotService.processMessage(from, "NEW_MENU_JOB", "987654321");
assertEquals("NEW_JOB_PHONE", userState.getState());
// Enter invalid phone
chatbotService.processMessage(from, "abc", "987654321");
assertEquals("NEW_JOB_PHONE", userState.getState()); // Should stay
// Enter valid phone
chatbotService.processMessage(from, "9876543210", "987654321");
assertEquals("NEW_JOB_EMAIL", userState.getState());
assertEquals("9876543210", userState.getSubCategory());
// Enter invalid email
chatbotService.processMessage(from, "invalid-email", "987654321");
assertEquals("NEW_JOB_EMAIL", userState.getState()); // Should stay
// Enter valid email
chatbotService.processMessage(from, "hr@rmchospital.com", "987654321");
assertEquals("NEW_JOB_CONFIRM", userState.getState());
assertEquals("hr@rmchospital.com", userState.getAddress());
}
@Test
void testDoctorAppointmentFlow() {
String from = "1234567890";
UserState userState = new UserState(from);
userState.setState("MAIN_MENU");
userState.setLanguagePreference("en");
when(userStateRepository.findById(from)).thenReturn(Optional.of(userState));
// Start Doc Appointment
chatbotService.processMessage(from, "NEW_MENU_DOCTOR", "987654321");
assertEquals("NEW_DOC_SPECIALITY", userState.getState());
// Select Cardiology
chatbotService.processMessage(from, "DEPT_CARDIO", "987654321");
assertEquals("NEW_DOC_DOCTOR", userState.getState());
assertEquals("Cardiology", userState.getCategory());
// Select Doctor Priya
chatbotService.processMessage(from, "DOC_PRIYA", "987654321");
assertEquals("NEW_DOC_DATE", userState.getState());
assertEquals("Dr. Priya Sharma", userState.getSelectedItem());
// Enter invalid date format
chatbotService.processMessage(from, "2026/08/17", "987654321");
assertEquals("NEW_DOC_DATE", userState.getState()); // Should stay
// Enter valid date
chatbotService.processMessage(from, "17/08/2026", "987654321");
assertEquals("NEW_DOC_TIME", userState.getState());
assertEquals("17/08/2026", userState.getBookingDate());
// Select Time slot 10 AM
chatbotService.processMessage(from, "TIME_10AM", "987654321");
assertEquals("NEW_DOC_CONFIRM", userState.getState());
assertEquals("10:00 AM", userState.getBookingTime());
// Confirm booking
chatbotService.processMessage(from, "APT_CONFIRM", "987654321");
// Verify appointment saved in database
ArgumentCaptor<Appointment> appCaptor = ArgumentCaptor.forClass(Appointment.class);
verify(appointmentRepository, times(1)).save(appCaptor.capture());
Appointment saved = appCaptor.getValue();
assertEquals("Cardiology", saved.getDepartment());
assertEquals("Dr. Priya Sharma", saved.getDoctor());
assertEquals("17/08/2026", saved.getAppointmentDate());
assertEquals("10:00 AM", saved.getAppointmentTime());
assertEquals("Confirmed", saved.getStatus());
}
@Test
void testHealthcarePackagesFlow_WithDiscount() {
String from = "1234567890";
UserState userState = new UserState(from);
userState.setState("MAIN_MENU");
userState.setLanguagePreference("en");
when(userStateRepository.findById(from)).thenReturn(Optional.of(userState));
// Start Healthcare Package
chatbotService.processMessage(from, "NEW_MENU_PACKAGES", "987654321");
assertEquals("NEW_PKG_SELECT", userState.getState());
// Select Advanced Cardiac Package (₹3000)
chatbotService.processMessage(from, "PKG_CARDIO", "987654321");
assertEquals("NEW_PKG_COUPON", userState.getState());
assertEquals("Advanced Cardiac Package", userState.getSelectedItem());
assertEquals("₹3,000", userState.getPrice());
// Enter valid coupon code RMC10 (10% discount)
chatbotService.processMessage(from, "RMC10", "987654321");
assertEquals("NEW_PKG_DATE", userState.getState());
assertEquals("₹2,700", userState.getPrice()); // 3000 - 10% = 2700
// Enter Date
chatbotService.processMessage(from, "18/08/2026", "987654321");
assertEquals("NEW_PKG_TIME", userState.getState());
// Select time slot
chatbotService.processMessage(from, "PKG_TIME_11AM", "987654321");
assertEquals("NEW_PKG_CONFIRM", userState.getState());
assertEquals("11:00 AM", userState.getBookingTime());
// Confirm
chatbotService.processMessage(from, "PKG_CONFIRM", "987654321");
// Verify package saved in database
ArgumentCaptor<ServiceBooking> bookingCaptor = ArgumentCaptor.forClass(ServiceBooking.class);
verify(serviceBookingRepository, times(1)).save(bookingCaptor.capture());
ServiceBooking saved = bookingCaptor.getValue();
assertEquals("Advanced Cardiac Package", saved.getServiceName());
assertEquals("₹2,700", saved.getPrice());
assertEquals("18/08/2026", saved.getBookingDate());
assertEquals("11:00 AM", saved.getBookingTime());
assertEquals("CONFIRMED", saved.getStatus());
}
@Test
void testBloodLabInvestigationFlow() {
String from = "1234567890";
UserState userState = new UserState(from);
userState.setState("MAIN_MENU");
userState.setLanguagePreference("en");
when(userStateRepository.findById(from)).thenReturn(Optional.of(userState));
// Start Investigation
chatbotService.processMessage(from, "NEW_MENU_INVESTIGATION", "987654321");
assertEquals("NEW_INV_TYPE", userState.getState());
// Select Blood
chatbotService.processMessage(from, "INV_BLOOD", "987654321");
assertEquals("NEW_BLOOD_SELECT", userState.getState());
// Select Complete Blood Count (CBC)
chatbotService.processMessage(from, "BLOOD_CBC", "987654321");
assertEquals("NEW_BLOOD_CONFIRM_TEST", userState.getState());
// Confirm selection
chatbotService.processMessage(from, "BLOOD_CONFIRM_YES", "987654321");
assertEquals("NEW_BLOOD_DATE", userState.getState());
// Enter Date
chatbotService.processMessage(from, "19/08/2026", "987654321");
assertEquals("NEW_BLOOD_TIME", userState.getState());
// Select Time
chatbotService.processMessage(from, "SLOT_7AM", "987654321");
assertEquals("NEW_BLOOD_CONFIRM", userState.getState());
// Confirm Booking
chatbotService.processMessage(from, "BLOOD_FINAL_CONFIRM", "987654321");
// Verify Lab Booking saved in database
ArgumentCaptor<TestBooking> testCaptor = ArgumentCaptor.forClass(TestBooking.class);
verify(testBookingRepository, times(1)).save(testCaptor.capture());
TestBooking saved = testCaptor.getValue();
assertEquals("Complete Blood Count (CBC)", saved.getPackageName());
assertEquals("₹300", saved.getPrice());
assertEquals("19/08/2026", saved.getBookingDate());
assertEquals("7 AM - 9 AM", saved.getBookingTime());
assertEquals("CONFIRMED", saved.getStatus());
}
@Test
void testNavigationCommands_Back() {
String from = "1234567890";
UserState userState = new UserState(from);
userState.setState("NEW_DOC_DOCTOR");
userState.setCategory("Cardiology");
userState.setLanguagePreference("en");
when(userStateRepository.findById(from)).thenReturn(Optional.of(userState));
// Send 'Back' command to go back to Speciality
chatbotService.processMessage(from, "Back", "987654321");
assertEquals("NEW_DOC_SPECIALITY", userState.getState());
}
}

View File

@@ -0,0 +1,12 @@
package com.example.whatsappautomation;
import org.junit.jupiter.api.Test;
import org.springframework.boot.test.context.SpringBootTest;
@SpringBootTest
class WhatsAppAutomationApplicationTests {
@Test
void contextLoads() {
}
}

49
test_restart.py Normal file
View File

@@ -0,0 +1,49 @@
import requests
payload = {
"object": "whatsapp_business_account",
"entry": [
{
"id": "1068191009070265",
"changes": [
{
"value": {
"messaging_product": "whatsapp",
"metadata": {
"display_phone_number": "15551914329",
"phone_number_id": "1236658909534007"
},
"contacts": [
{
"profile": {
"name": "Dev"
},
"wa_id": "919994834737",
"user_id": "IN.1458184416042595"
}
],
"messages": [
{
"from": "919994834737",
"from_user_id": "IN.1458184416042595",
"id": "wamid.HBgMOTE2Mzc5NzgzOTExFQIAEhgWM0VCMDU3MDk2N0Q2ODk0MzE2N0U4NwA=",
"timestamp": "1786945902",
"interactive": {
"type": "button_reply",
"button_reply": {
"id": "MENU_SERVICES",
"title": "My Services"
}
},
"type": "interactive"
}
]
},
"field": "messages"
}
]
}
]
}
requests.post('http://localhost:8085/webhook', json=payload)