Initial commit

This commit is contained in:
2026-03-23 16:40:56 +05:30
commit ceef6646c5
116 changed files with 13192 additions and 0 deletions

9
.env Normal file
View File

@@ -0,0 +1,9 @@
Secret_Key = 9f2a1b8c4d6e7f0123456789abcdef01
MYSQL_HOST=127.0.0.1
MYSQL_USER=root
MYSQL_PASSWORD=admin
MYSQL_DB=test
DEFAULT_USERNAME=admin
DEFAULT_PASSWORD=admin123

7
.gitignore vendored Normal file
View File

@@ -0,0 +1,7 @@
venv/
*.pyc
__pycache__/
.uploads
static/download/

3
.idea/.gitignore generated vendored Normal file
View File

@@ -0,0 +1,3 @@
# Default ignored files
/shelf/
/workspace.xml

1
.idea/.name generated Normal file
View File

@@ -0,0 +1 @@
main.py

10
.idea/ManagementApplicationt.iml generated Normal file
View File

@@ -0,0 +1,10 @@
<?xml version="1.0" encoding="UTF-8"?>
<module type="PYTHON_MODULE" version="4">
<component name="NewModuleRootManager">
<content url="file://$MODULE_DIR$">
<excludeFolder url="file://$MODULE_DIR$/.venv" />
</content>
<orderEntry type="jdk" jdkName="Python 3.13" jdkType="Python SDK" />
<orderEntry type="sourceFolder" forTests="false" />
</component>
</module>

View File

@@ -0,0 +1,6 @@
<component name="InspectionProjectProfileManager">
<settings>
<option name="USE_PROJECT_PROFILE" value="false" />
<version value="1.0" />
</settings>
</component>

7
.idea/misc.xml generated Normal file
View File

@@ -0,0 +1,7 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="Black">
<option name="sdkName" value="Python 3.13 (ManagementApplicationt)" />
</component>
<component name="ProjectRootManager" version="2" project-jdk-name="Python 3.13" project-jdk-type="Python SDK" />
</project>

8
.idea/modules.xml generated Normal file
View File

@@ -0,0 +1,8 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="ProjectModuleManager">
<modules>
<module fileurl="file://$PROJECT_DIR$/.idea/ManagementApplicationt.iml" filepath="$PROJECT_DIR$/.idea/ManagementApplicationt.iml" />
</modules>
</component>
</project>

6
.idea/vcs.xml generated Normal file
View File

@@ -0,0 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="VcsDirectoryMappings">
<mapping directory="" vcs="Git" />
</component>
</project>

18
Dockerfile Normal file
View File

@@ -0,0 +1,18 @@
# Use official Python image
FROM python:3.9
# Set working directory inside container
WORKDIR /app
# Copy all files to container
COPY . .
# Install dependencies
RUN pip install --no-cache-dir -r requirements.txt
# Expose Flask's default port
EXPOSE 5000
# Run Flask app
CMD ["python", "main.py"]

1
README.md Normal file
View File

@@ -0,0 +1 @@
# MA07-05-2025

217
activity.log Normal file
View File

@@ -0,0 +1,217 @@
Timestamp: 2026-03-22 16:57:59 | User: Unknown | Action: Logout | Details: User admin logged out
Timestamp: 2026-03-22 16:58:07 | User: Unknown | Action: Login | Details: User admin logged in (static user)
Timestamp: 2026-03-22 17:00:53 | User: Unknown | Action: Check State | Details: User admin Checked state 'MP'
Timestamp: 2026-03-22 17:00:54 | User: Unknown | Action: Add State | Details: User admin added state 'MP'
Timestamp: 2026-03-22 17:38:04 | User: Unknown | Action: Add GST Release | Details: User admin adding '121'
Timestamp: 2026-03-22 17:39:45 | User: Unknown | Action: Add GST Release | Details: User admin adding '121'
Timestamp: 2026-03-22 17:39:45 | User: Unknown | Action: Add GST Release | Details: User admin added GST release '59487'
Timestamp: 2026-03-22 17:40:03 | User: Unknown | Action: Add GST Release | Details: User admin adding '121'
Timestamp: 2026-03-22 17:40:03 | User: Unknown | Action: Add GST Release | Details: User admin added GST release '59486'
Timestamp: 2026-03-22 17:52:56 | User: Unknown | Action: Add GST Release | Details: User admin added GST release '59487'
Timestamp: 2026-03-22 17:53:01 | User: Unknown | Action: Delete GST Release | Details: User admin deleted GST release '34'
Timestamp: 2026-03-22 17:58:05 | User: Unknown | Action: Add GST Release | Details: User admin added GST release '58841'
Timestamp: 2026-03-22 17:58:28 | User: Unknown | Action: Add GST Release | Details: User admin added GST release '58841'
Timestamp: 2026-03-22 17:58:35 | User: Unknown | Action: Delete GST Release | Details: User admin deleted GST release '36'
Timestamp: 2026-03-22 18:03:24 | User: Unknown | Action: Add GST Release | Details: User admin added GST release '58841'
Timestamp: 2026-03-22 18:05:18 | User: Unknown | Action: Delete GST Release | Details: User admin deleted GST release '37'
Timestamp: 2026-03-22 18:06:09 | User: Unknown | Action: Delete GST Release | Details: User admin deleted GST release '35'
Timestamp: 2026-03-22 18:18:54 | User: Unknown | Action: Delete GST Release | Details: User admin deleted GST Release '33'
Timestamp: 2026-03-22 18:18:54 | User: Unknown | Action: Delete GST Release | Details: User admin deleted GST release
Timestamp: 2026-03-22 18:22:27 | User: Unknown | Action: Add GST Release | Details: User admin adding '1526'
Timestamp: 2026-03-22 18:24:10 | User: Unknown | Action: Add GST Release | Details: User admin adding '1526'
Timestamp: 2026-03-22 18:25:32 | User: Unknown | Action: Add GST Release | Details: User admin adding '478'
Timestamp: 2026-03-22 18:25:32 | User: Unknown | Action: Add GST Release | Details: User admin added GST release
Timestamp: 2026-03-22 18:27:38 | User: Unknown | Action: Add GST Release | Details: User admin adding '1500'
Timestamp: 2026-03-22 18:27:38 | User: Unknown | Action: Add GST Release | Details: User admin added GST release
Timestamp: 2026-03-22 18:37:52 | User: Unknown | Action: Add GST Release | Details: User admin adding '-'
Timestamp: 2026-03-22 18:37:52 | User: Unknown | Action: Add GST Release | Details: User added GST release
Timestamp: 2026-03-22 18:40:33 | User: Unknown | Action: Add GST Release | Details: User admin adding '-'
Timestamp: 2026-03-22 18:40:33 | User: Unknown | Action: Add GST Release | Details: User added GST release
Timestamp: 2026-03-22 18:42:45 | User: Unknown | Action: Add GST Release | Details: User admin adding '58841-500'
Timestamp: 2026-03-22 18:42:45 | User: Unknown | Action: Add GST Release | Details: User added GST release
Timestamp: 2026-03-22 18:45:23 | User: Unknown | Action: Add GST Release | Details: User admin adding '-'
Timestamp: 2026-03-22 18:45:23 | User: Unknown | Action: Add GST Release | Details: User added GST release
Timestamp: 2026-03-22 18:48:07 | User: Unknown | Action: Add GST Release | Details: User admin adding '-'
Timestamp: 2026-03-22 18:48:07 | User: Unknown | Action: Add GST Release | Details: User added GST release
Timestamp: 2026-03-22 18:49:42 | User: Unknown | Action: Add GST Release | Details: User admin adding '-'
Timestamp: 2026-03-22 18:49:42 | User: Unknown | Action: Add GST Release | Details: User added GST release
Timestamp: 2026-03-22 18:52:58 | User: Unknown | Action: Add GST Release | Details: User admin adding '-'
Timestamp: 2026-03-22 18:52:58 | User: Unknown | Action: Add GST Release | Details: User added GST release
Timestamp: 2026-03-22 18:53:54 | User: Unknown | Action: Add GST Release | Details: User admin adding '-'
Timestamp: 2026-03-22 18:53:54 | User: Unknown | Action: Add GST Release | Details: User added GST release
Timestamp: 2026-03-22 18:54:35 | User: Unknown | Action: Add GST Release | Details: User admin adding '-'
Timestamp: 2026-03-22 18:54:35 | User: Unknown | Action: Add GST Release | Details: User added GST release
Timestamp: 2026-03-22 18:57:03 | User: Unknown | Action: Add GST Release | Details: User admin adding '-'
Timestamp: 2026-03-22 18:57:03 | User: Unknown | Action: Add GST Release | Details: User added GST release
Timestamp: 2026-03-22 18:58:50 | User: Unknown | Action: Add GST Release | Details: User admin adding '58841-1526'
Timestamp: 2026-03-22 18:58:50 | User: Unknown | Action: Add GST Release | Details: User added GST release
Timestamp: 2026-03-22 18:59:22 | User: Unknown | Action: Add Payment | Details: User admin Add Payment '58841'
Timestamp: 2026-03-22 18:59:42 | User: Unknown | Action: Add GST Release | Details: User admin adding '58841-121'
Timestamp: 2026-03-22 18:59:42 | User: Unknown | Action: Add GST Release | Details: User added GST release
Timestamp: 2026-03-22 19:03:47 | User: Unknown | Action: Add GST Release | Details: User admin adding '58841-4500'
Timestamp: 2026-03-22 19:03:47 | User: Unknown | Action: Add GST Release | Details: User added GST release
Timestamp: 2026-03-22 19:04:04 | User: Unknown | Action: Add GST Release | Details: User admin adding '58841-121'
Timestamp: 2026-03-22 19:04:04 | User: Unknown | Action: Add GST Release | Details: User added GST release
Timestamp: 2026-03-22 19:05:03 | User: Unknown | Action: Add GST Release | Details: User admin adding '58841-1500'
Timestamp: 2026-03-22 19:05:03 | User: Unknown | Action: Add GST Release | Details: User added GST release
Timestamp: 2026-03-22 19:09:29 | User: Unknown | Action: Add GST Release | Details: User admin adding '58841-4100'
Timestamp: 2026-03-22 19:09:29 | User: Unknown | Action: Add GST Release | Details: User added GST release
Timestamp: 2026-03-22 19:11:35 | User: Unknown | Action: Add GST Release | Details: User admin adding '58841-4500'
Timestamp: 2026-03-22 19:11:35 | User: Unknown | Action: Add GST Release | Details: User added GST release
Timestamp: 2026-03-22 19:12:22 | User: Unknown | Action: Add GST Release | Details: User admin adding '58841-1001'
Timestamp: 2026-03-22 19:12:22 | User: Unknown | Action: Add GST Release | Details: User added GST release
Timestamp: 2026-03-22 19:12:56 | User: Unknown | Action: Add GST Release | Details: User admin adding '58841-101'
Timestamp: 2026-03-22 19:12:56 | User: Unknown | Action: Add GST Release | Details: User added GST release
Timestamp: 2026-03-22 19:13:03 | User: Unknown | Action: Delete GST Release | Details: User admin deleted GST Release '32'
Timestamp: 2026-03-22 19:13:03 | User: Unknown | Action: Delete GST Release | Details: User deleted GST release
Timestamp: 2026-03-22 19:13:07 | User: Unknown | Action: Delete GST Release | Details: User admin deleted GST Release '31'
Timestamp: 2026-03-22 19:13:07 | User: Unknown | Action: Delete GST Release | Details: User deleted GST release
Timestamp: 2026-03-22 19:13:34 | User: Unknown | Action: Add GST Release | Details: User admin adding '52429-1001'
Timestamp: 2026-03-22 19:13:34 | User: Unknown | Action: Add GST Release | Details: User added GST release
Timestamp: 2026-03-22 19:14:12 | User: Unknown | Action: Add GST Release | Details: User admin adding '57798-121'
Timestamp: 2026-03-22 19:14:12 | User: Unknown | Action: Add GST Release | Details: User added GST release
Timestamp: 2026-03-22 19:18:35 | User: Unknown | Action: Add GST Release | Details: User added GST release
Timestamp: 2026-03-22 19:21:14 | User: Unknown | Action: Add GST Release | Details: User added GST release
Timestamp: 2026-03-22 19:21:18 | User: Unknown | Action: Delete GST Release | Details: User deleted GST release
Timestamp: 2026-03-23 10:27:50 | User: Unknown | Action: Add GST Release | Details: User admin adding '58841-1250'
Timestamp: 2026-03-23 10:27:50 | User: Unknown | Action: Add GST Release | Details: User added GST release
Timestamp: 2026-03-23 10:28:03 | User: Unknown | Action: Delete GST Release | Details: User admin deleted GST Release '38'
Timestamp: 2026-03-23 10:28:03 | User: Unknown | Action: Delete GST Release | Details: User deleted GST release
Timestamp: 2026-03-23 11:02:36 | User: Unknown | Action: Add GST Release | Details: User admin adding '58841-1111'
Timestamp: 2026-03-23 11:02:36 | User: Unknown | Action: Add GST Release | Details: User added GST release
Timestamp: 2026-03-23 11:05:13 | User: Unknown | Action: Delete GST Release | Details: User admin deleted GST Release '5'
Timestamp: 2026-03-23 11:05:13 | User: Unknown | Action: Delete GST Release | Details: User deleted GST release
Timestamp: 2026-03-23 11:05:17 | User: Unknown | Action: Delete GST Release | Details: User admin deleted GST Release '30'
Timestamp: 2026-03-23 11:05:17 | User: Unknown | Action: Delete GST Release | Details: User deleted GST release
Timestamp: 2026-03-23 11:06:47 | User: Unknown | Action: Delete GST Release | Details: User admin deleted GST Release '29'
Timestamp: 2026-03-23 11:06:47 | User: Unknown | Action: Delete GST Release | Details: User deleted GST release
Timestamp: 2026-03-23 11:06:52 | User: Unknown | Action: Delete GST Release | Details: User admin deleted GST Release '28'
Timestamp: 2026-03-23 11:06:52 | User: Unknown | Action: Delete GST Release | Details: User deleted GST release
Timestamp: 2026-03-23 11:11:28 | User: Unknown | Action: Delete GST Release | Details: User admin deleted GST Release '27'
Timestamp: 2026-03-23 11:11:28 | User: Unknown | Action: Delete GST Release | Details: User deleted GST release
Timestamp: 2026-03-23 11:35:22 | User: Unknown | Action: Add GST Release | Details: User admin adding 'None'
Timestamp: 2026-03-23 11:38:16 | User: Unknown | Action: Add GST Release | Details: User admin adding '0'
Timestamp: 2026-03-23 11:38:16 | User: Unknown | Action: Add GST Release | Details: User added GST release
Timestamp: 2026-03-23 11:41:51 | User: Unknown | Action: Add GST Release | Details: User admin adding 'None'
Timestamp: 2026-03-23 11:43:30 | User: Unknown | Action: Add GST Release | Details: User admin adding ''
Timestamp: 2026-03-23 11:43:30 | User: Unknown | Action: Add GST Release | Details: User added GST release
Timestamp: 2026-03-23 11:46:51 | User: Unknown | Action: Add GST Release | Details: User admin adding '{'p_pmc_no': '59485', 'p_invoice_no': '', 'p_basic_amount': 0.0, 'p_final_amount': 0.0, 'p_total_amount': 0.0, 'p_utr': '', 'p_contractor_id': 0, 'Contractor_Name': ''}'
Timestamp: 2026-03-23 11:46:51 | User: Unknown | Action: Add GST Release | Details: User added GST release
Timestamp: 2026-03-23 11:51:01 | User: Unknown | Action: Delete GST Release | Details: User admin deleted GST Release '40'
Timestamp: 2026-03-23 11:51:01 | User: Unknown | Action: Delete GST Release | Details: User deleted GST release
Timestamp: 2026-03-23 11:51:27 | User: Unknown | Action: Add GST Release | Details: User added GST release
Timestamp: 2026-03-23 11:52:44 | User: Unknown | Action: Add GST Release | Details: User added GST release
Timestamp: 2026-03-23 11:53:03 | User: Unknown | Action: Get hold type | Details: User admin Get hold type '[{'hold_type_id': 58, 'hold_type': 'aaaaaaa'}, {'hold_type_id': 26, 'hold_type': 'Aadhar card recovery hold amt'}, {'hold_type_id': 39, 'hold_type': 'Additional Hold'}, {'hold_type_id': 13, 'hold_type': 'Aditional hold for quality'}, {'hold_type_id': 36, 'hold_type': 'Debit hold'}, {'hold_type_id': 12, 'hold_type': 'DPR Excess Hold'}, {'hold_type_id': 49, 'hold_type': 'DPR Excess Hold Amount'}, {'hold_type_id': 24, 'hold_type': 'DPR Hold'}, {'hold_type_id': 32, 'hold_type': 'DPR Qty Hold'}, {'hold_type_id': 20, 'hold_type': 'DPR Qty. Exceeded Hold / Advance'}, {'hold_type_id': 48, 'hold_type': 'DPR Quantity Exceede Hold'}, {'hold_type_id': 15, 'hold_type': 'Excess Claim Hold'}, {'hold_type_id': 29, 'hold_type': 'Excess Claim Hold amount'}, {'hold_type_id': 11, 'hold_type': 'Excess Hold'}, {'hold_type_id': 7, 'hold_type': 'Extra Hold'}, {'hold_type_id': 34, 'hold_type': 'HOLD'}, {'hold_type_id': 28, 'hold_type': 'Hold - Excess DPR qty'}, {'hold_type_id': 42, 'hold_type': 'Hold 25%'}, {'hold_type_id': 35, 'hold_type': 'Hold against extra work'}, {'hold_type_id': 2, 'hold_type': 'Hold amount'}, {'hold_type_id': 41, 'hold_type': 'Hold Amount (Excess work against DPR )'}, {'hold_type_id': 30, 'hold_type': 'Hold Amount 5% Against Painting & finishing'}, {'hold_type_id': 3, 'hold_type': 'Hold amount against DPR'}, {'hold_type_id': 17, 'hold_type': 'Hold Amount against Material'}, {'hold_type_id': 21, 'hold_type': 'Hold Amount As per site instructions'}, {'hold_type_id': 53, 'hold_type': 'Hold Amount Excess Against DPR'}, {'hold_type_id': 51, 'hold_type': 'Hold Amount for additional work done of Lowring work'}, {'hold_type_id': 14, 'hold_type': 'Hold Amount for excess Qty. against DPR'}, {'hold_type_id': 50, 'hold_type': 'Hold Amount for excess Qty. against DPR/ Advance'}, {'hold_type_id': 23, 'hold_type': 'Hold Amount for excess working as per Work Order'}, {'hold_type_id': 4, 'hold_type': 'Hold Amount For Material'}, {'hold_type_id': 44, 'hold_type': 'Hold Amount For Material/ other'}, {'hold_type_id': 52, 'hold_type': 'Hold Amount For Painting work'}, {'hold_type_id': 18, 'hold_type': 'Hold Amount For Quantity excess against DPR'}, {'hold_type_id': 46, 'hold_type': 'Hold Amount For Quantity excess against DPR / Hold amount for Rate Finalisation'}, {'hold_type_id': 1, 'hold_type': 'Hold Amount for quantity more than DPR'}, {'hold_type_id': 6, 'hold_type': 'Hold amount quantity excess against DPR'}, {'hold_type_id': 45, 'hold_type': 'Hold Amount/ Advance'}, {'hold_type_id': 38, 'hold_type': 'Hold Amunt for Quantity excess anainst DPR'}, {'hold_type_id': 54, 'hold_type': 'Hold Excess work against Work Order'}, {'hold_type_id': 19, 'hold_type': "Hold Excess work against Work Order'"}, {'hold_type_id': 33, 'hold_type': 'Hold for excess work against DPR and Work Order'}, {'hold_type_id': 10, 'hold_type': 'Hold for Extra work Claimed'}, {'hold_type_id': 25, 'hold_type': 'Hold for Painting & finishing (5%)'}, {'hold_type_id': 40, 'hold_type': 'Hold for painting and finishing'}, {'hold_type_id': 43, 'hold_type': 'Hold Painting and finishing'}, {'hold_type_id': 5, 'hold_type': 'Hold the Amount because the Qty. is more than the DPR'}, {'hold_type_id': 8, 'hold_type': 'Hold the Amount because the Qty. is more then the DPR'}, {'hold_type_id': 47, 'hold_type': 'Misc Hold'}, {'hold_type_id': 9, 'hold_type': 'Only FHTC Amount Hold'}, {'hold_type_id': 55, 'hold_type': 'Other Hold'}, {'hold_type_id': 27, 'hold_type': 'P & F hold'}, {'hold_type_id': 22, 'hold_type': 'Painting & Finishing hold'}, {'hold_type_id': 37, 'hold_type': 'Painting Hold'}, {'hold_type_id': 31, 'hold_type': 'Site Hold'}, {'hold_type_id': 16, 'hold_type': 'Staircase Hold amount'}, {'hold_type_id': 56, 'hold_type': 'Total Hold'}]'
Timestamp: 2026-03-23 11:54:42 | User: Unknown | Action: Add Payment | Details: User admin Add Payment '58841'
Timestamp: 2026-03-23 12:04:40 | User: Unknown | Action: Add GST Release | Details: User added GST release
Timestamp: 2026-03-23 12:15:42 | User: Unknown | Action: Add GST Release | Details: User added GST release
Timestamp: 2026-03-23 12:15:46 | User: Unknown | Action: Delete GST Release | Details: User admin deleted GST Release '44'
Timestamp: 2026-03-23 12:15:46 | User: Unknown | Action: Delete GST Release | Details: User deleted GST release
Timestamp: 2026-03-23 12:16:28 | User: Unknown | Action: Add GST Release | Details: User added GST release
Timestamp: 2026-03-23 12:16:33 | User: Unknown | Action: Delete GST Release | Details: User admin deleted GST Release '45'
Timestamp: 2026-03-23 12:16:33 | User: Unknown | Action: Delete GST Release | Details: User deleted GST release
Timestamp: 2026-03-23 12:16:46 | User: Unknown | Action: Check State | Details: User admin Checked state 'shamli'
Timestamp: 2026-03-23 12:16:50 | User: Unknown | Action: Check State | Details: User admin Checked state 'S'
Timestamp: 2026-03-23 12:16:51 | User: Unknown | Action: Check State | Details: User admin Checked state 'Sh'
Timestamp: 2026-03-23 12:16:51 | User: Unknown | Action: Check State | Details: User admin Checked state 'Sha'
Timestamp: 2026-03-23 12:16:51 | User: Unknown | Action: Check State | Details: User admin Checked state 'Sham'
Timestamp: 2026-03-23 12:16:51 | User: Unknown | Action: Check State | Details: User admin Checked state 'Shaml'
Timestamp: 2026-03-23 12:16:51 | User: Unknown | Action: Check State | Details: User admin Checked state 'Shamli'
Timestamp: 2026-03-23 12:16:53 | User: Unknown | Action: Check State | Details: User admin Checked state 'Shamli'
Timestamp: 2026-03-23 12:16:56 | User: Unknown | Action: Check State | Details: User admin Checked state 'Shamli'
Timestamp: 2026-03-23 12:17:00 | User: Unknown | Action: Check State | Details: User admin Checked state 's'
Timestamp: 2026-03-23 12:17:00 | User: Unknown | Action: Check State | Details: User admin Checked state 'sh'
Timestamp: 2026-03-23 12:17:00 | User: Unknown | Action: Check State | Details: User admin Checked state 'sha'
Timestamp: 2026-03-23 12:17:00 | User: Unknown | Action: Check State | Details: User admin Checked state 'sham'
Timestamp: 2026-03-23 12:17:00 | User: Unknown | Action: Check State | Details: User admin Checked state 'shaml'
Timestamp: 2026-03-23 12:17:01 | User: Unknown | Action: Check State | Details: User admin Checked state 'shamli'
Timestamp: 2026-03-23 12:17:01 | User: Unknown | Action: Check State | Details: User admin Checked state 'shamli'
Timestamp: 2026-03-23 12:17:02 | User: Unknown | Action: Check State | Details: User admin Checked state 'shaml'
Timestamp: 2026-03-23 12:17:02 | User: Unknown | Action: Check State | Details: User admin Checked state 'sham'
Timestamp: 2026-03-23 12:17:02 | User: Unknown | Action: Check State | Details: User admin Checked state 'sha'
Timestamp: 2026-03-23 12:17:03 | User: Unknown | Action: Check State | Details: User admin Checked state 'sh'
Timestamp: 2026-03-23 12:17:03 | User: Unknown | Action: Check State | Details: User admin Checked state 's'
Timestamp: 2026-03-23 12:17:06 | User: Unknown | Action: Check State | Details: User admin Checked state 'm'
Timestamp: 2026-03-23 12:17:06 | User: Unknown | Action: Check State | Details: User admin Checked state 'ma'
Timestamp: 2026-03-23 12:17:06 | User: Unknown | Action: Check State | Details: User admin Checked state 'mah'
Timestamp: 2026-03-23 12:17:06 | User: Unknown | Action: Check State | Details: User admin Checked state 'maha'
Timestamp: 2026-03-23 12:17:07 | User: Unknown | Action: Check State | Details: User admin Checked state 'mahar'
Timestamp: 2026-03-23 12:17:07 | User: Unknown | Action: Check State | Details: User admin Checked state 'mahara'
Timestamp: 2026-03-23 12:17:09 | User: Unknown | Action: Check State | Details: User admin Checked state 'Maharashtra'
Timestamp: 2026-03-23 12:17:13 | User: Unknown | Action: Check State | Details: User admin Checked state 'l'
Timestamp: 2026-03-23 12:17:13 | User: Unknown | Action: Check State | Details: User admin Checked state 'la'
Timestamp: 2026-03-23 12:17:13 | User: Unknown | Action: Check State | Details: User admin Checked state 'lat'
Timestamp: 2026-03-23 12:17:13 | User: Unknown | Action: Check State | Details: User admin Checked state 'latu'
Timestamp: 2026-03-23 12:17:14 | User: Unknown | Action: Check State | Details: User admin Checked state 'latur'
Timestamp: 2026-03-23 12:17:15 | User: Unknown | Action: Add State | Details: User admin added state 'latur'
Timestamp: 2026-03-23 12:17:28 | User: Unknown | Action: Edit State | Details: User admin Edited state 'Latur'
Timestamp: 2026-03-23 12:17:31 | User: Unknown | Action: Delete State | Details: User admin Deleted state '11'
Timestamp: 2026-03-23 13:04:08 | User: Unknown | Action: Delete Item | Details: User admin deleted Item '3'
Timestamp: 2026-03-23 13:04:11 | User: Unknown | Action: Delete Item | Details: User admin deleted Item '2'
Timestamp: 2026-03-23 13:14:04 | User: Unknown | Action: Get hold type | Details: User admin Get hold type '[{'hold_type_id': 58, 'hold_type': 'aaaaaaa'}, {'hold_type_id': 26, 'hold_type': 'Aadhar card recovery hold amt'}, {'hold_type_id': 39, 'hold_type': 'Additional Hold'}, {'hold_type_id': 13, 'hold_type': 'Aditional hold for quality'}, {'hold_type_id': 36, 'hold_type': 'Debit hold'}, {'hold_type_id': 12, 'hold_type': 'DPR Excess Hold'}, {'hold_type_id': 49, 'hold_type': 'DPR Excess Hold Amount'}, {'hold_type_id': 24, 'hold_type': 'DPR Hold'}, {'hold_type_id': 32, 'hold_type': 'DPR Qty Hold'}, {'hold_type_id': 20, 'hold_type': 'DPR Qty. Exceeded Hold / Advance'}, {'hold_type_id': 48, 'hold_type': 'DPR Quantity Exceede Hold'}, {'hold_type_id': 15, 'hold_type': 'Excess Claim Hold'}, {'hold_type_id': 29, 'hold_type': 'Excess Claim Hold amount'}, {'hold_type_id': 11, 'hold_type': 'Excess Hold'}, {'hold_type_id': 7, 'hold_type': 'Extra Hold'}, {'hold_type_id': 34, 'hold_type': 'HOLD'}, {'hold_type_id': 28, 'hold_type': 'Hold - Excess DPR qty'}, {'hold_type_id': 42, 'hold_type': 'Hold 25%'}, {'hold_type_id': 35, 'hold_type': 'Hold against extra work'}, {'hold_type_id': 2, 'hold_type': 'Hold amount'}, {'hold_type_id': 41, 'hold_type': 'Hold Amount (Excess work against DPR )'}, {'hold_type_id': 30, 'hold_type': 'Hold Amount 5% Against Painting & finishing'}, {'hold_type_id': 3, 'hold_type': 'Hold amount against DPR'}, {'hold_type_id': 17, 'hold_type': 'Hold Amount against Material'}, {'hold_type_id': 21, 'hold_type': 'Hold Amount As per site instructions'}, {'hold_type_id': 53, 'hold_type': 'Hold Amount Excess Against DPR'}, {'hold_type_id': 51, 'hold_type': 'Hold Amount for additional work done of Lowring work'}, {'hold_type_id': 14, 'hold_type': 'Hold Amount for excess Qty. against DPR'}, {'hold_type_id': 50, 'hold_type': 'Hold Amount for excess Qty. against DPR/ Advance'}, {'hold_type_id': 23, 'hold_type': 'Hold Amount for excess working as per Work Order'}, {'hold_type_id': 4, 'hold_type': 'Hold Amount For Material'}, {'hold_type_id': 44, 'hold_type': 'Hold Amount For Material/ other'}, {'hold_type_id': 52, 'hold_type': 'Hold Amount For Painting work'}, {'hold_type_id': 18, 'hold_type': 'Hold Amount For Quantity excess against DPR'}, {'hold_type_id': 46, 'hold_type': 'Hold Amount For Quantity excess against DPR / Hold amount for Rate Finalisation'}, {'hold_type_id': 1, 'hold_type': 'Hold Amount for quantity more than DPR'}, {'hold_type_id': 6, 'hold_type': 'Hold amount quantity excess against DPR'}, {'hold_type_id': 45, 'hold_type': 'Hold Amount/ Advance'}, {'hold_type_id': 38, 'hold_type': 'Hold Amunt for Quantity excess anainst DPR'}, {'hold_type_id': 54, 'hold_type': 'Hold Excess work against Work Order'}, {'hold_type_id': 19, 'hold_type': "Hold Excess work against Work Order'"}, {'hold_type_id': 33, 'hold_type': 'Hold for excess work against DPR and Work Order'}, {'hold_type_id': 10, 'hold_type': 'Hold for Extra work Claimed'}, {'hold_type_id': 25, 'hold_type': 'Hold for Painting & finishing (5%)'}, {'hold_type_id': 40, 'hold_type': 'Hold for painting and finishing'}, {'hold_type_id': 43, 'hold_type': 'Hold Painting and finishing'}, {'hold_type_id': 5, 'hold_type': 'Hold the Amount because the Qty. is more than the DPR'}, {'hold_type_id': 8, 'hold_type': 'Hold the Amount because the Qty. is more then the DPR'}, {'hold_type_id': 47, 'hold_type': 'Misc Hold'}, {'hold_type_id': 9, 'hold_type': 'Only FHTC Amount Hold'}, {'hold_type_id': 55, 'hold_type': 'Other Hold'}, {'hold_type_id': 27, 'hold_type': 'P & F hold'}, {'hold_type_id': 22, 'hold_type': 'Painting & Finishing hold'}, {'hold_type_id': 37, 'hold_type': 'Painting Hold'}, {'hold_type_id': 31, 'hold_type': 'Site Hold'}, {'hold_type_id': 16, 'hold_type': 'Staircase Hold amount'}, {'hold_type_id': 56, 'hold_type': 'Total Hold'}]'
Timestamp: 2026-03-23 14:23:17 | User: Unknown | Action: Delete Hold Type | Details: User admin deleted Hold Type '58'
Timestamp: 2026-03-23 14:23:22 | User: Unknown | Action: Delete Hold Type | Details: User admin deleted Hold Type '26'
Timestamp: 2026-03-23 14:23:25 | User: Unknown | Action: Delete Hold Type | Details: User admin deleted Hold Type '39'
Timestamp: 2026-03-23 14:23:27 | User: Unknown | Action: Delete Hold Type | Details: User admin deleted Hold Type '13'
Timestamp: 2026-03-23 14:23:32 | User: Unknown | Action: Delete Hold Type | Details: User admin deleted Hold Type '56'
Timestamp: 2026-03-23 14:23:37 | User: Unknown | Action: Delete Hold Type | Details: User admin deleted Hold Type '61'
Timestamp: 2026-03-23 14:23:41 | User: Unknown | Action: Delete Hold Type | Details: User admin deleted Hold Type '62'
Timestamp: 2026-03-23 14:23:45 | User: Unknown | Action: Delete Hold Type | Details: User admin deleted Hold Type '60'
Timestamp: 2026-03-23 14:23:48 | User: Unknown | Action: Delete Hold Type | Details: User admin deleted Hold Type '16'
Timestamp: 2026-03-23 14:23:51 | User: Unknown | Action: Delete Hold Type | Details: User admin deleted Hold Type '24'
Timestamp: 2026-03-23 14:23:53 | User: Unknown | Action: Delete Hold Type | Details: User admin deleted Hold Type '20'
Timestamp: 2026-03-23 14:23:55 | User: Unknown | Action: Delete Hold Type | Details: User admin deleted Hold Type '11'
Timestamp: 2026-03-23 14:23:58 | User: Unknown | Action: Delete Hold Type | Details: User admin deleted Hold Type '42'
Timestamp: 2026-03-23 14:24:03 | User: Unknown | Action: Delete Hold Type | Details: User admin deleted Hold Type '59'
Timestamp: 2026-03-23 14:24:08 | User: Unknown | Action: Delete Hold Type | Details: User admin deleted Hold Type '65'
Timestamp: 2026-03-23 14:24:14 | User: Unknown | Action: Delete Hold Type | Details: User admin deleted Hold Type '31'
Timestamp: 2026-03-23 14:24:19 | User: Unknown | Action: Delete Hold Type | Details: User admin deleted Hold Type '37'
Timestamp: 2026-03-23 14:24:24 | User: Unknown | Action: Delete Hold Type | Details: User admin deleted Hold Type '22'
Timestamp: 2026-03-23 14:24:29 | User: Unknown | Action: Delete Hold Type | Details: User admin deleted Hold Type '27'
Timestamp: 2026-03-23 14:24:34 | User: Unknown | Action: Delete Hold Type | Details: User admin deleted Hold Type '55'
Timestamp: 2026-03-23 14:24:39 | User: Unknown | Action: Delete Hold Type | Details: User admin deleted Hold Type '9'
Timestamp: 2026-03-23 14:24:44 | User: Unknown | Action: Delete Hold Type | Details: User admin deleted Hold Type '47'
Timestamp: 2026-03-23 14:24:49 | User: Unknown | Action: Delete Hold Type | Details: User admin deleted Hold Type '8'
Timestamp: 2026-03-23 14:25:11 | User: Unknown | Action: Delete Hold Type | Details: User admin deleted Hold Type '5'
Timestamp: 2026-03-23 14:25:16 | User: Unknown | Action: Delete Hold Type | Details: User admin deleted Hold Type '64'
Timestamp: 2026-03-23 14:25:29 | User: Unknown | Action: Delete Hold Type | Details: User admin deleted Hold Type '36'
Timestamp: 2026-03-23 14:25:31 | User: Unknown | Action: Delete Hold Type | Details: User admin deleted Hold Type '12'
Timestamp: 2026-03-23 14:25:33 | User: Unknown | Action: Delete Hold Type | Details: User admin deleted Hold Type '49'
Timestamp: 2026-03-23 14:25:36 | User: Unknown | Action: Delete Hold Type | Details: User admin deleted Hold Type '32'
Timestamp: 2026-03-23 14:26:47 | User: Unknown | Action: Delete Hold Type | Details: User admin deleted Hold Type '48'
Timestamp: 2026-03-23 14:27:06 | User: Unknown | Action: Delete Hold Type | Details: User admin deleted Hold Type '43'
Timestamp: 2026-03-23 14:27:09 | User: Unknown | Action: Delete Hold Type | Details: User admin deleted Hold Type '53'
Timestamp: 2026-03-23 14:27:11 | User: Unknown | Action: Delete Hold Type | Details: User admin deleted Hold Type '17'
Timestamp: 2026-03-23 14:27:13 | User: Unknown | Action: Delete Hold Type | Details: User admin deleted Hold Type '30'
Timestamp: 2026-03-23 14:27:15 | User: Unknown | Action: Delete Hold Type | Details: User admin deleted Hold Type '34'
Timestamp: 2026-03-23 14:27:18 | User: Unknown | Action: Delete Hold Type | Details: User admin deleted Hold Type '28'
Timestamp: 2026-03-23 14:27:20 | User: Unknown | Action: Delete Hold Type | Details: User admin deleted Hold Type '29'
Timestamp: 2026-03-23 14:27:22 | User: Unknown | Action: Delete Hold Type | Details: User admin deleted Hold Type '15'
Timestamp: 2026-03-23 14:27:24 | User: Unknown | Action: Delete Hold Type | Details: User admin deleted Hold Type '7'
Timestamp: 2026-03-23 14:27:26 | User: Unknown | Action: Delete Hold Type | Details: User admin deleted Hold Type '66'
Timestamp: 2026-03-23 14:27:28 | User: Unknown | Action: Delete Hold Type | Details: User admin deleted Hold Type '35'
Timestamp: 2026-03-23 14:27:30 | User: Unknown | Action: Delete Hold Type | Details: User admin deleted Hold Type '2'
Timestamp: 2026-03-23 14:27:32 | User: Unknown | Action: Delete Hold Type | Details: User admin deleted Hold Type '41'
Timestamp: 2026-03-23 14:27:34 | User: Unknown | Action: Delete Hold Type | Details: User admin deleted Hold Type '3'
Timestamp: 2026-03-23 14:27:36 | User: Unknown | Action: Delete Hold Type | Details: User admin deleted Hold Type '21'
Timestamp: 2026-03-23 14:27:38 | User: Unknown | Action: Delete Hold Type | Details: User admin deleted Hold Type '51'
Timestamp: 2026-03-23 14:27:40 | User: Unknown | Action: Delete Hold Type | Details: User admin deleted Hold Type '14'
Timestamp: 2026-03-23 14:27:42 | User: Unknown | Action: Delete Hold Type | Details: User admin deleted Hold Type '50'
Timestamp: 2026-03-23 14:27:44 | User: Unknown | Action: Delete Hold Type | Details: User admin deleted Hold Type '23'
Timestamp: 2026-03-23 14:27:46 | User: Unknown | Action: Delete Hold Type | Details: User admin deleted Hold Type '4'
Timestamp: 2026-03-23 14:27:48 | User: Unknown | Action: Delete Hold Type | Details: User admin deleted Hold Type '63'
Timestamp: 2026-03-23 14:27:50 | User: Unknown | Action: Delete Hold Type | Details: User admin deleted Hold Type '44'
Timestamp: 2026-03-23 14:27:52 | User: Unknown | Action: Delete Hold Type | Details: User admin deleted Hold Type '52'
Timestamp: 2026-03-23 14:27:55 | User: Unknown | Action: Delete Hold Type | Details: User admin deleted Hold Type '18'
Timestamp: 2026-03-23 14:27:58 | User: Unknown | Action: Delete Hold Type | Details: User admin deleted Hold Type '46'
Timestamp: 2026-03-23 14:28:00 | User: Unknown | Action: Delete Hold Type | Details: User admin deleted Hold Type '1'
Timestamp: 2026-03-23 14:28:05 | User: Unknown | Action: Delete Hold Type | Details: User admin deleted Hold Type '6'
Timestamp: 2026-03-23 14:28:07 | User: Unknown | Action: Delete Hold Type | Details: User admin deleted Hold Type '45'
Timestamp: 2026-03-23 14:28:09 | User: Unknown | Action: Delete Hold Type | Details: User admin deleted Hold Type '38'
Timestamp: 2026-03-23 14:28:11 | User: Unknown | Action: Delete Hold Type | Details: User admin deleted Hold Type '54'
Timestamp: 2026-03-23 14:28:13 | User: Unknown | Action: Delete Hold Type | Details: User admin deleted Hold Type '19'
Timestamp: 2026-03-23 14:28:15 | User: Unknown | Action: Delete Hold Type | Details: User admin deleted Hold Type '33'
Timestamp: 2026-03-23 14:28:17 | User: Unknown | Action: Delete Hold Type | Details: User admin deleted Hold Type '10'
Timestamp: 2026-03-23 14:28:19 | User: Unknown | Action: Delete Hold Type | Details: User admin deleted Hold Type '25'
Timestamp: 2026-03-23 14:28:21 | User: Unknown | Action: Delete Hold Type | Details: User admin deleted Hold Type '40'
Timestamp: 2026-03-23 14:42:49 | User: Unknown | Action: Delete Hold Type | Details: User admin deleted Hold Type '68'

114
app.log Normal file
View File

@@ -0,0 +1,114 @@
2025-02-15 11:07:16,100 - INFO - Logging is set up.
2025-02-15 11:07:16,100 - INFO - Logging is set up.
2025-02-15 11:07:16,131 - WARNING - * Debugger is active!
2025-02-15 11:07:16,131 - WARNING - * Debugger is active!
2025-02-15 11:07:16,137 - INFO - * Debugger PIN: 558-213-972
2025-02-15 11:07:16,137 - INFO - * Debugger PIN: 558-213-972
2025-02-15 11:13:26,290 - INFO - 127.0.0.1 - - [15/Feb/2025 11:13:26] "GET / HTTP/1.1" 200 -
2025-02-15 11:13:26,290 - INFO - 127.0.0.1 - - [15/Feb/2025 11:13:26] "GET / HTTP/1.1" 200 -
2025-02-15 11:13:26,315 - INFO - 127.0.0.1 - - [15/Feb/2025 11:13:26] "GET /static/css/index.css HTTP/1.1" 304 -
2025-02-15 11:13:26,315 - INFO - 127.0.0.1 - - [15/Feb/2025 11:13:26] "GET /static/css/index.css HTTP/1.1" 304 -
2025-02-15 11:13:26,504 - INFO - 127.0.0.1 - - [15/Feb/2025 11:13:26] "GET /static/css/style.css HTTP/1.1" 304 -
2025-02-15 11:13:26,504 - INFO - 127.0.0.1 - - [15/Feb/2025 11:13:26] "GET /static/css/style.css HTTP/1.1" 304 -
2025-02-15 11:13:26,626 - INFO - 127.0.0.1 - - [15/Feb/2025 11:13:26] "GET /static/js/validateFileInput.js HTTP/1.1" 304 -
2025-02-15 11:13:26,626 - INFO - 127.0.0.1 - - [15/Feb/2025 11:13:26] "GET /static/js/validateFileInput.js HTTP/1.1" 304 -
2025-02-15 11:13:26,633 - INFO - 127.0.0.1 - - [15/Feb/2025 11:13:26] "GET /static/js/searchContractor.js HTTP/1.1" 304 -
2025-02-15 11:13:26,633 - INFO - 127.0.0.1 - - [15/Feb/2025 11:13:26] "GET /static/js/searchContractor.js HTTP/1.1" 304 -
2025-02-15 11:13:26,950 - INFO - 127.0.0.1 - - [15/Feb/2025 11:13:26] "GET /favicon.ico HTTP/1.1" 404 -
2025-02-15 11:13:26,950 - INFO - 127.0.0.1 - - [15/Feb/2025 11:13:26] "GET /favicon.ico HTTP/1.1" 404 -
2025-02-15 11:13:28,623 - INFO - 127.0.0.1 - - [15/Feb/2025 11:13:28] "GET / HTTP/1.1" 200 -
2025-02-15 11:13:28,623 - INFO - 127.0.0.1 - - [15/Feb/2025 11:13:28] "GET / HTTP/1.1" 200 -
2025-02-15 11:13:28,933 - INFO - 127.0.0.1 - - [15/Feb/2025 11:13:28] "GET /static/css/index.css HTTP/1.1" 304 -
2025-02-15 11:13:28,933 - INFO - 127.0.0.1 - - [15/Feb/2025 11:13:28] "GET /static/css/index.css HTTP/1.1" 304 -
2025-02-15 11:13:28,952 - INFO - 127.0.0.1 - - [15/Feb/2025 11:13:28] "GET /static/js/validateFileInput.js HTTP/1.1" 304 -
2025-02-15 11:13:28,952 - INFO - 127.0.0.1 - - [15/Feb/2025 11:13:28] "GET /static/js/validateFileInput.js HTTP/1.1" 304 -
2025-02-15 11:13:28,954 - INFO - 127.0.0.1 - - [15/Feb/2025 11:13:28] "GET /static/js/searchContractor.js HTTP/1.1" 304 -
2025-02-15 11:13:28,955 - INFO - 127.0.0.1 - - [15/Feb/2025 11:13:28] "GET /static/css/style.css HTTP/1.1" 304 -
2025-02-15 11:13:28,954 - INFO - 127.0.0.1 - - [15/Feb/2025 11:13:28] "GET /static/js/searchContractor.js HTTP/1.1" 304 -
2025-02-15 11:13:28,955 - INFO - 127.0.0.1 - - [15/Feb/2025 11:13:28] "GET /static/css/style.css HTTP/1.1" 304 -
2025-02-15 11:13:31,608 - INFO - 127.0.0.1 - - [15/Feb/2025 11:13:31] "GET /add_state HTTP/1.1" 200 -
2025-02-15 11:13:31,608 - INFO - 127.0.0.1 - - [15/Feb/2025 11:13:31] "GET /add_state HTTP/1.1" 200 -
2025-02-15 11:13:31,639 - INFO - 127.0.0.1 - - [15/Feb/2025 11:13:31] "GET /static/css/style.css HTTP/1.1" 304 -
2025-02-15 11:13:31,639 - INFO - 127.0.0.1 - - [15/Feb/2025 11:13:31] "GET /static/css/style.css HTTP/1.1" 304 -
2025-02-15 11:13:31,649 - INFO - 127.0.0.1 - - [15/Feb/2025 11:13:31] "GET /static/images/icons/pen_blue_icon.png HTTP/1.1" 304 -
2025-02-15 11:13:31,649 - INFO - 127.0.0.1 - - [15/Feb/2025 11:13:31] "GET /static/images/icons/pen_blue_icon.png HTTP/1.1" 304 -
2025-02-15 11:13:31,967 - INFO - 127.0.0.1 - - [15/Feb/2025 11:13:31] "GET /static/images/icons/bin_red_icon.png HTTP/1.1" 304 -
2025-02-15 11:13:31,967 - INFO - 127.0.0.1 - - [15/Feb/2025 11:13:31] "GET /static/images/icons/bin_red_icon.png HTTP/1.1" 304 -
2025-02-15 11:15:01,349 - INFO - 127.0.0.1 - - [15/Feb/2025 11:15:01] "POST /check_state HTTP/1.1" 409 -
2025-02-15 11:15:01,349 - INFO - 127.0.0.1 - - [15/Feb/2025 11:15:01] "POST /check_state HTTP/1.1" 409 -
2025-02-15 11:15:21,783 - INFO - 127.0.0.1 - - [15/Feb/2025 11:15:21] "POST /check_state HTTP/1.1" 200 -
2025-02-15 11:15:21,783 - INFO - 127.0.0.1 - - [15/Feb/2025 11:15:21] "POST /check_state HTTP/1.1" 200 -
2025-02-15 11:15:22,127 - INFO - 127.0.0.1 - - [15/Feb/2025 11:15:22] "POST /check_state HTTP/1.1" 200 -
2025-02-15 11:15:22,127 - INFO - 127.0.0.1 - - [15/Feb/2025 11:15:22] "POST /check_state HTTP/1.1" 200 -
2025-02-15 11:15:22,151 - INFO - 127.0.0.1 - - [15/Feb/2025 11:15:22] "POST /check_state HTTP/1.1" 200 -
2025-02-15 11:15:22,151 - INFO - 127.0.0.1 - - [15/Feb/2025 11:15:22] "POST /check_state HTTP/1.1" 200 -
2025-02-15 11:15:22,391 - INFO - 127.0.0.1 - - [15/Feb/2025 11:15:22] "POST /check_state HTTP/1.1" 200 -
2025-02-15 11:15:22,391 - INFO - 127.0.0.1 - - [15/Feb/2025 11:15:22] "POST /check_state HTTP/1.1" 200 -
2025-02-15 11:15:22,440 - INFO - 127.0.0.1 - - [15/Feb/2025 11:15:22] "POST /check_state HTTP/1.1" 200 -
2025-02-15 11:15:22,440 - INFO - 127.0.0.1 - - [15/Feb/2025 11:15:22] "POST /check_state HTTP/1.1" 200 -
2025-02-15 11:15:24,266 - INFO - 127.0.0.1 - - [15/Feb/2025 11:15:24] "POST /add_state HTTP/1.1" 200 -
2025-02-15 11:15:24,266 - INFO - 127.0.0.1 - - [15/Feb/2025 11:15:24] "POST /add_state HTTP/1.1" 200 -
2025-02-15 11:15:25,418 - INFO - 127.0.0.1 - - [15/Feb/2025 11:15:25] "GET /add_state HTTP/1.1" 200 -
2025-02-15 11:15:25,418 - INFO - 127.0.0.1 - - [15/Feb/2025 11:15:25] "GET /add_state HTTP/1.1" 200 -
2025-02-15 11:15:25,716 - INFO - 127.0.0.1 - - [15/Feb/2025 11:15:25] "GET /static/css/style.css HTTP/1.1" 304 -
2025-02-15 11:15:25,716 - INFO - 127.0.0.1 - - [15/Feb/2025 11:15:25] "GET /static/css/style.css HTTP/1.1" 304 -
2025-02-15 11:15:25,749 - INFO - 127.0.0.1 - - [15/Feb/2025 11:15:25] "GET /static/images/icons/pen_blue_icon.png HTTP/1.1" 304 -
2025-02-15 11:15:25,749 - INFO - 127.0.0.1 - - [15/Feb/2025 11:15:25] "GET /static/images/icons/pen_blue_icon.png HTTP/1.1" 304 -
2025-02-15 11:15:25,752 - INFO - 127.0.0.1 - - [15/Feb/2025 11:15:25] "GET /static/images/icons/bin_red_icon.png HTTP/1.1" 304 -
2025-02-15 11:15:25,752 - INFO - 127.0.0.1 - - [15/Feb/2025 11:15:25] "GET /static/images/icons/bin_red_icon.png HTTP/1.1" 304 -
2025-02-15 11:16:46,338 - INFO - * Detected change in 'C:\\Users\\ADMIN\\PycharmProjects\\ManagementApplicationt\\main.py', reloading
2025-02-15 11:16:46,338 - INFO - * Detected change in 'C:\\Users\\ADMIN\\PycharmProjects\\ManagementApplicationt\\main.py', reloading
2025-02-15 11:16:48,798 - INFO - Logging is set up.
2025-02-15 11:16:48,798 - INFO - Logging is set up.
2025-02-15 11:16:48,843 - WARNING - * Debugger is active!
2025-02-15 11:16:48,843 - WARNING - * Debugger is active!
2025-02-15 11:16:48,847 - INFO - * Debugger PIN: 558-213-972
2025-02-15 11:16:48,847 - INFO - * Debugger PIN: 558-213-972
2025-02-15 11:16:52,045 - DEBUG - Fetched state data successfully.
2025-02-15 11:16:52,045 - DEBUG - Fetched state data successfully.
2025-02-15 11:16:52,054 - INFO - 127.0.0.1 - - [15/Feb/2025 11:16:52] "GET /add_state HTTP/1.1" 200 -
2025-02-15 11:16:52,054 - INFO - 127.0.0.1 - - [15/Feb/2025 11:16:52] "GET /add_state HTTP/1.1" 200 -
2025-02-15 11:16:52,076 - INFO - 127.0.0.1 - - [15/Feb/2025 11:16:52] "GET /static/css/style.css HTTP/1.1" 304 -
2025-02-15 11:16:52,076 - INFO - 127.0.0.1 - - [15/Feb/2025 11:16:52] "GET /static/css/style.css HTTP/1.1" 304 -
2025-02-15 11:16:52,078 - INFO - 127.0.0.1 - - [15/Feb/2025 11:16:52] "GET /static/images/icons/pen_blue_icon.png HTTP/1.1" 304 -
2025-02-15 11:16:52,078 - INFO - 127.0.0.1 - - [15/Feb/2025 11:16:52] "GET /static/images/icons/pen_blue_icon.png HTTP/1.1" 304 -
2025-02-15 11:16:52,377 - INFO - 127.0.0.1 - - [15/Feb/2025 11:16:52] "GET /static/images/icons/bin_red_icon.png HTTP/1.1" 304 -
2025-02-15 11:16:52,377 - INFO - 127.0.0.1 - - [15/Feb/2025 11:16:52] "GET /static/images/icons/bin_red_icon.png HTTP/1.1" 304 -
2025-02-15 11:16:54,758 - INFO - 127.0.0.1 - - [15/Feb/2025 11:16:54] "POST /check_state HTTP/1.1" 200 -
2025-02-15 11:16:54,758 - INFO - 127.0.0.1 - - [15/Feb/2025 11:16:54] "POST /check_state HTTP/1.1" 200 -
2025-02-15 11:16:54,992 - INFO - 127.0.0.1 - - [15/Feb/2025 11:16:54] "POST /check_state HTTP/1.1" 200 -
2025-02-15 11:16:54,992 - INFO - 127.0.0.1 - - [15/Feb/2025 11:16:54] "POST /check_state HTTP/1.1" 200 -
2025-02-15 11:16:55,016 - INFO - 127.0.0.1 - - [15/Feb/2025 11:16:55] "POST /check_state HTTP/1.1" 200 -
2025-02-15 11:16:55,016 - INFO - 127.0.0.1 - - [15/Feb/2025 11:16:55] "POST /check_state HTTP/1.1" 200 -
2025-02-15 11:16:55,669 - INFO - State 'sss' added successfully.
2025-02-15 11:16:55,669 - INFO - 'State 'sss added successfully.
2025-02-15 11:16:55,670 - INFO - 127.0.0.1 - - [15/Feb/2025 11:16:55] "POST /add_state HTTP/1.1" 200 -
2025-02-15 11:16:55,670 - INFO - 127.0.0.1 - - [15/Feb/2025 11:16:55] "POST /add_state HTTP/1.1" 200 -
2025-02-15 11:16:57,235 - DEBUG - Fetched state data successfully.
2025-02-15 11:16:57,235 - DEBUG - Fetched state data successfully.
2025-02-15 11:16:57,239 - INFO - 127.0.0.1 - - [15/Feb/2025 11:16:57] "GET /add_state HTTP/1.1" 200 -
2025-02-15 11:16:57,239 - INFO - 127.0.0.1 - - [15/Feb/2025 11:16:57] "GET /add_state HTTP/1.1" 200 -
2025-02-15 11:16:57,263 - INFO - 127.0.0.1 - - [15/Feb/2025 11:16:57] "GET /static/css/style.css HTTP/1.1" 304 -
2025-02-15 11:16:57,263 - INFO - 127.0.0.1 - - [15/Feb/2025 11:16:57] "GET /static/css/style.css HTTP/1.1" 304 -
2025-02-15 11:16:57,483 - INFO - 127.0.0.1 - - [15/Feb/2025 11:16:57] "GET /static/images/icons/pen_blue_icon.png HTTP/1.1" 304 -
2025-02-15 11:16:57,483 - INFO - 127.0.0.1 - - [15/Feb/2025 11:16:57] "GET /static/images/icons/pen_blue_icon.png HTTP/1.1" 304 -
2025-02-15 11:16:57,567 - INFO - 127.0.0.1 - - [15/Feb/2025 11:16:57] "GET /static/images/icons/bin_red_icon.png HTTP/1.1" 304 -
2025-02-15 11:16:57,567 - INFO - 127.0.0.1 - - [15/Feb/2025 11:16:57] "GET /static/images/icons/bin_red_icon.png HTTP/1.1" 304 -
2025-02-15 11:20:55,547 - INFO - * Detected change in 'C:\\Users\\ADMIN\\PycharmProjects\\ManagementApplicationt\\main.py', reloading
2025-02-15 11:20:55,547 - INFO - * Detected change in 'C:\\Users\\ADMIN\\PycharmProjects\\ManagementApplicationt\\main.py', reloading
2025-02-15 11:20:56,800 - INFO - Logging is set up.
2025-02-15 11:20:56,800 - INFO - Logging is set up.
2025-02-15 11:20:56,835 - WARNING - * Debugger is active!
2025-02-15 11:20:56,835 - WARNING - * Debugger is active!
2025-02-15 11:20:56,837 - INFO - * Debugger PIN: 558-213-972
2025-02-15 11:20:56,837 - INFO - * Debugger PIN: 558-213-972
2025-02-15 11:21:04,060 - INFO - * Detected change in 'C:\\Users\\ADMIN\\PycharmProjects\\ManagementApplicationt\\main.py', reloading
2025-02-15 11:21:04,060 - INFO - * Detected change in 'C:\\Users\\ADMIN\\PycharmProjects\\ManagementApplicationt\\main.py', reloading
2025-02-15 11:21:05,429 - INFO - Logging is set up.
2025-02-15 11:21:05,429 - INFO - Logging is set up.
2025-02-15 11:21:05,461 - WARNING - * Debugger is active!
2025-02-15 11:21:05,461 - WARNING - * Debugger is active!
2025-02-15 11:21:05,463 - INFO - * Debugger PIN: 558-213-972
2025-02-15 11:21:05,463 - INFO - * Debugger PIN: 558-213-972
2025-02-15 11:21:17,911 - INFO - * Detected change in 'C:\\Users\\ADMIN\\PycharmProjects\\ManagementApplicationt\\main.py', reloading
2025-02-15 11:21:17,911 - INFO - * Detected change in 'C:\\Users\\ADMIN\\PycharmProjects\\ManagementApplicationt\\main.py', reloading

21
config.py Normal file
View File

@@ -0,0 +1,21 @@
import mysql.connector
import os
from dotenv import load_dotenv
# Load environment variables from .env file
load_dotenv()
# Get MySQL credentials from environment variables
MYSQL_HOST = os.getenv("MYSQL_HOST")
MYSQL_USER = os.getenv("MYSQL_USER")
MYSQL_PASSWORD = os.getenv("MYSQL_PASSWORD")
MYSQL_DB = os.getenv("MYSQL_DB")
# Connect to MySQL
def get_db_connection():
return mysql.connector.connect(
host=MYSQL_HOST,
user=MYSQL_USER,
password=MYSQL_PASSWORD,
database=MYSQL_DB
)

View File

@@ -0,0 +1,46 @@
from flask import Blueprint, render_template, request, redirect, url_for, flash, session
from flask_login import login_user, logout_user, login_required, current_user
from model.Auth import LoginLDAP, User
from model.Log import LogHelper
auth_bp = Blueprint('auth', __name__)
@auth_bp.route('/login', methods=['GET', 'POST'])
def login():
if request.method == 'POST':
loginData = LoginLDAP(request)
if loginData.isValidLogin:
if loginData.isDefaultCredentials:
LogHelper.log_action('Login', f"User {loginData.username} logged in (static user)")
else:
LogHelper.log_action('Login', f"User {loginData.username} logged in (LDAP)")
session['username'] = loginData.username
login_user(User(loginData.username))
return redirect(url_for('index'))
else:
flash(loginData.errorMessage, 'danger')
return render_template("login.html")
@auth_bp.route('/logout')
@login_required
def logout():
LogHelper.log_action('Logout', f"User {current_user.id} logged out")
logout_user()
flash('You have been logged out.', 'info')
return redirect(url_for('auth.login'))

View File

@@ -0,0 +1,119 @@
from flask import Blueprint, render_template, request, redirect, url_for, jsonify
from flask_login import login_required
import config
import mysql.connector
from model.Block import Block
from model.Utilities import HtmlHelper
block_bp = Blueprint('block', __name__)
@block_bp.route('/add_block', methods=['GET', 'POST'])
@login_required
def add_block():
block = Block()
if request.method == 'POST':
block.AddBlock(request)
return block.resultMessage
connection = config.get_db_connection()
cursor = connection.cursor()
cursor.callproc("GetAllStates")
for rs in cursor.stored_results():
states = rs.fetchall()
block_data = block.GetAllBlocks(request)
cursor.close()
connection.close()
return render_template(
'add_block.html',
states=states,
block_data=block_data
)
# ✅ NEW ROUTE (FIX FOR DISTRICT FETCH)
@block_bp.route('/get_districts/<int:state_id>')
@login_required
def get_districts(state_id):
connection = config.get_db_connection()
cursor = connection.cursor()
cursor.callproc("GetDistrictsByStateId", (state_id,))
districts = []
for rs in cursor.stored_results():
districts = rs.fetchall()
cursor.close()
connection.close()
district_list = []
for district in districts:
district_list.append({
"id": district[0],
"name": district[1]
})
return jsonify(district_list)
@block_bp.route('/check_block', methods=['POST'])
@login_required
def check_block():
block = Block()
return block.CheckBlock(request)
@block_bp.route('/edit_block/<int:block_id>', methods=['GET', 'POST'])
@login_required
def edit_block(block_id):
block = Block()
if request.method == 'POST':
block.EditBlock(request, block_id)
return block.resultMessage
connection = config.get_db_connection()
cursor = connection.cursor()
cursor.callproc("GetAllStates")
for rs in cursor.stored_results():
states = rs.fetchall()
cursor.callproc("GetAllDistrictsData")
for rs in cursor.stored_results():
districts = rs.fetchall()
block_data = block.GetBlockByID(block_id)
cursor.close()
connection.close()
return render_template(
'edit_block.html',
block_data=block_data,
states=states,
districts=districts
)
@block_bp.route('/delete_block/<int:block_id>')
@login_required
def delete_block(block_id):
block = Block()
block.DeleteBlock(request, block_id)
return redirect(url_for('block.add_block'))

View File

@@ -0,0 +1,69 @@
from flask import Blueprint, render_template, request, redirect, url_for, flash
from flask_login import login_required
from model.District import District
from model.State import State
district_bp = Blueprint('district', __name__)
@district_bp.route('/add_district', methods=['GET', 'POST'])
@login_required
def add_district():
district = District()
state = State()
if request.method == 'POST':
district.AddDistrict(request=request)
if district.isSuccess:
flash(district.resultMessage, "success")
else:
flash(district.resultMessage, "error")
return redirect(url_for('district.add_district'))
states = state.GetAllStates(request=request)
districtdata = district.GetAllDistricts(request=request)
return render_template(
'add_district.html',
states=states,
districtdata=districtdata
)
@district_bp.route('/delete_district/<int:district_id>')
@login_required
def delete_district(district_id):
district = District()
district.DeleteDistrict(request=request, district_id=district_id)
if not district.isSuccess:
flash(district.resultMessage, "error")
else:
flash(district.resultMessage, "success")
return redirect(url_for('district.add_district'))
@district_bp.route('/edit_district/<int:district_id>', methods=['GET', 'POST'])
@login_required
def edit_district(district_id):
district = District()
state = State()
if request.method == 'POST':
district.EditDistrict(request=request, district_id=district_id)
if district.isSuccess:
flash("District updated successfully!", "success")
return redirect(url_for('district.add_district'))
else:
flash(district.resultMessage, "error")
districtdata = district.GetDistrictByID(request=request, district_id=district_id)
if not districtdata:
flash("District not found", "error")
return redirect(url_for('district.add_district'))
states = state.GetAllStates(request=request)
return render_template(
'edit_district.html',
districtdata=districtdata,
states=states
)

View File

@@ -0,0 +1,389 @@
import os
import ast
import re
from flask_login import login_required
import openpyxl
from flask import Blueprint, request, render_template, redirect, url_for, jsonify, current_app
from flask_login import current_user
from model.Log import LogHelper
import config # your database connection module
excel_bp = Blueprint('excel', __name__)
# Default folder in case config not set
DEFAULT_UPLOAD_FOLDER = 'uploads'
def get_upload_folder():
"""Returns the upload folder from Flask config or default, ensures it exists."""
folder = current_app.config.get('UPLOAD_FOLDER', DEFAULT_UPLOAD_FOLDER)
if not os.path.exists(folder):
os.makedirs(folder)
return folder
# ---------------- Upload Excel File ----------------
@excel_bp.route('/upload_excel_file', methods=['GET', 'POST'])
@login_required
def upload():
if request.method == 'POST':
file = request.files.get('file')
if file and file.filename.endswith('.xlsx'):
upload_folder = get_upload_folder()
filepath = os.path.join(upload_folder, file.filename)
file.save(filepath)
LogHelper.log_action(
"Upload Excel File",
f"User {current_user.id} Upload Excel File '{file.filename}'"
)
return redirect(url_for('excel.show_table', filename=file.filename))
return render_template('uploadExcelFile.html')
# ---------------- Show Excel Table ----------------
@excel_bp.route('/show_table/<filename>')
def show_table(filename):
global data
data = []
filepath = os.path.join(get_upload_folder(), filename)
wb = openpyxl.load_workbook(filepath, data_only=True)
sheet = wb.active
file_info = {
"Subcontractor": sheet.cell(row=1, column=2).value,
"State": sheet.cell(row=2, column=2).value,
"District": sheet.cell(row=3, column=2).value,
"Block": sheet.cell(row=4, column=2).value,
}
errors = []
subcontractor_data = None
state_data = None
district_data = None
block_data = None
connection = config.get_db_connection()
if connection:
try:
cursor = connection.cursor(dictionary=True)
print(f"Calling GetStateByName with: {file_info['State']}")
cursor.callproc('GetStateByName', [file_info['State']])
for result in cursor.stored_results():
state_data = result.fetchone()
if not state_data:
errors.append(f"State '{file_info['State']}' is not valid. Please add it.")
if state_data:
print(f"Calling GetDistrictByNameAndStates with: {file_info['District']}, {state_data['State_ID']}")
cursor.callproc('GetDistrictByNameAndStates', [file_info['District'], state_data['State_ID']])
for result in cursor.stored_results():
district_data = result.fetchone()
if not district_data:
errors.append(f"District '{file_info['District']}' is not valid under state '{file_info['State']}'.")
if district_data:
print(f"Calling GetBlockByNameAndDistricts with: {file_info['Block']}, {district_data['District_ID']}")
cursor.callproc('GetBlockByNameAndDistricts', [file_info['Block'], district_data['District_ID']])
for result in cursor.stored_results():
block_data = result.fetchone()
if not block_data:
errors.append(f"Block '{file_info['Block']}' is not valid under district '{file_info['District']}'.")
print(f"Calling GetSubcontractorByName with: {file_info['Subcontractor']}")
cursor.callproc('GetSubcontractorByName', [file_info['Subcontractor']])
for result in cursor.stored_results():
subcontractor_data = result.fetchone()
if not subcontractor_data:
print(f"Inserting subcontractor: {file_info['Subcontractor']}")
cursor.callproc('InsertSubcontractor', [file_info['Subcontractor']])
connection.commit()
cursor.callproc('GetSubcontractorByName', [file_info['Subcontractor']])
for result in cursor.stored_results():
subcontractor_data = result.fetchone()
print("Calling GetAllHoldTypes")
cursor.callproc("GetAllHoldTypes")
hold_types_data = []
for ht in cursor.stored_results():
hold_types_data = ht.fetchall()
hold_types_lookup = {row['hold_type'].lower(): row['hold_type_id'] for row in hold_types_data if row['hold_type']}
cursor.close()
except Exception as e:
print(f"Database error: {e}")
return f"Database operation failed: {e}", 500
finally:
connection.close()
variables = {}
hold_columns = []
hold_counter = 0
for j in range(1, sheet.max_column + 1):
col_value = sheet.cell(row=5, column=j).value
if col_value:
variables[col_value] = j
if 'hold' in str(col_value).lower():
hold_counter += 1
hold_type_key = str(col_value).lower().strip()
hold_type_id = hold_types_lookup.get(hold_type_key)
hold_columns.append({
'column_name': col_value,
'column_number': j,
'hold_type_id': hold_type_id
})
for i in range(6, sheet.max_row + 1):
row_data = {}
if sheet.cell(row=i, column=1).value:
row_data["Row Number"] = i
for var_name, col_num in variables.items():
row_data[var_name] = sheet.cell(row=i, column=col_num).value
if sum(1 for value in row_data.values() if value) >= 4:
data.append(row_data)
for hold in hold_columns:
if hold['hold_type_id']:
print(f" if Column: {hold['column_name']}, Column Number: {hold['column_number']}, Hold Type ID: {hold['hold_type_id']}")
else:
errors.append(f"Hold Type not added ! Column name '{hold['column_name']}'.")
print(f" else Column: {hold['column_name']}, Column Number: {hold['column_number']}, Hold Type ID: {hold['hold_type_id']}")
return render_template(
'show_excel_file.html',
file_info=file_info,
variables=variables,
data=data,
subcontractor_data=subcontractor_data,
state_data=state_data,
district_data=district_data,
block_data=block_data,
errors=errors,
hold_columns=hold_columns,
hold_counter=hold_counter
)
# save Excel data
@excel_bp.route('/save_data', methods=['POST'])
def save_data():
# Extract form data
subcontractor_id = request.form.get("subcontractor_data")
state_id = request.form.get("state_data")
district_id = request.form.get("district_data")
block_id = request.form.get("block_data")
variables = request.form.getlist('variables[]')
hold_columns = request.form.get("hold_columns")
hold_counter = request.form.get("hold_counter")
if not data:
return jsonify({"error": "No data provided to save"}), 400
if data:
connection = config.get_db_connection()
cursor = connection.cursor()
try:
for entry in data:
save_data = {
"PMC_No": entry.get("PMC_No"),
"Invoice_Details": entry.get("Invoice_Details", ''),
"Work_Type": 'none',
"Invoice_Date": entry.get("Invoice_Date").strftime('%Y-%m-%d') if entry.get(
"Invoice_Date") else None,
"Invoice_No": entry.get("Invoice_No", ''),
"Basic_Amount": entry.get("Basic_Amount", 0.00),
"Debit_Amount": entry.get("Debit_Amount", 0.00),
"After_Debit_Amount": entry.get("After_Debit_Amount", 0.00),
"Amount": entry.get("Amount", 0.00),
"GST_Amount": entry.get("GST_Amount", 0.00),
"TDS_Amount": entry.get("TDS_Amount", 0.00),
"SD_Amount": entry.get("SD_Amount", 0.00),
"On_Commission": entry.get("On_Commission", 0.00),
"Hydro_Testing": entry.get("Hydro_Testing", 0.00),
"Hold_Amount": 0,
"GST_SD_Amount": entry.get("GST_SD_Amount", 0.00),
"Final_Amount": entry.get("Final_Amount", 0.00),
"Payment_Amount": entry.get("Payment_Amount", 0.00),
"Total_Amount": entry.get("Total_Amount", 0.00),
"TDS_Payment_Amount": entry.get("TDS_Payment_Amount", 0.00),
"UTR": entry.get("UTR", ''),
}
village_name, work_type = None, None
village_id = 0
LogHelper.log_action("Data saved", f"User {current_user.id} Data saved'{ village_name}'")
PMC_No = save_data.get('PMC_No')
Invoice_Details = save_data.get('Invoice_Details')
Invoice_Date = save_data.get('Invoice_Date')
Invoice_No = save_data.get('Invoice_No')
Basic_Amount = save_data.get('Basic_Amount')
Debit_Amount = save_data.get('Debit_Amount')
After_Debit_Amount = save_data.get('After_Debit_Amount')
Amount = save_data.get('Amount')
GST_Amount = save_data.get('GST_Amount')
TDS_Amount = save_data.get('TDS_Amount')
SD_Amount = save_data.get('SD_Amount')
On_Commission = save_data.get('On_Commission')
Hydro_Testing = save_data.get('Hydro_Testing')
GST_SD_Amount = save_data.get('GST_SD_Amount')
Final_Amount = save_data.get('Final_Amount')
Payment_Amount = save_data.get('Payment_Amount')
Total_Amount = save_data.get('Total_Amount')
TDS_Payment_Amount = save_data.get('TDS_Payment_Amount')
UTR = save_data.get('UTR')
if Invoice_Details:
words = Invoice_Details.lower().split()
if 'village' in words:
village_pos = words.index('village')
village_name = " ".join(words[:village_pos])
if 'work' in words:
work_pos = words.index('work')
if village_name:
work_type = " ".join(words[village_pos + 1:work_pos + 1])
else:
work_type = " ".join(words[:work_pos + 1])
if Invoice_Details and 'village' in Invoice_Details.lower() and 'work' in Invoice_Details.lower():
print("village_name ::", village_name, "|| work_type ::", work_type)
if block_id and village_name:
village_id = None
cursor.callproc("GetVillageId", (block_id, village_name))
for result in cursor.stored_results():
result = result.fetchone()
village_id = result[0] if result else None
if not village_id:
cursor.callproc("SaveVillage", (village_name, block_id))
cursor.callproc("GetVillageId", (block_id, village_name))
for result in cursor.stored_results():
result = result.fetchone()
village_id = result[0] if result else None
print("village_id :", village_id)
print("block_id :", block_id)
print("invoice :", PMC_No, village_id, work_type, Invoice_Details, Invoice_Date, Invoice_No,
Basic_Amount, Debit_Amount, After_Debit_Amount, Amount, GST_Amount, TDS_Amount,
SD_Amount, On_Commission, Hydro_Testing, GST_SD_Amount, Final_Amount)
args = (
PMC_No, village_id, work_type, Invoice_Details, Invoice_Date, Invoice_No,
Basic_Amount, Debit_Amount, After_Debit_Amount, Amount, GST_Amount, TDS_Amount,
SD_Amount, On_Commission, Hydro_Testing, GST_SD_Amount, Final_Amount,
subcontractor_id, 0
)
print("All invoice Details ",args)
results = cursor.callproc('SaveInvoice', args)
invoice_id = results[-1]
print("invoice id from the excel ", invoice_id)
if isinstance(hold_columns, str):
hold_columns = ast.literal_eval(hold_columns)
if isinstance(hold_columns, list) and all(isinstance(hold, dict) for hold in hold_columns):
for hold in hold_columns:
print(f"Processing hold: {hold}")
hold_column_name = hold.get('column_name') # Get column name
hold_type_id = hold.get('hold_type_id') # Get hold_type_id
if hold_column_name:
hold_amount = entry.get(
hold_column_name) # Get the value for that specific hold column
if hold_amount is not None:
print(f"Processing hold type: {hold_column_name}, Hold Amount: {hold_amount}")
hold_join_data = {
"Contractor_Id": subcontractor_id,
"Invoice_Id": invoice_id,
"hold_type_id": hold_type_id,
"hold_amount": hold_amount
}
cursor.callproc('InsertHoldJoinData', [
hold_join_data['Contractor_Id'], hold_join_data['Invoice_Id'],
hold_join_data['hold_type_id'], hold_join_data['hold_amount']
])
connection.commit()
print(f"Inserted hold join data: {hold_join_data}")
else:
print(f"Invalid hold entry: {hold}")
else:
print("Hold columns data is not a valid list of dictionaries.")
#---------------------------------------------Credit Note---------------------------------------------------------------------------
elif any(keyword in Invoice_Details.lower() for keyword in ['credit note','logging report']):
print("Credit note found:", PMC_No, Invoice_No, Basic_Amount, Debit_Amount, Final_Amount,
After_Debit_Amount, GST_Amount, Amount, Final_Amount, Payment_Amount, Total_Amount, UTR, Invoice_No)
cursor.callproc(
'AddCreditNoteFromExcel',
[
PMC_No, Invoice_Details, Basic_Amount, Debit_Amount, After_Debit_Amount,
GST_Amount, Amount, Final_Amount, Payment_Amount, Total_Amount, UTR,
subcontractor_id, Invoice_No
]
)
#-----------------------------------------------Hold Amount----------------------------------------------------------------------
# Step 1: Normalize Invoice_Details: lowercase, trim, remove extra spaces
normalized_details = re.sub(r'\s+', ' ', Invoice_Details.strip()).lower()
# Step 2: Define lowercase keywords
keywords = [
'excess hold',
'ht',
'hold release amount',
'dpr excess hold amount',
'excess hold amount',
'Multi to Single layer bill',
'hold amount',
'logging report'
]
# Step 3: Matching condition
if any(kw in normalized_details for kw in keywords):
print("✅ Match found. Inserting hold release for:", Invoice_Details)
cursor.callproc(
'AddHoldReleaseFromExcel',
[PMC_No, Invoice_No, Invoice_Details, Basic_Amount, Final_Amount, UTR, subcontractor_id]
)
connection.commit()
print("✅ Hold release inserted for:", PMC_No, Invoice_Details)
#------------------------------------------------------------------------------------------------------------------
elif Invoice_Details and any(
keyword in Invoice_Details.lower() for keyword in ['gst', 'release', 'note']):
print("Gst rels :", PMC_No, Invoice_No, Basic_Amount, Final_Amount,Total_Amount,UTR, subcontractor_id)
cursor.callproc(
'AddGSTReleaseFromExcel',
[PMC_No, Invoice_No, Basic_Amount, Final_Amount, Total_Amount, UTR, subcontractor_id]
)
if PMC_No and Total_Amount and UTR:
print("Payment :", PMC_No, Invoice_No, Payment_Amount, TDS_Payment_Amount, Total_Amount, UTR )
cursor.callproc("SavePayment",(PMC_No, Invoice_No, Payment_Amount, TDS_Payment_Amount, Total_Amount, UTR ))
if not village_id:
village_id = None
cursor.callproc('InsertOrUpdateInPayment', (
PMC_No,
village_id,
work_type,
Invoice_Details,
Invoice_Date,
Invoice_No,
Basic_Amount,
Debit_Amount,
After_Debit_Amount,
Amount,
GST_Amount,
TDS_Amount,
SD_Amount,
On_Commission,
Hydro_Testing,
0,
GST_SD_Amount,
Final_Amount,
Payment_Amount,
TDS_Payment_Amount,
Total_Amount,
UTR,
subcontractor_id
))
connection.commit()
return jsonify({"success": "Data saved successfully!"}), 200
except Exception as e:
connection.rollback()
return jsonify({"error": f"An unexpected error occurred: {e}"}), 500
finally:
cursor.close()
connection.close()
return render_template('index.html')
# ---------------------- Report --------------------------------

View File

@@ -0,0 +1,46 @@
from flask import Blueprint, render_template, request, redirect, url_for
from flask_login import login_required
from model.gst_release import GSTRelease
from model.Log import LogHelper
from flask import flash, current_app
gst_release_bp = Blueprint('gst_release_bp', __name__)
gst_service = GSTRelease()
# ---------------- ADD GST RELEASE ----------------
@gst_release_bp.route('/add_gst_release', methods=['GET', 'POST'])
@login_required
def add_gst_release():
if request.method == 'POST':
gst_service.AddGSTRelease(request)
LogHelper.log_action("Add GST Release", f"User added GST release")
flash(gst_service.resultMessage, 'success' if gst_service.isSuccess else 'error')
return redirect(url_for('gst_release_bp.add_gst_release'))
gst_releases = gst_service.GetAllGSTReleases()
return render_template('add_gst_release.html', gst_releases=gst_releases)
# ---------------- EDIT GST RELEASE ----------------
@gst_release_bp.route('/edit_gst_release/<int:gst_release_id>', methods=['GET', 'POST'])
@login_required
def edit_gst_release(gst_release_id):
gst_data = gst_service.GetGSTReleaseByID(gst_release_id)
if not gst_data:
return "GST Release not found", 404
if request.method == 'POST':
gst_service.EditGSTRelease(request, gst_release_id)
LogHelper.log_action("Edit GST Release", f"User edited GST release")
flash(gst_service.resultMessage, 'success' if gst_service.isSuccess else 'error')
return redirect(url_for('gst_release_bp.add_gst_release'))
return render_template('edit_gst_release.html', gst_release_data=gst_data)
# ---------------- DELETE GST RELEASE ----------------
@gst_release_bp.route('/delete_gst_release/<int:gst_release_id>', methods=['GET', 'POST'])
@login_required
def delete_gst_release(gst_release_id):
gst_service.DeleteGSTRelease(gst_release_id) # remove request
LogHelper.log_action("Delete GST Release", f"User deleted GST release")
flash(gst_service.resultMessage, 'success' if gst_service.isSuccess else 'error')
return redirect(url_for('gst_release_bp.add_gst_release'))

View File

@@ -0,0 +1,88 @@
from flask import Blueprint, render_template, request, jsonify, redirect, url_for
from flask_login import login_required
from model.HoldTypes import HoldTypes
from model.GST import GST
hold_bp = Blueprint("hold_types", __name__)
# ---------------- ADD HOLD TYPE ----------------
@hold_bp.route('/add_hold_type', methods=['GET','POST'])
@login_required
def add_hold_type():
hold = HoldTypes()
if request.method == 'POST':
hold.AddHoldType(request)
# ✅ Redirect instead of returning JSON
return redirect(url_for("hold_types.add_hold_type"))
hold_types = hold.GetAllHoldTypes()
return render_template(
"add_hold_type.html",
Hold_Types_data=hold_types
)
# ---------------- CHECK HOLD TYPE (OPTIONAL LIKE BLOCK) ----------------
@hold_bp.route('/check_hold_type', methods=['POST'])
@login_required
def check_hold_type():
hold = HoldTypes()
return hold.CheckHoldType(request) # if exists
# ---------------- EDIT HOLD TYPE ----------------
from flask import flash, redirect, url_for
@hold_bp.route('/edit_hold_type/<int:id>', methods=['GET','POST'])
@login_required
def edit_hold_type(id):
hold = HoldTypes()
if request.method == 'POST':
hold.EditHoldType(request, id)
# ✅ Handle success/failure
if hold.isSuccess:
flash("Hold Type updated successfully!", "success")
else:
flash(hold.resultMessage, "error")
return redirect(url_for("hold_types.add_hold_type")) # ✅ FIX
hold_data = hold.GetHoldTypeByID(id)
return render_template(
"edit_hold_type.html",
hold_type=hold_data
)
# ---------------- DELETE HOLD TYPE ----------------
@hold_bp.route('/delete_hold_type/<int:id>')
@login_required
def delete_hold_type(id):
hold = HoldTypes()
hold.DeleteHoldType(request, id) # ✅
return redirect(url_for("hold_types.add_hold_type"))
# ---------------- GST ----------------
@hold_bp.route('/unreleased_gst')
@login_required
def unreleased_gst():
data = GST.get_unreleased_gst()
return render_template(
"unreleased_gst.html",
data=data
)

View File

@@ -0,0 +1,98 @@
# controllers/invoice_controller.py
from flask import Blueprint, request, jsonify, render_template
from flask_login import login_required, current_user
from model.Invoice import *
from model.Log import LogHelper
invoice_bp = Blueprint('invoice', __name__)
# -------------------------------- Add Invoice ---------------------------------
@invoice_bp.route('/add_invoice', methods=['GET', 'POST'])
@login_required
def add_invoice():
if request.method == 'POST':
try:
village_name = request.form.get('village')
village_result = get_village_id(village_name)
if not village_result:
return jsonify({"status": "error", "message": f"Village '{village_name}' not found"}), 400
village_id = village_result['Village_Id']
data = request.form
invoice_id = insert_invoice(data, village_id)
assign_subcontractor(data, village_id)
insert_hold_types(data, invoice_id)
LogHelper.log_action("Add invoice", f"User {current_user.id} Added invoice '{data.get('pmc_no')}'")
return jsonify({"status": "success", "message": "Invoice added successfully"}), 201
except Exception as e:
return jsonify({"status": "error", "message": str(e)}), 500
invoices = get_all_invoice_details()
villages = get_all_villages()
return render_template('add_invoice.html', invoices=invoices, villages=villages)
# ------------------- Search Subcontractor -------------------
@invoice_bp.route('/search_subcontractor', methods=['POST'])
@login_required
def search_subcontractor():
sub_query = request.form.get("query")
results = search_contractors(sub_query)
if not results:
return "<li>No subcontractor found</li>"
output = "".join(
f"<li data-id='{row['Contractor_Id']}'>{row['Contractor_Name']}</li>"
for row in results
)
return output
# ------------------- Get Hold Types -------------------
@invoice_bp.route('/get_hold_types', methods=['GET'])
@login_required
def get_hold_types():
hold_types = get_all_hold_types()
LogHelper.log_action("Get hold type", f"User {current_user.id} Get hold type '{hold_types}'")
return jsonify(hold_types)
# ------------------- Edit Invoice -------------------
@invoice_bp.route('/edit_invoice/<int:invoice_id>', methods=['GET', 'POST'])
@login_required
def edit_invoice(invoice_id):
if request.method == 'POST':
data = request.form
update_invoice(data, invoice_id)
update_inpayment(data)
LogHelper.log_action("Edit invoice", f"User {current_user.id} Edit invoice '{invoice_id}'")
return jsonify({"status": "success", "message": "Invoice updated successfully"}), 200
invoice = get_invoice_by_id(invoice_id)
return render_template('edit_invoice.html', invoice=invoice)
# ------------------- Delete Invoice -------------------
@invoice_bp.route('/delete_invoice/<int:invoice_id>', methods=['GET'])
@login_required
def delete_invoice_route(invoice_id):
try:
delete_invoice_data(invoice_id, current_user.id)
LogHelper.log_action("Delete Invoice", f"User {current_user.id} deleted Invoice '{invoice_id}'")
return jsonify({
"message": f"Invoice {invoice_id} deleted successfully.",
"status": "success"
})
except Exception as e:
return jsonify({
"message": str(e),
"status": "error"
}), 500

View File

@@ -0,0 +1,31 @@
from flask import Blueprint, render_template, request
from flask_login import login_required
from model.Log import LogData
log_bp = Blueprint('log', __name__)
@log_bp.route('/activity_log', methods=['GET', 'POST'])
@login_required
def activity_log():
start_date = request.values.get("start_date")
end_date = request.values.get("end_date")
user_name = request.values.get("username")
logData = LogData()
filtered_logs = logData.GetFilteredActivitiesLog(
start_date,
end_date,
user_name
)
return render_template(
"activity_log.html",
logs=filtered_logs,
start_date=start_date,
end_date=end_date,
username=user_name
)

View File

@@ -0,0 +1,101 @@
from flask import Blueprint, render_template, request, redirect, url_for, jsonify, flash
from flask_login import login_required, current_user
from model.payment import Paymentmodel
from model.Log import LogHelper
payment_bp = Blueprint('payment_bp', __name__)
# ------------------- Add Payment -------------------
@payment_bp.route('/add_payment', methods=['GET', 'POST'])
@login_required
def add_payment():
payments_dicts = Paymentmodel.fetch_all_payments()
# Convert to array for template
payments = [
[
p['Payment_Id'], p['PMC_No'], p['Invoice_No'],
p['Payment_Amount'], p['TDS_Payment_Amount'], p['Total_Amount'], p['UTR']
] for p in payments_dicts
] if payments_dicts else []
if request.method == 'POST':
subcontractor_id = request.form.get('subcontractor_id')
pmc_no = request.form['PMC_No']
invoice_no = request.form['invoice_No']
amount = request.form['Payment_Amount']
tds_amount = request.form['TDS_Payment_Amount']
total_amount = request.form['total_amount']
utr = request.form['utr']
LogHelper.log_action("Add Payment", f"User {current_user.id} Add Payment '{pmc_no}'")
Paymentmodel.insert_payment(pmc_no, invoice_no, amount, tds_amount, total_amount, utr)
Paymentmodel.update_inpayment(subcontractor_id, pmc_no, invoice_no, amount, tds_amount, total_amount, utr)
return redirect(url_for('payment_bp.add_payment'))
return render_template('add_payment.html', payments=payments)
# ------------------- Get PMC Nos -------------------
@payment_bp.route('/get_pmc_nos_by_subcontractor/<subcontractorId>')
@login_required
def get_pmc_nos_by_subcontractor(subcontractorId):
connection = Paymentmodel.get_connection()
cur = connection.cursor()
cur.callproc('GetDistinctPMCNoByContractorId', [subcontractorId])
results = []
for result in cur.stored_results():
results = result.fetchall()
cur.close()
pmc_nos = [row[0] for row in results]
return jsonify({'pmc_nos': pmc_nos})
# ------------------- Edit Payment -------------------
@payment_bp.route('/edit_payment/<int:payment_id>', methods=['GET', 'POST'])
@login_required
def edit_payment(payment_id):
payment_data = Paymentmodel.fetch_payment_by_id(payment_id)
if not payment_data:
return "Payment not found", 404
if request.method == 'POST':
pmc_no = request.form['PMC_No']
invoice_no = request.form['invoice_No']
amount = request.form['Payment_Amount']
tds_amount = request.form['TDS_Payment_Amount']
total_amount = request.form['total_amount']
utr = request.form['utr']
LogHelper.log_action("Edit Payment", f"User {current_user.id} Edit Payment '{pmc_no}'")
Paymentmodel.call_update_payment_proc(payment_id, pmc_no, invoice_no, amount, tds_amount, total_amount, utr)
# Update inpayment
connection = Paymentmodel.get_connection()
cursor = connection.cursor()
cursor.callproc(
'UpdateInpaymentByPMCInvoiceUTR',
[amount, tds_amount, total_amount, pmc_no, invoice_no, utr]
)
connection.commit()
cursor.close()
connection.close()
return redirect(url_for('payment_bp.add_payment'))
return render_template('edit_payment.html', payment_data=payment_data)
# ------------------- Delete Payment -------------------
@payment_bp.route('/delete_payment/<int:payment_id>', methods=['POST'])
@login_required
def delete_payment(payment_id):
success, pmc_no, invoice_no = Paymentmodel.delete_payment(payment_id)
if not success:
flash("Payment not found or failed to delete", "error")
else:
LogHelper.log_action("Delete Payment", f"User {current_user.id} deleted Payment '{payment_id}'")
flash(f"Payment ID {payment_id} deleted successfully.", "success")
return redirect(url_for('payment_bp.add_payment'))

View File

@@ -0,0 +1,36 @@
from flask import Blueprint, render_template, send_from_directory
from model.PmcReport import PmcReport
pmc_report_bp = Blueprint("pmc_report", __name__)
@pmc_report_bp.route("/pmc_report/<pmc_no>")
def pmc_report(pmc_no):
data = PmcReport.get_pmc_report(pmc_no)
if not data:
return "No PMC found with this number", 404
return render_template(
"pmc_report.html",
info=data["info"],
invoices=data["invoices"],
hold_types=data["hold_types"],
gst_rel=data["gst_rel"],
payments=data["payments"],
credit_note=data["credit_note"],
hold_release=data["hold_release"],
total=data["total"]
)
@pmc_report_bp.route("/download_pmc_report/<pmc_no>")
def download_pmc_report(pmc_no):
result = PmcReport.download_pmc_report(pmc_no)
if not result:
return "No contractor found for this PMC No", 404
output_folder, file_name = result
return send_from_directory(output_folder, file_name, as_attachment=True)

View File

@@ -0,0 +1,202 @@
from flask import Blueprint, render_template, request, jsonify, send_file
from flask_login import login_required, current_user
from model.Report import ReportHelper
from model.Log import LogHelper
import config
from datetime import datetime
import os
import openpyxl
from openpyxl.styles import Font
from model.ContractorInfo import ContractorInfo
report_bp = Blueprint("report", __name__)
# ---------------- Report Page ----------------
@report_bp.route("/report")
@login_required
def report_page():
return render_template("/report.html")
# ---------------- Search Contractor ----------------
@report_bp.route("/search_contractor", methods=["POST"])
@login_required
def search_contractor():
subcontractor_name = request.form.get("subcontractor_name")
pmc_no = request.form.get("pmc_no")
state = request.form.get("state")
district = request.form.get("district")
block = request.form.get("block")
village = request.form.get("village")
year_from = request.form.get("year_from")
year_to = request.form.get("year_to")
LogHelper.log_action(
"Search Contractor",
f"User {current_user.id} Search contractor '{subcontractor_name}'"
)
data = ReportHelper.search_contractor(
subcontractor_name,
pmc_no,
state,
district,
block,
village,
year_from,
year_to
)
return jsonify(data)
# ---------------- Contractor Report ----------------
@report_bp.route('/contractor_report/<int:contractor_id>')
@login_required
def contractor_report(contractor_id):
data = ReportHelper.get_contractor_report(contractor_id)
return render_template(
'subcontractor_report.html',
contractor_id=contractor_id,
**data
)
class FilePathData:
downloadReportFolder = "static/download"
@report_bp.route('/download_report/<int:contractor_id>')
@login_required
def download_report(contractor_id):
try:
connection = config.get_db_connection()
cursor = connection.cursor(dictionary=True)
# -------- Contractor Info --------
contractor = ContractorInfo(contractor_id)
contInfo = contractor.contInfo
if not contInfo:
return "No contractor found", 404
# -------- Invoice Data --------
cursor.callproc('FetchInvoicesByContractor', [contractor_id])
invoices = []
for result in cursor.stored_results():
invoices.extend(result.fetchall())
if not invoices:
return "No invoice data found"
# -------- Create Workbook --------
workbook = openpyxl.Workbook()
sheet = workbook.active
sheet.title = "Contractor Report"
# ================= CONTRACTOR DETAILS =================
sheet.append(["SUB CONTRACTOR DETAILS"])
sheet.cell(row=sheet.max_row, column=1).font = Font(bold=True)
sheet.append([])
sheet.append(["Name", contInfo.get("Contractor_Name") or ""])
sheet.append(["Mobile No", contInfo.get("Mobile_No") or ""])
sheet.append(["Email", contInfo.get("Email") or ""])
sheet.append(["Village", contInfo.get("Village_Name") or ""])
sheet.append(["Block", contInfo.get("Block_Name") or ""])
sheet.append(["District", contInfo.get("District_Name") or ""])
sheet.append(["State", contInfo.get("State_Name") or ""])
sheet.append(["Address", contInfo.get("Address") or ""])
sheet.append(["GST No", contInfo.get("GST_No") or ""])
sheet.append(["PAN No", contInfo.get("PAN_No") or ""])
sheet.append([])
sheet.append([])
# ================= TABLE HEADERS =================
headers = [
"PMC No", "Village", "Invoice No", "Invoice Date", "Work Type","Invoice_Details",
"Basic Amount", "Debit Amount", "After Debit Amount",
"Amount", "GST Amount", "TDS Amount", "SD Amount",
"On Commission", "Hydro Testing", "Hold Amount",
"GST SD Amount", "Final Amount",
"Payment Amount", "TDS Payment",
"Total Amount", "UTR"
]
sheet.append(headers)
for col in range(1, len(headers) + 1):
sheet.cell(row=sheet.max_row, column=col).font = Font(bold=True)
# ================= DATA =================
total_final = 0
total_payment = 0
total_amount = 0
for inv in invoices:
row = [
inv.get("PMC_No"),
inv.get("Village_Name"),
inv.get("invoice_no"),
inv.get("Invoice_Date"),
inv.get("Work_Type"),
inv.get("Invoice_Details"),
inv.get("Basic_Amount"),
inv.get("Debit_Amount"),
inv.get("After_Debit_Amount"),
inv.get("Amount"),
inv.get("GST_Amount"),
inv.get("TDS_Amount"),
inv.get("SD_Amount"),
inv.get("On_Commission"),
inv.get("Hydro_Testing"),
inv.get("Hold_Amount"),
inv.get("GST_SD_Amount"),
inv.get("Final_Amount"),
inv.get("Payment_Amount"),
inv.get("TDS_Payment_Amount"),
inv.get("Total_Amount"),
inv.get("UTR")
]
total_final += float(inv.get("Final_Amount") or 0)
total_payment += float(inv.get("Payment_Amount") or 0)
total_amount += float(inv.get("Total_Amount") or 0)
sheet.append(row)
# ================= TOTAL ROW =================
sheet.append([])
sheet.append([
"", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "",
"TOTAL",
total_final,
total_payment,
"",
total_amount,
""
])
# ================= AUTO WIDTH =================
for column in sheet.columns:
max_length = 0
column_letter = column[0].column_letter
for cell in column:
if cell.value:
max_length = max(max_length, len(str(cell.value)))
sheet.column_dimensions[column_letter].width = max_length + 2
# ================= SAVE FILE =================
output_folder = "downloads"
os.makedirs(output_folder, exist_ok=True)
filename = f"Contractor_Report_{contInfo.get('Contractor_Name')}.xlsx"
output_file = os.path.join(output_folder, filename)
workbook.save(output_file)
return send_file(output_file, as_attachment=True)
except Exception as e:
return str(e)

View File

@@ -0,0 +1,69 @@
from flask import Blueprint, render_template, request, redirect, url_for
from flask_login import login_required
from model.State import State
from model.Log import LogData
state_bp = Blueprint('state', __name__)
@state_bp.route('/add_state', methods=['GET', 'POST'])
@login_required
def add_state():
state = State()
if request.method == 'POST':
state.AddState(request=request)
return state.resultMessage
statedata = state.GetAllStates(request=request)
return render_template('add_state.html', statedata=statedata)
@state_bp.route('/check_state', methods=['POST'])
@login_required
def check_state():
state = State()
return state.CheckState(request=request)
@state_bp.route('/delete_state/<int:id>')
@login_required
def deleteState(id):
state = State()
msg = state.DeleteState(request=request, id=id)
if not state.isSuccess:
return state.resultMessage
else:
return redirect(url_for('state.add_state'))
@state_bp.route('/edit_state/<int:id>', methods=['GET', 'POST'])
@login_required
def editState(id):
state = State()
if request.method == 'POST':
state.EditState(request=request, id=id)
if state.isSuccess:
return redirect(url_for('state.add_state'))
else:
return state.resultMessage
statedata = state.GetStateByID(request=request, id=id)
if not state.isSuccess:
return state.resultMessage
if statedata is None:
statedata = []
return render_template('edit_state.html', state=statedata)

View File

@@ -0,0 +1,117 @@
from flask import Blueprint, render_template, request, redirect, url_for, jsonify
from flask_login import login_required
from model.Subcontractor import Subcontractor
subcontractor_bp = Blueprint('subcontractor', __name__)
# ----------------------------------------------------------
# Helpers (unchanged)
# ----------------------------------------------------------
class HtmlHelper:
@staticmethod
def json_response(data, status=200):
return jsonify(data), status
class ResponseHandler:
@staticmethod
def fetch_failure(entity):
return {"status": "error", "message": f"Failed to fetch {entity}"}
@staticmethod
def add_failure(entity):
return {"status": "error", "message": f"Failed to add {entity}"}
@staticmethod
def update_failure(entity):
return {"status": "error", "message": f"Failed to update {entity}"}
@staticmethod
def delete_failure(entity):
return {"status": "error", "message": f"Failed to delete {entity}"}
# ----------------------------------------------------------
# LIST + ADD
# ----------------------------------------------------------
@subcontractor_bp.route('/subcontractor', methods=['GET', 'POST'])
@login_required
def subcontract():
sub = Subcontractor()
# ---------------- GET ----------------
if request.method == 'GET':
subcontractor = sub.GetAllSubcontractors(request)
if not sub.isSuccess:
return HtmlHelper.json_response(
ResponseHandler.fetch_failure("Subcontractor"), 500
)
return render_template('add_subcontractor.html', subcontractor=subcontractor)
# ---------------- POST (ADD) ----------------
if request.method == 'POST':
sub.AddSubcontractor(request)
if not sub.isSuccess:
return HtmlHelper.json_response(
ResponseHandler.add_failure("Subcontractor"), 500
)
# Reload list after insert
subcontractor = sub.GetAllSubcontractors(request)
return render_template('add_subcontractor.html', subcontractor=subcontractor)
# ----------------------------------------------------------
# EDIT
# ----------------------------------------------------------
@subcontractor_bp.route('/edit_subcontractor/<int:id>', methods=['GET', 'POST'])
@login_required
def edit_subcontractor(id):
sub = Subcontractor()
# Fetch data
subcontractor = sub.GetSubcontractorByID(id)
if not subcontractor:
return HtmlHelper.json_response(
ResponseHandler.fetch_failure("Subcontractor"), 404
)
# ---------------- POST (UPDATE) ----------------
if request.method == 'POST':
sub.EditSubcontractor(request, id)
if not sub.isSuccess:
return HtmlHelper.json_response(
ResponseHandler.update_failure("Subcontractor"), 500
)
return redirect(url_for('subcontractor.subcontract'))
return render_template('edit_subcontractor.html', subcontractor=subcontractor)
# ----------------------------------------------------------
# DELETE
# ----------------------------------------------------------
@subcontractor_bp.route('/deleteSubContractor/<int:id>', methods=['GET', 'POST'])
@login_required
def deleteSubContractor(id):
sub = Subcontractor()
sub.DeleteSubcontractor(request, id)
if not sub.isSuccess:
return HtmlHelper.json_response(
ResponseHandler.delete_failure("Subcontractor"), 500
)
return redirect(url_for('subcontractor.subcontract'))

View File

@@ -0,0 +1,167 @@
from flask import Blueprint, render_template, request, redirect, url_for, flash, jsonify
from flask_login import login_required
import config
from model.Village import Village
from model.State import State
# Create Blueprint
village_bp = Blueprint('village', __name__)
# ------------------------- Add Village -------------------------
@village_bp.route('/add_village', methods=['GET', 'POST'])
@login_required
def add_village():
village = Village()
if request.method == 'POST':
village.AddVillage(request=request)
return village.resultMessage
state = State()
states = state.GetAllStates(request=request)
villages = village.GetAllVillages(request=request)
return render_template(
'add_village.html',
states=states,
villages=villages
)
# ------------------------- Fetch Districts -------------------------
@village_bp.route('/get_districts/<int:state_id>')
@login_required
def get_districts(state_id):
connection = config.get_db_connection()
cursor = connection.cursor()
cursor.callproc("GetDistrictByStateID", [state_id])
districts = []
for rs in cursor.stored_results():
districts = rs.fetchall()
cursor.close()
connection.close()
district_list = []
for d in districts:
district_list.append({
"id": d[0],
"name": d[1]
})
return jsonify(district_list)
# ------------------------- Fetch Blocks -------------------------
@village_bp.route('/get_blocks/<int:district_id>')
@login_required
def get_blocks(district_id):
connection = config.get_db_connection()
cursor = connection.cursor()
cursor.callproc("GetBlocksByDistrictID", [district_id])
blocks = []
for rs in cursor.stored_results():
blocks = rs.fetchall()
cursor.close()
connection.close()
block_list = []
for b in blocks:
block_list.append({
"id": b[0],
"name": b[1]
})
return jsonify(block_list)
# ------------------------- Check Village -------------------------
@village_bp.route('/check_village', methods=['POST'])
@login_required
def check_village():
village = Village()
return village.CheckVillage(request=request)
# ------------------------- Delete Village -------------------------
@village_bp.route('/delete_village/<int:village_id>')
@login_required
def delete_village(village_id):
village = Village()
village.DeleteVillage(request=request, village_id=village_id)
if not village.isSuccess:
flash(village.resultMessage, "error")
else:
flash(village.resultMessage, "success")
return redirect(url_for('village.add_village'))
# ------------------------- Edit Village -------------------------
@village_bp.route('/edit_village/<int:village_id>', methods=['GET', 'POST'])
@login_required
def edit_village(village_id):
village = Village()
if request.method == 'POST':
village.EditVillage(request=request, village_id=village_id)
if village.isSuccess:
flash(village.resultMessage, "success")
return redirect(url_for('village.add_village'))
else:
flash(village.resultMessage, "error")
village_data = village.GetVillageByID(request=request, id=village_id)
blocks = village.GetAllBlocks(request=request)
return render_template(
'edit_village.html',
village_data=village_data,
blocks=blocks
)
else:
village_data = village.GetVillageByID(request=request, id=village_id)
if not village.isSuccess:
flash(village.resultMessage, "error")
return redirect(url_for('village.add_village'))
blocks = village.GetAllBlocks(request=request)
if village_data is None:
village_data = []
if blocks is None:
blocks = []
return render_template(
'edit_village.html',
village_data=village_data,
blocks=blocks
)

35
docker-compose.yml Normal file
View File

@@ -0,0 +1,35 @@
version: "3.8"
services:
flask-app:
build: .
ports:
- "5000:5000"
depends_on:
- mysql
environment:
- MYSQL_HOST=mysql
- MYSQL_USER=root
- MYSQL_PASSWORD=root
- MYSQL_DB=test
networks:
- mynetwork
mysql:
image: mysql:8.0
restart: always
environment:
MYSQL_ROOT_PASSWORD: root
MYSQL_DATABASE: test
ports:
- "3306:3306"
volumes:
- mysql-data:/var/lib/mysql
networks:
- mynetwork
volumes:
mysql-data:
networks:
mynetwork:

0
logs/activity.log Normal file
View File

74
logs/audit.log Normal file
View File

@@ -0,0 +1,74 @@
2025-08-22 13:29:24,485 | User: v.sinha | Action: Login | Details: User v.sinha logged in | IP: 192.168.0.180
2025-08-22 13:41:42,046 | User: v.sinha | Action: Logout | Details: User v.sinha logged out | IP: 192.168.0.180
2025-08-22 14:07:01,924 | User: v.sinha | Action: Login | Details: User v.sinha logged in | IP: 192.168.0.180
2025-08-22 15:05:59,287 | User: v.sinha | Action: Logout | Details: User v.sinha logged out | IP: 192.168.0.180
2025-08-22 15:06:05,201 | User: v.sinha | Action: Login | Details: User v.sinha logged in | IP: 192.168.0.180
2025-08-23 15:58:28,248 | User: v.sinha | Action: Login | Details: User v.sinha logged in | IP: 192.168.0.180
2025-08-23 17:33:06,648 | User: v.sinha | Action: Login | Details: User v.sinha logged in | IP: 192.168.0.180
2025-08-23 17:39:08,442 | User: v.sinha | Action: Login | Details: User v.sinha logged in | IP: 192.168.0.180
2025-08-23 18:14:51,722 | User: v.sinha | Action: Login | Details: User v.sinha logged in | IP: 192.168.0.180
2025-08-25 11:57:12,202 | User: v.sinha | Action: Login | Details: User v.sinha logged in | IP: 192.168.0.238
2025-08-25 12:00:17,780 | User: v.sinha | Action: Login | Details: User v.sinha logged in | IP: 192.168.0.181
2025-08-25 14:09:29,385 | User: v.sinha | Action: Login | Details: User v.sinha logged in | IP: 192.168.0.181
2025-08-25 14:12:35,084 | User: v.sinha | Action: Login | Details: User v.sinha logged in | IP: 192.168.0.181
2025-08-25 14:23:53,539 | User: v.sinha | Action: Checked State | Details: User MP Checking State | IP: 192.168.0.181
2025-08-25 14:23:54,024 | User: v.sinha | Action: Added State | Details: User MP Adding State | IP: 192.168.0.181
2025-08-25 14:23:57,113 | User: v.sinha | Action: Deleted State | Details: User 11 Deleting State | IP: 192.168.0.181
2025-08-25 14:31:55,715 | User: v.sinha | Action: Login | Details: User v.sinha logged in | IP: 192.168.0.181
2025-08-25 14:36:21,158 | User: v.sinha | Action: Checked State | Details: User MP Checking State | IP: 192.168.0.181
2025-08-25 14:36:21,496 | User: v.sinha | Action: Added State | Details: State 'MP' added. | IP: 192.168.0.181
2025-08-25 14:47:08,719 | User: v.sinha | Action: Checked State | Details: User Maharashtra Checking State | IP: 192.168.0.181
2025-08-25 14:47:14,759 | User: v.sinha | Action: Deleted State | Details: User 12 Deleting State | IP: 192.168.0.181
2025-08-25 14:47:16,915 | User: v.sinha | Action: Checked State | Details: User MP Checking State | IP: 192.168.0.181
2025-08-25 14:47:17,708 | User: v.sinha | Action: Added State | Details: State 'MP' added. | IP: 192.168.0.181
2025-08-25 14:49:09,480 | User: v.sinha | Action: Checked State | Details: User MP Checking State | IP: 192.168.0.181
2025-08-25 14:49:13,014 | User: v.sinha | Action: Deleted State | Details: User 13 Deleting State | IP: 192.168.0.181
2025-08-25 14:49:14,584 | User: v.sinha | Action: Checked State | Details: User MP Checking State | IP: 192.168.0.181
2025-08-25 14:49:15,055 | User: v.sinha | Action: Added State | Details: State 'MP' added. | IP: 192.168.0.181
2025-08-25 14:51:55,187 | User: v.sinha | Action: Checked State | Details: User MP Checking State | IP: 192.168.0.181
2025-08-25 14:51:58,463 | User: v.sinha | Action: Deleted State | Details: User 14 Deleting State | IP: 192.168.0.181
2025-08-25 14:52:00,606 | User: v.sinha | Action: Checked State | Details: User MP Checking State | IP: 192.168.0.181
2025-08-25 14:52:00,953 | User: v.sinha | Action: Added State | Details: State 'MP' added. | IP: 192.168.0.181
2025-08-25 14:54:26,674 | User: v.sinha | Action: Deleted State | Details: User 15 Deleting State | IP: 192.168.0.181
2025-08-25 14:54:28,892 | User: v.sinha | Action: Checked State | Details: User MP Checking State | IP: 192.168.0.181
2025-08-25 14:54:29,553 | User: v.sinha | Action: Added State | Details: State 'MP' added. | IP: 192.168.0.181
2025-08-25 15:18:37,773 | User: v.sinha | Action: Logout | Details: User v.sinha logged out | IP: 192.168.0.181
2025-08-25 15:18:43,347 | User: v.sinha | Action: Login | Details: User v.sinha logged in | IP: 192.168.0.181
2025-08-25 15:20:41,331 | User: v.sinha | Action: Logout | Details: User v.sinha logged out | IP: 192.168.0.181
2025-08-25 15:20:47,525 | User: v.sinha | Action: Login | Details: User v.sinha logged in | IP: 192.168.0.181
2025-08-25 15:20:55,687 | User: v.sinha | Action: Checked State | Details: User MP Checking State | IP: 192.168.0.181
2025-08-25 15:20:58,544 | User: v.sinha | Action: Deleted State | Details: User 16 Deleting State | IP: 192.168.0.181
2025-08-25 16:33:49,898 | User: v.sinha | Action: Check State | Details: User v.sinha Checked state 'MP' | IP: 192.168.0.181
2025-08-25 16:33:50,394 | User: v.sinha | Action: Add State | Details: User v.sinha added state 'MP' | IP: 192.168.0.181
2025-08-25 16:43:46,446 | User: v.sinha | Action: Login | Details: User v.sinha logged in | IP: 192.168.0.181
2025-08-25 16:43:49,710 | User: v.sinha | Action: Logout | Details: User v.sinha logged out | IP: 192.168.0.181
2025-08-25 16:43:58,093 | User: v.sinha | Action: Login | Details: User v.sinha logged in | IP: 192.168.0.181
2025-08-25 16:44:11,935 | User: v.sinha | Action: Check State | Details: User v.sinha Checked state 'shamli' | IP: 192.168.0.181
2025-08-25 16:44:12,466 | User: v.sinha | Action: Add State | Details: User v.sinha added state 'shamli' | IP: 192.168.0.181
2025-08-25 16:44:17,731 | User: v.sinha | Action: Delete State | Details: User v.sinha Deleted state '18' | IP: 192.168.0.181
2025-08-25 16:57:27,983 | User: v.sinha | Action: Logout | Details: User v.sinha logged out | IP: 192.168.0.181
2025-08-25 16:57:33,498 | User: v.sinha | Action: Login | Details: User v.sinha logged in | IP: 192.168.0.181
2025-08-25 16:57:41,438 | User: v.sinha | Action: Check State | Details: User v.sinha Checked state 'shamli' | IP: 192.168.0.181
2025-08-25 16:57:42,250 | User: v.sinha | Action: Add State | Details: User v.sinha added state 'shamli' | IP: 192.168.0.181
2025-08-25 16:57:45,339 | User: v.sinha | Action: Delete State | Details: User v.sinha Deleted state '19' | IP: 192.168.0.181
2025-08-25 16:57:48,794 | User: v.sinha | Action: Delete State | Details: User v.sinha Deleted state '17' | IP: 192.168.0.181
2025-08-25 17:04:11,021 | User: v.sinha | Action: Logout | Details: User v.sinha logged out | IP: 192.168.0.181
2025-08-25 17:04:16,165 | User: v.sinha | Action: Login | Details: User v.sinha logged in | IP: 192.168.0.181
2025-08-25 17:04:21,702 | User: v.sinha | Action: Check State | Details: User v.sinha Checked state 'shamli' | IP: 192.168.0.181
2025-08-25 17:04:22,159 | User: v.sinha | Action: Add State | Details: User v.sinha added state 'shamli' | IP: 192.168.0.181
2025-08-25 17:04:26,850 | User: v.sinha | Action: Check State | Details: User v.sinha Checked state 'M' | IP: 192.168.0.181
2025-08-25 17:04:27,076 | User: v.sinha | Action: Check State | Details: User v.sinha Checked state 'MP' | IP: 192.168.0.181
2025-08-25 17:04:28,070 | User: v.sinha | Action: Add State | Details: User v.sinha added state 'MP' | IP: 192.168.0.181
2025-08-25 17:04:32,165 | User: v.sinha | Action: Delete State | Details: User v.sinha Deleted state '21' | IP: 192.168.0.181
2025-08-25 17:04:35,058 | User: v.sinha | Action: Delete State | Details: User v.sinha Deleted state '20' | IP: 192.168.0.181
2025-08-25 17:06:05,113 | User: v.sinha | Action: Check District | Details: User v.sinha Checked District 'Shamli' | IP: 192.168.0.181
2025-08-25 17:06:05,114 | User: v.sinha | Action: Check District | Details: User v.sinha Checked District 'Shamli' | IP: 192.168.0.181
2025-08-25 17:06:08,040 | User: v.sinha | Action: Check District | Details: User v.sinha Checked District 'p' | IP: 192.168.0.181
2025-08-25 17:06:08,360 | User: v.sinha | Action: Check District | Details: User v.sinha Checked District 'pu' | IP: 192.168.0.181
2025-08-25 17:06:08,554 | User: v.sinha | Action: Check District | Details: User v.sinha Checked District 'pun' | IP: 192.168.0.181
2025-08-25 17:06:08,756 | User: v.sinha | Action: Check District | Details: User v.sinha Checked District 'pune' | IP: 192.168.0.181
2025-08-25 17:06:10,190 | User: v.sinha | Action: Check District | Details: User v.sinha Checked District 'pune' | IP: 192.168.0.181
2025-08-25 17:06:11,204 | User: v.sinha | Action: Check District | Details: User v.sinha Checked District 'pune' | IP: 192.168.0.181
2025-08-25 17:06:11,206 | User: v.sinha | Action: Check District | Details: User v.sinha Checked District 'pune' | IP: 192.168.0.181
2025-08-25 17:06:13,085 | User: v.sinha | Action: Add District | Details: User v.sinha Added District 'pune' | IP: 192.168.0.181
2025-08-25 17:06:19,524 | User: v.sinha | Action: Delete District | Details: User v.sinha Deleted District '5' | IP: 192.168.0.181

69
main.py Normal file
View File

@@ -0,0 +1,69 @@
# main.py
from flask import Flask, render_template
from flask_login import LoginManager
from model.Auth import User
# Import Blueprints / Controllers
from controllers.auth_controller import auth_bp
from controllers.log_controller import log_bp
from controllers.state_controller import state_bp
from controllers.district_controller import district_bp
from controllers.block_controller import block_bp
from controllers.village_controller import village_bp
from controllers.invoice_controller import invoice_bp
from controllers.subcontractor_controller import subcontractor_bp
from controllers.payment_controller import payment_bp
from controllers.gst_release_controller import gst_release_bp
from controllers.excel_upload_controller import excel_bp
from controllers.report_controller import report_bp
from controllers.pmc_report_controller import pmc_report_bp
from controllers.hold_types_controller import hold_bp
from dotenv import load_dotenv
import os
# ---------------- Initialize App ----------------
app = Flask(__name__)
load_dotenv()
Secret_Key = os.getenv("SECRET_KEY")
app.secret_key = Secret_Key
# ---------------- Login Manager ----------------
login_manager = LoginManager()
login_manager.init_app(app)
login_manager.login_view = 'auth.login'
@login_manager.user_loader
def load_user(user_id):
return User(user_id)
# ---------------- Home Route ----------------
@app.route('/')
def index():
return render_template("index.html")
# ---------------- Register Blueprints ----------------
app.register_blueprint(auth_bp)
app.register_blueprint(log_bp)
app.register_blueprint(state_bp)
app.register_blueprint(district_bp)
app.register_blueprint(block_bp)
app.register_blueprint(village_bp)
app.register_blueprint(invoice_bp)
app.register_blueprint(subcontractor_bp)
app.register_blueprint(payment_bp)
app.register_blueprint(gst_release_bp)
app.register_blueprint(excel_bp)
app.register_blueprint(report_bp)
app.register_blueprint(pmc_report_bp)
app.register_blueprint(hold_bp)
# ---------------- Run App ----------------
if __name__ == '__main__':
app.run(host='0.0.0.0', port=5000, debug=True)

63
model/Auth.py Normal file
View File

@@ -0,0 +1,63 @@
import os
from dotenv import load_dotenv
from flask_login import UserMixin
from ldap3 import Server, Connection, ALL
from ldap3.core.exceptions import LDAPBindError
# Load .env
load_dotenv()
class DefaultCredentials:
username = os.getenv("DEFAULT_USERNAME")
password = os.getenv("DEFAULT_PASSWORD")
class LoginLDAP:
def __init__(self, request):
self.username = request.form.get("username", "").strip()
self.password = request.form.get("password", "")
self.isDefaultCredentials = False
self.isValidLogin = False
self.errorMessage = ""
ldap_server = "ldap://localhost:389"
ldap_user_dn = f"uid={self.username},ou=users,dc=lcepl,dc=org"
# fallback admin login
if (
self.username == DefaultCredentials.username
and self.password == DefaultCredentials.password
):
self.isDefaultCredentials = True
self.isValidLogin = True
return
try:
server = Server(ldap_server, get_info=ALL)
conn = Connection(
server,
user=ldap_user_dn,
password=self.password,
auto_bind=True
)
if conn.bound:
self.isValidLogin = True
except LDAPBindError:
self.errorMessage = "Invalid LDAP credentials"
except Exception as e:
self.errorMessage = str(e)
class User(UserMixin):
def __init__(self, username):
self.id = username

165
model/Block.py Normal file
View File

@@ -0,0 +1,165 @@
from flask import Flask, render_template, request, redirect, url_for, send_from_directory, flash, jsonify, json
from flask import current_app
from datetime import datetime
from flask_login import LoginManager, UserMixin, login_user, logout_user, login_required, current_user
from model.Utilities import RegEx, ResponseHandler, HtmlHelper, ItemCRUDType
from model.Log import LogData, LogHelper
import os
import config
import re
import mysql.connector
from mysql.connector import Error
from model.ItemCRUD import ItemCRUD, itemCRUDMapping
class Block:
isSuccess = False
resultMessage = ""
def __init__(self):
self.isSuccess = False
self.resultMessage = ""
# ----------------------------------------------------------
# Add Block
# ----------------------------------------------------------
def AddBlock(self, request):
block = ItemCRUD(itemType=ItemCRUDType.Block)
district_id = request.form.get('district_Id')
block_name = request.form.get('block_Name', '').strip()
block.AddItem(request=request, parentid=district_id, childname=block_name, storedprocfetch="GetBlockByNameAndDistricts", storedprocadd="SaveBlock" )
self.isSuccess = block.isSuccess
self.resultMessage = block.resultMessage
return
# ----------------------------------------------------------
# Get All Blocks
# ----------------------------------------------------------
# def GetAllBlocks(self):
# block = ItemCRUD(itemType=ItemCRUDType.Block)
# blocksdata = block.GetAllData(request=request, storedproc="GetAllBlock")
# self.isSuccess = block.isSuccess
# self.resultMessage = block.resultMessage
# return blocksdata
def GetAllBlocks(self, request):
block = ItemCRUD(itemType=ItemCRUDType.Block)
blocksdata = block.GetAllData(request=request, storedproc="GetAllBlock")
self.isSuccess = block.isSuccess
self.resultMessage = block.resultMessage
return blocksdata
# ----------------------------------------------------------
# Check Block Exists
# ----------------------------------------------------------
# def CheckBlock(self, request):
# block = ItemCRUD(itemType=ItemCRUDType.Block)
# block_name = request.json.get('block_Name', '').strip()
# district_id = request.json.get('district_Id')
# result = block.CheckItem(request=request, parentid=district_id, childname=block_name, storedprocfetch="GetBlockByNameAndDistrict")
# self.isSuccess = block.isSuccess
# self.resultMessage = block.resultMessage
# return result
def CheckBlock(self, request):
block = ItemCRUD(itemType=ItemCRUDType.Block)
data = request.get_json(silent=True) or request.form
block_name = (data.get('block_Name') or '').strip()
district_id = data.get('district_Id')
result = block.CheckItem(
request=request,
parentid=district_id,
childname=block_name,
storedprocfetch="GetBlockByNameAndDistrict"
)
self.isSuccess = block.isSuccess
self.resultMessage = block.resultMessage
return result
# ----------------------------------------------------------
# Get Block By ID
# ----------------------------------------------------------
# def GetBlockByID(self, id):
# block = ItemCRUD(itemType=ItemCRUDType.Village)
# blockdata = block.GetAllData(id=id,storedproc="GetBlockDataByID")
# self.isSuccess = block.isSuccess
# self.resultMessage = block.resultMessage
# print("akash"+blockdata)
# return blockdata
# def GetBlockByID(self,request,id):
# block = ItemCRUD(itemType=ItemCRUDType.Block)
# blockdata = block.GetDataByID(request=request,id=id,storedproc="GetBlockDataByID")
# self.isSuccess = block.isSuccess
# self.resultMessage = block.resultMessage
# return blockdata
def GetBlockByID(self, id):
block = ItemCRUD(itemType=ItemCRUDType.Block)
blockdata = block.GetDataByID(
id=id,
storedproc="GetBlockDataByID"
)
self.isSuccess = block.isSuccess
self.resultMessage = block.resultMessage
return blockdata
# ----------------------------------------------------------
# Update Block
# ----------------------------------------------------------
# def EditBlock(self, request, block_id):
# block = ItemCRUD(itemType=ItemCRUDType.Block)
# district_id = request.form.get('district_Id')
# block_name = request.form.get('block_Name', '').strip()
# block.EditItem(request=request, childid=block_id, parentid=district_id, childname=block_name, storedprocadd="UpdateBlockById" )
# self.isSuccess = block.isSuccess
# self.resultMessage = block.resultMessage
# return
def EditBlock(self, request, block_id):
block = ItemCRUD(itemType=ItemCRUDType.Block)
district_id = request.form.get('district_Id')
block_name = request.form.get('block_Name', '').strip()
block.EditItem(
request=request,
childid=block_id,
parentid=district_id,
childname=block_name,
storedprocupdate="UpdateBlockById"
)
self.isSuccess = block.isSuccess
self.resultMessage = block.resultMessage
return render_template('add_block.html')
# ----------------------------------------------------------
# Delete Block
# ---------------------------------------------------------
def DeleteBlock(self,request, id):
block = ItemCRUD(itemType=ItemCRUDType.Block)
block.DeleteItem(request=request,itemID=id, storedprocDelete="DeleteBlock")
self.isSuccess = block.isSuccess
self.resultMessage = block.resultMessage
return

74
model/ContractorInfo.py Normal file
View File

@@ -0,0 +1,74 @@
import mysql.connector
from mysql.connector import Error
import config
import openpyxl
import os
import re
import ast
from datetime import datetime
class ContractorInfo:
ID = ""
contInfo = None
def __init__(self, id):
self.ID = id
print(id)
self.fetchData()
def fetchData(self):
try:
connection = config.get_db_connection()
cursor = connection.cursor(dictionary=True, buffered=True)
print("here", flush=True)
cursor.callproc('GetContractorInfoById', [self.ID])
for result in cursor.stored_results():
self.contInfo = result.fetchone()
print(self.contInfo,flush=True)
finally:
cursor.close()
connection.close()
def fetchalldata(self):
try:
connection = config.get_db_connection()
cursor = connection.cursor(dictionary=True, buffered=True)
print("here", flush=True)
# ---------------- Hold Types ----------------
cursor = connection.cursor(dictionary=True)
cursor.callproc('GetHoldTypesByContractor', [self.ID])
hold_types = []
for result in cursor.stored_results():
hold_types = result.fetchall()
hold_type_map = {ht['hold_type_id']: ht['hold_type'] for ht in hold_types}
# ---------------- Invoices ----------------
cursor = connection.cursor(dictionary=True)
cursor.callproc('GetInvoicesByContractor', [self.ID])
invoices = []
for result in cursor.stored_results():
invoices = result.fetchall()
# Remove duplicate invoices
invoice_ids_seen = set()
unique_invoices = []
for inv in invoices:
if inv["Invoice_Id"] not in invoice_ids_seen:
invoice_ids_seen.add(inv["Invoice_Id"])
unique_invoices.append(inv)
invoices = unique_invoices
finally:
cursor.close()
connection.close()

106
model/District.py Normal file
View File

@@ -0,0 +1,106 @@
from model.ItemCRUD import ItemCRUD
from model.Utilities import ItemCRUDType
class District:
def __init__(self):
self.isSuccess = False
self.resultMessage = ""
# Add new district
def AddDistrict(self, request):
district = ItemCRUD(ItemCRUDType.District)
district_name = request.form.get('district_Name', '').strip()
state_id = request.form.get('state_Id', '').strip()
if not district_name or not state_id:
self.isSuccess = False
self.resultMessage = "Please enter district name and select a state."
return
district.AddItem(
request=request,
parentid=state_id,
childname=district_name,
storedprocfetch="GetDistrictByNameAndState",
storedprocadd="SaveDistrict"
)
self.isSuccess = district.isSuccess
self.resultMessage = district.resultMessage
# Edit existing district
def EditDistrict(self, request, district_id):
district = ItemCRUD(ItemCRUDType.District)
district_name = request.form.get('district_Name', '').strip()
state_id = request.form.get('state_Id', '').strip()
if not district_name or not state_id:
self.isSuccess = False
self.resultMessage = "Please enter district name and select a state."
return
district.EditItem(
request=request,
childid=district_id,
parentid=state_id,
childname=district_name,
storedprocupdate="UpdateDistrict"
)
self.isSuccess = district.isSuccess
self.resultMessage = district.resultMessage
# Get all districts
def GetAllDistricts(self, request):
district = ItemCRUD(ItemCRUDType.District)
districtsdata = district.GetAllData(request=request, storedproc="GetAllDistricts")
self.isSuccess = district.isSuccess
self.resultMessage = district.resultMessage
return districtsdata
# Check district exists (used for AJAX, optional)
def CheckDistrict(self, request):
district = ItemCRUD(ItemCRUDType.District)
if request.is_json:
district_name = request.json.get('district_Name', '').strip()
state_id = request.json.get('state_Id', '').strip()
else:
district_name = request.form.get('district_Name', '').strip()
state_id = request.form.get('state_Id', '').strip()
result = district.CheckItem(
request=request,
parentid=state_id,
childname=district_name,
storedprocfetch="GetDistrictByNameAndState"
)
self.isSuccess = district.isSuccess
self.resultMessage = district.resultMessage
return result
# Get district by ID
def GetDistrictByID(self, request, district_id):
district = ItemCRUD(ItemCRUDType.District)
districtdata = district.GetDataByID(
id=district_id,
storedproc="GetDistrictDataByID"
)
if districtdata:
self.isSuccess = True
else:
self.isSuccess = False
self.resultMessage = "District not found"
return districtdata
# Delete district
def DeleteDistrict(self, request, district_id):
district = ItemCRUD(ItemCRUDType.District)
district.DeleteItem(
request=request,
itemID=district_id,
storedprocDelete="DeleteDistrict"
)
self.isSuccess = district.isSuccess
self.resultMessage = str(district.resultMessage)

51
model/GST.py Normal file
View File

@@ -0,0 +1,51 @@
from model.ItemCRUD import ItemCRUD
from model.Utilities import ItemCRUDType
class GST:
@staticmethod
def get_unreleased_gst():
# Use ItemCRUD for Invoices
invoice_crud = ItemCRUD(itemType=ItemCRUDType.Invoice)
invoices_rows = invoice_crud.GetAllData(storedproc="GetAllInvoicesBasic")
if not invoice_crud.isSuccess:
return [] # Could also log invoice_crud.resultMessage
invoices = [
dict(
Invoice_No=row[1],
GST_SD_Amount=float(row[2]) if row[2] is not None else 0
)
for row in invoices_rows
]
# Use ItemCRUD for GST Releases
gst_crud = ItemCRUD(itemType=ItemCRUDType.GSTRelease)
gst_rows = gst_crud.GetAllData(storedproc="GetAllGSTReleasesBasic")
if not gst_crud.isSuccess:
return [] # Could also log gst_crud.resultMessage
gst_invoice_nos = {
g[2] # Invoice_No is at index 2
for g in gst_rows
if g[2]
}
gst_basic_amounts = {
float(g[3]) # Basic_Amount at index 3
for g in gst_rows
if g[3] is not None
}
# Filter unreleased invoices
unreleased = []
for inv in invoices:
match_by_invoice = inv['Invoice_No'] in gst_invoice_nos
match_by_gst_amount = inv['GST_SD_Amount'] in gst_basic_amounts
if not (match_by_invoice or match_by_gst_amount):
unreleased.append(inv)
return unreleased

90
model/HoldTypes.py Normal file
View File

@@ -0,0 +1,90 @@
from flask import request
from model.ItemCRUD import ItemCRUD
from model.Utilities import ItemCRUDType
class HoldTypes:
"""CRUD operations for Hold Types using ItemCRUD"""
def __init__(self):
self.isSuccess = False
self.resultMessage = ""
# ------------------- Add Hold Type -------------------
def AddHoldType(self, request):
hold = ItemCRUD(itemType=ItemCRUDType.HoldType)
hold_name = request.form.get('hold_type', '').strip()
hold.AddItem(
request=request,
parentid=None,
childname=hold_name,
storedprocfetch="CheckHoldTypeExists",
storedprocadd="SaveHoldType"
)
self.isSuccess = hold.isSuccess
self.resultMessage = hold.resultMessage
# ------------------- Get All Hold Types -------------------
def GetAllHoldTypes(self, request=None):
hold = ItemCRUD(itemType=ItemCRUDType.HoldType)
rows = hold.GetAllData(request=request, storedproc="GetAllHoldTypes")
self.isSuccess = hold.isSuccess
self.resultMessage = hold.resultMessage
# ✅ Convert tuple → dictionary
data = []
for row in rows:
data.append({
"hold_type_id": row[0],
"hold_type": row[1]
})
return data
# ------------------- Get Hold Type By ID -------------------
def GetHoldTypeByID(self, hold_type_id):
hold = ItemCRUD(itemType=ItemCRUDType.HoldType)
row = hold.GetDataByID(hold_type_id, storedproc="GetHoldTypesById")
self.isSuccess = hold.isSuccess
self.resultMessage = hold.resultMessage
# ✅ Convert tuple → dictionary
if row:
return {
"hold_type_id": row[0],
"hold_type": row[1]
}
return None
# ------------------- Update Hold Type -------------------
def EditHoldType(self, request, hold_type_id):
hold = ItemCRUD(itemType=ItemCRUDType.HoldType)
hold_name = request.form.get('hold_type', '').strip()
hold.EditItem(
request=request,
childid=hold_type_id,
parentid=None,
childname=hold_name,
storedprocupdate="UpdateHoldTypeById"
)
self.isSuccess = hold.isSuccess
self.resultMessage = hold.resultMessage
# ------------------- Delete Hold Type -------------------
def DeleteHoldType(self, request, hold_type_id):
hold = ItemCRUD(itemType=ItemCRUDType.HoldType)
hold.DeleteItem(
request=request,
itemID=hold_type_id,
storedprocDelete="DeleteHoldType"
)
self.isSuccess = hold.isSuccess
self.resultMessage = hold.resultMessage

379
model/Invoice.py Normal file
View File

@@ -0,0 +1,379 @@
import config
import mysql.connector
# ------------------- Helper -------------------
def clear_results(cursor):
for r in cursor.stored_results():
r.fetchall()
# ------------------- Get Village Id -------------------
def get_village_id(village_name):
connection = config.get_db_connection()
cursor = connection.cursor(dictionary=True)
cursor.callproc("GetVillageIdByName", (village_name,))
village_result = None
for rs in cursor.stored_results():
village_result = rs.fetchone()
cursor.close()
connection.close()
return village_result
# ------------------- Insert Invoice -------------------
def insert_invoice(data, village_id):
connection = config.get_db_connection()
cursor = connection.cursor(dictionary=True)
try:
# 1. Insert Invoice
cursor.callproc('InsertInvoice', [
data.get('pmc_no'),
village_id,
data.get('work_type'),
data.get('invoice_details'),
data.get('invoice_date'),
data.get('invoice_no'),
float(data.get('basic_amount') or 0),
float(data.get('debit_amount') or 0),
float(data.get('after_debit_amount') or 0),
float(data.get('amount') or 0),
float(data.get('gst_amount') or 0),
float(data.get('tds_amount') or 0),
float(data.get('sd_amount') or 0),
float(data.get('on_commission') or 0),
float(data.get('hydro_testing') or 0),
float(data.get('gst_sd_amount') or 0),
float(data.get('final_amount') or 0)
])
invoice_id = None
for result in cursor.stored_results():
row = result.fetchone()
if row:
invoice_id = row.get('invoice_id')
if not invoice_id:
raise Exception("Invoice ID not returned")
# 2. Insert Inpayment
cursor.callproc('InsertInpayment', [
data.get('pmc_no'),
village_id,
data.get('work_type'),
data.get('invoice_details'),
data.get('invoice_date'),
data.get('invoice_no'),
float(data.get('basic_amount') or 0),
float(data.get('debit_amount') or 0),
float(data.get('after_debit_amount') or 0),
float(data.get('amount') or 0),
float(data.get('gst_amount') or 0),
float(data.get('tds_amount') or 0),
float(data.get('sd_amount') or 0),
float(data.get('on_commission') or 0),
float(data.get('hydro_testing') or 0),
float(data.get('gst_sd_amount') or 0),
float(data.get('final_amount') or 0),
data.get('subcontractor_id')
])
clear_results(cursor)
connection.commit()
return invoice_id
except Exception as e:
connection.rollback()
raise e
finally:
cursor.close()
connection.close()
# ------------------- Assign Subcontractor -------------------
def assign_subcontractor(data, village_id):
connection = config.get_db_connection()
cursor = connection.cursor(dictionary=True)
try:
cursor.callproc('AssignSubcontractor', [
data.get('pmc_no'),
data.get('subcontractor_id'),
village_id
])
clear_results(cursor)
connection.commit()
except Exception as e:
connection.rollback()
raise e
finally:
cursor.close()
connection.close()
# ------------------- Insert Hold Types -------------------
def insert_hold_types(data, invoice_id):
connection = config.get_db_connection()
cursor = connection.cursor(dictionary=True)
try:
hold_types = data.getlist('hold_type[]')
hold_amounts = data.getlist('hold_amount[]')
for hold_type, hold_amount in zip(hold_types, hold_amounts):
if not hold_type:
continue
cursor.callproc('GetHoldTypeIdByName', [hold_type])
hold_type_result = None
for result in cursor.stored_results():
hold_type_result = result.fetchone()
if not hold_type_result:
cursor.callproc('InsertHoldType', [hold_type, 0])
cursor.execute("SELECT @_InsertHoldType_1")
hold_type_id = cursor.fetchone()[0]
else:
hold_type_id = hold_type_result['hold_type_id']
hold_amount = float(hold_amount or 0)
cursor.callproc('InsertInvoiceSubcontractorHold', [
data.get('subcontractor_id'),
invoice_id,
hold_type_id,
hold_amount
])
clear_results(cursor)
connection.commit()
except Exception as e:
connection.rollback()
raise e
finally:
cursor.close()
connection.close()
# ------------------- Get All Invoices -------------------
def get_all_invoice_details():
connection = config.get_db_connection()
cursor = connection.cursor(dictionary=True)
cursor.callproc('GetAllInvoiceDetails')
invoices = []
for result in cursor.stored_results():
invoices = result.fetchall()
cursor.close()
connection.close()
return invoices
# ------------------- Get All Villages -------------------
def get_all_villages():
connection = config.get_db_connection()
cursor = connection.cursor(dictionary=True)
cursor.callproc("GetAllVillages")
villages = []
for result in cursor.stored_results():
villages = result.fetchall()
cursor.close()
connection.close()
return villages
# ------------------- Search Contractors -------------------
def search_contractors(sub_query):
connection = config.get_db_connection()
cursor = connection.cursor(dictionary=True)
cursor.callproc('SearchContractorsByName', [sub_query])
results = []
for result in cursor.stored_results():
results = result.fetchall()
cursor.close()
connection.close()
return results
# ------------------- Get All Hold Types -------------------
def get_all_hold_types():
connection = config.get_db_connection()
cursor = connection.cursor(dictionary=True)
cursor.callproc("GetAllHoldTypes")
hold_types = []
for result in cursor.stored_results():
hold_types = result.fetchall()
cursor.close()
connection.close()
return hold_types
# ------------------- Get Invoice By Id -------------------
def get_invoice_by_id(invoice_id):
connection = config.get_db_connection()
cursor = connection.cursor(dictionary=True)
cursor.callproc('GetInvoiceDetailsById', [invoice_id])
invoice = None
for result in cursor.stored_results():
invoice = result.fetchone()
cursor.callproc('GetHoldAmountsByInvoiceId', [invoice_id])
hold_amounts = []
for result in cursor.stored_results():
hold_amounts = result.fetchall()
if invoice:
invoice["hold_amounts"] = hold_amounts
cursor.close()
connection.close()
return invoice
# ------------------- Update Invoice -------------------
def update_invoice(data, invoice_id):
connection = config.get_db_connection()
cursor = connection.cursor(dictionary=True)
try:
cursor.callproc("GetVillageIdByName", (data.get('village'),))
village = None
for rs in cursor.stored_results():
village = rs.fetchone()
village_id = village['Village_Id']
numeric = [
float(data.get('basic_amount') or 0),
float(data.get('debit_amount') or 0),
float(data.get('after_debit_amount') or 0),
float(data.get('amount') or 0),
float(data.get('gst_amount') or 0),
float(data.get('tds_amount') or 0),
float(data.get('sd_amount') or 0),
float(data.get('on_commission') or 0),
float(data.get('hydro_testing') or 0),
float(data.get('gst_sd_amount') or 0),
float(data.get('final_amount') or 0),
]
cursor.callproc('UpdateInvoice', [
data.get('pmc_no'),
village_id,
data.get('work_type'),
data.get('invoice_details'),
data.get('invoice_date'),
data.get('invoice_no'),
*numeric,
invoice_id
])
clear_results(cursor)
connection.commit()
except Exception as e:
connection.rollback()
raise e
finally:
cursor.close()
connection.close()
# ------------------- Update Inpayment -------------------
def update_inpayment(data):
connection = config.get_db_connection()
cursor = connection.cursor(dictionary=True)
try:
numeric = [
float(data.get('basic_amount') or 0),
float(data.get('debit_amount') or 0),
float(data.get('after_debit_amount') or 0),
float(data.get('amount') or 0),
float(data.get('gst_amount') or 0),
float(data.get('tds_amount') or 0),
float(data.get('sd_amount') or 0),
float(data.get('on_commission') or 0),
float(data.get('hydro_testing') or 0),
float(data.get('gst_sd_amount') or 0),
float(data.get('final_amount') or 0),
]
cursor.callproc('UpdateInpayment', [
data.get('work_type'),
data.get('invoice_details'),
data.get('invoice_date'),
*numeric,
data.get('pmc_no'),
data.get('invoice_no')
])
clear_results(cursor)
connection.commit()
except Exception as e:
connection.rollback()
raise e
finally:
cursor.close()
connection.close()
# ------------------- Delete Invoice -------------------
def delete_invoice_data(invoice_id, user_id):
connection = config.get_db_connection()
cursor = connection.cursor(dictionary=True)
try:
cursor.callproc('GetInvoicePMCById', [invoice_id])
record = {}
for result in cursor.stored_results():
record = result.fetchone() or {}
if not record:
raise Exception("Invoice not found")
cursor.callproc("DeleteInvoice", (invoice_id,))
clear_results(cursor)
cursor.callproc(
'DeleteInpaymentByPMCInvoice',
[record['PMC_No'], record['invoice_no']]
)
connection.commit()
except Exception as e:
connection.rollback()
raise e
finally:
cursor.close()
connection.close()

633
model/ItemCRUD.py Normal file
View File

@@ -0,0 +1,633 @@
# from flask_login import current_user
# from model.Utilities import RegEx, ResponseHandler, HtmlHelper, ItemCRUDType
# from model.Log import LogHelper
# import config
# import re
# import mysql.connector
# # ----------------------------------------------------------
# # Mapping Class
# # ----------------------------------------------------------
# class itemCRUDMapping:
# def __init__(self, itemType):
# if itemType is ItemCRUDType.Village:
# self.name = "Village"
# elif itemType is ItemCRUDType.Block:
# self.name = "Block"
# elif itemType is ItemCRUDType.State:
# self.name = "State"
# elif itemType is ItemCRUDType.HoldType:
# self.name = "Hold Type"
# elif itemType is ItemCRUDType.Subcontractor:
# self.name = "Subcontractor"
# elif itemType is ItemCRUDType.GSTRelease:
# self.name = "GST Release"
# else:
# self.name = "Item"
# # ----------------------------------------------------------
# # Generic CRUD Class
# # ----------------------------------------------------------
# class ItemCRUD:
# def __init__(self, itemType):
# self.isSuccess = False
# self.resultMessage = ""
# self.itemCRUDType = itemType
# self.itemCRUDMapping = itemCRUDMapping(itemType)
# # ----------------------------------------------------------
# # DELETE
# # ----------------------------------------------------------
# def DeleteItem(self, request, itemID, storedprocDelete):
# connection = config.get_db_connection()
# cursor = connection.cursor()
# LogHelper.log_action(
# f"Delete {self.itemCRUDMapping.name}",
# f"User {current_user.id} deleted {self.itemCRUDMapping.name} '{itemID}'"
# )
# try:
# cursor.callproc(storedprocDelete, (itemID,))
# connection.commit()
# self.isSuccess = True
# self.resultMessage = HtmlHelper.json_response(
# ResponseHandler.delete_success(self.itemCRUDMapping.name), 200
# )
# except mysql.connector.Error as e:
# print(f"Error deleting {self.itemCRUDMapping.name}: {e}")
# self.isSuccess = False
# self.resultMessage = HtmlHelper.json_response(
# ResponseHandler.delete_failure(self.itemCRUDMapping.name), 500
# )
# finally:
# cursor.close()
# connection.close()
# # ----------------------------------------------------------
# # ADD
# # ----------------------------------------------------------
# def AddItem(self, request, parentid=None, childname=None, storedprocfetch=None, storedprocadd=None, data=None):
# connection = config.get_db_connection()
# if not connection:
# self.isSuccess = False
# self.resultMessage = HtmlHelper.json_response(
# ResponseHandler.db_connection_failure(), 500
# )
# return
# cursor = connection.cursor()
# LogHelper.log_action(
# f"Add {self.itemCRUDMapping.name}",
# f"User {current_user.id} adding '{childname if childname else (data.get('Contractor_Name') if data else '')}'"
# )
# try:
# # ======================================================
# # SUBCONTRACTOR (MULTI-FIELD)
# # ======================================================
# if data:
# # Duplicate check
# cursor.callproc(storedprocfetch, (data['Contractor_Name'],))
# existing_item = None
# for rs in cursor.stored_results():
# existing_item = rs.fetchone()
# if existing_item:
# self.isSuccess = False
# self.resultMessage = HtmlHelper.json_response(
# ResponseHandler.already_exists(self.itemCRUDMapping.name), 409
# )
# return
# # Insert
# cursor.callproc(storedprocadd, (
# data['Contractor_Name'],
# data['Address'],
# data['Mobile_No'],
# data['PAN_No'],
# data['Email'],
# data['Gender'],
# data['GST_Registration_Type'],
# data['GST_No'],
# data['Contractor_password']
# ))
# connection.commit()
# self.isSuccess = True
# self.resultMessage = HtmlHelper.json_response(
# ResponseHandler.add_success(self.itemCRUDMapping.name), 200
# )
# return
# # ======================================================
# # NORMAL (Village / Block / State)
# # ======================================================
# if not re.match(RegEx.patternAlphabetOnly, childname):
# self.isSuccess = False
# self.resultMessage = HtmlHelper.json_response(
# ResponseHandler.invalid_name(self.itemCRUDMapping.name), 400
# )
# return
# # Duplicate check
# if parentid is None:
# cursor.callproc(storedprocfetch, (childname,))
# else:
# cursor.callproc(storedprocfetch, (childname, parentid))
# existing_item = None
# for rs in cursor.stored_results():
# existing_item = rs.fetchone()
# if existing_item:
# self.isSuccess = False
# self.resultMessage = HtmlHelper.json_response(
# ResponseHandler.already_exists(self.itemCRUDMapping.name), 409
# )
# return
# # Insert
# if parentid is None:
# cursor.callproc(storedprocadd, (childname,))
# else:
# cursor.callproc(storedprocadd, (childname, parentid))
# connection.commit()
# self.isSuccess = True
# self.resultMessage = HtmlHelper.json_response(
# ResponseHandler.add_success(self.itemCRUDMapping.name), 200
# )
# except mysql.connector.Error as e:
# print(f"Database Error: {e}")
# self.isSuccess = False
# self.resultMessage = HtmlHelper.json_response(
# ResponseHandler.add_failure(self.itemCRUDMapping.name), 500
# )
# finally:
# cursor.close()
# connection.close()
# # ----------------------------------------------------------
# # EDIT
# # ----------------------------------------------------------
# def EditItem(self, request, childid, parentid=None, childname=None, storedprocupdate=None, data=None):
# connection = config.get_db_connection()
# cursor = connection.cursor()
# LogHelper.log_action(
# f"Edit {self.itemCRUDMapping.name}",
# f"User {current_user.id} edited '{childid}'"
# )
# try:
# # ======================================================
# # SUBCONTRACTOR (MULTI-FIELD)
# # ======================================================
# if data:
# cursor.callproc(storedprocupdate, (
# childid,
# data['Contractor_Name'],
# data['Address'],
# data['Mobile_No'],
# data['PAN_No'],
# data['Email'],
# data['Gender'],
# data['GST_Registration_Type'],
# data['GST_No'],
# data['Contractor_password']
# ))
# connection.commit()
# self.isSuccess = True
# self.resultMessage = HtmlHelper.json_response(
# ResponseHandler.update_success(self.itemCRUDMapping.name), 200
# )
# return
# # ======================================================
# # NORMAL
# # ======================================================
# if not re.match(RegEx.patternAlphabetOnly, childname):
# self.isSuccess = False
# self.resultMessage = ResponseHandler.update_failure(self.itemCRUDMapping.name)['message']
# return
# if parentid is None:
# cursor.callproc(storedprocupdate, (childid, childname))
# else:
# cursor.callproc(storedprocupdate, (childid, parentid, childname))
# connection.commit()
# self.isSuccess = True
# self.resultMessage = ResponseHandler.update_success(self.itemCRUDMapping.name)['message']
# except mysql.connector.Error as e:
# print(f"Error updating {self.itemCRUDMapping.name}: {e}")
# self.isSuccess = False
# self.resultMessage = HtmlHelper.json_response(
# ResponseHandler.update_failure(self.itemCRUDMapping.name), 500
# )
# finally:
# cursor.close()
# connection.close()
# # ----------------------------------------------------------
# # GET ALL
# # ----------------------------------------------------------
# def GetAllData(self, request, storedproc):
# data = []
# connection = config.get_db_connection()
# if not connection:
# return []
# cursor = connection.cursor()
# try:
# cursor.callproc(storedproc)
# for result in cursor.stored_results():
# data = result.fetchall()
# self.isSuccess = True
# except mysql.connector.Error as e:
# print(f"Error fetching {self.itemCRUDMapping.name}: {e}")
# self.isSuccess = False
# self.resultMessage = HtmlHelper.json_response(
# ResponseHandler.fetch_failure(self.itemCRUDMapping.name), 500
# )
# return []
# finally:
# cursor.close()
# connection.close()
# return data
# # ----------------------------------------------------------
# # GET BY ID
# # ----------------------------------------------------------
# def GetDataByID(self, id, storedproc):
# data = None
# connection = config.get_db_connection()
# cursor = connection.cursor()
# try:
# cursor.callproc(storedproc, (id,))
# for rs in cursor.stored_results():
# data = rs.fetchone()
# except mysql.connector.Error as e:
# print(f"Error fetching {self.itemCRUDMapping.name}: {e}")
# finally:
# cursor.close()
# connection.close()
# return data
# # ----------------------------------------------------------
# # CHECK ITEM
# # ----------------------------------------------------------
# def CheckItem(self, request, parentid, childname, storedprocfetch):
# connection = config.get_db_connection()
# cursor = connection.cursor()
# LogHelper.log_action(
# f"Check {self.itemCRUDMapping.name}",
# f"User {current_user.id} checked '{childname}'"
# )
# if not re.match(RegEx.patternAlphabetOnly, childname):
# return HtmlHelper.json_response(
# ResponseHandler.invalid_name(self.itemCRUDMapping.name), 400
# )
# try:
# if parentid is None:
# cursor.callproc(storedprocfetch, (childname,))
# else:
# cursor.callproc(storedprocfetch, (childname, parentid))
# existing_item = None
# for rs in cursor.stored_results():
# existing_item = rs.fetchone()
# if existing_item:
# return HtmlHelper.json_response(
# ResponseHandler.already_exists(self.itemCRUDMapping.name), 409
# )
# return HtmlHelper.json_response(
# ResponseHandler.is_available(self.itemCRUDMapping.name), 200
# )
# except mysql.connector.Error as e:
# print(f"Error checking {self.itemCRUDMapping.name}: {e}")
# return HtmlHelper.json_response(
# ResponseHandler.fetch_failure(self.itemCRUDMapping.name), 500
# )
# finally:
# cursor.close()
# connection.close()
from flask_login import current_user
from model.Utilities import RegEx, ResponseHandler, HtmlHelper, ItemCRUDType
from model.Log import LogHelper
import config
import re
import mysql.connector
# ----------------------------------------------------------
# Mapping Class
# ----------------------------------------------------------
class itemCRUDMapping:
def __init__(self, itemType):
if itemType is ItemCRUDType.Village:
self.name = "Village"
elif itemType is ItemCRUDType.Block:
self.name = "Block"
elif itemType is ItemCRUDType.State:
self.name = "State"
elif itemType is ItemCRUDType.HoldType:
self.name = "Hold Type"
elif itemType is ItemCRUDType.Subcontractor:
self.name = "Subcontractor"
elif itemType is ItemCRUDType.GSTRelease:
self.name = "GST Release"
else:
self.name = "Item"
# ----------------------------------------------------------
# Generic CRUD Class
# ----------------------------------------------------------
class ItemCRUD:
def __init__(self, itemType):
self.isSuccess = False
self.resultMessage = ""
self.itemCRUDType = itemType
self.itemCRUDMapping = itemCRUDMapping(itemType)
# ------------------- DELETE -------------------
def DeleteItem(self, request, itemID, storedprocDelete):
connection = config.get_db_connection()
cursor = connection.cursor()
LogHelper.log_action(
f"Delete {self.itemCRUDMapping.name}",
f"User {current_user.id} deleted {self.itemCRUDMapping.name} '{itemID}'"
)
try:
cursor.callproc(storedprocDelete, (itemID,))
connection.commit()
self.isSuccess = True
self.resultMessage = HtmlHelper.json_response(
ResponseHandler.delete_success(self.itemCRUDMapping.name), 200
)
except mysql.connector.Error as e:
print(f"Error deleting {self.itemCRUDMapping.name}: {e}")
self.isSuccess = False
self.resultMessage = HtmlHelper.json_response(
ResponseHandler.delete_failure(self.itemCRUDMapping.name), 500
)
finally:
cursor.close()
connection.close()
# ------------------- ADD -------------------
def AddItem(self, request, parentid=None, childname=None, storedprocfetch=None, storedprocadd=None, data=None):
connection = config.get_db_connection()
if not connection:
self.isSuccess = False
self.resultMessage = HtmlHelper.json_response(
ResponseHandler.db_connection_failure(), 500
)
return
cursor = connection.cursor()
try:
# ---------- GST Release (multi-field) ----------
if self.itemCRUDType == ItemCRUDType.GSTRelease and data:
cursor.callproc(storedprocadd, (
data['PMC_No'],
data['Invoice_No'],
data['Basic_Amount'],
data['Final_Amount'],
data['Total_Amount'],
data['UTR'],
data['Contractor_ID']
))
connection.commit()
self.isSuccess = True
self.resultMessage = HtmlHelper.json_response(
ResponseHandler.add_success(self.itemCRUDMapping.name), 200
)
return
# ---------- Subcontractor (multi-field) ----------
if data and self.itemCRUDType == ItemCRUDType.Subcontractor:
cursor.callproc(storedprocadd, (
data['Contractor_Name'],
data['Address'],
data['Mobile_No'],
data['PAN_No'],
data['Email'],
data['Gender'],
data['GST_Registration_Type'],
data['GST_No'],
data['Contractor_password']
))
connection.commit()
self.isSuccess = True
self.resultMessage = HtmlHelper.json_response(
ResponseHandler.add_success(self.itemCRUDMapping.name), 200
)
return
# ---------- Normal Items (Village / Block / State) ----------
if not re.match(r'^[A-Za-z0-9 %]+$', childname):
self.isSuccess = False
self.resultMessage = HtmlHelper.json_response(
ResponseHandler.invalid_name(self.itemCRUDMapping.name), 400
)
return
# Duplicate check
if parentid is None:
cursor.callproc(storedprocfetch, (childname,))
else:
cursor.callproc(storedprocfetch, (childname, parentid))
existing_item = None
for rs in cursor.stored_results():
existing_item = rs.fetchone()
if existing_item:
self.isSuccess = False
self.resultMessage = HtmlHelper.json_response(
ResponseHandler.already_exists(self.itemCRUDMapping.name), 409
)
return
# Insert
if parentid is None:
cursor.callproc(storedprocadd, (childname,))
else:
cursor.callproc(storedprocadd, (childname, parentid))
connection.commit()
self.isSuccess = True
self.resultMessage = HtmlHelper.json_response(
ResponseHandler.add_success(self.itemCRUDMapping.name), 200
)
except mysql.connector.Error as e:
print(f"Database Error: {e}")
self.isSuccess = False
self.resultMessage = HtmlHelper.json_response(
ResponseHandler.add_failure(self.itemCRUDMapping.name), 500
)
finally:
cursor.close()
connection.close()
# ------------------- EDIT -------------------
def EditItem(self, request, childid, parentid=None, childname=None, storedprocupdate=None, data=None):
connection = config.get_db_connection()
cursor = connection.cursor()
try:
# ---------- GST Release ----------
if self.itemCRUDType == ItemCRUDType.GSTRelease and data:
cursor.callproc(storedprocupdate, (
data['PMC_No'],
data['Invoice_No'],
data['Basic_Amount'],
data['Final_Amount'],
data['Total_Amount'],
data['UTR'],
childid
))
connection.commit()
self.isSuccess = True
self.resultMessage = HtmlHelper.json_response(
ResponseHandler.update_success(self.itemCRUDMapping.name), 200
)
return
# ---------- Subcontractor ----------
if self.itemCRUDType == ItemCRUDType.Subcontractor and data:
cursor.callproc(storedprocupdate, (
childid,
data['Contractor_Name'],
data['Address'],
data['Mobile_No'],
data['PAN_No'],
data['Email'],
data['Gender'],
data['GST_Registration_Type'],
data['GST_No'],
data['Contractor_password']
))
connection.commit()
self.isSuccess = True
self.resultMessage = HtmlHelper.json_response(
ResponseHandler.update_success(self.itemCRUDMapping.name), 200
)
return
# ---------- Normal Items ----------
if not re.match(RegEx.patternAlphabetOnly, childname):
self.isSuccess = False
self.resultMessage = ResponseHandler.update_failure(self.itemCRUDMapping.name)['message']
return
if parentid is None:
cursor.callproc(storedprocupdate, (childid, childname))
else:
cursor.callproc(storedprocupdate, (childid, parentid, childname))
connection.commit()
self.isSuccess = True
self.resultMessage = ResponseHandler.update_success(self.itemCRUDMapping.name)['message']
except mysql.connector.Error as e:
print(f"Error updating {self.itemCRUDMapping.name}: {e}")
self.isSuccess = False
self.resultMessage = HtmlHelper.json_response(
ResponseHandler.update_failure(self.itemCRUDMapping.name), 500
)
finally:
cursor.close()
connection.close()
# ------------------- GET ALL -------------------
def GetAllData(self, request, storedproc):
data = []
connection = config.get_db_connection()
if not connection:
return []
cursor = connection.cursor()
try:
cursor.callproc(storedproc)
for result in cursor.stored_results():
data = result.fetchall()
self.isSuccess = True
except mysql.connector.Error as e:
print(f"Error fetching {self.itemCRUDMapping.name}: {e}")
self.isSuccess = False
self.resultMessage = HtmlHelper.json_response(
ResponseHandler.fetch_failure(self.itemCRUDMapping.name), 500
)
return []
finally:
cursor.close()
connection.close()
return data
# ------------------- GET BY ID -------------------
def GetDataByID(self, id, storedproc):
data = None
connection = config.get_db_connection()
cursor = connection.cursor()
try:
cursor.callproc(storedproc, (id,))
for rs in cursor.stored_results():
data = rs.fetchone()
except mysql.connector.Error as e:
print(f"Error fetching {self.itemCRUDMapping.name}: {e}")
finally:
cursor.close()
connection.close()
return data

104
model/Log.py Normal file
View File

@@ -0,0 +1,104 @@
import os
from datetime import datetime
from flask import current_app
from flask_login import current_user
class LogHelper:
@staticmethod
def log_action(action, details=""):
"""Add a log entry."""
log_data = LogData()
log_data.add_log(action, details)
class LogData:
def __init__(self):
self.filepath = os.path.join(current_app.root_path, 'activity.log')
self.timestamp = datetime.now().strftime("%Y-%m-%d %H:%M:%S")
self.user = getattr(current_user, "cn", None) \
or getattr(current_user, "username", None) \
or getattr(current_user, "sAMAccountName", "Unknown")
def add_log(self, action, details=""):
"""Create/Add a log entry."""
with open(self.filepath, "a", encoding="utf-8") as f:
f.write(
f"Timestamp: {self.timestamp} | "
f"User: {self.user} | "
f"Action: {action} | "
f"Details: {details}\n"
)
def get_all_logs(self):
"""Read all logs."""
logs = []
if os.path.exists(self.filepath):
with open(self.filepath, 'r', encoding="utf-8") as f:
for line in f:
parts = line.strip().split(" | ")
if len(parts) == 4:
logs.append({
"timestamp": parts[0].split(":", 1)[1].strip(),
"user": parts[1].split(":", 1)[1].strip(),
"action": parts[2].split(":", 1)[1].strip(),
"details": parts[3].split(":", 1)[1].strip()
})
return logs
def get_filtered_logs(self, start_date=None, end_date=None, user_name=None):
"""Filter logs by date and/or user."""
logs = self.get_all_logs()
# Filter by date
if start_date or end_date:
start_dt = datetime.strptime(start_date, "%Y-%m-%d") if start_date else datetime.min
end_dt = datetime.strptime(end_date, "%Y-%m-%d") if end_date else datetime.max
logs = [
log for log in logs
if start_dt <= datetime.strptime(log["timestamp"], "%Y-%m-%d %H:%M:%S") <= end_dt
]
# Filter by username
if user_name:
logs = [log for log in logs if user_name.lower() in log.get("user", "").lower()]
return logs
def update_log(self, index, action=None, details=None):
"""Update a specific log entry by index (0-based)."""
logs = self.get_all_logs()
if 0 <= index < len(logs):
if action:
logs[index]["action"] = action
if details:
logs[index]["details"] = details
self._rewrite_logs_file(logs)
return True
return False
def delete_log(self, index):
"""Delete a specific log entry by index (0-based)."""
logs = self.get_all_logs()
if 0 <= index < len(logs):
logs.pop(index)
self._rewrite_logs_file(logs)
return True
return False
# ------------------- INTERNAL HELPER -------------------
def _rewrite_logs_file(self, logs):
"""Overwrite the log file with current logs."""
with open(self.filepath, "w", encoding="utf-8") as f:
for log in logs:
f.write(
f"Timestamp: {log['timestamp']} | "
f"User: {log['user']} | "
f"Action: {log['action']} | "
f"Details: {log['details']}\n"
)

293
model/PmcReport.py Normal file
View File

@@ -0,0 +1,293 @@
import os
import openpyxl
from openpyxl.styles import Font, PatternFill
from decimal import Decimal
from datetime import datetime
import config
from flask_login import current_user
from model.Log import LogHelper
class PmcReport:
@staticmethod
def get_pmc_report(pmc_no):
connection = config.get_db_connection()
cursor = connection.cursor(dictionary=True, buffered=True)
try:
cursor.callproc("GetContractorInfoByPmcNo", (pmc_no,))
pmc_info = next(cursor.stored_results()).fetchone()
if not pmc_info:
return None
cursor.callproc("Get_pmc_hold_types", (pmc_no, pmc_info["Contractor_Id"]))
hold_types = next(cursor.stored_results()).fetchall()
# Extract hold_type_ids
hold_type_ids = [ht['hold_type_id'] for ht in hold_types]
invoices = []
hold_amount_total = 0
if hold_type_ids:
hold_type_ids_str = ",".join(map(str, hold_type_ids))
cursor.callproc(
'GetInvoices_WithHold',
[pmc_no, pmc_info["Contractor_Id"], hold_type_ids_str]
)
else:
cursor.callproc(
'GetInvoices_NoHold',
[pmc_no, pmc_info["Contractor_Id"]]
)
for result in cursor.stored_results():
invoices = result.fetchall()
if hold_type_ids:
hold_amount_total = sum(row.get('hold_amount', 0) or 0 for row in invoices)
total_invo_final = sum(row.get('Final_Amount', 0) or 0 for row in invoices)
# GST RELEASE
cursor.callproc('GetGSTReleaseByPMC', [pmc_no])
gst_rel = []
for result in cursor.stored_results():
gst_rel = result.fetchall()
total_gst_basic = sum(row.get('basic_amount', 0) or 0 for row in gst_rel)
total_gst_final = sum(row.get('final_amount', 0) or 0 for row in gst_rel)
# ---------------- HOLD RELEASE ----------------
cursor.callproc('GetHoldReleaseByPMC', [pmc_no])
hold_release = []
for result in cursor.stored_results():
hold_release = result.fetchall()
# ---------------- CREDIT NOTE ----------------
cursor.callproc('GetCreditNoteByPMC', [pmc_no])
credit_note = []
for result in cursor.stored_results():
credit_note = result.fetchall()
# ---------------- PAYMENTS ----------------
cursor.callproc('GetPaymentsByPMC', [pmc_no])
payments = []
for result in cursor.stored_results():
payments = result.fetchall()
total_pay_amount = sum(row.get('Payment_Amount', 0) or 0 for row in payments)
total_pay_total = sum(row.get('Total_amount', 0) or 0 for row in payments)
totals = {
"sum_invo_basic_amt": sum(row.get('Basic_Amount', 0) or 0 for row in invoices),
"sum_invo_debit_amt": sum(row.get('Debit_Amount', 0) or 0 for row in invoices),
"sum_invo_after_debit_amt": sum(row.get('After_Debit_Amount', 0) or 0 for row in invoices),
"sum_invo_amt": sum(row.get('Amount', 0) or 0 for row in invoices),
"sum_invo_gst_amt": sum(row.get('GST_Amount', 0) or 0 for row in invoices),
"sum_invo_tds_amt": sum(row.get('TDS_Amount', 0) or 0 for row in invoices),
"sum_invo_ds_amt": sum(row.get('SD_Amount', 0) or 0 for row in invoices),
"sum_invo_on_commission": sum(row.get('On_Commission', 0) or 0 for row in invoices),
"sum_invo_hydro_test": sum(row.get('Hydro_Testing', 0) or 0 for row in invoices),
"sum_invo_gst_sd_amt": sum(row.get('GST_SD_Amount', 0) or 0 for row in invoices),
"sum_invo_final_amt": total_invo_final,
"sum_invo_hold_amt": hold_amount_total,
"sum_gst_basic_amt": total_gst_basic,
"sum_gst_final_amt": total_gst_final,
"sum_pay_payment_amt": total_pay_amount,
"sum_pay_tds_payment_amt": sum(row.get('TDS_Payment_Amount', 0) or 0 for row in payments),
"sum_pay_total_amt": total_pay_total
}
return {
"info": pmc_info,
"invoices": invoices,
"hold_types": hold_types,
"gst_rel": gst_rel,
"payments": payments,
"credit_note": credit_note,
"hold_release": hold_release,
"total": totals
}
finally:
cursor.close()
connection.close()
@staticmethod
def download_pmc_report(pmc_no):
connection = config.get_db_connection()
output_folder = "static/download"
output_file = os.path.join(output_folder, f"PMC_Report_{pmc_no}.xlsx")
if not os.path.exists(output_folder):
os.makedirs(output_folder)
cursor = connection.cursor(dictionary=True)
try:
cursor.callproc('GetContractorDetailsByPMC', [pmc_no])
contractor_info = next(cursor.stored_results()).fetchone()
if not contractor_info:
return None
cursor.callproc('GetHoldTypesByContractor', [contractor_info["Contractor_Id"]])
hold_types = next(cursor.stored_results()).fetchall()
hold_type_map = {ht['hold_type_id']: ht['hold_type'] for ht in hold_types}
cursor.callproc('GetInvoicesAndGstReleaseByPmcNo', [pmc_no])
invoices = next(cursor.stored_results()).fetchall()
cursor.callproc('GetCreditNoteByContractor',[contractor_info["Contractor_Id"]])
credit_notes = []
for result in cursor.stored_results():
credit_notes = result.fetchall()
credit_note_map = {}
for cn in credit_notes:
key = (cn["PMC_No"], cn["Invoice_No"])
credit_note_map.setdefault(key, []).append(cn)
cursor.callproc('GetHoldAmountsByContractor', [contractor_info["Contractor_Id"]])
hold_amounts = next(cursor.stored_results()).fetchall()
hold_data = {}
for h in hold_amounts:
hold_data.setdefault(h['Invoice_Id'], {})[h['hold_type_id']] = h['hold_amount']
cursor.callproc('GetAllPaymentsByPMC', [pmc_no])
all_payments = next(cursor.stored_results()).fetchall()
payments_map = {}
extra_payments = []
for pay in all_payments:
if pay['invoice_no']:
payments_map.setdefault(pay['invoice_no'], []).append(pay)
else:
extra_payments.append(pay)
# ---------------- GST RELEASE DETAILS ----------------
cursor.callproc('GetGSTReleaseDetailsByPMC', [pmc_no])
gst_releases = []
for result in cursor.stored_results():
gst_releases = result.fetchall()
gst_release_map = {}
for gr in gst_releases:
invoice_nos = []
if gr['Invoice_No']:
cleaned = gr['Invoice_No'].replace(' ', '')
if '&' in cleaned:
invoice_nos = cleaned.split('&')
elif ',' in cleaned:
invoice_nos = cleaned.split(',')
else:
invoice_nos = [cleaned]
for inv_no in invoice_nos:
gst_release_map.setdefault(inv_no, []).append(gr)
LogHelper.log_action(
"Download PMC Report",
f"User {current_user.id} Download PMC Report '{pmc_no}'"
)
workbook = openpyxl.Workbook()
sheet = workbook.active
sheet.title = "PMC Report"
sheet.append(["", "", "Laxmi Civil Engineering Services PVT. LTD."])
sheet.append(["Contractor Name", contractor_info["Contractor_Name"], "", "GST No", contractor_info["GST_No"], "", "GST Type", contractor_info["GST_Registration_Type"]])
sheet.append(["State", contractor_info["State_Name"], "", "PAN No", contractor_info["PAN_No"], "", "Address", contractor_info["Address"]])
sheet.append(["District", contractor_info["District_Name"], "", "Mobile No", contractor_info["Mobile_No"]])
sheet.append(["Block", contractor_info["Block_Name"], "", "Email", contractor_info["Email"]])
sheet.append([])
base_headers = [
"PMC No","Village","Work Type","Invoice Details","Invoice Date","Invoice No",
"Basic Amount","Debit","After Debit Amount","GST (18%)","Amount","TDS (1%)",
"SD (5%)","On Commission","Hydro Testing","GST SD Amount"
]
hold_headers = [ht['hold_type'] for ht in hold_types]
payment_headers = [
"Final Amount","Payment Amount","TDS Payment","Total Paid","UTR"
]
sheet.append(base_headers + hold_headers + payment_headers)
header_fill = PatternFill(start_color="ADD8E6",end_color="ADD8E6",fill_type="solid")
header_font = Font(bold=True)
for cell in sheet[sheet.max_row]:
cell.font = header_font
cell.fill = header_fill
seen_invoices = set()
processed_payments = set()
for inv in invoices:
invoice_no = inv["Invoice_No"]
payments = payments_map.get(invoice_no, [])
if invoice_no not in seen_invoices:
seen_invoices.add(invoice_no)
first_payment = payments[0] if payments else None
row = [
pmc_no, inv["Village_Name"], inv["Work_Type"],
inv["Invoice_Details"], inv["Invoice_Date"], invoice_no,
inv["Basic_Amount"], inv["Debit_Amount"],
inv["After_Debit_Amount"], inv["GST_Amount"],
inv["Amount"], inv["TDS_Amount"], inv["SD_Amount"],
inv["On_Commission"], inv["Hydro_Testing"], inv["GST_SD_Amount"]
]
invoice_holds = hold_data.get(inv["Invoice_Id"], {})
for ht_id in hold_type_map.keys():
row.append(invoice_holds.get(ht_id, ""))
row += [
inv["Final_Amount"],
first_payment["Payment_Amount"] if first_payment else "",
first_payment["TDS_Payment_Amount"] if first_payment else "",
first_payment["Total_amount"] if first_payment else "",
first_payment["UTR"] if first_payment else ""
]
sheet.append(row)
workbook.save(output_file)
workbook.close()
return output_folder, f"PMC_Report_{pmc_no}.xlsx"
finally:
cursor.close()
connection.close()

116
model/Report.py Normal file
View File

@@ -0,0 +1,116 @@
import config
from datetime import datetime
class ReportHelper:
@staticmethod
def search_contractor(subcontractor_name, pmc_no, state, district, block, village, year_from, year_to):
connection = config.get_db_connection()
cursor = connection.cursor(dictionary=True)
cursor.callproc("search_contractor_info", [
subcontractor_name or None,
pmc_no or None,
state or None,
district or None,
block or None,
village or None,
year_from or None,
year_to or None
])
data = []
for result in cursor.stored_results():
data = result.fetchall()
cursor.close()
connection.close()
return data
@staticmethod
def get_contractor_report(contractor_id):
connection = config.get_db_connection()
cursor = connection.cursor(dictionary=True, buffered=True)
try:
# Contractor Info
cursor.callproc('GetContractorInfo', [contractor_id])
for result in cursor.stored_results():
contInfo = result.fetchone()
# Hold Types
cursor.callproc('GetContractorHoldTypes', [contractor_id])
for result in cursor.stored_results():
hold_types = result.fetchall()
# Invoices
cursor.callproc('GetContractorInvoices', [contractor_id])
for result in cursor.stored_results():
invoices = result.fetchall()
# GST Release
cursor.callproc('GetGSTRelease', [contractor_id])
for result in cursor.stored_results():
gst_rel = result.fetchall()
# Hold Release
cursor.callproc('GetHoldRelease', [contractor_id])
for result in cursor.stored_results():
hold_release = result.fetchall()
# Credit Note
cursor.callproc('GetCreditNote', [contractor_id])
for result in cursor.stored_results():
credit_note = result.fetchall()
# Payments
cursor.callproc('GetPayments', [contractor_id])
for result in cursor.stored_results():
payments = result.fetchall()
# Totals
total = {
"sum_invo_basic_amt": float(sum(row['Basic_Amount'] or 0 for row in invoices)),
"sum_invo_debit_amt": float(sum(row['Debit_Amount'] or 0 for row in invoices)),
"sum_invo_after_debit_amt": float(sum(row['After_Debit_Amount'] or 0 for row in invoices)),
"sum_invo_amt": float(sum(row['Amount'] or 0 for row in invoices)),
"sum_invo_gst_amt": float(sum(row['GST_Amount'] or 0 for row in invoices)),
"sum_invo_tds_amt": float(sum(row['TDS_Amount'] or 0 for row in invoices)),
"sum_invo_ds_amt": float(sum(row['SD_Amount'] or 0 for row in invoices)),
"sum_invo_on_commission": float(sum(row['On_Commission'] or 0 for row in invoices)),
"sum_invo_hydro_test": float(sum(row['Hydro_Testing'] or 0 for row in invoices)),
"sum_invo_gst_sd_amt": float(sum(row['GST_SD_Amount'] or 0 for row in invoices)),
"sum_invo_final_amt": float(sum(row['Final_Amount'] or 0 for row in invoices)),
"sum_invo_hold_amt": float(sum(row['hold_amount'] or 0 for row in invoices)),
"sum_gst_basic_amt": float(sum(row['basic_amount'] or 0 for row in gst_rel)),
"sum_gst_final_amt": float(sum(row['final_amount'] or 0 for row in gst_rel)),
"sum_pay_payment_amt": float(sum(row['Payment_Amount'] or 0 for row in payments)),
"sum_pay_tds_payment_amt": float(sum(row['TDS_Payment_Amount'] or 0 for row in payments)),
"sum_pay_total_amt": float(sum(row['Total_amount'] or 0 for row in payments))
}
current_date = datetime.now().strftime('%Y-%m-%d')
finally:
cursor.close()
connection.close()
return {
"contInfo": contInfo,
"invoices": invoices,
"hold_types": hold_types,
"gst_rel": gst_rel,
"payments": payments,
"credit_note": credit_note,
"hold_release": hold_release,
"total": total,
"current_date": current_date
}

246
model/State.py Normal file
View File

@@ -0,0 +1,246 @@
from flask import Flask, render_template, request, redirect, url_for, send_from_directory, flash, jsonify, json
from flask import current_app
from datetime import datetime
from flask_login import LoginManager, UserMixin, login_user, logout_user, login_required, current_user
from model.Utilities import RegEx, ResponseHandler, HtmlHelper, ItemCRUDType
from model.Log import LogData, LogHelper
import os
import config
import re
import mysql.connector
from mysql.connector import Error
class State:
isSuccess = False
resultMessage = ""
def __init__(self):
self.isSuccess = False
self.resultMessage = ""
def AddState(self, request):
"""Log user actions with timestamp, user, action, and details."""
statedata = []
connection = config.get_db_connection()
if connection:
cursor = connection.cursor()
state_name = request.form['state_Name'].strip()
LogHelper.log_action("Add State", f"User {current_user.id} added state '{state_name}'")
if not re.match(RegEx.patternAlphabetOnly, state_name):
self.isSuccess = False
self.resultMessage = HtmlHelper.json_response(ResponseHandler.invalid_name("state"), 400)
return
try:
cursor.callproc("CheckStateExists", (state_name,))
for data in cursor.stored_results():
existing_state = data.fetchone()
if existing_state:
self.isSuccess = False
self.resultMessage = HtmlHelper.json_response(ResponseHandler.already_exists("state"), 409)
return
# cursor.execute("call SaveState (%s)", (state_name,))
cursor.callproc("SaveState", (state_name,))
connection.commit()
self.isSuccess = True
self.resultMessage = HtmlHelper.json_response(ResponseHandler.add_success("state"), 200)
return
except mysql.connector.Error as e:
print(f"Error inserting state: {e}")
self.isSuccess = False
self.resultMessage = HtmlHelper.json_response(ResponseHandler.add_failure("state"), 500)
return
#Need to make this seperate
def GetAllStates(self, request):
"""Log user actions with timestamp, user, action, and details."""
statedata = []
connection = config.get_db_connection()
self.isSuccess = False
self.resultMessage = ""
if not connection:
return []
cursor = connection.cursor()
try:
cursor.callproc("GetAllStates")
for res in cursor.stored_results():
statedata = res.fetchall()
self.isSuccess = True
except mysql.connector.Error as e:
print(f"Error fetching states: {e}")
self.isSuccess = False
self.resultMessage = HtmlHelper.json_response(ResponseHandler.fetch_failure("state"), 500)
return []
finally:
cursor.close()
connection.close()
return statedata
def CheckState(self, request):
self.isSuccess = False
self.resultMessage = ""
connection = config.get_db_connection()
#connection closing needs to be verified
if connection:
cursor = connection.cursor()
state_name = request.json.get('state_Name', '').strip()
LogHelper.log_action("Check State", f"User {current_user.id} Checked state '{state_name}'")
if not re.match(RegEx.patternAlphabetOnly, state_name):
self.isSuccess = False
self.resultMessage = HtmlHelper.json_response(ResponseHandler.invalid_name("state"), 400)
return HtmlHelper.json_response(ResponseHandler.invalid_name("state"), 400)
try:
# cursor.execute("SELECT * FROM states WHERE State_Name = %s", (state_name,))
# existing_state = cursor.fetchone()
cursor.callproc("CheckStateExists", (state_name,))
for data in cursor.stored_results():
existing_state = data.fetchone()
if existing_state:
self.isSuccess = False
self.resultMessage = HtmlHelper.json_response(ResponseHandler.already_exists("state"), 409)
return HtmlHelper.json_response(ResponseHandler.already_exists("state"), 409)
else:
self.isSuccess = True
self.resultMessage = HtmlHelper.json_response(ResponseHandler.is_available("state"), 200)
return HtmlHelper.json_response(ResponseHandler.is_available("state"), 200)
except mysql.connector.Error as e:
self.isSuccess = False
self.resultMessage = HtmlHelper.json_response(ResponseHandler.add_failure("state"), 500)
print(f"Error checking state: {e}")
return HtmlHelper.json_response(ResponseHandler.add_failure("state"), 500)
finally:
cursor.close()
connection.close()
def DeleteState(self, request, id):
self.isSuccess = False
self.resultMessage = ""
connection = config.get_db_connection()
cursor = connection.cursor()
LogHelper.log_action("Delete State", f"User {current_user.id} Deleted state '{id}'")
try:
cursor.callproc('DeleteState', (id,))
connection.commit()
self.resultMessage = "Successfully Deleted"
self.isSuccess = True
except mysql.connector.Error as e:
print(f"Error deleting data: {e}")
self.isSuccess = False
self.resultMessage = HtmlHelper.json_response(ResponseHandler.delete_failure("state"), 500)
return HtmlHelper.json_response(ResponseHandler.delete_failure("state"), 500)
finally:
cursor.close()
connection.close()
return self.resultMessage
def EditState(self, request, id):
self.isSuccess = False
self.resultMessage = ""
connection = config.get_db_connection()
cursor = connection.cursor()
# str_pattern_reg = r"^[A-Za-z\s]+$"
state_name = request.form['state_Name'].strip()
LogHelper.log_action("Edit State", f"User {current_user.id} Edited state '{state_name}'")
if not re.match(RegEx.patternAlphabetOnly, state_name):
self.isSuccess = False
self.resultMessage = ResponseHandler.invalid_name("state"), 400
return ResponseHandler.invalid_name("state"), 400
try:
# cursor.execute("UPDATE states SET State_Name = %s WHERE State_ID = %s", (state_name, id))
cursor.callproc("UpdateStateById", (id, state_name))
connection.commit()
self.isSuccess = True
self.resultMessage = "Successfully Edited"
return redirect(url_for('state.add_state'))
except mysql.connector.Error as e:
print(f"Error updating data: {e}")
self.isSuccess = True
self.resultMessage = ResponseHandler.add_failure("state"), 500
return ResponseHandler.add_failure("state"), 500
finally:
cursor.close()
connection.close()
def GetStateByID(self, request, id):
"""Log user actions with timestamp, user, action, and details."""
statedata = []
self.isSuccess = False
self.resultMessage = ""
connection = config.get_db_connection()
if not connection:
return []
cursor = connection.cursor()
try:
cursor.callproc("GetStateByID", (id,))
for res in cursor.stored_results():
statedata = res.fetchone()
if statedata:
self.isSuccess = True
self.resultMessage = "Success in Fetching"
else:
self.isSuccess = False
self.resultMessage = "State Not Found"
except mysql.connector.Error as e:
print(f"Error fetching states: {e}")
self.isSuccess = False
self.resultMessage = HtmlHelper.json_response(ResponseHandler.fetch_failure("state"), 500)
return []
finally:
cursor.close()
connection.close()
return statedata

140
model/Subcontractor.py Normal file
View File

@@ -0,0 +1,140 @@
from model.Utilities import ItemCRUDType
from model.ItemCRUD import ItemCRUD
class Subcontractor:
def __init__(self):
self.isSuccess = False
self.resultMessage = ""
# ----------------------------------------------------------
# ADD
# ----------------------------------------------------------
def AddSubcontractor(self, request):
subcontractor = ItemCRUD(itemType=ItemCRUDType.Subcontractor)
data = {
"Contractor_Name": request.form.get('Contractor_Name', '').strip(),
"Address": request.form.get('Address', '').strip(),
"Mobile_No": request.form.get('Mobile_No', '').strip(),
"PAN_No": request.form.get('PAN_No', '').strip(),
"Email": request.form.get('Email', '').strip(),
"Gender": request.form.get('Gender', '').strip(),
"GST_Registration_Type": request.form.get('GST_Registration_Type', '').strip(),
"GST_No": request.form.get('GST_No', '').strip(),
"Contractor_password": request.form.get('Contractor_password', '').strip()
}
subcontractor.AddItem(
request=request,
data=data,
storedprocfetch="GetSubcontractorByName",
storedprocadd="SaveContractor"
)
self.isSuccess = subcontractor.isSuccess
self.resultMessage = subcontractor.resultMessage
return
# ----------------------------------------------------------
# GET ALL
# ----------------------------------------------------------
def GetAllSubcontractors(self, request):
subcontractor = ItemCRUD(itemType=ItemCRUDType.Subcontractor)
data = subcontractor.GetAllData(
request=request,
storedproc="GetAllSubcontractors"
)
self.isSuccess = subcontractor.isSuccess
self.resultMessage = subcontractor.resultMessage
return data
# ----------------------------------------------------------
# GET BY ID
# ----------------------------------------------------------
def GetSubcontractorByID(self, id):
subcontractor = ItemCRUD(itemType=ItemCRUDType.Subcontractor)
data = subcontractor.GetDataByID(
id=id,
storedproc="GetSubcontractorById"
)
if data:
self.isSuccess = True
else:
self.isSuccess = False
self.resultMessage = "Subcontractor not found"
return data
# ----------------------------------------------------------
# CHECK (Duplicate)
# ----------------------------------------------------------
def CheckSubcontractor(self, request):
subcontractor = ItemCRUD(itemType=ItemCRUDType.Subcontractor)
name = request.form.get('Contractor_Name', '').strip()
result = subcontractor.CheckItem(
request=request,
childname=name,
storedprocfetch="GetSubcontractorByName"
)
self.isSuccess = subcontractor.isSuccess
self.resultMessage = subcontractor.resultMessage
return result
# ----------------------------------------------------------
# EDIT
# ----------------------------------------------------------
def EditSubcontractor(self, request, subcontractor_id):
subcontractor = ItemCRUD(itemType=ItemCRUDType.Subcontractor)
data = {
"Contractor_Name": request.form.get('Contractor_Name', '').strip(),
"Address": request.form.get('Address', '').strip(),
"Mobile_No": request.form.get('Mobile_No', '').strip(),
"PAN_No": request.form.get('PAN_No', '').strip(),
"Email": request.form.get('Email', '').strip(),
"Gender": request.form.get('Gender', '').strip(),
"GST_Registration_Type": request.form.get('GST_Registration_Type', '').strip(),
"GST_No": request.form.get('GST_No', '').strip(),
"Contractor_password": request.form.get('Contractor_password', '').strip()
}
subcontractor.EditItem(
request=request,
childid=subcontractor_id,
data=data,
storedprocupdate="UpdateSubcontractor"
)
self.isSuccess = subcontractor.isSuccess
self.resultMessage = subcontractor.resultMessage
return
# ----------------------------------------------------------
# DELETE
# ----------------------------------------------------------
def DeleteSubcontractor(self, request, subcontractor_id):
subcontractor = ItemCRUD(itemType=ItemCRUDType.Subcontractor)
subcontractor.DeleteItem(
request=request,
itemID=subcontractor_id,
storedprocDelete="DeleteSubcontractor"
)
self.isSuccess = subcontractor.isSuccess
self.resultMessage = subcontractor.resultMessage
return

67
model/Utilities.py Normal file
View File

@@ -0,0 +1,67 @@
from flask import flash, jsonify, json
from enum import Enum
class ItemCRUDType(Enum):
Village = 1
Block = 2
District = 3
State = 4
HoldType = 5
Subcontractor = 6
Payment = 7
GSTRelease = 8
class RegEx:
patternAlphabetOnly = r"^[A-Za-z0-9 %&\-_]+$"
class ResponseHandler:
@staticmethod
def invalid_name(entity):
return {'status': 'error', 'message': f'Invalid {entity} name. Only letters are allowed!'}
@staticmethod
def already_exists(entity):
return {'status': 'exists', 'message': f'{entity.capitalize()} already exists!'}
@staticmethod
def add_success(entity):
return {'status': 'success', 'message': f'{entity.capitalize()} added successfully!'}
@staticmethod
def add_failure(entity):
return {'status': 'error', 'message': f'Failed to add {entity}.'}
@staticmethod
def is_available(entity):
return {'status': 'available', 'message': f'{entity.capitalize()} name is available!'}
@staticmethod
def delete_success(entity):
return {'status': 'success', 'message': f'{entity.capitalize()} deleted successfully!'}
@staticmethod
def delete_failure(entity):
return {'status': 'error', 'message': f'Failed to delete {entity}.'}
@staticmethod
def update_success(entity):
return {'status': 'success', 'message': f'{entity.capitalize()} updated successfully!'}
@staticmethod
def update_failure(entity):
return {'status': 'error', 'message': f'Failed to update {entity}.'}
@staticmethod
def fetch_failure(entity):
return {'status': 'error', 'message': f'Failed to fetch {entity}.'}
class HtmlHelper:
# Helper: JSON Response Formatter
@staticmethod
def json_response(message_obj, status_code):
return jsonify(message_obj), status_code
#May need to refactor further

121
model/Village.py Normal file
View File

@@ -0,0 +1,121 @@
from flask_login import LoginManager, UserMixin, login_user, logout_user, login_required, current_user
from model.Utilities import RegEx, ResponseHandler, HtmlHelper, ItemCRUDType
from model.Log import LogData, LogHelper
import config
import mysql.connector
from mysql.connector import Error
from model.ItemCRUD import ItemCRUD
class Village:
isSuccess = False
resultMessage = ""
def __init__(self):
self.isSuccess = False
self.resultMessage = ""
def AddVillage(self, request):
village = ItemCRUD(itemType=ItemCRUDType.Village)
block_id = request.form.get('block_Id')
village_name = request.form.get('Village_Name', '').strip()
village.AddItem(request=request, parentid=block_id, childname=village_name, storedprocfetch="GetVillageByNameAndBlock", storedprocadd="SaveVillage" )
self.isSuccess = village.isSuccess
self.resultMessage = village.resultMessage
return
#self.isSuccess = False
def GetAllVillages(self, request):
village = ItemCRUD(itemType=ItemCRUDType.Village)
villagesdata = village.GetAllData(request=request, storedproc="GetAllVillages")
self.isSuccess = village.isSuccess
self.resultMessage = village.resultMessage
return villagesdata
def CheckVillage(self, request):
village = ItemCRUD(itemType=ItemCRUDType.Village)
block_id = request.form.get('block_Id')
village_name = request.form.get('Village_Name', '').strip()
result = village.CheckItem(request=request, parentid=block_id, childname=village_name, storedprocfetch="GetVillageByNameAndBlocks")
self.isSuccess = village.isSuccess
self.resultMessage = village.resultMessage
return result
def DeleteVillage(self, request, village_id):
village = ItemCRUD(itemType=ItemCRUDType.Village)
village.DeleteItem(request=request, itemID=village_id, storedprocDelete="DeleteVillage" )
self.isSuccess = village.isSuccess
self.resultMessage = village.resultMessage
return
def EditVillage(self, request, village_id):
corsor=None
village = ItemCRUD(itemType=ItemCRUDType.Village)
block_id = request.form.get('block_Id')
village_name = request.form.get('Village_Name', '').strip()
village.EditItem(request=request,childid=village_id,parentid=block_id,childname=village_name,storedprocupdate="UpdateVillage" )
self.isSuccess = village.isSuccess
self.resultMessage = village.resultMessage
return
# def GetVillageByID(self, request, id):
# village = ItemCRUD(itemType=ItemCRUDType.Village)
# villagedetailsdata = village.GetAllData(request=request, storedproc="GetVillageDetailsById")
# self.isSuccess = village.isSuccess
# self.resultMessage = village.resultMessage
# return villagedetailsdata
def GetVillageByID(self, request, id):
village = ItemCRUD(itemType=ItemCRUDType.Village)
villagedetailsdata = village.GetDataByID(id=id,storedproc="GetVillageDetailsById")
if villagedetailsdata:
self.isSuccess = True
else:
self.isSuccess = False
self.resultMessage = "Village not found"
return villagedetailsdata
def GetAllBlocks(self, request):
blocks = []
self.isSuccess = False
self.resultMessage = ""
connection = config.get_db_connection()
if not connection:
return []
cursor = connection.cursor()
try:
cursor.callproc('GetAllBlocks')
for result in cursor.stored_results():
blocks = result.fetchall()
self.isSuccess = True
except mysql.connector.Error as e:
print(f"Error fetching blocks: {e}")
self.isSuccess = False
self.resultMessage = HtmlHelper.json_response(ResponseHandler.fetch_failure("block"), 500)
finally:
cursor.close()
connection.close()
return blocks

246
model/gst_release.py Normal file
View File

@@ -0,0 +1,246 @@
# from flask import request
# from model.ItemCRUD import ItemCRUD
# from model.Utilities import ItemCRUDType
# class GSTRelease:
# """CRUD operations for GST Release using ItemCRUD"""
# def __init__(self):
# self.isSuccess = False
# self.resultMessage = ""
# # ------------------- Add GST Release -------------------
# def AddGSTRelease(self, request):
# pmc_no = request.form.get('PMC_No', '').strip()
# invoice_no = request.form.get('invoice_No', '').strip()
# gst = ItemCRUD(itemType=ItemCRUDType.GSTRelease)
# gst.AddItem(
# request=request,
# parentid=None,
# childname=f"{pmc_no}-{invoice_no}",
# storedprocfetch="CheckGSTReleaseExists",
# storedprocadd="AddGSTReleaseFromExcel" # your stored procedure handles extra fields
# )
# self.isSuccess = gst.isSuccess
# self.resultMessage = str(gst.resultMessage)
# # ------------------- Get All GST Releases -------------------
# def GetAllGSTReleases(self):
# gst = ItemCRUD(itemType=ItemCRUDType.GSTRelease)
# # Pass request=None for fetch
# rows = gst.GetAllData(request=None, storedproc="GetAllGSTReleases")
# self.isSuccess = gst.isSuccess
# self.resultMessage = str(gst.resultMessage)
# data = []
# for row in rows:
# data.append({
# "gst_release_id": row[0],
# "pmc_no": row[1],
# "invoice_no": row[2],
# "basic_amount": row[3],
# "final_amount": row[4],
# "total_amount": row[5],
# "utr": row[6],
# "contractor_id": row[7]
# })
# return data
# # ------------------- Get GST Release By ID -------------------
# def GetGSTReleaseByID(self, gst_release_id):
# gst = ItemCRUD(itemType=ItemCRUDType.GSTRelease)
# row = gst.GetDataByID(gst_release_id, request=None, storedproc="GetGSTReleaseById")
# self.isSuccess = gst.isSuccess
# self.resultMessage = str(gst.resultMessage)
# if row:
# return {
# "gst_release_id": row[0],
# "pmc_no": row[1],
# "invoice_no": row[2],
# "basic_amount": row[3],
# "final_amount": row[4],
# "total_amount": row[5],
# "utr": row[6],
# "contractor_id": row[7]
# }
# return None
# # ------------------- Edit GST Release -------------------
# def EditGSTRelease(self, request, gst_release_id):
# pmc_no = request.form.get('PMC_No', '').strip()
# invoice_no = request.form.get('invoice_No', '').strip()
# gst = ItemCRUD(itemType=ItemCRUDType.GSTRelease)
# gst.EditItem(
# request=request,
# childid=gst_release_id,
# parentid=None,
# childname=f"{pmc_no}-{invoice_no}",
# storedprocupdate="UpdateGSTRelease" # stored procedure handles extra fields
# )
# self.isSuccess = gst.isSuccess
# self.resultMessage = str(gst.resultMessage)
# # ------------------- Delete GST Release -------------------
# def DeleteGSTRelease(self, gst_release_id):
# gst = ItemCRUD(itemType=ItemCRUDType.GSTRelease)
# gst.DeleteItem(
# itemID=gst_release_id,
# request=None,
# storedprocDelete="DeleteGSTReleaseById"
# )
# self.isSuccess = gst.isSuccess
# self.resultMessage = str(gst.resultMessage)
from flask import request, jsonify
from model.ItemCRUD import ItemCRUD
from model.Utilities import ItemCRUDType
class GSTRelease:
def __init__(self):
self.isSuccess = False
self.resultMessage = ""
# ------------------- Add GST Release -------------------
def AddGSTRelease(self, request):
try:
gst = ItemCRUD(itemType=ItemCRUDType.GSTRelease)
data = {
"PMC_No": request.form.get("PMC_No", "").strip(),
"Invoice_No": request.form.get("Invoice_No", "").strip(),
"Basic_Amount": float(request.form.get("Basic_Amount", 0) or 0),
"Final_Amount": float(request.form.get("Final_Amount", 0) or 0),
"Total_Amount": float(request.form.get("Total_Amount", 0) or 0),
"UTR": request.form.get("UTR", "").strip(),
"Contractor_ID": int(request.form.get("Contractor_ID", 0) or 0)
}
gst.AddItem(
request=request,
data=data,
storedprocfetch="CheckGSTReleaseExists",
storedprocadd="AddGSTReleaseFromExcel"
)
self.isSuccess = gst.isSuccess
self.resultMessage = str(gst.resultMessage)
except Exception as e:
print("ERROR in AddGSTRelease:", e)
self.isSuccess = False
self.resultMessage = str(e)
return jsonify({"success": self.isSuccess, "message": self.resultMessage})
# ------------------- Edit GST Release -------------------
def EditGSTRelease(self, request, gst_release_id):
try:
gst = ItemCRUD(itemType=ItemCRUDType.GSTRelease)
data = {
"PMC_No": request.form.get("PMC_No", "").strip(),
"Invoice_No": request.form.get("Invoice_No", "").strip(),
"Basic_Amount": float(request.form.get("Basic_Amount", 0) or 0),
"Final_Amount": float(request.form.get("Final_Amount", 0) or 0),
"Total_Amount": float(request.form.get("Total_Amount", 0) or 0),
"UTR": request.form.get("UTR", "").strip()
}
gst.EditItem(
request=request,
childid=gst_release_id,
data=data,
storedprocupdate="UpdateGSTRelease"
)
self.isSuccess = gst.isSuccess
self.resultMessage = str(gst.resultMessage)
except Exception as e:
print("ERROR in EditGSTRelease:", e)
self.isSuccess = False
self.resultMessage = str(e)
return jsonify({"success": self.isSuccess, "message": self.resultMessage})
# ------------------- Delete GST Release -------------------
def DeleteGSTRelease(self, gst_release_id):
try:
gst = ItemCRUD(itemType=ItemCRUDType.GSTRelease)
gst.DeleteItem(
request=None,
itemID=gst_release_id,
storedprocDelete="DeleteGSTReleaseById"
)
self.isSuccess = gst.isSuccess
self.resultMessage = str(gst.resultMessage)
except Exception as e:
print("ERROR in DeleteGSTRelease:", e)
self.isSuccess = False
self.resultMessage = str(e)
return jsonify({"success": self.isSuccess, "message": self.resultMessage})
# ------------------- Get All GST Releases -------------------
def GetAllGSTReleases(self):
try:
gst = ItemCRUD(itemType=ItemCRUDType.GSTRelease)
rows = gst.GetAllData(None, "GetAllGSTReleases")
data = []
for row in rows:
data.append({
"gst_release_id": row[0],
"pmc_no": row[1],
"invoice_no": row[2],
"basic_amount": row[3],
"final_amount": row[4],
"total_amount": row[5],
"utr": row[6],
"contractor_id": row[7]
})
return data
except Exception as e:
print("ERROR in GetAllGSTReleases:", e)
return []
# ------------------- Get GST Release By ID -------------------
def GetGSTReleaseByID(self, gst_release_id):
try:
gst = ItemCRUD(itemType=ItemCRUDType.GSTRelease)
row = gst.GetDataByID(gst_release_id, "GetGSTReleaseById")
if row:
return {
"gst_release_id": row[0],
"pmc_no": row[1],
"invoice_no": row[2],
"basic_amount": row[3],
"final_amount": row[4],
"total_amount": row[5],
"utr": row[6],
"contractor_id": row[7]
}
return None
except Exception as e:
print("ERROR in GetGSTReleaseByID:", e)
return None

214
model/payment.py Normal file
View File

@@ -0,0 +1,214 @@
import config
import mysql.connector
import config
import mysql.connector
from enum import Enum
from model.Utilities import ItemCRUDType
class Paymentmodel:
# ---------------- Database Connection ----------------
@staticmethod
def get_connection():
connection = config.get_db_connection()
if not connection:
return None
return connection
# ---------------- Payment Methods ----------------
@staticmethod
def fetch_all_payments():
connection = Paymentmodel.get_connection()
payments = []
if connection:
cursor = connection.cursor(dictionary=True)
try:
cursor.callproc('GetAllPayments')
for result in cursor.stored_results():
payments = result.fetchall()
except mysql.connector.Error as e:
print(f"Error fetching payment history: {e}")
finally:
cursor.close()
connection.close()
return payments
@staticmethod
def insert_payment(pmc_no, invoice_no, amount, tds_amount, total_amount, utr):
connection = Paymentmodel.get_connection()
if not connection:
return False
try:
cursor = connection.cursor()
cursor.callproc('InsertPayments', [pmc_no, invoice_no, amount, tds_amount, total_amount, utr])
connection.commit()
return True
except mysql.connector.Error as e:
print(f"Error inserting payment: {e}")
return False
finally:
cursor.close()
connection.close()
@staticmethod
def update_inpayment(subcontractor_id, pmc_no, invoice_no, amount, tds_amount, total_amount, utr):
connection = Paymentmodel.get_connection()
if not connection:
return False
try:
cursor = connection.cursor()
cursor.callproc('UpdateInpaymentRecord', [
subcontractor_id, pmc_no, invoice_no, amount, tds_amount, total_amount, utr
])
connection.commit()
return True
except mysql.connector.Error as e:
print(f"Error updating inpayment: {e}")
return False
finally:
cursor.close()
connection.close()
@staticmethod
def fetch_payment_by_id(payment_id):
connection = Paymentmodel.get_connection()
if not connection:
return None
payment_data = {}
try:
cursor = connection.cursor(dictionary=True)
cursor.callproc("GetPaymentById", (payment_id,))
for result in cursor.stored_results():
payment_data = result.fetchone()
if payment_data:
payment_data = [
payment_data.get('Payment_Id'),
payment_data.get('PMC_No'),
payment_data.get('Invoice_No'),
payment_data.get('Payment_Amount'),
payment_data.get('TDS_Payment_Amount'),
payment_data.get('Total_Amount'),
payment_data.get('UTR')
]
except mysql.connector.Error as e:
print(f"Error fetching payment data: {e}")
finally:
cursor.close()
connection.close()
return payment_data
@staticmethod
def call_update_payment_proc(payment_id, pmc_no, invoice_no, amount, tds_amount, total_amount, utr):
connection = Paymentmodel.get_connection()
if not connection:
return False
try:
cursor = connection.cursor()
cursor.callproc("UpdatePayment", (payment_id, pmc_no, invoice_no, amount, tds_amount, total_amount, utr))
connection.commit()
return True
except mysql.connector.Error as e:
print(f"Error updating payment: {e}")
return False
finally:
cursor.close()
connection.close()
@staticmethod
def delete_payment(payment_id):
connection = Paymentmodel.get_connection()
if not connection:
return False, None, None
try:
cursor = connection.cursor(dictionary=True)
# Fetch PMC & Invoice before deleting
cursor.callproc('GetPaymentPMCInvoiceById', [payment_id])
record = {}
for result in cursor.stored_results():
record = result.fetchone() or {}
if not record:
return False, None, None
pmc_no = record['PMC_No']
invoice_no = record['Invoice_No']
# Delete payment
cursor.callproc("DeletePayment", (payment_id,))
connection.commit()
# Reset inpayment fields
cursor.callproc("ResetInpayment", [pmc_no, invoice_no])
connection.commit()
return True, pmc_no, invoice_no
except mysql.connector.Error as e:
print(f"Error deleting payment: {e}")
return False, None, None
finally:
cursor.close()
connection.close()
# ---------------- Item CRUD Methods ----------------
@staticmethod
def fetch_items(item_type: ItemCRUDType):
connection = Paymentmodel.get_connection()
items = []
if connection:
cursor = connection.cursor(dictionary=True)
try:
cursor.callproc('GetItemsByType', [item_type.value])
for result in cursor.stored_results():
items = result.fetchall()
except mysql.connector.Error as e:
print(f"Error fetching {item_type.name}: {e}")
finally:
cursor.close()
connection.close()
return items
@staticmethod
def insert_item(item_type: ItemCRUDType, name: str):
connection = Paymentmodel.get_connection()
if not connection:
return False
try:
cursor = connection.cursor()
cursor.callproc('InsertItem', [item_type.value, name])
connection.commit()
return True
except mysql.connector.Error as e:
print(f"Error inserting {item_type.name}: {e}")
return False
finally:
cursor.close()
connection.close()
@staticmethod
def update_item(item_type: ItemCRUDType, item_id: int, new_name: str):
connection = Paymentmodel.get_connection()
if not connection:
return False
try:
cursor = connection.cursor()
cursor.callproc('UpdateItem', [item_type.value, item_id, new_name])
connection.commit()
return True
except mysql.connector.Error as e:
print(f"Error updating {item_type.name}: {e}")
return False
finally:
cursor.close()
connection.close()
@staticmethod
def delete_item(item_type: ItemCRUDType, item_id: int):
connection = Paymentmodel.get_connection()
if not connection:
return False
try:
cursor = connection.cursor()
cursor.callproc('DeleteItem', [item_type.value, item_id])
connection.commit()
return True
except mysql.connector.Error as e:
print(f"Error deleting {item_type.name}: {e}")
return False
finally:
cursor.close()
connection.close()

4
requirements.txt Normal file
View File

@@ -0,0 +1,4 @@
Flask
mysql-connector-python
openpyxl
pandas

89
static/css/base.css Normal file
View File

@@ -0,0 +1,89 @@
body {
margin: 0;
font-family: Arial, sans-serif;
background-color: #f5f5f5;
display: flex;
}
/* Sidebar */
.sidebar {
width: 250px;
height: 100vh;
background-color: #ffffff;
position: fixed;
left: -250px;
transition: left 0.3s ease;
box-shadow: 2px 0 5px rgba(0, 0, 0, 0.1);
padding-top: 20px;
}
.sidebar.open {
left: 0;
}
/* Sidebar Navigation */
.nav-menu {
list-style: none;
padding: 0;
margin: 0;
}
.nav-item {
width: 100%;
}
.nav-link {
display: flex;
align-items: center;
text-decoration: none;
color: #333;
padding: 15px 20px;
font-size: 16px;
transition: background-color 0.3s, color 0.3s;
}
.nav-link i {
margin-right: 10px;
font-size: 18px;
}
.nav-link:hover, .nav-link.active {
background-color: #e6f7ff;
color: #007bff;
}
/* Menu Button */
.menu-icon {
position: absolute;
top: 15px;
left: 15px;
font-size: 24px;
cursor: pointer;
background: none;
border: none;
}
/* Content Area */
.content {
margin-left: 0;
padding: 20px;
width: 100%;
transition: margin-left 0.3s ease;
}
.content.shift {
margin-left: 250px;
}
@media (max-width: 768px) {
.sidebar {
width: 200px;
left: -200px;
}
.sidebar.open {
left: 0;
}
.content.shift {
margin-left: 200px;
}
}

226
static/css/index.css Normal file
View File

@@ -0,0 +1,226 @@
body {
margin: 0;
font-family: Arial, sans-serif;
display: flex;
flex-direction: column;
background-color: #f5f5f5;
}
/* Sidebar */
.sidebar {
width: 250px;
height: 100%;
background-color: #ffffff;
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
display: flex;
flex-direction: column;
align-items: center;
padding-top: 20px;
position: fixed;
}
.logo {
width: 80px;
margin-bottom: 20px;
}
.nav-menu {
width: 100%;
list-style: none;
padding: 0;
margin: 0;
}
.nav-item {
width: 100%;
}
.nav-link {
display: flex;
align-items: center;
text-decoration: none;
color: #333;
padding: 15px 20px;
font-size: 16px;
transition: background-color 0.3s, color 0.3s;
}
.nav-link:hover,
.nav-link.active {
background-color: #e6f7ff;
color: #007bff;
}
.nav-link i {
margin-right: 10px;
font-size: 18px;
}
.user-section {
margin-top: auto;
width: 100%;
padding: 20px;
display: flex;
align-items: center;
border-top: 1px solid #eee;
}
.user-section img {
width: 50px;
height: 50px;
border-radius: 50%;
margin-right: 10px;
}
.user-info {
display: flex;
flex-direction: column;
}
.user-info span {
font-size: 14px;
color: #333;
}
/* Main content area */
.content {
margin-left: 250px;
padding: 20px;
width: calc(100% - 250px);
}
/* Menu Cards */
.menu {
display: flex;
flex-wrap: wrap;
gap: 20px;
}
.card {
background-color: #fff;
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
border-radius: 8px;
padding: 20px;
text-align: center;
flex: 1 1 calc(30% - 20px);
max-width: calc(30% - 20px);
}
.card h2 {
margin: 0 0 10px;
font-size: 20px;
}
.btn {
display: inline-block;
padding: 10px 20px;
color: #fff;
background-color: #007bff;
border-radius: 4px;
text-decoration: none;
font-size: 14px;
transition: background-color 0.3s;
}
.btn:hover {
background-color: #0056b3;
}
/* Company Info */
.company-info {
text-align: center;
padding: 20px;
background-color: Whitesmoke;
color: blue;
margin-left: 250px;
}
.company-name {
font-size: 30px;
font-weight: bold;
margin: 0;
}
.app-name {
font-weight: bold;
app-name-shadow:0 2px 4px rgba(0, 0, 0, 0.1);
font-size: 24px;
margin: 5px 0 0;
}
/* Responsive Design */
@media screen and (max-width: 768px) {
.sidebar {
width: 100%;
height: auto;
position: static;
}
.content {
margin-left: 0;
width: 100%;
}
.menu {
flex-direction: column;
align-items: center;
}
.card {
flex: 1 1 100%;
max-width: 100%;
min-width: 50%;
}
}
@media screen and (max-width: 480px) {
.nav-link {
font-size: 14px;
padding: 10px 15px;
}
.btn {
font-size: 12px;
padding: 8px 15px;
}
}
@media screen and (max-width: 768px) {
.sidebar {
width: 100%;
height: auto;
position: static;
}
.content {
margin-left: 0;
width: 100%;
}
.menu {
flex-direction: column;
align-items: center;
}
.card {
flex: 1 1 100%;
max-width: 100%;
min-width: 50%;
}
.company-info {
margin-left: 0;
}
}
@media screen and (max-width: 480px) {
.nav-link {
font-size: 14px;
padding: 10px 15px;
}
.btn {
font-size: 12px;
padding: 8px 15px;
}
.user-section {
flex-direction: column;
align-items: flex-start;
gap: 10px;
}
.user-section img {
margin-right: 0;
}
.card {
min-width: 90%;
}
}

395
static/css/invoice.css Normal file
View File

@@ -0,0 +1,395 @@
/* General Styles */
h2 {
text-align: center;
font-size: 24px;
color: #333;
margin-bottom: 20px;
}
/* Form Styling */
form {
width:50%;
background: #fff;
padding: 20px;
border-radius: 8px;
box-shadow: 0px 4px 10px rgba(0, 0, 0, 0.1);
display: flex;
flex-direction: column;
gap: 15px;
}
/* Responsive Form Layout */
.row1,
.row2,
.row3 {
display: grid;
gap: 15px;
}
.row2,
.row3 {
grid-template-columns: repeat(auto-fit, minmax(250px, 1fr));
}
label {
font-weight: bold;
display: block;
margin-bottom: 6px;
}
input,
textarea,
select {
width: 100%;
padding: 10px;
border: 1px solid #ccc;
border-radius: 6px;
font-size: 16px;
box-sizing: border-box;
}
/* Button Styling */
.button {
padding: 12px;
background-color: #007bff;
color: white;
border: none;
border-radius: 6px;
font-size: 18px;
cursor: pointer;
transition: background-color 0.3s ease-in-out;
}
.button:hover {
background-color: #0056b3;
}
/* Dynamic Hold Amount Fields */
.hold-row {
display: flex;
flex-wrap: wrap;
gap: 10px;
}
.hold-amount-field {
display: flex;
align-items: center;
gap: 10px;
padding: 10px;
background-color: #f9f9f9;
border: 1px solid #ddd;
border-radius: 4px;
flex-wrap: wrap;
}
.hold-amount-field select,
.hold-amount-field input {
flex: 1;
min-width: 100px;
}
.hold-amount-field button {
background-color: red;
color: white;
font-size: 14px;
padding: 8px 10px;
border: none;
border-radius: 4px;
cursor: pointer;
}
.hold-amount-field button:hover {
background-color: #c0392b;
}
/* Success Alert Box */
.success-alert {
position: fixed;
top: 20px;
left: 50%;
transform: translateX(-50%);
background-color: #4CAF50;
color: #fff;
padding: 15px 25px;
border-radius: 5px;
box-shadow: 0px 4px 10px rgba(0, 0, 0, 0.1);
opacity: 0;
visibility: hidden;
transition: opacity 0.5s ease, visibility 0.5s ease;
z-index: 1000;
}
.success-alert.show {
opacity: 1;
visibility: visible;
}
/* Table Styles */
.invoice-table {
width: 100%;
border-collapse: collapse;
margin-top: 20px;
overflow-x: auto;
}
.invoice-table th,
.invoice-table td {
border: 1px solid #ddd;
padding: 10px;
text-align: left;
font-size: 14px;
word-break: break-word;
}
.invoice-table th {
background-color: #007bff;
color: #fff;
}
.invoice-table tr:nth-child(even) {
background-color: #f9f9f9;
}
.invoice-table tr:hover {
background-color: #f1f1f1;
}
/* icon */
.icon {
width: 20px;
height: 20px;
cursor: pointer;
transition: transform 0.2s ease-in-out, opacity 0.2s ease-in-out;
}
.icon:hover {
transform: scale(1.5);
opacity: 0.8;
}
.edit-icon:hover {
filter: drop-shadow(0 0 5px #007bff); /* Blue glow for edit */
}
.delete-icon:hover {
filter: drop-shadow(0 0 5px #ff0000); /* Red glow for delete */
}
/* Center the buttons and apply consistent styling */
.button-container {
display: flex;
justify-content: center; /* Center buttons horizontally */
gap: 10px; /* Space between buttons */
margin-top: 20px; /* Add some top margin */
}
.action-button {
background-color: #007BFF; /* Blue background */
color: white; /* White text */
padding: 15px 30px; /* Larger padding for bigger buttons */
border: none; /* Remove border */
border-radius: 5px; /* Rounded corners */
cursor: pointer; /* Pointer cursor on hover */
font-size: 18px; /* Larger font size */
text-align: center; /* Center text */
text-decoration: none; /* Remove underline */
transition: background-color 0.3s ease; /* Smooth hover transition */
}
.action-button:hover {
background-color: #0056b3; /* Darker blue on hover */
}
#addStateForm, #stateTable {
display: none; /* Initially hide both the form and the table */
}
/* Success Popup */
.success-popup {
display: none;
color: green;
font-size: 1.2em;
margin-top: 10px;
}
/* Sorting buttons */
.sortable .sort-buttons {
margin-left: 5px;
}
.sortable {
cursor: pointer;
user-select: none;
}
.sort-buttons a {
text-decoration: none;
color: black;
font-weight: bold;
margin-left: 5px;
margin-right: 5px;
}
.sort-buttons a:hover {
text-decoration: underline;
}
.back-button {
display: inline-block;
margin-top: 20px;
padding: 8px 15px;
background-color: #28a745;
color: white;
border-radius: 5px;
text-decoration: none;
font-size: 16px;
}
.back-button:hover {
background-color: #218838;
}
span .sort-desc:hover{
cursor: pointer;
}
span .sort-asc:hover{
cursor: pointer;
}
/* Responsive Design */
@media (max-width: 1024px) {
form {
padding: 15px;
}
.row2,
.row3 {
grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
}
.invoice-table th,
.invoice-table td {
font-size: 12px;
padding: 8px;
}
}
@media (max-width: 768px) {
.row1,
.row2,
.row3 {
grid-template-columns: 1fr;
}
.hold-amount-field {
flex-direction: column;
gap: 5px;
align-items: flex-start;
}
.hold-amount-field select,
.hold-amount-field input {
width: 100%;
}
.invoice-table {
display: block;
overflow-x: auto;
white-space: nowrap;
}
}
@media (max-width: 480px) {
body {
max-width: 100%;
padding: 10px;
}
form {
padding: 10px;
}
.invoice-table th,
.invoice-table td {
font-size: 12px;
padding: 5px;
}
button {
font-size: 16px;
padding: 10px;
}
}
/* Additional Media Queries */
/* For tablets and medium devices */
@media (max-width: 992px) {
form {
width: 90%;
}
.button-container {
flex-direction: column;
align-items: center;
}
.action-button {
width: 100%;
max-width: 300px;
}
}
/* For smaller tablets and large phones */
@media (max-width: 600px) {
.button-container {
flex-direction: column;
align-items: stretch;
}
.action-button {
width: 100%;
}
.icon {
width: 16px;
height: 16px;
}
.success-alert {
width: 90%;
font-size: 14px;
padding: 10px 15px;
}
}
/* For very small phones */
@media (max-width: 360px) {
h2 {
font-size: 20px;
}
.back-button {
width: 100%;
text-align: center;
font-size: 14px;
padding: 8px 10px;
}
.sort-buttons a {
font-size: 12px;
}
.invoice-table th,
.invoice-table td {
font-size: 10px;
padding: 4px;
}
}

313
static/css/invoice1.css Normal file
View File

@@ -0,0 +1,313 @@
/* General Styling */
body {
font-family: Arial, sans-serif;
margin: 20px;
background-color: #f9f9f9;
color: #333;
}
/* Header Styling */
h1, h2, h3 {
color: #0056b3;
text-align: center;
}
/* Table Styling */
table {
width: 100%;
border-collapse: collapse;
margin: 20px 0;
}
table th, table td {
border: 1px solid #ddd;
padding: 8px;
text-align: center;
}
table th {
background-color: #007bff;
color: white;
font-weight: bold;
cursor: pointer;
position: relative;
}
table tr:nth-child(even) {
background-color: #f2f2f2;
}
table tr:hover {
background-color: #ddd;
}
/* Sort Dropdown */
.sort-options {
display: none;
position: absolute;
background: white;
border: 1px solid black;
padding: 5px;
z-index: 100;
width: 120px;
text-align: center;
}
.sort-options button {
display: block;
width: 100%;
border: none;
background: none;
padding: 5px;
cursor: pointer;
}
.sort-options button:hover {
background-color: #f0f0f0;
}
/* Form Styling */
form {
max-width: 700px;
margin: 0 auto;
padding: 20px;
background: #fff;
border: 1px solid #ddd;
border-radius: 5px;
box-shadow: 0 0 10px rgba(0, 0, 0, 0.1);
}
form label {
display: block;
margin-bottom: 5px;
font-weight: bold;
}
form input[type="text"],
form input[type="number"],
form input[type="date"],
form input[type="email"],
form textarea,
select {
width: 100%;
padding: 10px;
margin-bottom: 15px;
border: 1px solid #ddd;
border-radius: 5px;
box-sizing: border-box;
}
form textarea {
resize: vertical;
}
/* Button Styling */
.button, form button {
width: 100%;
padding: 10px;
background-color: #007bff;
color: white;
border: none;
border-radius: 5px;
cursor: pointer;
font-size: 16px;
text-align: center;
}
.button:hover, form button:hover {
background-color: #0056b3;
}
/* Back Button */
.back-button {
background-color: red;
color: white;
padding: 10px 20px;
text-decoration: none;
font-weight: bold;
border-radius: 5px;
display: inline-block;
margin-top: 20px;
text-align: center;
}
.back-button:hover {
background-color: darkred;
}
/* Success Alert */
.success-alert {
display: none;
position: fixed;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
background-color: #d4edda;
color: #155724;
padding: 20px;
border-radius: 10px;
box-shadow: 0 0 10px rgba(0, 0, 0, 0.2);
text-align: center;
font-size: 18px;
font-weight: bold;
}
.success-popup i {
color: green;
font-size: 30px;
margin-right: 10px;
}
/* Error Message */
.error-message {
color: red;
font-size: 14px;
margin-top: 5px;
}
/* Table Icons */
td img {
width: 20px; /* Adjust as needed */
height: 20px; /* Adjust as needed */
transition: transform 0.3s ease; /* Smooth transition for hover effect */
}
td img:hover {
transform: scale(1.2); /* Slight zoom effect on hover */
}
/* Select Dropdown */
select {
width: 100%;
padding: 12px 15px;
margin: 10px 0;
border: 1px solid #ccc;
border-radius: 5px;
font-size: 14px;
background-color: #fff;
box-sizing: border-box;
appearance: none;
}
/* Custom Dropdown Arrow */
select::-ms-expand {
display: none;
}
select:focus {
outline: none;
border-color: #4CAF50;
}
select option {
padding: 12px 15px;
font-size: 14px;
}
option[disabled] {
color: #aaa;
font-style: italic;
}
/* Save Button */
.save-button {
background-color: green;
color: white;
padding: 15px;
text-decoration: none;
font-weight: bold;
border-radius: 5px;
display: flex;
justify-content: center;
align-items: center;
margin: 20px auto;
text-align: center;
}
.save-button:hover {
background-color: darkgreen;
}
/* Responsive Design */
@media (max-width: 768px) {
form {
max-width: 100%;
padding: 15px;
}
table th, table td {
padding: 6px;
font-size: 14px;
}
.button, form button {
font-size: 14px;
padding: 8px;
}
}
/* Additional Media Queries */
/* For tablets and medium-sized screens */
@media (max-width: 992px) {
form {
padding: 18px;
}
table th, table td {
font-size: 14px;
padding: 6px;
}
.save-button, .back-button, .button, form button {
font-size: 15px;
}
}
/* For smaller tablets and large phones */
@media (max-width: 600px) {
body {
margin: 10px;
}
h1, h2, h3 {
font-size: 20px;
}
table th, table td {
font-size: 13px;
padding: 5px;
}
.success-alert {
width: 90%;
font-size: 16px;
padding: 15px;
}
.button, form button, .save-button, .back-button {
padding: 10px;
font-size: 14px;
}
}
/* For very small phones */
@media (max-width: 360px) {
h1, h2, h3 {
font-size: 18px;
}
.save-button, .back-button {
width: 100%;
font-size: 13px;
padding: 10px;
}
table th, table td {
font-size: 12px;
padding: 4px;
}
form input, form select, form textarea {
font-size: 14px;
}
}

181
static/css/report.css Normal file
View File

@@ -0,0 +1,181 @@
h2 {
text-align: center;
font-size: 24px;
color: #333;
margin-bottom: 20px;
}
/* Form Styling */
.info {
width: 60%;
background: #fff;
padding: 20px;
border-radius: 8px;
box-shadow: 0px 4px 10px rgba(0, 0, 0, 0.1);
display: flex;
flex-direction: column;
gap: 15px;
}
/* Responsive Form Layout */
.row1,
.row2,
.row3 {
display: grid;
gap: 15px;
}
.row2,
.row3 {
grid-template-columns: repeat(auto-fit, minmax(250px, 1fr));
}
label {
font-weight: bold;
display: block;
margin-bottom: 6px;
}
input,
textarea,
select {
width: 100%;
padding: 10px;
border: 1px solid #ccc;
border-radius: 6px;
font-size: 16px;
box-sizing: border-box;
}
/* Table styling */
.table-wrapper {
overflow-x: auto;
}
table {
width: 100%;
border-collapse: collapse;
margin: 20px 0;
}
table th, table td {
border: 1px solid #ddd;
padding: 8px;
text-align: center;
}
table th {
background-color: #007bff;
color: white;
font-weight: bold;
}
table tr:nth-child(even) {
background-color: #f2f2f2;
}
table tr:hover {
background-color: #ddd;
}
.download-btn {
display: inline-block;
padding: 10px 15px;
background-color: #28a745;
color: white;
text-decoration: none;
border-radius: 5px;
margin-top: 15px;
}
.total-table {
width: 35%;
}
/* Responsive Design */
@media (max-width: 1024px) {
.info {
padding: 15px;
}
.row2,
.row3 {
grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
}
.invoice-table th,
.invoice-table td {
font-size: 12px;
padding: 8px;
}
}
@media (max-width: 768px) {
.row1,
.row2,
.row3 {
grid-template-columns: 1fr;
}
}
@media (max-width: 480px) {
body {
max-width: 100%;
padding: 10px;
}
.info {
padding: 10px;
}
table th,
table td {
font-size: 10px;
padding: 6px;
}
}
/* Ensure table responsiveness */
@media (max-width: 600px) {
.table-wrapper {
overflow-x: auto;
}
table {
min-width: 600px;
}
}
/* Extra Small Devices (max-width: 360px) */
@media (max-width: 360px) {
h2 {
font-size: 20px;
margin-bottom: 15px;
}
.info {
width: 100%;
padding: 8px;
}
input,
textarea,
select {
font-size: 14px;
padding: 8px;
}
.download-btn {
font-size: 14px;
padding: 8px 12px;
}
table th,
table td {
font-size: 9px;
padding: 5px;
}
.total-table {
width: 100%;
}
}

212
static/css/show_excel.css Normal file
View File

@@ -0,0 +1,212 @@
/* General Styles */
body {
font-family: Arial, sans-serif;
margin: 20px;
padding: 20px;
background-color: #f4f4f9;
color: #333;
}
/* Headings */
h1, h2 {
color: #2c3e50;
}
/* File Information Section */
ul {
list-style-type: none;
padding: 0;
}
ul li {
background: #ecf0f1;
margin: 5px 0;
padding: 10px;
border-radius: 5px;
}
/* Error Messages */
.errors {
background-color: #ffdddd;
border: 1px solid #ff4d4d;
padding: 15px;
margin-bottom: 20px;
border-radius: 5px;
}
.errors h2 {
color: #ff4d4d;
}
.error {
color: #d8000c;
font-weight: bold;
}
/* Table Styles */
.table-container {
max-width: 100%;
overflow-x: auto; /* Allows horizontal scrolling */
margin-bottom: 20px;
border: 1px solid #ddd; /* Optional for better visibility */
}
table {
width: 100%;
border-collapse: collapse;
background-color: white;
table-layout: auto; /* Let the columns adjust based on content */
}
th, td {
padding: 10px;
border: 1px solid #ddd;
text-align: left;
word-wrap: break-word; /* Prevents text from overflowing */
}
th {
background-color: #3498db;
color: white;
}
tr:nth-child(even) {
background-color: #f2f2f2;
}
/* Set input width to 100px */
input[type="text"] {
width: 200px; /* Set a fixed width of 100px */
padding: 8px;
border: 1px solid #ccc;
border-radius: 4px;
box-sizing: border-box; /* Ensures padding is included in width calculation */
white-space: nowrap; /* Prevent line breaks */
overflow: hidden; /* Prevents overflow */
text-overflow: ellipsis; /* Shows ellipsis if the content is too long */
}
/* Buttons */
button {
display: inline-block;
background-color: #27ae60;
color: white;
padding: 10px 15px;
text-decoration: none;
border: none;
cursor: pointer;
border-radius: 5px;
transition: background 0.3s ease;
font-size: 16px;
width: 100%; /* Ensures button is centered */
}
.back-button {
display: inline-block;
background-color: #27ae60;
color: white;
padding: 10px 15px;
text-decoration: none;
border: none;
cursor: pointer;
border-radius: 5px;
transition: background 0.3s ease;
font-size: 16px;
width: 10%; /* Ensures button is centered */
}
/* Hover Effects */
button:hover, .back-button:hover {
background-color: #219150;
}
button:disabled {
background-color: #95a5a6;
cursor: not-allowed;
}
/* Back Button */
.back-button {
background-color: #e74c3c;
}
.back-button:hover {
background-color: #c0392b;
}
/* Center Save Data Button */
form {
display: flex;
flex-direction: column;
align-items: center;
}
.save-button {
margin-top: 20px;
width: 50%;
}
/* Responsive Design */
@media (max-width: 768px) {
.table-container {
display: block;
overflow-x: auto;
white-space: nowrap;
}
table {
width: 100%;
table-layout: auto; /* Let the table adjust to fit its content */
}
input[type="text"] {
width: 100px; /* Fixed width of 100px */
padding: 6px; /* Smaller padding on mobile */
}
button, .back-button {
width: 100%;
text-align: center;
}
}
@media (max-width: 480px) {
body {
margin: 10px;
padding: 10px;
}
h1, h2 {
font-size: 20px;
text-align: center;
}
input[type="text"] {
width: 80px;
font-size: 14px;
}
th, td {
font-size: 12px;
padding: 6px;
}
.save-button {
width: 100%;
}
.back-button {
width: 100%;
font-size: 14px;
}
button {
font-size: 14px;
padding: 8px 12px;
}
.errors {
font-size: 14px;
padding: 10px;
}
}

448
static/css/style.css Normal file
View File

@@ -0,0 +1,448 @@
/* General styling */
body {
font-family: Arial, sans-serif;
margin: 20px;
background-color: #f9f9f9;
color: #333;
}
/* Header styling */
h1 {
color: back;
text-align: center;
}
h2 {
color: #0056b3;
text-align: center;
}
h3 {
color: #0056b3;
}
/* Table styling */
table {
width: 100%;
border-collapse: collapse;
margin: 20px 0;
}
table th, table td {
border: 1px solid #ddd;
padding: 8px;
text-align: center;
}
table th {
background-color: #007bff;
color: white;
font-weight: bold;
}
table tr:nth-child(even) {
background-color: #f2f2f2;
}
table tr:hover {
background-color: #ddd;
}
/* Form styling */
form {
max-width: 600px;
margin: 0 auto;
padding: 20px;
background: #fff;
border: 1px solid #ddd;
border-radius: 5px;
box-shadow: 0 0 10px rgba(0, 0, 0, 0.1);
}
form label {
display: block;
margin-bottom: 5px;
font-weight: bold;
}
form input[type="text"], form input[type="number"], form input[type="date"],form input[type="email"], form textarea {
width: 100%;
padding: 10px;
margin-bottom: 15px;
border: 1px solid #ddd;
border-radius: 5px;
box-sizing: border-box;
}
form textarea {
resize: vertical;
}
form button {
width: 100%;
padding: 10px;
background-color: #007bff;
color: white;
border: none;
border-radius: 5px;
cursor: pointer;
font-size: 16px;
}
form button:hover {
background-color: #0056b3;
}
/* Style the <h1> elements to display inline */
h1 {
display: inline-block;
margin-right: 10px; /* Spacing between buttons */
}
/* Style the <a> tags to look like buttons */
.button {
text-decoration: none; /* Remove underline */
padding: 10px 20px; /* Add padding for size */
background-color: #4CAF50; /* Green background color */
color: white; /* White text */
font-size: 16px; /* Font size */
border-radius: 5px; /* Rounded corners */
border: none; /* Remove default border */
cursor: pointer; /* Cursor style */
transition: background-color 0.3s ease; /* Smooth transition for hover */
}
/* Change background color on hover */
.button:hover {
background-color: #45a049;
}
/* Style for the Back Button */
.back-button {
background-color: red;
color: white;
padding: 10px 20px;
text-decoration: none;
font-weight: bold;
border-radius: 5px;
display: inline-block;
margin-top: 20px;
text-align: center;
}
.back-button:hover {
background-color: darkred;
}
/* Styling for the select dropdown */
select {
width: 100%; /* Use full width for better responsiveness */
padding: 12px 15px; /* Increased padding for better spacing */
margin: 10px 0; /* Spacing above and below */
border: 1px solid #ccc; /* Border color */
border-radius: 5px; /* Rounded corners */
font-size: 14px; /* Font size for text */
background-color: #fff; /* White background */
box-sizing: border-box; /* Ensures padding and border are included in width */
appearance: none; /* Remove default dropdown arrow for custom styling */
}
/* Custom dropdown arrow */
select::-ms-expand {
display: none; /* Remove the default arrow for IE/Edge */
}
select:focus {
outline: none; /* Remove outline when focused */
border-color: #4CAF50; /* Change border color when focused */
}
/* Option styling for the select */
select option {
padding: 12px 15px; /* Padding for each option */
font-size: 14px; /* Font size */
}
/* Style the placeholder (default) option */
option[disabled] {
color: #aaa; /* Light grey color for disabled option */
font-style: italic; /* Italicize the disabled option */
}
/* Label styling */
label {
font-size: 16px;
font-weight: bold;
margin-bottom: 5px;
display: block;
}
.save-button {
background-color: Green;
color: white;
padding: 15px;
text-decoration: none;
font-weight: bold;
border-radius: 5px;
display:flex;
justify-content: center;
align-item: center;
margin: 20px;
text-align: center;
}
.save-button:hover {
background-color: darkGreen;
}
.success-popup {
display: none;
position: fixed;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
background-color: #d4edda;
color: #155724;
padding: 20px;
border-radius: 10px;
box-shadow: 0 0 10px rgba(0, 0, 0, 0.2);
text-align: center;
font-size: 18px;
font-weight: bold;
}
.success-popup i {
color: green;
font-size: 30px;
margin-right: 10px;
}
.error-message {
color: red;
font-size: 14px;
margin-top: 5px;
}
.icon {
width: 20px;
height: 20px;
cursor: pointer;
transition: transform 0.2s ease-in-out, opacity 0.2s ease-in-out;
}
.icon:hover {
transform: scale(1.5);
opacity: 0.8;
}
.edit-icon:hover {
filter: drop-shadow(0 0 5px #007bff); /* Blue glow for edit */
}
.delete-icon:hover {
filter: drop-shadow(0 0 5px #ff0000); /* Red glow for delete */
}
/* Search Bar Container */
.search-container {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 10px;
}
/* Search Bar Input */
#searchBar {
padding: 8px;
font-size: 14px;
border: 1px solid #ccc;
border-radius: 5px;
width: 200px;
transition: 0.3s;
}
/* Search Bar Focus Effect */
#searchBar:focus {
border-color: #007bff;
outline: none;
box-shadow: 0 0 5px rgba(0, 123, 255, 0.5);
}
/* Center the buttons and apply consistent styling */
.button-container {
display: flex;
justify-content: center; /* Center buttons horizontally */
gap: 10px; /* Space between buttons */
margin-top: 20px; /* Add some top margin */
}
.action-button {
background-color: #007BFF; /* Blue background */
color: white; /* White text */
padding: 15px 30px; /* Larger padding for bigger buttons */
border: none; /* Remove border */
border-radius: 5px; /* Rounded corners */
cursor: pointer; /* Pointer cursor on hover */
font-size: 18px; /* Larger font size */
text-align: center; /* Center text */
text-decoration: none; /* Remove underline */
transition: background-color 0.3s ease; /* Smooth hover transition */
}
.action-button:hover {
background-color: #0056b3; /* Darker blue on hover */
}
#addStateForm, #stateTable {
display: none; /* Initially hide both the form and the table */
}
/* Success Popup */
.success-popup {
display: none;
color: green;
font-size: 1.2em;
margin-top: 10px;
}
/* Sorting buttons */
.sortable .sort-buttons {
margin-left: 5px;
}
.sortable {
cursor: pointer;
user-select: none;
}
.sort-buttons a {
text-decoration: none;
color: black;
font-weight: bold;
margin-left: 5px;
margin-right: 5px;
}
.sort-buttons a:hover {
text-decoration: underline;
}
.back-button {
display: inline-block;
margin-top: 20px;
padding: 8px 15px;
background-color: #28a745;
color: white;
border-radius: 5px;
text-decoration: none;
font-size: 16px;
}
.back-button:hover {
background-color: #218838;
}
span .sort-desc:hover{
cursor: pointer;
}
span .sort-asc:hover{
cursor: pointer;
}
.sortable select {
background-color: #007BFF; /* Blue background */
color: white; /* White text */
border: none;
border-radius: 4px;
padding: 5px 10px;
font-size: 14px;
cursor: pointer;
appearance: none; /* Remove default browser styling */
background-image: url("data:image/svg+xml;charset=US-ASCII,%3Csvg xmlns='http://www.w3.org/2000/svg' width='16' height='16' fill='white' class='bi bi-caret-down-fill' viewBox='0 0 16 16'%3E%3Cpath d='M7.247 11.14 2.451 5.658c-.566-.63-.106-1.658.753-1.658h9.592c.86 0 1.32 1.027.753 1.658L8.753 11.14a1 1 0 0 1-1.506 0z'/%3E%3C/svg%3E");
background-repeat: no-repeat;
background-position: right 10px center;
background-size: 12px;
}
.sortable select:focus {
outline: none;
background-color: #0056b3; /* Darker blue on focus */
}
.download-btn {
display: inline-block;
padding: 10px 15px;
background-color: #28a745;
color: white;
text-decoration: none;
border-radius: 5px;
margin-top: 15px;
}
@media (max-width: 480px) {
body {
margin: 10px;
padding: 10px;
}
h1, h2, h3 {
font-size: 18px;
text-align: center;
}
table th, table td {
font-size: 12px;
padding: 6px;
}
form {
padding: 10px;
max-width: 100%;
}
form input[type="text"],
form input[type="number"],
form input[type="date"],
form input[type="email"],
form textarea,
select {
padding: 8px;
font-size: 14px;
}
form button,
.button,
.back-button,
.save-button,
.action-button {
font-size: 14px;
padding: 10px;
width: 100%;
}
.button-container {
flex-direction: column;
gap: 10px;
}
#searchBar {
width: 100%;
font-size: 14px;
}
.sortable select {
font-size: 14px;
padding: 8px 10px;
background-position: right 8px center;
}
}

View File

@@ -0,0 +1,199 @@
/* Global box-sizing for predictable sizing */
*, *::before, *::after {
box-sizing: border-box;
}
h2 {
text-align: center;
/* Fluid font size between 20px and 24px */
font-size: clamp(20px, 3vw, 24px);
color: #333;
margin-bottom: 20px;
}
/* Form Styling */
.info {
width: 60%;
max-width: 900px; /* optional max-width */
background: #fff;
padding: 20px;
border-radius: 8px;
box-shadow: 0px 4px 10px rgba(0, 0, 0, 0.1);
display: flex;
flex-direction: column;
gap: 15px;
margin: 0 auto; /* center horizontally */
}
/* Responsive Form Layout */
.row1,
.row2,
.row3 {
display: grid;
gap: 15px;
}
.row2,
.row3 {
grid-template-columns: repeat(auto-fit, minmax(250px, 1fr));
}
label {
font-weight: bold;
display: block;
margin-bottom: 6px;
}
input,
textarea,
select {
width: 100%;
padding: 10px;
border: 1px solid #ccc;
border-radius: 6px;
/* Fluid font size between 14px and 16px */
font-size: clamp(14px, 1.5vw, 16px);
}
/* Table styling */
.table-wrapper {
overflow-x: auto;
}
table {
width: 100%;
border-collapse: collapse;
margin: 20px 0;
}
table th,
table td {
border: 1px solid #ddd;
padding: 8px;
text-align: center;
/* Fluid font size between 10px and 14px */
font-size: clamp(10px, 1vw, 14px);
}
table th {
background-color: #007bff;
color: white;
font-weight: bold;
}
table tr:nth-child(even) {
background-color: #f2f2f2;
}
table tr:hover {
background-color: #ddd;
}
.download-btn {
display: inline-block;
padding: 10px 15px;
background-color: #28a745;
color: white;
text-decoration: none;
border-radius: 5px;
margin-top: 15px;
/* Fluid font size between 12px and 16px */
font-size: clamp(12px, 1.5vw, 16px);
}
.total-table {
width: 35%;
}
/* Responsive Design */
@media (max-width: 1024px) {
.info {
padding: 15px;
width: 80%;
}
.row2,
.row3 {
grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
}
.invoice-table th,
.invoice-table td {
font-size: 12px;
padding: 8px;
}
}
@media (max-width: 768px) {
.info {
width: 90%;
}
.row1,
.row2,
.row3 {
grid-template-columns: 1fr;
}
}
@media (max-width: 600px) {
.table-wrapper {
overflow-x: auto;
}
table {
min-width: 600px;
}
}
@media (max-width: 480px) {
body {
max-width: 100%;
padding: 10px;
}
.info {
padding: 10px;
width: 100%;
}
table th,
table td {
font-size: 10px;
padding: 6px;
}
}
@media (max-width: 360px) {
h2 {
font-size: 20px;
margin-bottom: 15px;
}
.info {
width: 100%;
padding: 8px;
gap: 10px;
}
input,
textarea,
select {
font-size: 14px;
padding: 8px;
}
table th,
table td {
font-size: 8px;
padding: 4px;
}
.total-table {
width: 100%;
}
.download-btn {
padding: 8px 12px;
font-size: 14px;
}
}

View File

@@ -0,0 +1,111 @@
body {
font-family: Arial, sans-serif;
margin: 0;
padding: 0;
display: flex;
justify-content: center;
align-items: center;
min-height: 100vh;
background-color: #f9f9f9;
}
.container {
max-width: 500px;
width: 90%;
background: #ffffff;
padding: 20px;
border-radius: 8px;
box-shadow: 0 4px 8px rgba(0, 0, 0, 0.2);
text-align: center;
}
h1 {
margin-bottom: 20px;
color: #333;
}
form {
margin-bottom: 20px;
}
input[type="file"] {
display: block;
margin: 0 auto 15px auto;
font-size: 18px;
padding: 10px;
cursor: pointer;
border: 2px solid #007bff;
border-radius: 5px;
background-color: #f4f4f4;
}
input[type="file"]:hover {
border-color: #0056b3;
}
button {
background-color: #4CAF50;
color: white;
border: none;
padding: 15px 25px;
font-size: 18px;
border-radius: 5px;
cursor: pointer;
}
button:hover {
background-color: #45a049;
}
/* Style for the Back Button */
.back-button {
background-color: red;
color: white;
padding: 10px 20px;
text-decoration: none;
font-weight: bold;
border-radius: 5px;
display: inline-block;
margin-top: 20px;
text-align: center;
}
.back-button:hover {
background-color: darkred;
}
@media screen and (max-width: 600px) {
.container {
padding: 15px;
}
h1 {
font-size: 20px;
}
input[type="file"] {
font-size: 16px;
padding: 8px;
}
button, .back-button {
font-size: 14px;
padding: 10px 20px;
}
}
@media screen and (max-width: 360px) {
.container {
width: 95%;
padding: 10px;
}
h1 {
font-size: 18px;
margin-bottom: 15px;
}
input[type="file"] {
font-size: 14px;
padding: 6px;
}
button, .back-button {
font-size: 12px;
padding: 8px 16px;
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.9 KiB

116
static/js/block.js Normal file
View File

@@ -0,0 +1,116 @@
window.onload = function () {
document.getElementById('block_Name').focus();
};
$(document).ready(function () {
$("#block_Name").on("input", function () {
let blockName = $(this).val();
let cleanedName = blockName.replace(/[^A-Za-z ]/g, "");
$(this).val(cleanedName);
});
$("#block_Name, #district_Id").on("input change", function () {
let blockName = $("#block_Name").val().trim();
let districtId = $("#district_Id").val();
if (blockName === "" || districtId === "") {
$("#blockMessage").text("").css("color", "");
$("#submitButton").prop("disabled", true);
return;
}
$.ajax({
url: "/check_block",
type: "POST",
contentType: "application/json",
data: JSON.stringify({
block_Name: blockName,
district_Id: districtId
}),
success: function (response) {
if (response.status === "available") {
$("#blockMessage").text(response.message).css("color", "green");
$("#submitButton").prop("disabled", false);
}
},
error: function (xhr) {
if (xhr.status === 409) {
$("#blockMessage").text("Block already exists!").css("color", "red");
$("#submitButton").prop("disabled", true);
}
else if (xhr.status === 400) {
$("#blockMessage").text("Invalid block name! Only letters allowed.").css("color", "red");
$("#submitButton").prop("disabled", true);
}
}
});
});
$("#blockForm").on("submit", function (event) {
event.preventDefault();
$.ajax({
url: "/add_block",
type: "POST",
data: $(this).serialize(),
success: function (response) {
alert(response.message);
location.reload();
},
error: function (xhr) {
alert(xhr.responseJSON.message);
}
});
});
// ✅ FETCH DISTRICTS WHEN STATE CHANGES
$('#state_Id').change(function () {
var stateId = $(this).val();
if (stateId) {
$.ajax({
url: '/get_districts/' + stateId,
type: 'GET',
success: function (data) {
var districtDropdown = $('#district_Id');
districtDropdown.empty();
districtDropdown.append('<option value="" disabled selected>Select District</option>');
data.forEach(function (district) {
districtDropdown.append(
'<option value="' + district.id + '">' + district.name + '</option>'
);
});
districtDropdown.prop('disabled', false);
},
error: function () {
alert('Error fetching districts. Please try again.');
}
});
} else {
$('#district_Id').prop('disabled', true);
}
});
});

62
static/js/district.js Normal file
View File

@@ -0,0 +1,62 @@
window.onload = function () {
document.getElementById('district_Name').focus();
};
$(document).ready(function () {
$("#district_Name").on("input", function () {
let districtName = $(this).val();
// Remove numbers and special characters automatically
let cleanedName = districtName.replace(/[^A-Za-z ]/g, "");
$(this).val(cleanedName);
});
$("#district_Name, #state_Id").on("input change", function () {
let districtName = $("#district_Name").val().trim();
let stateId = $("#state_Id").val();
if (districtName === "" || stateId === "") {
$("#districtMessage").text("").css("color", "");
$("#submitButton").prop("disabled", true);
return;
}
$.ajax({
url: "/check_district",
type: "POST",
contentType: "application/json",
data: JSON.stringify({ district_Name: districtName, state_Id: stateId }),
success: function (response) {
if (response.status === "available") {
$("#districtMessage").text(response.message).css("color", "green");
$("#submitButton").prop("disabled", false);
}
},
error: function (xhr) {
if (xhr.status === 409) {
$("#districtMessage").text("District already exists!").css("color", "red");
$("#submitButton").prop("disabled", true);
} else if (xhr.status === 400) {
$("#districtMessage").text("Invalid district name! Only letters are allowed.").css("color", "red");
$("#submitButton").prop("disabled", true);
}
}
});
});
$("#districtForm").on("submit", function (event) {
event.preventDefault();
$.ajax({
url: "/add_district",
type: "POST",
data: $(this).serialize(),
success: function (response) {
alert(response.message);
location.reload();
},
error: function (xhr) {
alert(xhr.responseJSON.message);
}
});
});
});

View File

@@ -0,0 +1,18 @@
$("#updateHoldTypeForm").on("submit", function(event) {
event.preventDefault();
let holdTypeId = $("#hold_type_id").val();
let newHoldType = $("#edit_hold_type").val().trim();
let reg = /^[A-Za-z]/;
if (!reg.test(newHoldType)) {
alert("Hold Type must start with a letter.");
return;
}
$.post(`/update_hold_type/${holdTypeId}`, { hold_type: newHoldType }, function(response) {
alert(response.message);
window.location.href = "/";
}).fail(function(xhr) {
alert(xhr.responseJSON.message);
});
});

95
static/js/holdAmount.js Normal file
View File

@@ -0,0 +1,95 @@
$(document).ready(function () {
// Create a module to manage hold amounts
window.holdAmountModule = {
holdCount: 0,
holdTypes: [],
init: function() {
this.loadHoldTypes();
this.setupEventListeners();
},
loadHoldTypes: function() {
$.ajax({
url: '/get_hold_types',
method: 'GET',
dataType: 'json',
success: (data) => {
this.holdTypes = data;
$("#add_hold_amount").prop('disabled', false);
},
error: (err) => {
console.error('Failed to load hold types', err);
}
});
},
setupEventListeners: function() {
$("#add_hold_amount").click(() => this.addHoldAmountField());
$(document).on("click", ".remove-hold", (e) => this.removeHoldAmountField(e));
$(document).on("change", ".hold-type-dropdown", () => this.refreshDropdowns());
$(document).on("input", "input[name='hold_amount[]']", () => this.triggerHoldAmountChanged());
},
addHoldAmountField: function() {
this.holdCount++;
$("#hold_amount_container").append(`
<div class="hold-amount-field" id="hold_${this.holdCount}">
<select name="hold_type[]" class="hold-type-dropdown" required>
${this.generateOptions()}
</select>
<input type="number" step="0.01" name="hold_amount[]"
class="hold-amount-input" placeholder="Hold Amount" required>
<button type="button" class="remove-hold" data-id="hold_${this.holdCount}">X</button>
</div>
`);
this.refreshDropdowns();
this.triggerHoldAmountChanged();
},
removeHoldAmountField: function(e) {
const id = $(e.target).attr("data-id");
$(`#${id}`).remove();
this.refreshDropdowns();
this.triggerHoldAmountChanged();
},
generateOptions: function(selectedForThisDropdown = '') {
const selectedValues = $("select[name='hold_type[]']").map(function() {
return $(this).val();
}).get();
let options = '<option value="">Select Hold Type</option>';
this.holdTypes.forEach((type) => {
if (!selectedValues.includes(type.hold_type) || type.hold_type === selectedForThisDropdown) {
options += `<option value="${type.hold_type}">${type.hold_type}</option>`;
}
});
return options;
},
refreshDropdowns: function() {
$("select[name='hold_type[]']").each(function() {
const currentVal = $(this).val();
$(this).html(window.holdAmountModule.generateOptions(currentVal));
$(this).val(currentVal);
});
},
getTotalHoldAmount: function() {
let total = 0;
$("input[name='hold_amount[]']").each(function() {
total += parseFloat($(this).val()) || 0;
});
return total;
},
triggerHoldAmountChanged: function() {
const event = new Event('holdAmountChanged');
document.dispatchEvent(event);
}
};
// Initialize the module
window.holdAmountModule.init();
});

39
static/js/hold_types.js Normal file
View File

@@ -0,0 +1,39 @@
$(document).ready(function () {
$("#hold_type").on("input", function () {
let holdType = $(this).val().replace(/^\s+/, "");
$(this).val(holdType);
let reg = /^[A-Za-z]/;
if (!reg.test(holdType)) {
$("#holdTypeMessage").text("Hold Type must start with a letter.").css("color", "red");
$("#addButton").prop("disabled", true);
return;
} else {
$("#holdTypeMessage").text("").css("color", "");
$("#addButton").prop("disabled", false);
}
});
$("#holdTypeForm").on("submit", function (event) {
event.preventDefault();
$.post("/add_hold_type", $(this).serialize(), function (response) {
alert(response.message);
location.reload();
}).fail(function (xhr) {
alert(xhr.responseJSON.message);
});
});
$(".delete-button").on("click", function () {
let id = $(this).data("id");
if (confirm("Are you sure?")) {
$.post(`/delete_hold_type/${id}`, function (response) {
alert(response.message);
location.reload();
}).fail(function (xhr) {
alert(xhr.responseJSON.message);
});
}
});
});

62
static/js/invoice.js Normal file
View File

@@ -0,0 +1,62 @@
// Subcontractor autocomplete functionality
$(document).ready(function () {
$("#subcontractor").keyup(function () {
let query = $(this).val();
if (query !== "") {
$.ajax({
url: "/search_subcontractor",
method: "POST",
data: { query: query },
success: function (data) {
$("#subcontractor_list").fadeIn().html(data);
}
});
} else {
$("#subcontractor_list").fadeOut();
}
});
$(document).on("click", "li", function () {
$("#subcontractor").val($(this).text());
$("#subcontractor_id").val($(this).attr("data-id"));
$("#subcontractor_list").fadeOut();
});
});
// Success Alert: show alert and reload after 3 seconds
function showSuccessAlert() {
const alertBox = document.getElementById("invoiceSuccessAlert");
alertBox.classList.add("show");
setTimeout(() => {
alertBox.classList.remove("show");
// Reload page or redirect after alert hides
window.location.href = '/add_invoice';
}, 3000);
}
// Submit form via AJAX
$("#invoiceForm").on("submit", function (e) {
e.preventDefault();
let formData = $(this).serialize();
$.ajax({
url: '/add_invoice',
method: 'POST',
data: formData,
success: function (response) {
if(response.status === "success") {
showSuccessAlert();
} else {
alert(response.message);
}
},
error: function (xhr, status, error) {
alert("Submission failed: " + error);
}
});
});
window.onload = function () {
document.getElementById('subcontractor').focus();
};

View File

@@ -0,0 +1,39 @@
document.addEventListener('DOMContentLoaded', function () {
const form = document.getElementById('saveForm');
form.addEventListener('submit', function (e) {
e.preventDefault(); // Prevent normal form submission
const formData = new FormData(form);
fetch('/save_data', {
method: 'POST',
body: formData,
}).then(response => response.json()).then(data => {
if (data.success) {
Swal.fire({
icon: 'success',
title: 'Success!',
text: data.success,
showConfirmButton: true,
confirmButtonText: 'OK'
}).then(() => {
const redirectUrl = "{{ url_for('upload_excel_file') }}"; // Redirect after success pop
});
} else if (data.error) {
Swal.fire({
icon: 'error',
title: 'Error!',
text: data.error,
});
}
}).catch(error => {
Swal.fire({
icon: 'error',
title: 'Error!',
text: 'An unexpected error occurred.',
});
console.error('Error:', error);
});
});
});

View File

@@ -0,0 +1,21 @@
$("#saveForm").on("submit", function (event) {
event.preventDefault();
$.ajax({
url: "/save_data",
type: "POST",
data: $(this).serialize(),
success: function (response) {
if (response.success) {
alert("Success: " + response.success); // Show success alert
window.location.href = "/upload_excel_file"; // Redirect to the upload page
}
},
error: function (xhr) {
if (xhr.responseJSON && xhr.responseJSON.error) {
alert("Error: " + xhr.responseJSON.error);
} else {
alert("An unexpected error occurred. Please try again.");
}
}
});
});

View File

@@ -0,0 +1,43 @@
$(document).ready(function () {
function fetchResults() {
let formData = $('#search-form').serialize();
$.ajax({
type: 'POST',
url: '/search_contractor',
data: formData,
success: function (data) {
let tableBody = $('#result-table tbody');
tableBody.empty();
if (data.length === 0) {
tableBody.append('<tr><td colspan="6">No data found</td></tr>');
} else {
data.forEach(function (row) {
tableBody.append(`
<tr>
<td><a href="/contractor_report/${row.Contractor_Id}" target="_blank">${row.Contractor_Name}</a></td>
<td><a href="/pmc_report/${row.PMC_No}" target="_blank">${row.PMC_No}</a></td>
<td>${row.State_Name}</td>
<td>${row.District_Name}</td>
<td>${row.Block_Name}</td>
<td>${row.Village_Name}</td>
</tr>
`);
});
}
},
error: function (xhr) {
alert(xhr.responseJSON.error);
}
});
}
$('#search-form input').on('keyup change', function () {
fetchResults();
});
});
window.onload = function () {
document.getElementById('subcontractor_name').focus();
};

View File

@@ -0,0 +1,108 @@
// Search on table using search inpute options
function searchTable() {
let input = document.getElementById("searchBar").value.toLowerCase();
let rows = document.querySelectorAll("table tbody tr");
rows.forEach(row => {
let blockName = row.cells[1].textContent.toLowerCase();
let districtName = row.cells[2].textContent.toLowerCase();
let villageName = row.cells[3].textContent.toLowerCase();
if (blockName.includes(input) || districtName.includes(input)|| villageName.includes(input)) {
row.style.display = "";
} else {
row.style.display = "none";
}
});
}
// Common Sorting Script for Tables
function sortTable(n, dir) {
var table, rows, switching, i, x, y, shouldSwitch;
table = document.getElementById("sortableTable"); // Ensure your table has this ID
switching = true;
while (switching) {
switching = false;
rows = table.rows;
for (i = 1; i < (rows.length - 1); i++) {
shouldSwitch = false;
x = rows[i].getElementsByTagName("TD")[n];
y = rows[i + 1].getElementsByTagName("TD")[n];
if (dir == "asc") {
if (x.innerHTML.toLowerCase() > y.innerHTML.toLowerCase()) {
shouldSwitch = true;
break;
}
} else if (dir == "desc") {
if (x.innerHTML.toLowerCase() < y.innerHTML.toLowerCase()) {
shouldSwitch = true;
break;
}
}
}
if (shouldSwitch) {
rows[i].parentNode.insertBefore(rows[i + 1], rows[i]);
switching = true;
}
}
}
// Attach sorting functionality to all sortable tables
document.addEventListener("DOMContentLoaded", function() {
// Find all elements with the class "sortable-header"
var sortableHeaders = document.querySelectorAll(".sortable-header");
sortableHeaders.forEach(function(header) {
// Attach click event for ascending sort
if (header.querySelector(".sort-asc")) {
header.querySelector(".sort-asc").addEventListener("click", function() {
var columnIndex = Array.from(header.parentNode.children).indexOf(header);
sortTable(columnIndex, "asc");
});
}
// Attach click event for descending sort
if (header.querySelector(".sort-desc")) {
header.querySelector(".sort-desc").addEventListener("click", function() {
var columnIndex = Array.from(header.parentNode.children).indexOf(header);
sortTable(columnIndex, "desc");
});
}
});
});
// ADD & Dispaly screen show
document.addEventListener("DOMContentLoaded", function () {
const addButton = document.getElementById("addButton");
const displayButton = document.getElementById("displayButton");
const addForm = document.getElementById("addForm");
const addTable = document.getElementById("addTable");
// Show "Add State" form by default
addForm.style.display = "block";
addButton.classList.add("active-button"); // Optional: Add styling for active button
addButton.addEventListener("click", function () {
addForm.style.display = "block";
addTable.style.display = "none";
addButton.classList.add("active-button");
displayButton.classList.remove("active-button");
});
displayButton.addEventListener("click", function () {
addForm.style.display = "none";
addTable.style.display = "block";
displayButton.classList.add("active-button");
addButton.classList.remove("active-button");
});
});

View File

@@ -0,0 +1,8 @@
function showSuccessAlert(event) {
event.preventDefault(); // Prevent form submission
document.getElementById("successPopup").style.display = "block";
setTimeout(function() {
document.getElementById("successPopup").style.display = "none";
event.target.submit(); // Submit the form after showing the message
}, 2000);
}

66
static/js/sorting.js Normal file
View File

@@ -0,0 +1,66 @@
$(document).ready(function () {
function fetchResults(sortBy = '', sortOrder = '') {
let formData = $('#search-form').serialize();
formData += &sort_by=${sortBy}&sort_order=${sortOrder};
$.ajax({
type: 'POST',
url: '/search_contractor',
data: formData,
success: function (data) {
let tableBody = $('#result-table tbody');
tableBody.empty();
if (data.length === 0) {
tableBody.append('<tr><td colspan="6">No data found</td></tr>');
} else {
data.forEach(function (row) {
tableBody.append(`
<tr>
<td><a href="/contractor_report/${row.Contractor_Id}" target="_blank">${row.Contractor_Name}</a></td>
<td><a href="/pmc_report/${row.PMC_No}" target="_blank">${row.PMC_No}</a></td>
<td>${row.State_Name}</td>
<td>${row.District_Name}</td>
<td>${row.Block_Name}</td>
<td>${row.Village_Name}</td>
</tr>
`);
});
}
},
error: function (xhr) {
alert(xhr.responseJSON.error);
}
});
}
$('#search-form input').on('keyup change', function () {
fetchResults();
});
function showSortOptions(thElement, column) {
let sortMenu = $('#sort-options');
let offset = $(thElement).position();
let thHeight = $(thElement).outerHeight();
sortMenu.html(`
<button onclick="fetchResults('${column}', 'ASC')">Ascending</button>
<button onclick="fetchResults('${column}', 'DESC')">Descending</button>
`);
sortMenu.css({
display: 'block',
top: offset.top + thHeight + 'px',
left: offset.left + 'px'
});
}
$(document).click(function(event) {
if (!$(event.target).closest('.sort-options, th').length) {
$('#sort-options').hide();
}
});
window.fetchResults = fetchResults;
window.showSortOptions = showSortOptions;
});

61
static/js/state.js Normal file
View File

@@ -0,0 +1,61 @@
window.onload = function () {
document.getElementById('state_Name').focus();
};
$(document).ready(function () {
$("#state_Name").on("input", function () {
let stateName = $(this).val();
// Remove numbers and special characters automatically
let cleanedName = stateName.replace(/[^A-Za-z ]/g, "");
$(this).val(cleanedName);
});
$("#state_Name").on("input", function () {
let stateName = $("#state_Name").val().trim();
if (stateName === "") {
$("#stateMessage").text("").css("color", "");
$("#submitButton").prop("disabled", true);
return;
}
$.ajax({
url: "/check_state",
type: "POST",
contentType: "application/json",
data: JSON.stringify({ state_Name: stateName }),
success: function (response) {
if (response.status === "available") {
$("#stateMessage").text(response.message).css("color", "green");
$("#submitButton").prop("disabled", false);
}
},
error: function (xhr) {
if (xhr.status === 409) {
$("#stateMessage").text("State already exists!").css("color", "red");
$("#submitButton").prop("disabled", true);
} else if (xhr.status === 400) {
$("#stateMessage").text("Invalid state name! Only letters are allowed.").css("color", "red");
$("#submitButton").prop("disabled", true);
}
}
});
});
$("#stateForm").on("submit", function (event) {
event.preventDefault();
$.ajax({
url: "/add_state",
type: "POST",
data: $(this).serialize(),
success: function (response) {
alert(response.message);
location.reload();
},
error: function (xhr) {
alert(xhr.responseJSON.message);
}
});
});
});

View File

@@ -0,0 +1,49 @@
function validateInput() {
let isValid = true;
// Get form elements
let contractorName = document.getElementById("Contractor_Name").value;
let mobileNo = document.getElementById("Mobile_No").value;
let panNo = document.getElementById("PAN_No").value;
let email = document.getElementById("Email").value;
let passwordField = document.getElementById("Contractor_password");
let submitBtn = document.getElementById("submitBtn");
// Validation patterns
let mobileRegex = /^[0-9]{10}$/;
let panRegex = /^[A-Z0-9]{10}$/;
let emailRegex = /^[a-z]+@[a-z]+\.[a-z]{2,6}$/;
// Validate Mobile No
if (!mobileNo.match(mobileRegex)) {
document.getElementById("mobileError").innerText = "Mobile No must be exactly 10 digits.";
isValid = false;
} else {
document.getElementById("mobileError").innerText = "";
}
// Validate PAN No
if (!panNo.match(panRegex)) {
document.getElementById("panError").innerText = "PAN No must be uppercase letters or digits (10 chars).";
isValid = false;
} else {
document.getElementById("panError").innerText = "";
}
// Validate Email
if (!email.match(emailRegex)) {
document.getElementById("emailError").innerText = "Email must be lowercase, contain '@' and '.'";
isValid = false;
} else {
document.getElementById("emailError").innerText = "";
}
// Enable or disable the submit button
submitBtn.disabled = !isValid;
}
window.onload = function () {
document.getElementById('Contractor_Name').focus();
};

View File

@@ -0,0 +1,12 @@
function validateFileInput() {
const fileInput = document.querySelector('input[type="file"]');
const filePath = fileInput.value;
const allowedExtensions = /(\.xlsx|\.xls)$/i;
if (!allowedExtensions.exec(filePath)) {
alert("Please upload a valid Excel file (.xlsx or .xls only).");
fileInput.value = '';
return false;
}
return true;
}

198
static/js/village.js Normal file
View File

@@ -0,0 +1,198 @@
window.onload = function () {
document.getElementById('Village_Name').focus();
};
$(document).ready(function () {
// STATE → DISTRICT
$('#state_Id').change(function () {
var stateId = $(this).val();
if (stateId) {
$.ajax({
url: '/get_districts/' + stateId,
type: 'GET',
success: function (data) {
var districtDropdown = $('#district_Id');
districtDropdown.empty();
districtDropdown.append('<option value="" disabled selected>Select District</option>');
data.forEach(function (district) {
districtDropdown.append(
'<option value="' + district.id + '">' + district.name + '</option>'
);
});
districtDropdown.prop('disabled', false);
}
});
}
});
// DISTRICT → BLOCK
$('#district_Id').change(function () {
var districtId = $(this).val();
if (districtId) {
$.ajax({
url: '/get_blocks/' + districtId,
type: 'GET',
success: function (data) {
var blockDropdown = $('#block_Id');
blockDropdown.empty();
blockDropdown.append('<option value="" disabled selected>Select Block</option>');
data.forEach(function (block) {
blockDropdown.append(
'<option value="' + block.id + '">' + block.name + '</option>'
);
});
blockDropdown.prop('disabled', false);
}
});
}
});
// VILLAGE NAME VALIDATION
$('#Village_Name').on('input', function () {
var villageName = $(this).val();
var validPattern = /^[A-Za-z ]*$/;
if (!validPattern.test(villageName)) {
$('#villageMessage')
.text('Only letters and spaces are allowed!')
.css('color', 'red');
$('#submitVillage').prop('disabled', true);
} else {
$('#villageMessage').text('');
$('#submitVillage').prop('disabled', false);
}
});
// CHECK DUPLICATE VILLAGE
$('#Village_Name, #block_Id').on('change keyup', function () {
var blockId = $('#block_Id').val();
var villageName = $('#Village_Name').val().trim();
if (blockId && villageName) {
$.ajax({
url: '/check_village',
type: 'POST',
data: {
block_Id: blockId,
Village_Name: villageName
},
success: function (response) {
if (response.status === 'exists') {
$('#villageMessage')
.text(response.message)
.css('color', 'red');
$('#submitVillage').prop('disabled', true);
} else {
$('#villageMessage')
.text(response.message)
.css('color', 'green');
$('#submitVillage').prop('disabled', false);
}
},
error: function () {
$('#villageMessage')
.text('Error checking village name')
.css('color', 'red');
$('#submitVillage').prop('disabled', true);
}
});
}
});
// ADD VILLAGE
$('#villageForm').submit(function (event) {
event.preventDefault();
$.ajax({
url: '/add_village',
type: 'POST',
data: $(this).serialize(),
success: function (response) {
if (response.status === 'success') {
alert('Village added successfully!');
location.reload();
} else {
alert('Error adding village. Please try again.');
}
},
error: function () {
alert('An error occurred. Please try again.');
}
});
});
});

102
templates/activity_log.html Normal file
View File

@@ -0,0 +1,102 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Activity Logs</title>
<style>
body {
font-family: "Segoe UI", Tahoma, sans-serif;
background-color: #f8f9fa;
margin: 20px;
}
h2 {
text-align: center;
margin-bottom: 20px;
}
form {
display: flex;
gap: 10px;
justify-content: center;
margin-bottom: 20px;
flex-wrap: wrap;
}
input, button {
padding: 8px;
border-radius: 6px;
border: 1px solid #ccc;
}
button {
background-color: #007bff;
color: white;
cursor: pointer;
}
button:hover {
background-color: #0056b3;
}
table {
width: 90%;
margin: auto;
border-collapse: collapse;
background: white;
box-shadow: 0 0 8px rgba(0,0,0,0.1);
}
th, td {
padding: 12px;
border: 1px solid #ddd;
text-align: center;
}
th {
background-color: #007bff;
color: white;
}
tr:nth-child(even) {
background-color: #f2f2f2;
}
</style>
</head>
<body>
<h2>Activity Logs</h2>
<form method="get" action="{{ url_for('log.activity_log') }}" class="filter-form">
<label for="username">Username:</label>
<input type="text" id="username" name="username" placeholder="Enter username" value="{{ username or '' }}">
<label for="start_date">Start Date:</label>
<input type="date" id="start_date" name="start_date" value="{{ start_date or '' }}">
<label for="end_date">End Date:</label>
<input type="date" id="end_date" name="end_date" value="{{ end_date or '' }}">
<button type="submit" class="btn btn-primary">Filter</button>
<button type="button" style="background-color: #6c757d;" onclick="resetFilter()">Reset</button>
</form>
<table>
<tr>
<th>Timestamp</th>
<th>User</th>
<th>Action</th>
<th>Details</th>
</tr>
{% for log in logs %}
<tr>
<td>{{ log.timestamp }}</td>
<td>{{ log.user }}</td>
<td>{{ log.action }}</td>
<td>{{ log.details }}</td>
</tr>
{% endfor %}
{% if logs|length == 0 %}
<tr>
<td colspan="4">No logs found</td>
</tr>
{% endif %}
</table>
<script>
function resetFilter() {
window.location.href = "{{ url_for('log.activity_log') }}";
}
</script>
</body>
</html>

99
templates/add_block.html Normal file
View File

@@ -0,0 +1,99 @@
{% extends 'base.html' %}
{% block content %}
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Add Block</title>
<link rel="stylesheet" type="text/css" href="{{ url_for('static', filename='css/style.css') }}">
<script src="{{ url_for('static', filename='js/block.js') }}"></script>
<script src="{{ url_for('static', filename='js/search_on_table.js') }}"></script>
</head>
<body>
<!-- Button Container to Center Buttons -->
<div class="button-container">
<button id="addButton" class="action-button">Add</button>
<button id="displayButton" class="action-button">Display</button>
</div>
<div id="addForm" style="display: none;">
<div class="container">
<div class="form-block">
<h2>Create a New Block</h2>
<form id="blockForm" method="POST">
<!-- Select State Dropdown -->
<label for="state_Id">State:</label>
<select id="state_Id" name="state_Id" required>
<option value="" disabled selected>Select State</option>
{% for state in states %}
<option value="{{ state[0] }}">{{ state[1] }}</option>
{% endfor %}
</select>
<!-- Select District Dropdown -->
<label for="district_Id">District:</label>
<select id="district_Id" name="district_Id" required disabled>
<option value="" disabled selected>Select District</option>
</select>
<!-- Block Name Input -->
<label for="block_Name">Enter Block Name:</label>
<input type="text" id="block_Name" name="block_Name" placeholder="Block Name" required>
<span id="blockMessage"></span>
<button type="submit" id="submitButton" disabled>Add Block</button>
</form>
</div>
</div>
</div>
<div id="addTable" style="display: none;">
<div class="search-container">
<h2>Display Blocks</h2>
<input type="text" id="searchBar" placeholder="Searching..." onkeyup="searchTable()">
</div>
<!-- Display Blocks -->
<table id="sortableTable" border="1">
<tr>
<th>Block Sr no</th>
<th class="sortable-header">Block Name
<span class="sort-buttons">
<span class="sort-asc">⬆️</span>
<span class="sort-desc">⬇️</span>
</span>
</th>
<th class="sortable-header">District Name
<span class="sort-buttons">
<span class="sort-asc">⬆️</span>
<span class="sort-desc">⬇️</span>
</span>
</th>
<th>Update</th>
<th>Delete</th>
</tr>
{% for block in block_data %}
<tr>
<td>{{ block[0] }}</td>
<td>{{ block[1] }}</td>
<td>{{ block[3] }}</td>
<td>
<a href="{{ url_for('block.edit_block', block_id=block[0]) }}">
<img src="{{ url_for('static', filename='images/icons/pen_blue_icon.png') }}" alt="Edit"
class="icon">
</a>
</td>
<td>
<a href="{{ url_for('block.delete_block', block_id=block[0]) }}"
onclick="return confirm('Are you sure you want to delete this block?');">
<img src="{{ url_for('static', filename='images/icons/bin_red_icon.png') }}" alt="Delete"
class="icon">
</a>
</td>
</tr>
{% endfor %}
</table>
</div>
</body>
{% endblock %}

View File

@@ -0,0 +1,99 @@
{% extends 'base.html' %}
{% block content %}
<head>
<title>Add District</title>
<link rel="stylesheet" type="text/css" href="{{ url_for('static', filename='css/style.css') }}">
<script src="{{ url_for('static', filename='js/search_on_table.js') }}"></script>
</head>
<body>
<!-- Flash Messages -->
{% with messages = get_flashed_messages(with_categories=true) %}
{% if messages %}
<div class="flash-container">
{% for category, message in messages %}
<div class="flash {{ category }}">{{ message }}</div>
{% endfor %}
</div>
{% endif %}
{% endwith %}
<!-- Buttons -->
<div class="button-container">
<button id="addButton" class="action-button">Add</button>
<button id="displayButton" class="action-button">Display</button>
</div>
<!-- Add District Form -->
<div id="addForm" style="display: block;">
<h2>Add District</h2>
<form id="districtForm" method="POST" action="{{ url_for('district.add_district') }}">
<label for="state_Id">State :</label>
<select name="state_Id" id="state_Id" required>
<option value="" disabled selected>Select State</option>
{% for state in states %}
<option value="{{ state[0] }}">{{ state[1] }}</option>
{% endfor %}
</select>
<label>Enter District :</label>
<input type="text" id="district_Name" name="district_Name" placeholder="District Name" required>
<button type="submit" id="submitButton">Add District</button>
</form>
</div>
<!-- Display Table -->
<div id="addTable" style="display: none;">
<div class="search-container">
<h2>Districts</h2>
<input type="text" id="searchBar" placeholder="Search..." onkeyup="searchTable()">
</div>
<table id="sortableTable" border="1">
<thead>
<tr>
<th>District ID</th>
<th>District Name</th>
<th>State Name</th>
<th>Edit</th>
<th>Delete</th>
</tr>
</thead>
<tbody>
{% for district in districtdata %}
<tr>
<td>{{ district[0] }}</td>
<td>{{ district[1] }}</td>
<td>{{ district[2] }}</td>
<td>
<a href="{{ url_for('district.edit_district', district_id=district[0]) }}">
Edit
</a>
</td>
<td>
<a href="{{ url_for('district.delete_district', district_id=district[0]) }}"
onclick="return confirm('Are you sure?')">
Delete
</a>
</td>
</tr>
{% endfor %}
</tbody>
</table>
</div>
<script>
// Toggle Add / Display
document.getElementById('addButton').addEventListener('click', () => {
document.getElementById('addForm').style.display = 'block';
document.getElementById('addTable').style.display = 'none';
});
document.getElementById('displayButton').addEventListener('click', () => {
document.getElementById('addForm').style.display = 'none';
document.getElementById('addTable').style.display = 'block';
});
</script>
</body>
{% endblock %}

View File

@@ -0,0 +1,85 @@
{% extends 'base.html' %}
{% block content %}
<head>
<title>Add District</title>
<link rel="stylesheet" type="text/css" href="{{ url_for('static', filename='css/style.css') }}">
<script src="{{ url_for('static', filename='js/district.js') }}"></script>
<script src="{{ url_for('static', filename='js/search_on_table.js') }}"></script>
</head>
<body>
<!-- Button Container to Center Buttons -->
<div class="button-container">
<button id="addButton" class="action-button">Add</button>
<button id="displayButton" class="action-button">Display</button>
</div>
<div id="addForm" style="display: none;">
<h2>Add District</h2>
<form id="districtForm" method="POST">
<label for="state_Id">State :</label>
<select name="state_Id" id="state_Id" required>
<option value="" disabled selected>Select State</option>
{% for state in states %}
<option value="{{ state[0] }}">{{ state[1] }}</option>
{% endfor %}
</select>
<label>Enter District :</label>
<input type="text" id="district_Name" name="district_Name" placeholder="District Name" required>
<span id="districtMessage"></span>
<button type="submit" id="submitButton" disabled>Add District</button>
</form>
</div>
<div id="addTable" style="display: none;">
<div class="search-container">
<h2>Display Districts</h2>
<input type="text" id="searchBar" placeholder="Searching..." onkeyup="searchTable()">
</div>
<table id="sortableTable" border="1">
<tr>
<th>District ID</th>
<th class="sortable-header">
District Name
<span class="sort-buttons">
<span class="sort-asc">⬆️</span>
<span class="sort-desc">⬇️</span>
</span></th>
<th class="sortable-header">
State Name
<span class="sort-buttons">
<span class="sort-asc">⬆️</span>
<span class="sort-desc">⬇️</span>
</span></th>
<th>Edit District</th>
<th>Delete District</th>
</tr>
{% for district in districtdata %}
<tr>
<td>{{ district[0] }}</td>
<td>{{ district[1] }}</td>
<td>{{ district[2] }}</td>
<td>
<a href="{{ url_for('district.edit_district', district_id=district[0]) }}">
<img src="{{ url_for('static', filename='images/icons/pen_blue_icon.png') }}" alt="Edit"
class="icon">
</a>
</td>
<td>
<a href="{{ url_for('district.delete_district', district_id=district[0]) }}"
onclick="return confirm('Are you sure you want to delete this district?')">
<img src="{{ url_for('static', filename='images/icons/bin_red_icon.png') }}" alt="Delete"
class="icon">
</a>
</td>
</tr>
{% endfor %}
</table>
</div>
</body>
{% endblock %}

View File

@@ -0,0 +1,72 @@
{% extends 'base.html' %}
{% block content %}
<head>
<title>Manage Hold Types</title>
<script src="https://code.jquery.com/jquery-3.6.0.min.js"></script>
<!-- <script src="{{ url_for('static', filename='js/hold_types.js') }}"></script> -->
<link rel="stylesheet" type="text/css" href="{{ url_for('static', filename='css/style.css') }}">
<script src="{{ url_for('static', filename='js/search_on_table.js') }}"></script>
</head>
<body>
<div class="button-container">
<button id="addButton" class="action-button">Add</button>
<button id="displayButton" class="action-button">Display</button>
</div>
<div id="addForm">
<h2>Add Hold Types</h2>
<form id="holdTypeForm" method="POST" action="{{ url_for('hold_types.add_hold_type') }}">
<label>Enter Hold Amount Type:</label>
<input type="text" id="hold_type" name="hold_type" placeholder="Enter Type" required>
<span id="holdTypeMessage"></span>
<button type="submit" value="Add Hold Type" id="successPopup">Add Hold Type</button>
</form>
</div>
<div id="addTable" style="display: none;">
<div class="search-container">
<h2>Hold Type List</h2>
<input type="text" id="searchBar" placeholder="Searching..." onkeyup="searchTable()">
</div>
<table id="sortableTable" border="1">
<tr>
<th>ID</th>
<th class="sortable-header">
Hold Type
<span class="sort-buttons">
<span class="sort-asc">⬆️</span>
<span class="sort-desc">⬇️</span>
</span>
</th>
<th>Update</th>
<th>Delete</th>
</tr>
{% for htd in Hold_Types_data %}
<tr>
<td>{{ htd['hold_type_id'] }}</td>
<td>{{ htd['hold_type'] }}</td>
<td>
<a href="{{ url_for('hold_types.edit_hold_type', id=htd['hold_type_id']) }}">
<img src="{{ url_for('static', filename='images/icons/pen_blue_icon.png') }}" alt="Edit"
class="icon">
</a>
</td>
<td>
<a href="{{ url_for('hold_types.delete_hold_type', id=htd['hold_type_id']) }}"
onclick="return confirm('Are you sure you want to delete this Hold Type?')">
<img src="{{ url_for('static', filename='images/icons/bin_red_icon.png') }}" alt="Delete"
class="icon">
</a>
</td>
</tr>
{% endfor %}
</table>
<a href="/">Back to Dashboard</a>
</div>
</body>
{% endblock %}

370
templates/add_invoice.html Normal file
View File

@@ -0,0 +1,370 @@
{% extends 'base.html' %}
{% block content %}
<head xmlns="http://www.w3.org/1999/html">
<meta charset="UTF-8"/>
<meta name="viewport" content="width=device-width, initial-scale=1.0"/>
<title>Add Invoice</title>
<link rel="stylesheet" type="text/css" href="{{ url_for('static', filename='css/invoice.css') }}">
<script src="{{ url_for('static', filename='js/invoice.js') }}"></script>
<script src="{{ url_for('static', filename='js/holdAmount.js') }}"></script>
<script src="{{ url_for('static', filename='js/search_on_table.js') }}"></script>
</head>
<body>
{% if success == 'true' %}
<div class="alert alert-success alert-dismissible fade show mt-3" role="alert">
✅ Invoice added successfully!
<button type="button" class="btn-close" data-bs-dismiss="alert" aria-label="Close"></button>
</div>
{% endif %}
<!-- Flash Messages -->
{% with messages = get_flashed_messages(with_categories=true) %}
{% if messages %}
<div class="flash-messages">
{% for category, message in messages %}
<div class="alert {{ category }}">{{ message }}</div>
{% endfor %}
</div>
{% endif %}
{% endwith %}
<div class="button-container">
<button id="addButton" class="action-button">Add</button>
<button id="displayButton" class="action-button">Display</button>
</div>
<div id="addForm" style="display: none;">
<h2>Add Invoice</h2>
<form id="invoiceForm" action="{{ url_for('invoice.add_invoice') }}" method="POST">
<div class="row1">
<div>
<label for="subcontractor">Subcontractor Name:</label>
<input type="text" id="subcontractor" name="subcontractor" required autocomplete="off"/>
<input type="hidden" id="subcontractor_id" name="subcontractor_id"/>
<div id="subcontractor_list"></div>
</div>
</div>
<div class="row2">
<div>
<label for="village">Village Name:</label>
<select id="village" name="village" required>
<option value="">-- Select Village --</option>
{% for village in villages %}
<option value="{{ village.Village_Name }}">{{ village.Village_Name }}</option>
{% endfor %}
</select>
</div>
<div>
<label for="pmc_no">PMC No:</label>
<input type="text" id="pmc_no" name="pmc_no" required/>
<div id="pmc_no_list" class="autocomplete-list"></div>
</div>
</div>
<div class="row2">
<div>
<label for="work_type">Work Type:</label>
<input type="text" id="work_type" name="work_type" required/>
</div>
<div>
<label for="invoice_details">Invoice Details:</label>
<textarea id="invoice_details" name="invoice_details" required></textarea>
</div>
</div>
<div class="row2">
<div>
<label for="invoice_no">Invoice No:</label>
<input type="text" id="invoice_no" name="invoice_no" required/>
</div>
<div>
<label for="invoice_date">Invoice Date:</label>
<input type="date" id="invoice_date" name="invoice_date" required/>
</div>
</div>
<div class="row3">
<div>
<label for="basic_amount">Basic Amount:</label>
<input type="number" step="0.01" id="basic_amount" name="basic_amount" placeholder="₹ - 00.00" required/>
</div>
<div>
<label for="debit_amount">Debit Amount:</label>
<input type="number" step="0.01" id="debit_amount" name="debit_amount" placeholder="₹ - 00.00" required/>
</div>
<div>
<label for="after_debit_amount">After Debit Amount:</label>
<input type="number" step="0.01" id="after_debit_amount" name="after_debit_amount" placeholder="₹ - 00.00" readonly required/>
</div>
</div>
<div class="row3">
<div class="percentage-field">
<label for="gst_percentage">GST %:</label>
<input type="number" step="0.01" id="gst_percentage" name="gst_percentage" placeholder="%"/>
<label for="gst_amount">GST Amount:</label>
<input type="number" step="0.01" id="gst_amount" name="gst_amount" placeholder="₹ - 00.00" readonly required/>
</div>
<div>
<label for="amount">Amount:</label>
<input type="number" step="0.01" id="amount" name="amount" placeholder="₹ - 00.00" readonly required/>
</div>
<div class="percentage-field">
<label for="tds_percentage">TDS %:</label>
<input type="number" step="0.01" id="tds_percentage" name="tds_percentage" placeholder="%"/>
<label for="tds_amount">TDS Amount:</label>
<input type="number" step="0.01" id="tds_amount" name="tds_amount" placeholder="₹ - 00.00" readonly required/>
</div>
</div>
<div class="row3">
<div class="percentage-field">
<label for="sd_percentage">SD %:</label>
<input type="number" step="0.01" id="sd_percentage" name="sd_percentage" placeholder="%"/>
<label for="sd_amount">SD Amount:</label>
<input type="number" step="0.01" id="sd_amount" name="sd_amount" placeholder="₹ - 00.00" readonly required>
</div>
<div class="percentage-field">
<label for="commission_percentage">On Commission %:</label>
<input type="number" step="0.01" id="commission_percentage" name="commission_percentage" placeholder="%"/>
<label for="on_commission">On Commission:</label>
<input type="number" step="0.01" id="on_commission" name="on_commission" placeholder="₹ - 00.00" readonly required>
</div>
<div class="percentage-field">
<label for="hydro_percentage">Hydro Testing %:</label>
<input type="number" step="0.01" id="hydro_percentage" name="hydro_percentage" placeholder="%"/>
<label for="hydro_testing">Hydro Testing:</label>
<input type="number" step="0.01" id="hydro_testing" name="hydro_testing" placeholder="₹ - 00.00" readonly required>
</div>
</div>
<div class="hold-row">
<button type="button" id="add_hold_amount" class="button">+ Add Hold Amount</button>
</div>
<!-- Dynamically added hold amount fields -->
<div id="hold_amount_container"></div>
<div class="row2">
<div>
<label for="gst_sd_amount">GST SD Amount:</label>
<input type="number" step="0.01" id="gst_sd_amount" name="gst_sd_amount" placeholder="₹ - 00.00" required/>
</div>
<div>
<label for="final_amount">Final Amount:</label>
<input type="number" step="0.01" id="final_amount" name="final_amount" placeholder="₹ - 00.00" required/>
</div>
</div>
<button type="submit" class="button">Submit</button>
</form>
</div>
<div id="addTable" style="display: none;">
<!-- Invoice Table Section -->
<div class="search-container">
<h2>Invoice List</h2>
<input type="text" id="searchBar" placeholder="Searching..." onkeyup="searchTable()">
{% if invoices %}
<table class="invoice-table">
<thead>
<tr>
<th>Invoice Id</th>
<th>SubContractor Name</th>
<th>PMC No</th>
<th>Village</th>
<th>Work Type</th>
<th>Invoice Details</th>
<th>Invoice Date</th>
<th>Invoice No</th>
<th>Basic Amount</th>
<th>Debit Amount</th>
<th>After Debit Amount</th>
<th>Amount</th>
<th>GST Amount</th>
<th>TDS Amount</th>
<th>SD Amount</th>
<th>On Commission</th>
<th>Hydro Testing</th>
<th>GST SD Amount</th>
<th>Final Amount</th>
<th>Update</th>
<th>Delete</th>
</tr>
</thead>
<tbody>
{% for invoice in invoices %}
<tr>
<td>{{ invoice.Invoice_Id }}</td>
<td>{{ invoice.Contractor_Name }}</td>
<td>{{ invoice.PMC_No }}</td>
<td>{{ invoice.Village_Name or 'N/A' }}</td>
<td>{{ invoice.Work_Type }}</td>
<td>{{ invoice.Invoice_Details }}</td>
<td>{{ invoice.Invoice_Date }}</td>
<td>{{ invoice.Invoice_No }}</td>
<td>{{ invoice.Basic_Amount }}</td>
<td>{{ invoice.Debit_Amount }}</td>
<td>{{ invoice.After_Debit_Amount }}</td>
<td>{{ invoice.Amount }}</td>
<td>{{ invoice.GST_Amount }}</td>
<td>{{ invoice.TDS_Amount }}</td>
<td>{{ invoice.SD_Amount }}</td>
<td>{{ invoice.On_Commission }}</td>
<td>{{ invoice.Hydro_Testing }}</td>
<td>{{ invoice.GST_SD_Amount }}</td>
<td>{{ invoice.Final_Amount }}</td>
<td>
<!-- Edit -->
<a href="{{ url_for('invoice.edit_invoice', invoice_id=invoice.Invoice_Id) }}">
<img src="{{ url_for('static', filename='images/icons/pen_blue_icon.png') }}" alt="Edit" class="icon">
</a>
</td>
<td>
<!-- Delete -->
<a href="{{ url_for('invoice.delete_invoice_route', invoice_id=invoice.Invoice_Id) }}" onclick="return confirm('Are you sure?')">
<img src="{{ url_for('static', filename='images/icons/bin_red_icon.png') }}" alt="Delete" class="icon">
</a>
</td>
</tr>
{% endfor %}
</tbody>
</table>
{% else %}
<p>No invoices found.</p>
{% endif %}
</div>
<script>
document.addEventListener('DOMContentLoaded', function() {
// Get all the input fields
const basicAmount = document.getElementById('basic_amount');
const debitAmount = document.getElementById('debit_amount');
const afterDebitAmount = document.getElementById('after_debit_amount');
const amount = document.getElementById('amount');
// Percentage fields
const gstPercentage = document.getElementById('gst_percentage');
const gstAmount = document.getElementById('gst_amount');
const tdsPercentage = document.getElementById('tds_percentage');
const tdsAmount = document.getElementById('tds_amount');
const sdPercentage = document.getElementById('sd_percentage');
const sdAmountInput = document.getElementById('sd_amount');
const commissionPercentage = document.getElementById('commission_percentage');
const onCommission = document.getElementById('on_commission');
const hydroPercentage = document.getElementById('hydro_percentage');
const hydroTesting = document.getElementById('hydro_testing');
const gstSdAmount = document.getElementById('gst_sd_amount');
const finalAmount = document.getElementById('final_amount');
// Calculate after debit amount when basic or debit amount changes
function calculateAfterDebitAmount() {
const basic = parseFloat(basicAmount.value) || 0;
const debit = parseFloat(debitAmount.value) || 0;
const afterDebit = basic - debit;
afterDebitAmount.value = afterDebit.toFixed(2);
calculateGST();
}
// Calculate GST and Amount
function calculateGST() {
const baseAmount = parseFloat(afterDebitAmount.value) || 0;
if (gstPercentage.value) {
const gstPerc = parseFloat(gstPercentage.value) || 0;
const gstAmt = (baseAmount * gstPerc) / 100;
gstAmount.value = gstAmt.toFixed(2);
gstSdAmount.value = gstAmt.toFixed(2);
// Calculate Amount (After Debit + GST)
amount.value = (baseAmount + gstAmt).toFixed(2);
} else {
amount.value = baseAmount.toFixed(2);
}
calculateOtherDeductions();
}
// Calculate other deductions (TDS, SD, Commission, Hydro)
function calculateOtherDeductions() {
const baseAmount = parseFloat(afterDebitAmount.value) || 0;
// Calculate TDS
if (tdsPercentage.value) {
const tdsPerc = parseFloat(tdsPercentage.value) || 0;
const tdsAmt = (baseAmount * tdsPerc) / 100;
tdsAmount.value = tdsAmt.toFixed(2);
}
// Calculate SD
if (sdPercentage.value) {
const sdPerc = parseFloat(sdPercentage.value) || 0;
const sdAmt = (baseAmount * sdPerc) / 100;
sdAmountInput.value = sdAmt.toFixed(2);
}
// Calculate Commission
if (commissionPercentage.value) {
const commPerc = parseFloat(commissionPercentage.value) || 0;
const commAmt = (baseAmount * commPerc) / 100;
onCommission.value = commAmt.toFixed(2);
}
// Calculate Hydro Testing
if (hydroPercentage.value) {
const hydroPerc = parseFloat(hydroPercentage.value) || 0;
const hydroAmt = (baseAmount * hydroPerc) / 100;
hydroTesting.value = hydroAmt.toFixed(2);
}
calculateFinalAmount();
}
// Calculate final amount
function calculateFinalAmount() {
const amt = parseFloat(amount.value) || 0;
const tds = parseFloat(tdsAmount.value) || 0;
const sd = parseFloat(sdAmountInput.value) || 0;
const commission = parseFloat(onCommission.value) || 0;
const hydro = parseFloat(hydroTesting.value) || 0;
const gstSd = parseFloat(gstSdAmount.value) || 0;
// Get hold amounts
let totalHold = 0;
document.querySelectorAll('input[name="hold_amount[]"]').forEach(input => {
totalHold += parseFloat(input.value) || 0;
});
// Final Amount = Amount - TDS - SD - Commission - Hydro - GST SD - Hold Amounts
const final = amt - tds - sd - commission - hydro - gstSd - totalHold;
finalAmount.value = final.toFixed(2);
}
// Add event listeners
basicAmount.addEventListener('input', calculateAfterDebitAmount);
debitAmount.addEventListener('input', calculateAfterDebitAmount);
// Percentage fields
gstPercentage.addEventListener('input', calculateGST);
tdsPercentage.addEventListener('input', calculateOtherDeductions);
sdPercentage.addEventListener('input', calculateOtherDeductions);
commissionPercentage.addEventListener('input', calculateOtherDeductions);
hydroPercentage.addEventListener('input', calculateOtherDeductions);
// Listen for changes in hold amounts
document.addEventListener('holdAmountChanged', calculateFinalAmount);
});
// Optional JS for auto-hiding flash
setTimeout(() => {
document.querySelectorAll('.alert').forEach(el => el.style.display = 'none');
}, 5000);
</script>
</div>
</body>
{% endblock %}

209
templates/add_payment.html Normal file
View File

@@ -0,0 +1,209 @@
{% extends 'base.html' %}
{% block content %}
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Add Payment</title>
<link rel="stylesheet" href="{{ url_for('static', filename='css/style.css') }}">
<script src="{{ url_for('static', filename='js/search_on_table.js') }}"></script>
<script src="{{ url_for('static', filename='js/invoice.js') }}"></script>
</head>
<body>
<div class="button-container">
<button id="addButton" class="action-button">Add</button>
<button id="displayButton" class="action-button">Display</button>
</div>
<div id="addForm" style="display: none;">
<h2>Add Payment</h2>
<form action="/add_payment" method="POST" onsubmit="showSuccessAlert(event)">
<div class="row1">
<div>
<label for="subcontractor">Subcontractor Name:</label>
<input type="text" id="subcontractor" name="subcontractor" required autocomplete="off" />
<input type="hidden" id="subcontractor_id" name="subcontractor_id" />
<div id="subcontractor_list" class="autocomplete-items"></div>
</div>
</div>
<label for="PMC_No">PMC No:</label><br>
<select id="PMC_No" name="PMC_No" required>
<option value="">Select PMC No</option>
</select><br><br>
<label for="invoice_No">Invoice No:</label><br>
<input type="number" step="0.01" id="invoice_No" name="invoice_No"><br><br>
<label for="Payment_Amount">Amount:</label><br>
<input type="number" step="0.01" id="Payment_Amount" name="Payment_Amount" required
oninput="calculateTDSAndTotal()"><br><br>
<label for="TDS_Percentage">TDS Percentage (%):</label><br>
<input type="number" step="0.01" id="TDS_Percentage" name="TDS_Percentage"
oninput="calculateTDSAndTotal()"><br><br>
<label for="TDS_Payment_Amount">TDS Amount:</label><br>
<input type="number" step="0.01" id="TDS_Payment_Amount" name="TDS_Payment_Amount" required
readonly><br><br>
<label for="total_amount">Total Amount:</label><br>
<input type="number" step="0.01" id="total_amount" name="total_amount" required readonly><br><br>
<label for="utr">UTR:</label><br>
<input type="text" id="utr" name="utr"><br><br>
<button type="submit">Submit Payment</button>
</form>
</div>
<div id="successPopup" class="success-popup">
<i>&#10004;</i> Payment added successfully!
</div>
<div id="addTable" style="display: none;">
<div class="search-container">
<h2>Payment History</h2>
<input type="text" id="searchBar" placeholder="Searching..." onkeyup="searchTable()">
</div>
<table id="sortableTable" border="1">
<thead>
<tr>
<th class="sortable-header">Payment ID</th>
<th class="sortable-header">PMC No</th>
<th>Invoice No</th>
<th>Payment Amount</th>
<th>TDS Amount</th>
<th>Total Amount</th>
<th>UTR</th>
<th>Update</th>
<th>Delete</th>
</tr>
</thead>
<tbody>
{% for payment in payments %}
<tr>
<td>{{ payment[0] }}</td>
<td>{{ payment[1] }}</td>
<td>{{ payment[2] }}</td>
<td>{{ payment[3] }}</td>
<td>{{ payment[4] }}</td>
<td>{{ payment[5] }}</td>
<td>{{ payment[6] }}</td>
<td><a href="/edit_payment/{{ payment[0] }}"><img
src="{{ url_for('static', filename='images/icons/pen_blue_icon.png') }}" alt="Edit"
class="icon"></a></td>
<td>
<form action="{{ url_for('payment_bp.delete_payment', payment_id=payment[0]) }}" method="POST"
style="display:inline;"
onsubmit="return confirm('Are you sure you want to delete this payment?')">
<button type="submit" style="border:none; background:none; padding:0; cursor:pointer;">
<img src="{{ url_for('static', filename='images/icons/bin_red_icon.png') }}"
alt="Delete" class="icon">
</button>
</form>
</td>
</tr>
<!-- <tr>
<td>{{ payment['Payment_Id'] }}</td>
<td>{{ payment['PMC_No'] }}</td>
<td>{{ payment['invoice_no'] }}</td>
<td>{{ payment['Payment_Amount'] }}</td>
<td>{{ payment['TDS_Payment_Amount'] }}</td>
<td>{{ payment['Total_Amount'] }}</td>
<td>{{ payment['UTR'] }}</td>
<td><a href="/edit_payment/{{ payment['Payment_Id'] }}"><img src="{{ url_for('static', filename='images/icons/pen_blue_icon.png') }}" alt="Edit" class="icon"></a></td>
<td><a href="/delete_payment/{{ payment['Payment_Id'] }}" onclick="return confirm('Are you sure you want to delete this payment?')"><img src="{{ url_for('static', filename='images/icons/bin_red_icon.png') }}" alt="Delete" class="icon"></a></td>
</tr> -->
{% endfor %}
</tbody>
</table>
</div>
<script>
document.getElementById("subcontractor").addEventListener("input", function () {
const query = this.value;
const list = document.getElementById("subcontractor_list");
if (query.length < 2) {
list.innerHTML = '';
return;
}
fetch(`/search_subcontractor?query=${encodeURIComponent(query)}`)
.then(response => response.json())
.then(data => {
list.innerHTML = '';
data.results.forEach(item => {
const div = document.createElement("div");
div.setAttribute("data-id", item.id);
div.textContent = item.name;
list.appendChild(div);
});
});
});
document.getElementById("subcontractor_list").addEventListener("click", function (e) {
const selectedId = e.target.getAttribute("data-id");
const selectedName = e.target.textContent;
if (selectedId) {
document.getElementById("subcontractor_id").value = selectedId;
document.getElementById("subcontractor").value = selectedName;
document.getElementById("subcontractor_list").innerHTML = ""; // hide the list
console.log("Contractor id is", selectedId);
// Fetch PMC numbers
fetch(`/get_pmc_nos_by_subcontractor/${encodeURIComponent(selectedId)}`)
.then(response => response.json())
.then(data => {
console.log("Fetched PMC Nos:", data.pmc_nos);
const pmcDropdown = document.getElementById("PMC_No");
pmcDropdown.innerHTML = "";
const defaultOption = document.createElement("option");
defaultOption.value = "";
defaultOption.textContent = "Select PMC No";
pmcDropdown.appendChild(defaultOption);
data.pmc_nos.forEach(pmc => {
const option = document.createElement("option");
option.value = pmc;
option.textContent = pmc;
pmcDropdown.appendChild(option);
});
if (data.pmc_nos.length === 0) {
alert("No PMC Nos found for this subcontractor.");
}
})
.catch(error => {
console.error("Error fetching PMC Nos:", error);
alert("Failed to fetch PMC numbers.");
});
}
});
</script>
<script>
function calculateTDSAndTotal() {
const amount = parseFloat(document.getElementById("Payment_Amount").value) || 0;
const tdsPercent = parseFloat(document.getElementById("TDS_Percentage").value) || 0;
const tdsAmount = (amount * tdsPercent / 100).toFixed(2);
const totalAmount = (amount - tdsAmount).toFixed(2);
document.getElementById("TDS_Payment_Amount").value = tdsAmount;
document.getElementById("total_amount").value = totalAmount;
}
</script>
<script src="{{ url_for('static', filename='js/showSuccessAlert.js') }}"></script>
</body>
{% endblock %}

View File

@@ -0,0 +1,147 @@
{% extends 'base.html' %}
{% block content %}
<head>
<title>Manage Purchases</title>
<script src="https://code.jquery.com/jquery-3.6.0.min.js"></script>
<link rel="stylesheet" href="{{ url_for('static', filename='css/style.css') }}">
</head>
<body>
<div class="button-container">
<button id="addButton" class="action-button">Add Purchase</button>
<button id="displayButton" class="action-button">Display Purchases</button>
</div>
<!-- ADD PURCHASE FORM -->
<!-- ADD PURCHASE FORM -->
<div id="addForm" style="display: none;">
<h2>Add Purchase Entry</h2>
<form action="/add_purchase_order" method="POST">
<label for="purchase_date">Purchase Date:</label><br>
<input type="date" id="purchase_date" name="purchase_date" required><br><br>
<label for="supplier_name">Supplier Name:</label><br>
<input type="text" id="supplier_name" name="supplier_name" required><br><br>
<label for="Purchase Order No">Purchase Order No:</label><br>
<input type="text" name="purchase_order_no" required><br><br>
<label for="unit">Item Name:</label><br>
<input type="text" id="item_name" name="item_name" required><br><br>
<label for="quantity">Quantity:</label><br>
<input type="number" id="quantity" name="quantity" step="1" required><br><br>
<label for="unit">Unit:</label><br>
<input type="text" id="unit" name="unit" required><br><br>
<label for="rate">Rate:</label><br>
<input type="number" step="0.01" id="rate" name="rate" required><br><br>
<label for="amount">Amount:</label><br>
<input type="number" step="0.01" id="amount" name="amount" readonly><br><br>
<!-- GST input -->
<label for="gst_percent">GST %:</label><br>
<input type="number" step="0.01" id="gst_percent"><br><br>
<label>GST Amount:</label><br>
<input type="number" step="0.01" id="GST_Amount" name="GST_Amount" readonly><br><br>
<!-- TDS input -->
<label for="tds_percent">TDS %:</label><br>
<input type="number" step="0.01" id="tds_percent"><br><br>
<label>TDS:</label><br>
<input type="number" step="0.01" id="TDS" name="TDS" readonly><br><br>
<label>Final Amount:</label><br>
<input type="number" step="0.01" id="final_amount" name="final_amount" readonly><br><br>
<input type="submit" value="Submit">
</form>
</div>
<!-- DISPLAY TABLE -->
<div id="displayTable" style="display: none;">
<h2>Purchase List</h2>
<table border="1">
<tr>
<th>ID</th>
<th>Purchase Date</th>
<th>Supplier Name</th>
<th>Purchase Order No</th>
<th>Item Name</th>
<th>Quantity</th>
<th>Unit</th>
<th>Rate</th>
<th>Amount</th>
<th>GST Amount</th>
<th>TDS</th>
<th>Final Amount</th>
<th>Edit</th>
<th>Delete</th>
</tr>
{% for purchase in purchases %}
<tr>
<td>{{ purchase['purchase_id'] }}</td>
<td>{{ purchase['purchase_date'] }}</td>
<td>{{ purchase['supplier_name'] }}</td>
<td>{{ purchase['purchase_order_no'] }}</td>
<td>{{ purchase['item_name'] }}</td>
<td>{{ purchase['quantity'] }}</td>
<td>{{ purchase['unit'] }}</td>
<td>{{ purchase['rate'] }}</td>
<td>{{ purchase['amount'] }}</td>
<td>{{ purchase['GST_Amount'] }}</td>
<td>{{ purchase['TDS'] }}</td>
<td>{{ purchase['final_amount'] }}</td>
<td><a href="{{ url_for('update_purchase', id=purchase['purchase_id']) }}">Edit</a></td>
<td>
<form method="POST" action="{{ url_for('delete_purchase', id=purchase['purchase_id']) }}" style="display:inline;">
<button type="submit" onclick="return confirm('Are you sure?')">Delete</button>
</form>
</td>
</tr>
{% endfor %}
</table>
</div>
<script>
$(document).ready(function() {
$('#addButton').click(function() {
$('#addForm').toggle();
});
$('#displayButton').click(function() {
$('#displayTable').toggle();
});
});
</script>
<script>
function calculateAmounts() {
const qty = parseFloat(document.getElementById('quantity').value) || 0;
const rate = parseFloat(document.getElementById('rate').value) || 0;
const gstPercent = parseFloat(document.getElementById('gst_percent').value) || 0;
const tdsPercent = parseFloat(document.getElementById('tds_percent').value) || 0;
const amount = qty * rate;
document.getElementById('amount').value = amount.toFixed(2);
const gstAmount = (gstPercent / 100) * amount;
document.getElementById('GST_Amount').value = gstAmount.toFixed(2);
const tdsAmount = (tdsPercent / 100) * amount;
document.getElementById('TDS').value = tdsAmount.toFixed(2);
const finalAmount = amount + gstAmount - tdsAmount;
document.getElementById('final_amount').value = finalAmount.toFixed(2);
}
// Attach events
document.getElementById('quantity').addEventListener('input', calculateAmounts);
document.getElementById('rate').addEventListener('input', calculateAmounts);
document.getElementById('gst_percent').addEventListener('input', calculateAmounts);
document.getElementById('tds_percent').addEventListener('input', calculateAmounts);
</script>
</body>
{% endblock %}

74
templates/add_state.html Normal file
View File

@@ -0,0 +1,74 @@
{% extends 'base.html' %}
{% block content %}
<head>
<title>Add State</title>
<link rel="stylesheet" type="text/css" href="{{ url_for('static', filename='css/style.css') }}">
<script src="{{ url_for('static', filename='js/state.js') }}"></script>
<script src="{{ url_for('static', filename='js/search_on_table.js') }}"></script>
</head>
<body>
<!-- Button Container to Center Buttons -->
<div class="button-container">
<button id="addButton" class="action-button">Add</button>
<button id="displayButton" class="action-button">Display</button>
</div>
<div id="addForm" style="display: none;">
<h2>Add State</h2>
<form id="stateForm" method="POST">
<label>Enter State :</label>
<input type="text" id="state_Name" name="state_Name" placeholder="State Name" required>
<span id="stateMessage"></span>
<button type="submit" id="submitButton" disabled>Add State</button>
</form>
</div>
<div id="addTable" style="display: none;">
<div class="search-container">
<h2>Display States</h2>
<input type="text" id="searchBar" placeholder="Searching..." onkeyup="searchTable()">
</div>
<script>
console.log(statedata)
</script>
<table id="sortableTable" border="1">
<tr>
<th>State ID</th>
<th class="sortable-header">
State Name
<span class="sort-buttons">
<span class="sort-asc">⬆️</span>
<span class="sort-desc">⬇️</span>
</span>
</th>
<th>Update State</th>
<th>Delete State</th>
</tr>
{% for state in statedata %}
<tr>
<td>{{ state[0] }}</td>
<td>{{ state[1]}}</td>
<td>
<a href="{{ url_for('state.editState', id=state[0]) }}">
<img src="{{ url_for('static', filename='images/icons/pen_blue_icon.png') }}" alt="Edit"
class="icon">
</a>
</td>
<td>
<a href="{{ url_for('state.deleteState', id=state[0]) }}"
onclick="return confirm('Are you sure you want to delete this state?')">
<img src="{{ url_for('static', filename='images/icons/bin_red_icon.png') }}" alt="Delete"
class="icon">
</a>
</td>
</tr>
{% endfor %}
</table>
</div>
</body>
{% endblock %}

View File

@@ -0,0 +1,127 @@
{% extends 'base.html' %}
{% block content %}
<head>
<meta charset="UTF-8">
<title>SubContractor</title>
<link rel="stylesheet" type="text/css" href="{{ url_for('static', filename='css/style.css') }}">
<script src="{{ url_for('static', filename='js/subcontractor.js') }}"></script>
<script src="{{ url_for('static', filename='js/search_on_table.js') }}"></script>
</head>
<body>
<!-- Button Container to Center Buttons -->
<div class="button-container">
<button id="addButton" class="action-button">Add</button>
<button id="displayButton" class="action-button">Display</button>
</div>
<div id="addForm" style="display: none;">
<h2>Add Sub-Contractor</h2>
<form name="subcontractorForm" method="POST">
<label>Enter Contractor Name :</label>
<input type="text" id="Contractor_Name" name="Contractor_Name" required onkeyup="validateInput()"><br>
<label>Address :</label>
<!-- <input type="text" name="Address" required>-->
<textarea id="Address" name="Address" required></textarea>
<br>
<label>Mobile No :</label>
<input type="text" id="Mobile_No" name="Mobile_No" required onkeyup="validateInput()" maxlength="10" placeholder="Ex - 9091012011">
<span class="error" id="mobileError" style="color: red;"></span><br>
<label>PAN No :</label>
<input type="text" id="PAN_No" name="PAN_No" required onkeyup="validateInput()" maxlength="10" placeholder="Ex - ABCDE1234F">
<span class="error" id="panError" style="color: red;"></span><br>
<label>Email :</label>
<input type="email" id="Email" name="Email" required onkeyup="validateInput()" placeholder="Ex - user@example.com">
<span class="error" id="emailError" style="color: red;"></span><br>
<label>Gender :</label>
<select name="Gender" required>
<option value="Male">Male</option>
<option value="Female">Female</option>
<option value="Other">Other</option>
</select><br>
<label>GST Registration Type :</label>
<input type="text" name="GST_Registration_Type" ><br>
<label>GST No :</label>
<input type="text" id="GST_No" name="GST_No" placeholder="Ex - 27AAACL5602N1ZE" ><br>
<label>Generated Password :</label>
<input type="text" id="Contractor_password" name="Contractor_password" >
<span class="error" id="passwordError" style="color: red;"></span><br>
<button type="submit" id="submitBtn" disabled>Submit</button>
</form>
</div>
<div id="addTable" style="display: none;">
<div class="search-container">
<h2>Display SubContractor</h2>
<input type="text" id="searchBar" placeholder="Searching..." onkeyup="searchTable()">
</div>
<table id="sortableTable" border="1">
<tr>
<th>Contractor ID</th>
<th class="sortable-header">Contractor Name
<span class="sort-buttons">
<span class="sort-asc">⬆️</span>
<span class="sort-desc">⬇️</span>
</span>
</th>
<th>Address</th>
<th>Mobile No</th>
<th>PAN No</th>
<th>Email</th>
<th>Gender</th>
<th>GST Registration Type</th>
<th class="sortable-header">GST No
<span class="sort-buttons">
<span class="sort-asc">⬆️</span>
<span class="sort-desc">⬇️</span>
</span>
</th>
<th>Update Contractor</th>
<th>Delete Contractor</th>
</tr>
{% for subc in subcontractor %}
<tr>
<td>{{ subc[0] }}</td>
<td>{{ subc[1] }}</td>
<td>{{ subc[2] }}</td>
<td>{{ subc[3] }}</td>
<td>{{ subc[4] }}</td>
<td>{{ subc[5] }}</td>
<td>{{ subc[6] }}</td>
<td>{{ subc[7] }}</td>
<td>{{ subc[8] }}</td>
<td>
<a href="{{ url_for('subcontractor.edit_subcontractor', id=subc[0]) }}">
<img src="{{ url_for('static', filename='images/icons/pen_blue_icon.png') }}" alt="Edit"
class="icon">
</a>
</td>
<td>
{# <a href="{{ url_for('subcontractor.deleteSubContractor', id=subc[0]) }}"#}
{# onclick="return confirm('Are you sure you want to delete?')">#}
{# <img src="{{ url_for('static', filename='images/icons/bin_red_icon.png') }}" alt="Delete"#}
{# class="icon">#}
{# </a>#}
</td>
</tr>
{% endfor %}
</table>
</div>
</body>
{% endblock %}

View File

@@ -0,0 +1,99 @@
{% extends 'base.html' %}
{% block content %}
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Village Management</title>
<link rel="stylesheet" type="text/css" href="{{ url_for('static', filename='css/style.css') }}">
<script src="{{ url_for('static', filename='js/village.js') }}"></script>
<script src="{{ url_for('static', filename='js/search_on_table.js') }}"></script>
</head>
<body>
<!-- Button Container to Center Buttons -->
<div class="button-container">
<button id="addButton" class="action-button">Add</button>
<button id="displayButton" class="action-button">Display</button>
</div>
<div id="addForm" style="display: none;">
<div class="container">
<div class="form-block">
<h2>Add a New Village</h2>
<form id="villageForm" method="POST">
<label for="state_Id">State:</label>
<select id="state_Id" name="state_Id" required>
<option value="" disabled selected>Select State</option>
{% for state in states %}
<option value="{{ state[0] }}">{{ state[1] }}</option>
{% endfor %}
</select>
<label for="district_Id">District:</label>
<select id="district_Id" name="district_Id" required disabled>
<option value="" disabled selected>Select District</option>
</select>
<label for="block_Id">Block:</label>
<select id="block_Id" name="block_Id" required disabled>
<option value="" disabled selected>Select Block</option>
</select>
<label for="Village_Name">Village Name:</label>
<input type="text" id="Village_Name" name="Village_Name" placeholder="Enter Village Name" required>
<span id="villageMessage"></span>
<button type="submit" id="submitVillage" disabled>Add Village</button>
</form>
</div>
</div>
</div>
<div id="addTable" style="display: none;">
<div class="search-container">
<h2>Display Villages</h2>
<input type="text" id="searchBar" placeholder="Searching..." onkeyup="searchTable()">
</div>
<table id="sortableTable" border="1">
<tr>
<th>Village Sr No</th>
<th class="sortable-header">
Village Name
<span class="sort-buttons">
<span class="sort-asc">⬆️</span>
<span class="sort-desc">⬇️</span>
</span></th>
<th class="sortable-header">Block Name
<span class="sort-buttons">
<span class="sort-asc">⬆️</span>
<span class="sort-desc">⬇️</span>
</span></th>
<th>Update</th>
<th>Delete</th>
</tr>
{% for village in villages %}
<tr>
<td>{{ village[0] }}</td>
<td>{{ village[1] }}</td>
<td>{{ village[2] }}</td>
<td>
<a href="{{ url_for('village.edit_village', village_id=village[0]) }}">
<img src="{{ url_for('static', filename='images/icons/pen_blue_icon.png') }}" alt="Edit"
class="icon">
</a>
</td>
<td>
<a href="{{ url_for('village.delete_village', village_id=village[0]) }}"
onclick="return confirm('Are you sure you want to delete this village?');">
<img src="{{ url_for('static', filename='images/icons/bin_red_icon.png') }}" alt="Delete"
class="icon">
</a>
</td>
</tr>
{% endfor %}
</table>
</div>
</body>
{% endblock %}

View File

@@ -0,0 +1,137 @@
{% extends 'base.html' %}
{% block content %}
<head>
<title>Manage Work Order</title>
<script src="https://code.jquery.com/jquery-3.6.0.min.js"></script>
<link rel="stylesheet" type="text/css" href="{{ url_for('static', filename='css/style.css') }}">
</head>
</head>
<body>
<div class="button-container">
<button id="addButton" class="action-button">Add</button>
<button id="displayButton" class="action-button">Display</button>
</div>
<form action="/submit_work_order" method="POST">
<div id="addForm" style="display: none;">
<h2>Create Work Order</h2>
<!-- Subcontractor Dropdown -->
<label for="subcontractor_id">Select Vender Name:</label><br>
<select name="vendor_name" id="vendor_name" required>
<option value="">-- Select Subcontractor --</option>
{% for sc in subcontractor %}
<option value="{{ sc.Contractor_Name }}">{{ sc.Contractor_Name }}</option>
{% endfor %}
</select><br><br>
<label for="work_order_number">Work Order Number:</label><br>
<input type="text" id="work_order_number" name="work_order_number" required><br><br>
<label for="work_order_amount">Work Order Amount</label><br>
<input type="number" step="0.01" id="work_order_amount" name="work_order_amount" required><br><br>
<label for="gst_amount">GST Amount:</label><br>
<input type="number" step="0.01" id="gst_amount" name="gst_amount" required><br><br>
<label for="tds_amount">TDS Amount:</label><br>
<input type="number" step="0.01" id="tds_amount" name="tds_amount" required><br><br>
<label for="security_deposit">Security Deposit:</label><br>
<input type="number" step="0.01" id="security_deposit" name="security_deposit" required><br><br>
<label for="sd_against_gst">SD Against GST:</label><br>
<input type="number" step="0.01" id="sd_against_gst" name="sd_against_gst" required><br><br>
<label for="final_total">Final Total:</label><br>
<input type="number" step="0.01" id="final_total" name="final_total" readonly><br><br>
<input type="submit" value="Submit">
</div>
</form>
{# display data #}
<div id="addTable" style="display: none;">
{# <div class="search-container">#}
{# <h2>Hold Type List</h2>#}
{# <input type="text" id="searchBar" placeholder="Searching..." onkeyup="searchTable()">#}
{# </div>#}
<table id="sortableTable" border="1">
<tr>
<th>ID</th>
<th class="sortable-header">
Vender Name
{# <span class="sort-buttons">#}
{# <span class="sort-asc">⬆️</span>#}
{# <span class="sort-desc">⬇️</span>#}
{# </span>#}
</th>
<th>Work Order Type</th>
<th>Work Order Amount</th>
<th>BOQ Amount</th>
<th>Work Done Percentage</th>
<th>Update</th>
<th>Delete</th>
</tr>
{% for htd in wo %}
<tr>
<td>{{ htd.work_order_id }}</td>
<td>{{ htd['vendor_name'] }}</td>
<td>{{ htd['work_order_type'] }}</td>
<td>{{ htd['work_order_amount'] }}</td>
<td>{{ htd['boq_amount'] }}</td>
<td>{{ htd['work_done_percentage'] }}</td>
<td>{{ htd['work_order_number'] }}</td>
<td>{{ htd['gst_amount'] }}</td>
<td>{{ htd['tds_amount'] }}</td>
<td>{{ htd[''] }}</td>
<td><a href="{{ url_for('update_work_order', id=htd['work_order_id']) }}">Edit</a></td>
<td>
<form action="{{ url_for('delete_work_order', id=htd['work_order_id']) }}" method="POST" style="display:inline;">
<button type="submit" onclick="return confirm('Are you sure you want to delete this work order?');" class="delete-button">Delete</button>
</form>
</td>
</tr>
{% endfor %}
</table>
<a href="/">Back to Dashboard</a>
</div>
<script>
$(document).ready(function () {
$('#displayButton').click(function () {
$('#addTable').toggle();
});
$('#addButton').click(function () {
$('#addForm').toggle();
// Bind input event handlers ONLY after form is shown
setTimeout(function () {
["work_order_amount", "gst_amount", "tds_amount", "security_deposit", "sd_against_gst"].forEach(function (id) {
document.getElementById(id).addEventListener("input", calculateFinalTotal);
});
}, 100); // Delay ensures elements are available in DOM
});
function calculateFinalTotal() {
const workOrderAmount = parseFloat(document.getElementById("work_order_amount").value) || 0;
const gstAmount = parseFloat(document.getElementById("gst_amount").value) || 0;
const tdsAmount = parseFloat(document.getElementById("tds_amount").value) || 0;
const securityDeposit = parseFloat(document.getElementById("security_deposit").value) || 0;
const sdAgainstGst = parseFloat(document.getElementById("sd_against_gst").value) || 0;
const finalTotal = workOrderAmount + gstAmount - (tdsAmount + securityDeposit + sdAgainstGst);
document.getElementById("final_total").value = finalTotal.toFixed(2);
}
});
</script>
</body>
{% endblock %}

View File

@@ -0,0 +1,49 @@
{% extends 'base.html' %}
{% block content %}
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Admin Profile</title>
</head>
<body>
<div class="container">
<h2>Admin Profile</h2>
{% with messages = get_flashed_messages(with_categories=true) %}
{% if messages %}
{% for category, message in messages %}
<p class="{{ category }}">{{ message }}</p>
{% endfor %}
{% endif %}
{% endwith %}
<form action="{{ url_for('admin_profile') }}" method="POST" enctype="multipart/form-data">
<div class="profile-picture">
<!-- <img id="profile-pic-preview" src="{{ url_for('static', filename='images/' + (profile['profile_picture'] or 'default.png')) }}" alt="Profile Picture" />-->
<input type="file" name="profile_picture" id="profile-pic-input" onchange="previewImage()">
</div>
<label>Username:</label>
<input type="text" name="username" value="{{ profile['user_name'] }}" required>
<label>Phone:</label>
<input type="tel" name="phone" value="{{ profile['mobile'] }}" required>
<label>Email:</label>
<input type="email" name="email" value="{{ profile['email'] }}" required>
<label>New Password:</label>
<input type="password" name="new_password">
<label>Current Password (required for password change):</label>
<input type="password" name="current_password">
<button type="submit">Save Changes</button>
</form>
</div>
<script>
function previewImage() {
const file = document.getElementById('profile-pic-input').files[0];
const reader = new FileReader();
reader.onload = function(e) {
document.getElementById('profile-pic-preview').src = e.target.result;
};
if (file) {
reader.readAsDataURL(file);
}
}
</script>
</body>
{% endblock %}

97
templates/base.html Normal file
View File

@@ -0,0 +1,97 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>{% block title %}Payment Reconciliation{% endblock %}</title>
<link rel="stylesheet" type="text/css" href="{{ url_for('static', filename='css/base.css') }}">
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.2/css/all.min.css">
<script src="https://code.jquery.com/jquery-3.6.0.min.js"></script>
</head>
<body>
<!-- Sidebar Toggle Button -->
<button class="menu-icon" onclick="toggleSidebar()">
<i class="fa-solid fa-bars"></i>
</button>
<!-- Sidebar -->
<div class="sidebar" id="sidebar">
<ul class="nav-menu">
<li class="nav-item">
<a href="/" class="nav-link active">
<i class="fas fa-home"></i> Dashboard
</a>
</li>
<li class="nav-item">
<a href="/upload_excel_file" class="nav-link">
<i class="fas fa-book"></i> Import Excel
</a>
</li>
<li class="nav-item">
<a href="/report" class="nav-link">
<i class="fas fa-cog"></i> Report
</a>
</li>
<li class="card">
<a href="/add_state" class="nav-link">
<i class="fas fa-arrow-right"></i> State
</a>
</li>
<li class="card">
<a href="/add_district" class="nav-link">
<i class="fas fa-arrow-right"></i> District
</a>
</li>
<li class="card">
<a href="/add_block" class="nav-link">
<i class="fas fa-arrow-right"></i> Blocks
</a>
</li>
<li class="card">
<a href="/add_village" class="nav-link">
<i class="fas fa-arrow-right"></i> village
</a>
</li>
<li class="card">
<a href="/subcontractor" class="nav-link">
<i class="fas fa-arrow-right"></i> Sub-Contractor
</a>
</li>
<li class="card">
<a href="/add_invoice" class="nav-link">
<i class="fas fa-arrow-right"></i> Invoice
</a>
</li>
<li class="card">
<a href="/add_payment" class="nav-link">
<i class="fas fa-arrow-right"></i> Payment
</a>
</li>
<li class="card">
<a href="/add_gst_release" class="nav-link">
<i class="fas fa-arrow-right"></i> GST Release
</a>
</li>
<li class="card">
<a href="/add_hold_type" class="nav-link">
<i class="fas fa-arrow-right"></i> Hold Types
</a>
</li>
</ul>
</div>
<!-- Main Content -->
<div class="content" id="content">
{% block content %}{% endblock %}
</div>
<!-- Sidebar Toggle Script -->
<script>
function toggleSidebar() {
document.getElementById("sidebar").classList.toggle("open");
document.getElementById("content").classList.toggle("shift");
}
</script>
</body>
</html>

60
templates/edit_block.html Normal file
View File

@@ -0,0 +1,60 @@
{% extends 'base.html' %}
{% block content %}
<head>
<title>Edit Block</title>
<link rel="stylesheet" type="text/css" href="{{ url_for('static', filename='css/style.css') }}">
<script>
window.onload = function () {
const flash = document.getElementById('flash-message');
if (flash) {
alert(flash.innerText);
}
}
</script>
</head>
<body>
<h2>Edit Block</h2>
<!-- Flash Message -->
{% with messages = get_flashed_messages(with_categories=true) %}
{% if messages %}
{% for category, message in messages %}
<div id="flash-message" style="display:none;" data-category="{{ category }}">{{ message }}</div>
{% endfor %}
{% endif %}
{% endwith %}
<!-- Edit Block Form -->
<form method="POST">
<label for="state_Id">State:</label>
<select name="state_Id" id="state_Id" required>
<option value="" disabled>Select State</option>
{% for state in states %}
<option value="{{ state[0] }}" {% if state[0] == block_data[1] %} selected {% endif %}>
{{ state[1] }}
</option>
{% endfor %}
</select><br><br>
<label for="district_Id">District:</label>
<select name="district_Id" id="district_Id" required>
<option value="" disabled>Select District</option>
{% for district in districts %}
<option value="{{ district[0] }}" {% if district[0] == block_data[1] %} selected {% endif %}>
{{ district[1] }}
</option>
{% endfor %}
</select><br><br>
<label for="block_Name">Block Name:</label>
<input type="text" name="block_Name" value="{{ block_data[0] }}" placeholder="Enter Block Name" required><br><br>
<button type="submit">Update Block</button>
</form>
</body>
{% endblock %}

View File

@@ -0,0 +1,28 @@
{% extends 'base.html' %}
{% block content %}
<head>
<title>Edit District</title>
<link rel="stylesheet" type="text/css" href="{{ url_for('static', filename='css/style.css') }}">
</head>
<body>
<h2>Edit District</h2>
<form method="POST">
<label for="state_Id">State :</label>
<select name="state_Id" required>
<option value="" disabled>Select State</option>
{% for state in states %}
<option value="{{ state[0] }}" {% if state[0] == districtdata[1] %}selected{% endif %}>
{{ state[1] }}
</option>
{% endfor %}
</select>
<label>Enter District :</label>
<input type="text" name="district_Name" placeholder="District Name" value="{{ districtdata[0] }}" required>
<button type="submit">Update District</button>
</form>
</body>
{% endblock %}

39
templates/edit_grn.html Normal file
View File

@@ -0,0 +1,39 @@
<!DOCTYPE html>
<html>
<head><title>Edit GRN</title>
<link rel="stylesheet" href="{{ url_for('static', filename='css/style.css') }}">
</head>
<body>
<h2>Edit GRN</h2>
<form method="POST">
<label>GRN Date:</label>
<input type="date" name="grn_date" value="{{ grn['grn_date'] }}" required><br><br>
<label>Purchase ID:</label>
<input type="number" name="purchase_id" value="{{ grn['purchase_id'] }}" required><br><br>
<label>Supplier Name:</label>
<input type="text" name="supplier_name" value="{{ grn['supplier_name'] }}" required><br><br>
<label>Item Description:</label>
<input type="text" name="item_description" value="{{ grn['item_description'] }}" required><br><br>
<label>Received Quantity:</label>
<input type="number" name="received_quantity" value="{{ grn['received_quantity'] }}" required><br><br>
<label>Unit:</label>
<input type="text" name="unit" value="{{ grn['unit'] }}" required><br><br>
<label>Rate:</label>
<input type="number" step="0.01" name="rate" value="{{ grn['rate'] }}" required><br><br>
<label>Amount:</label>
<input type="number" step="0.01" name="amount" value="{{ grn['amount'] }}" required><br><br>
<label>Remarks:</label>
<input type="text" name="remarks" value="{{ grn['remarks'] }}"><br><br>
<input type="submit" value="Update GRN">
</form>
</body>
</html>

View File

@@ -0,0 +1,45 @@
{% extends 'base.html' %}
{% block content %}
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Edit GST Release</title>
<link rel="stylesheet" type="text/css" href="{{ url_for('static', filename='css/style.css') }}">
</head>
<body>
<h2>Edit GST Release</h2>
<form action="/edit_gst_release/{{ gst_release_data[0] }}" method="POST">
<!-- <label for="invoice_id">Invoice Id:</label><br>-->
<!-- <input type="number" id="invoice_id" name="invoice_id" value="{{ gst_release_data[0] }}" required><br><br>-->
<label for="PMC_No">PMC No :</label><br>
<input type="number" id="PMC_No" name="PMC_No" value="{{ gst_release_data[1] }}" required><br><br>
<label for="invoice_No">Invoice No:</label><br>
<input type="number" step="0.01" id="invoice_No" name="invoice_No" value="{{ gst_release_data[2] }}"
required><br><br>
<label for="basic_amount">Basic Amount:</label><br>
<input type="number" step="0.01" id="basic_amount" name="basic_amount" value="{{ gst_release_data[3] }}"
required><br><br>
<label for="final_amount">Final Amount:</label><br>
<input type="number" step="0.01" id="final_amount" name="final_amount" value="{{ gst_release_data[4] }}"
required><br><br>
<label for="total_amount">Total Amount:</label><br>
<input type="number" step="0.01" id="total_amount" name="total_amount" value="{{ gst_release_data[5] }}"
required><br><br>
<label for="utr">UTR:</label><br>
<input type="text" id="utr" name="utr" value="{{ gst_release_data[6] }}"
required readonly><br><br>
<button type="submit">Update GST Release</button>
</form>
</body>
{% endblock %}

View File

@@ -0,0 +1,36 @@
{% extends 'base.html' %}
{% block content %}
<head>
<title>Edit Hold Type</title>
<link rel="stylesheet" type="text/css" href="{{ url_for('static', filename='css/style.css') }}">
</head>
<body>
<h2>Edit Hold Type</h2>
<form action="{{ url_for('hold_types.edit_hold_type', id=hold_type['hold_type_id']) }}" method="POST">
<!-- Hold Type Input -->
<label for="hold_type">Hold Type:</label><br>
<input type="text"
id="hold_type"
name="hold_type"
value="{{ hold_type['hold_type'] }}"
required><br><br>
<!-- Submit Button -->
<button type="submit" class="action-button">Update Hold Type</button>
</form>
<br>
<!-- Back Button -->
<!-- <a href="{{ url_for('hold_types.add_hold_type') }}" class="action-button">
Back
</a> -->
</body>
{% endblock %}

207
templates/edit_invoice.html Normal file
View File

@@ -0,0 +1,207 @@
{% extends 'base.html' %}
{% block content %}
<head>
<meta charset="UTF-8"/>
<meta name="viewport" content="width=device-width, initial-scale=1.0"/>
<title>Edit Invoice</title>
<link rel="stylesheet" type="text/css" href="{{ url_for('static', filename='css/invoice.css') }}">
<link rel="stylesheet" type="text/css" href="{{ url_for('static', filename='css/style1.css') }}">
<script src="https://code.jquery.com/jquery-3.6.0.min.js"></script>
</head>
<body>
<div id="editForm">
<h2>Edit Invoice</h2>
<!-- Success Alert Box -->
<div id="invoiceSuccessAlert" class="success-alert" style="display:none;">
Invoice successfully updated!
</div>
<form id="invoiceForm" action="{{ url_for('invoice.edit_invoice', invoice_id=invoice.Invoice_Id) }}" method="POST">
<!-- Subcontractor Field -->
<div class="row1">
<div>
<label for="subcontractor">Subcontractor Name:</label>
<input class="form-control" list="subcontractor_list" id="subcontractor" name="subcontractor"
value="{{ invoice.Contractor_Name }}" placeholder="Type to search..." required>
<input type="hidden" id="subcontractor_id" name="subcontractor_id" value="{{ invoice.Subcontractor_Id }}">
<datalist id="subcontractor_list"></datalist>
</div>
</div>
<!-- Village and PMC No Fields -->
<div class="row2">
<div>
<label for="village">Village Name:</label>
<input type="text" id="village" name="village" value="{{ invoice.Village_Name }}" required/>
<datalist id="village_list"></datalist>
</div>
<div>
<label for="pmc_no">PMC No:</label>
<input type="text" id="pmc_no" name="pmc_no" value="{{ invoice.PMC_No }}" required/>
</div>
</div>
<!-- Work Type and Invoice Details -->
<div class="row2">
<div>
<label for="work_type">Work Type:</label>
<input type="text" id="work_type" name="work_type" value="{{ invoice.Work_Type }}" required/>
</div>
<div>
<label for="invoice_details">Invoice Details:</label>
<textarea id="invoice_details" name="invoice_details" required>{{ invoice.Invoice_Details }}</textarea>
</div>
</div>
<!-- Invoice No and Invoice Date -->
<div class="row2">
<div>
<label for="invoice_no">Invoice No:</label>
<input type="text" id="invoice_no" name="invoice_no" value="{{ invoice.Invoice_No }}" required/>
</div>
<div>
<label for="invoice_date">Invoice Date:</label>
<input type="date" id="invoice_date" name="invoice_date" value="{{ invoice.Invoice_Date }}" required/>
</div>
</div>
<!-- Amount Fields -->
<div class="row3">
<div>
<label for="basic_amount">Basic Amount:</label>
<input type="number" step="0.01" id="basic_amount" name="basic_amount" value="{{ invoice.Basic_Amount }}" required/>
</div>
<div>
<label for="debit_amount">Debit Amount:</label>
<input type="number" step="0.01" id="debit_amount" name="debit_amount" value="{{ invoice.Debit_Amount }}" required/>
</div>
<div>
<label for="after_debit_amount">After Debit Amount:</label>
<input type="number" step="0.01" id="after_debit_amount" name="after_debit_amount" value="{{ invoice.After_Debit_Amount }}" required/>
</div>
</div>
<!-- GST, TDS, and Other Amounts -->
<div class="row3">
<div>
<label for="amount">Amount:</label>
<input type="number" step="0.01" id="amount" name="amount" value="{{ invoice.Amount }}" required/>
</div>
<div>
<label for="gst_amount">GST Amount:</label>
<input type="number" step="0.01" id="gst_amount" name="gst_amount" value="{{ invoice.GST_Amount }}" required/>
</div>
<div>
<label for="tds_amount">TDS Amount:</label>
<input type="number" step="0.01" id="tds_amount" name="tds_amount" value="{{ invoice.TDS_Amount }}" required/>
</div>
</div>
<!-- SD, On Commission, Hydro Testing -->
<div class="row3">
<div>
<label for="sd_amount">SD Amount:</label>
<input type="number" step="0.01" id="sd_amount" name="sd_amount" value="{{ invoice.SD_Amount }}" required/>
</div>
<div>
<label for="on_commission">On Commission:</label>
<input type="number" step="0.01" id="on_commission" name="on_commission" value="{{ invoice.On_Commission }}" required/>
</div>
<div>
<label for="hydro_testing">Hydro Testing:</label>
<input type="number" step="0.01" id="hydro_testing" name="hydro_testing" value="{{ invoice.Hydro_Testing }}" required/>
</div>
</div>
<!-- Hold Amount Section -->
<div id="hold_amount_container">
{% set seen_types = [] %}
{% for hold in invoice.hold_amounts %}
{% if hold.hold_type not in seen_types %}
{% set _ = seen_types.append(hold.hold_type) %}
<div class="hold-amount-row">
<label for="hold_type_{{ loop.index }}">Hold Type:</label>
<input type="text" id="hold_type_{{ loop.index }}" name="hold_type[]" value="{{ hold.hold_type }}" required/>
<label for="hold_amount_{{ loop.index }}">Hold Amount:</label>
<input type="number" step="0.01" id="hold_amount_{{ loop.index }}" name="hold_amount[]" value="{{ hold.hold_amount }}" required/>
<button type="button" class="remove-hold-amount">Remove</button>
</div>
{% endif %}
{% endfor %}
</div>
<div class="hold-row">
<button type="button" id="add_hold_amount" class="button">+ Add Hold Amount</button>
</div>
<!-- Final Amounts -->
<div class="row2">
<div>
<label for="gst_sd_amount">GST SD Amount:</label>
<input type="number" step="0.01" id="gst_sd_amount" name="gst_sd_amount" value="{{ invoice.GST_SD_Amount }}" required/>
</div>
<div>
<label for="final_amount">Final Amount:</label>
<input type="number" step="0.01" id="final_amount" name="final_amount" value="{{ invoice.Final_Amount }}" required/>
</div>
</div>
<!-- Submit Button -->
<button type="submit" class="button">Update</button>
</form>
</div>
<!-- JS for dynamic hold amount rows -->
<script>
$(document).ready(function() {
// Add new hold amount row
$("#add_hold_amount").click(function() {
const index = $("#hold_amount_container .hold-amount-row").length;
const newRow = `
<div class="hold-amount-row">
<label for="hold_type_${index}">Hold Type:</label>
<input type="text" id="hold_type_${index}" name="hold_type[]" required/>
<label for="hold_amount_${index}">Hold Amount:</label>
<input type="number" step="0.01" id="hold_amount_${index}" name="hold_amount[]" required/>
<button type="button" class="remove-hold-amount">Remove</button>
</div>
`;
$("#hold_amount_container").append(newRow);
});
// Remove hold amount row
$(document).on("click", ".remove-hold-amount", function() {
$(this).closest(".hold-amount-row").remove();
});
// Submit form via AJAX
$("#invoiceForm").submit(function(e) {
e.preventDefault();
$.ajax({
type: "POST",
url: $(this).attr("action"),
data: $(this).serialize(),
success: function(response) {
if(response.status === "success") {
$("#invoiceSuccessAlert").fadeIn().delay(3000).fadeOut();
alert("Invoice updated successfully!"); // <-- Popup alert
}
},
error: function(xhr) {
alert("Error: " + xhr.responseJSON.message);
}
});
});
});
</script>
</body>
{% endblock %}

View File

@@ -0,0 +1,49 @@
{% extends 'base.html' %}
{% block content %}
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Edit Payment</title>
<link rel="stylesheet" type="text/css" href="{{ url_for('static', filename='css/style.css') }}">
</head>
<body>
<h2>Edit Payment</h2>
<form action="/edit_payment/{{ payment_data[0] }}" method="POST">
<!-- <label for="invoice_id">Invoice ID:</label><br>-->
<!-- <select id="invoice_id" name="invoice_id" required>-->
<!-- {% for invoice in invoices %}-->
<!-- <option value="{{ invoice[0] }}" {% if invoice[0] == payment_data[0] %}selected{% endif %}>-->
<!-- {{ invoice[1] }}-->
<!-- </option>-->
<!-- {% endfor %}-->
<!-- </select><br><br>-->
<label for="PMC_No">PMC No:</label><br>
<input type="number" step="0.01" id="PMC_No" name="PMC_No" value="{{ payment_data[1] }}" required><br><br>
<label for="invoice_No">Invoice No:</label><br>
<input type="number" step="0.01" id="invoice_No" name="invoice_No" value="{{ payment_data[2] }}" required><br><br>
<label for="Payment_Amount">Amount:</label><br>
<input type="number" step="0.01" id="Payment_Amount" name="Payment_Amount" value="{{ payment_data[3] }}"
required><br><br>
<label for="TDS_Payment_Amount">TDS Amount:</label><br>
<input type="number" step="0.01" id="TDS_Payment_Amount" name="TDS_Payment_Amount" value="{{ payment_data[4] }}"
required><br><br>
<label for="total_amount">Total Amount:</label><br>
<input type="number" step="0.01" id="total_amount" name="total_amount" value="{{ payment_data[5] }}"
required><br><br>
<label for="utr">UTR:</label><br>
<input type="text" id="utr" name="utr" value="{{ payment_data[6] }}" readonly><br><br>
<button type="submit">Update Payment</button>
</form>
</body>
{% endblock %}

View File

@@ -0,0 +1,47 @@
<!DOCTYPE html>
<html>
<head>
<title>Edit Purchase</title>
<link rel="stylesheet" href="{{ url_for('static', filename='css/style.css') }}">
</head>
<body>
<h2>Edit Purchase Order</h2>
<form method="POST">
<label>Purchase Date:</label>
<input type="date" name="purchase_date" value="{{ purchase['purchase_date'] }}" required><br><br>
<label>Supplier Name:</label>
<input type="text" name="supplier_name" value="{{ purchase['supplier_name'] }}" required><br><br>
<label>Purchase Order No:</label>
<input type="text" name="purchase_order_no" value="{{ purchase['purchase_order_no'] }}" required><br><br>
<label>Item Name:</label>
<input type="text" name="item_name" value="{{ purchase['item_name'] }}" required><br><br>
<label>Quantity:</label>
<input type="number" name="quantity" value="{{ purchase['quantity'] }}" required><br><br>
<label>Unit:</label>
<input type="text" name="unit" value="{{ purchase['unit'] }}" required><br><br>
<label>Rate:</label>
<input type="number" step="0.01" name="rate" value="{{ purchase['rate'] }}" required><br><br>
<label>Amount:</label>
<input type="number" step="0.01" name="amount" value="{{ purchase['amount'] }}" required><br><br>
<label>GST Amount:</label>
<input type="text" name="GST_Amount" value="{{ purchase['GST_Amount'] }}" required><br><br>
<label>TDS:</label>
<input type="number" step="0.01" name="TDS" value="{{ purchase['TDS'] }}" required><br><br>
<label>Final Amount:</label>
<input type="number" step="0.01" name="final_amount" value="{{ purchase['final_amount'] }}" required><br><br>
<input type="submit" value="Update">
</form>
</body>
</html>

17
templates/edit_state.html Normal file
View File

@@ -0,0 +1,17 @@
{% extends 'base.html' %}
{% block content %}
<head>
<title>Edit State</title>
<link rel="stylesheet" type="text/css" href="{{ url_for('static', filename='css/style.css') }}">
</head>
<body>
<h2>Edit State</h2>
<form method="POST">
<input type="text" name="state_Name" placeholder="State Name" value="{{ state[1] }}" required>
<button type="submit">Update</button>
</form>
</body>
{% endblock %}

Some files were not shown because too many files have changed in this diff Show More