You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

551 lines
21 KiB

1 year ago
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
  1. <!DOCTYPE html>
  2. <html xmlns:th="http://www.thymeleaf.org">
  3. <head th:replace="~{fragments/head-caretaker :: head}"></head>
  4. <body>
  5. <!-- Page Container -->
  6. <div id="page-container"
  7. class="sidebar-o sidebar-dark enable-page-overlay side-scroll page-header-fixed page-header-dark">
  8. <!-- Side Overlay aside (right)-->
  9. <aside th:replace="~{fragments/aside-right :: aside}"></aside>
  10. <!-- END Side Overlay (right)-->
  11. <!-- Sidebar Nav-->
  12. <nav th:replace="~{fragments/nav-admin :: nav}"></nav>
  13. <!-- END Sidebar -->
  14. <!-- Header -->
  15. <header th:replace="~{fragments/header-admin :: header}"></header>
  16. <!-- END Header -->
  17. <!-- Main Container -->
  18. <main id="main-container">
  19. <!-- Page Content -->
  20. <!-- Card Container -->
  21. <div class="content">
  22. <div class="block block-rounded">
  23. <div class="block-header bg-primary text-white">
  24. <h3 class="block-title">Electricity Bill and Damage Details</h3>
  25. </div>
  26. <div class="block-content">
  27. <h4>User Details<span>*</span></h4>
  28. <div class="table-responsive">
  29. <table class="table table-bordered table-vcenter">
  30. <thead>
  31. <tr>
  32. <th>Booking Id</th>
  33. <th>Name</th>
  34. <th>Mobile Number</th>
  35. </tr>
  36. </thead>
  37. <tbody>
  38. <tr th:each="booking : ${booking}">
  39. <td> <span th:text="${booking.bookingId}"></span></td>
  40. <td><span th:text="${booking.users.name}"></span></td>
  41. <td><span th:text="${booking.users.mobileNo}"></span></td>
  42. </tr>
  43. </tbody>
  44. </table>
  45. </div>
  46. <form id="billForm" onsubmit="return submitBillForm()">
  47. <input type="hidden" th:value="${booking.bookingId}" name="bookingId" id="bookingId">
  48. <input type="hidden" th:value="${booking.ebDetails.ebId}" name="ebId" id="ebId">
  49. <!-- EB Details -->
  50. <h4>EB Details<span>*</span></h4>
  51. <div class="table-responsive">
  52. <table class="table table-bordered table-vcenter">
  53. <thead>
  54. <tr>
  55. <th colspan="4">Consumer Number</th>
  56. <th>Start Reading</th>
  57. <th>End Reading</th>
  58. <th>EB Cost Per Unit</th>
  59. <th>Total Units</th>
  60. <th>Total Amount</th>
  61. </tr>
  62. </thead>
  63. <tbody>
  64. <tr th:each="booking : ${booking}">
  65. <td colspan="4">
  66. <input type="text" class="form-control"
  67. th:value="${booking.hallDetails.ebNumber}"
  68. placeholder="Enter EB Number" required>
  69. </td>
  70. <td>
  71. <input type="number" id="startReading" name="startReading"
  72. class="form-control"
  73. th:value="${booking.ebDetails != null ? booking.ebDetails.startingReading : ''}"
  74. placeholder="Enter Starting Reading" required readonly>
  75. </td>
  76. <td>
  77. <input type="text" id="endReading" name="endReading"
  78. class="form-control"
  79. oninput="calculateEBAmount(); calculateUsedUnits();" required>
  80. </td>
  81. <td>
  82. <input type="number" id="ebCostPerUnit" name="ebCostPerUnit"
  83. class="form-control" th:value="${booking.hallDetails.ebCostPerUnit}"
  84. readonly>
  85. </td>
  86. <td>
  87. <span id="totalUsedUnits" class="form-control-static"
  88. style="display: none;"></span>
  89. </td>
  90. <td>
  91. <span id="totalEBAmount" class="form-control-static"
  92. style="display: none;"></span>
  93. </td>
  94. </tr>
  95. </tbody>
  96. </table>
  97. </div>
  98. <!-- Hidden input for Used Units -->
  99. <input type="hidden" id="hiddenTotalUsedUnits" name="totalUsedUnits">
  100. <input type="hidden" id="hiddenTotalEBAmount" name="totalEBAmount">
  101. <!-- Damage Details -->
  102. <h4>Damage Details</h4>
  103. <div class="table-responsive">
  104. <table class="table table-bordered table-vcenter" id="damageDetailsTable">
  105. <thead>
  106. <tr>
  107. <th>Materials</th>
  108. <th>No. of Quantity</th>
  109. <th>Per Material Amount</th>
  110. <th>Comments</th>
  111. <th>Damaged Item Photo</th>
  112. <th>Total Amount</th>
  113. </tr>
  114. </thead>
  115. <tbody id="damageDetailsbody">
  116. <!-- Initial Row will be here -->
  117. </tbody>
  118. </table>
  119. </div>
  120. <button type="button" class="btn btn-success mb-3" onclick="addMaterialRow()"><i
  121. class="fas fa-plus"></i>Add
  122. Material</button>
  123. <!-- Hidden input for total Damage Amount -->
  124. <input type="hidden" id="hiddenTotalDamageAmount" name="totalDamageAmount">
  125. <!-- Settlement Amount -->
  126. <div style="text-align: right;">
  127. <h4>Settlement Amount</h4>
  128. <h6>Total Settlement Amount: <span id="totalSettlementAmount"></span></h6>
  129. </div>
  130. <!-- Raise Bill Button -->
  131. <div style="text-align: center;">
  132. <button id="backButton" class="btn btn-secondary" onclick="goBack()">Back</button>
  133. <button id="submitButton" class="btn btn-primary" type="submit">Raise Bill</button>
  134. </div>
  135. <br>
  136. </form>
  137. </div>
  138. </div>
  139. </div>
  140. <!-- END Card Container -->
  141. <!-- END Page Content -->
  142. </main>
  143. <!-- END Main Container -->
  144. <!-- Footer -->
  145. <footer th:replace="~{fragments/footer2 :: footer}"></footer>
  146. <!-- END Footer -->
  147. </div>
  148. <!-- END Page Container -->
  149. <!-- Scripts -->
  150. <div th:replace="~{fragments/resetpassword :: div}"></div>
  151. <script th:replace="~{fragments/script :: script}"></script>
  152. <script src="https://code.jquery.com/ui/1.12.1/jquery-ui.js"></script>
  153. <link rel="stylesheet" href="https://code.jquery.com/ui/1.12.1/themes/base/jquery-ui.css">
  154. <script type="text/javascript">
  155. document.addEventListener("DOMContentLoaded", function () {
  156. addMaterialRow(); // Add the first row automatically
  157. });
  158. let selectedMaterials = {}; // Object to track selected materials for each row
  159. var damageindex = 0;
  160. function addMaterialRow() {
  161. const tableBody = document.getElementById('damageDetailsTable').getElementsByTagName('tbody')[0];
  162. const rowIndex = tableBody.rows.length;
  163. selectedMaterials[rowIndex] = ""; // Initialize tracking for this row
  164. const row = tableBody.insertRow(rowIndex);
  165. // Set row's HTML with dropdown including a unique index
  166. row.innerHTML = generateRowHTML(rowIndex);
  167. updateAllDropdowns();
  168. calculateDamageAmount();
  169. }
  170. function generateRowHTML(index) {
  171. damageindex = index;
  172. return `
  173. <td>
  174. <select id="materials${index}" name="materials" class="form-control"
  175. onchange="materialSelectionChanged(this, ${index});">
  176. <option value="">Select</option>
  177. <option value="chair">Chair</option>
  178. <option value="table">Table</option>
  179. <option value="kitchenessentials">Kitchen Essentials</option>
  180. <option value="electronics">Electronics</option>
  181. <option value="Others">Electronics</option>
  182. </select>
  183. </td>
  184. <td><input type="number" id="quantity${index}" name="quantity[${index}]" class="form-control"
  185. oninput="calculateDamageAmount()"></td>
  186. <td><input type="number" id="perMaterialAmount${index}" name="perMaterialAmount[${index}]"
  187. class="form-control" oninput="calculateDamageAmount()"></td>
  188. <td><input type="text" id="comments${index}" name="comments" class="form-control"
  189. placeholder="Enter Comments"></td>
  190. <td><input type="file" id="damagePhoto${index}" name="damagePhoto${index}" class="form-control damage-image mb-1"></td>
  191. <td><span id="totalDamageAmount${index}" class="form-control-static"></span></td>
  192. <td><button type="button" class="btn btn-danger" onclick="removeMaterialRow(${index})"><i class="fas fa-minus"></i></button></td>
  193. `;
  194. }
  195. function removeMaterialRow(index) {
  196. const tableBody = document.getElementById('damageDetailsTable').getElementsByTagName('tbody')[0];
  197. tableBody.deleteRow(index);
  198. // Update selectedMaterials object
  199. delete selectedMaterials[index];
  200. // Reassign the indices for the remaining rows
  201. const rows = tableBody.rows;
  202. for (let i = index; i < rows.length; i++) {
  203. updateRowIndices(rows[i], i);
  204. selectedMaterials[i] = selectedMaterials[i + 1];
  205. }
  206. delete selectedMaterials[rows.length]; // Remove the last entry which is now undefined
  207. updateAllDropdowns();
  208. calculateDamageAmount();
  209. }
  210. function updateRowIndices(row, newIndex) {
  211. row.cells[0].querySelector('select').setAttribute('id', `materials${newIndex}`);
  212. row.cells[0].querySelector('select').setAttribute('onchange', `materialSelectionChanged(this, ${newIndex});`);
  213. row.cells[1].querySelector('input').setAttribute('id', `quantity${newIndex}`);
  214. row.cells[1].querySelector('input').setAttribute('name', `quantity[${newIndex}]`);
  215. row.cells[1].querySelector('input').setAttribute('oninput', `calculateDamageAmount()`);
  216. row.cells[2].querySelector('input').setAttribute('id', `perMaterialAmount${newIndex}`);
  217. row.cells[2].querySelector('input').setAttribute('name', `perMaterialAmount[${newIndex}]`);
  218. row.cells[2].querySelector('input').setAttribute('oninput', `calculateDamageAmount()`);
  219. row.cells[3].querySelector('input').setAttribute('id', `comments${newIndex}`);
  220. row.cells[4].querySelector('input').setAttribute('id', `damagePhoto${newIndex}`);
  221. row.cells[5].querySelector('span').setAttribute('id', `totalDamageAmount${newIndex}`);
  222. row.cells[6].querySelector('button').setAttribute('onclick', `removeMaterialRow(${newIndex})`);
  223. }
  224. function updateAllDropdowns() {
  225. const allMaterials = ["chair", "table", "kitchenessentials", "electronics", "others"];
  226. Object.keys(selectedMaterials).forEach(rowIndex => {
  227. const dropdown = document.getElementById(`materials${rowIndex}`);
  228. const selectedValue = selectedMaterials[rowIndex];
  229. dropdown.innerHTML = '<option value="">Select</option>';
  230. allMaterials.forEach(material => {
  231. if (!Object.values(selectedMaterials).includes(material) || material === selectedValue) {
  232. const option = new Option(material, material);
  233. dropdown.add(option);
  234. }
  235. });
  236. dropdown.value = selectedValue;
  237. });
  238. }
  239. function materialSelectionChanged(select, index) {
  240. // Update the selection tracking
  241. selectedMaterials[index] = select.value;
  242. updateAllDropdowns(); // Refresh all dropdowns to reflect the current selections
  243. calculateDamageAmount(); // Recalculate damage amount as materials selection might change the calculation
  244. }
  245. function calculateEBAmount() {
  246. let startReading = parseFloat(document.getElementById("startReading").value) || 0;
  247. let endReading = parseFloat(document.getElementById("endReading").value) || 0;
  248. let ebCostPerUnit = parseFloat(document.getElementById("ebCostPerUnit").value) || 0;
  249. let totalEBAmount = (endReading - startReading) * ebCostPerUnit; // Assuming rate per unit is 10
  250. if (!isNaN(totalEBAmount)) {
  251. document.getElementById("totalEBAmount").style.display = "block"; // Show the totalEBAmount
  252. document.getElementById("totalEBAmount").innerText = totalEBAmount.toFixed(2);
  253. document.getElementById("hiddenTotalEBAmount").value = totalEBAmount;
  254. calculateSettlementAmount();
  255. }
  256. }
  257. function calculateUsedUnits() {
  258. let startReading = parseFloat(document.getElementById("startReading").value) || 0;
  259. let endReading = parseFloat(document.getElementById("endReading").value) || 0;
  260. // Get the endReading input element
  261. let endReadingInput = document.getElementById("endReading");
  262. // Ensure end reading cannot be less than start reading
  263. if (endReading < startReading) {
  264. // Set custom validity message
  265. endReadingInput.setCustomValidity("End reading cannot be less than start reading.");
  266. endReadingInput.reportValidity();
  267. } else {
  268. // Clear any previous custom validity message
  269. endReadingInput.setCustomValidity("");
  270. }
  271. let usedUnits = endReading - startReading;
  272. // Show total used units
  273. document.getElementById("totalUsedUnits").style.display = "block";
  274. document.getElementById("totalUsedUnits").innerText = usedUnits;
  275. document.getElementById("hiddenTotalUsedUnits").value = usedUnits;
  276. let totalEBAmount = usedUnits * parseFloat(document.getElementById("ebCostPerUnit").value) || 0;
  277. // Show total EB amount
  278. document.getElementById("totalEBAmount").style.display = "block";
  279. document.getElementById("totalEBAmount").innerText = totalEBAmount.toFixed(2);
  280. document.getElementById("hiddenTotalEBAmount").value = totalEBAmount;
  281. calculateSettlementAmount();
  282. }
  283. function calculateDamageAmount() {
  284. const rows = document.getElementById('damageDetailsTable').getElementsByTagName('tbody')[0].rows;
  285. let totalDamageAmount = 0;
  286. for (let i = 0; i < rows.length; i++) {
  287. const quantity = parseFloat(document.getElementById(`quantity${i}`).value) || 0;
  288. const perMaterialAmount = parseFloat(document.getElementById(`perMaterialAmount${i}`).value) || 0;
  289. const rowDamageAmount = quantity * perMaterialAmount;
  290. document.getElementById(`totalDamageAmount${i}`).innerText = rowDamageAmount.toFixed(2);
  291. totalDamageAmount += rowDamageAmount;
  292. }
  293. document.getElementById("hiddenTotalDamageAmount").value = totalDamageAmount.toFixed(2);
  294. calculateSettlementAmount();
  295. }
  296. function calculateSettlementAmount() {
  297. let totalEBAmount = parseFloat(document.getElementById("hiddenTotalEBAmount").value) || 0;
  298. let totalDamageAmount = parseFloat(document.getElementById("hiddenTotalDamageAmount").value) || 0;
  299. let totalSettlementAmount = totalEBAmount + totalDamageAmount;
  300. document.getElementById("totalSettlementAmount").innerText = totalSettlementAmount.toFixed(2);
  301. }
  302. function goBack() {
  303. window.history.back();
  304. }
  305. function submitBillForm() {
  306. var bookingId = $("#bookingId").val();
  307. var ebId = $("#ebId").val();
  308. var endReading = $("#endReading").val();
  309. var startReading = $("#startReading").val();
  310. var usedUnit = $("#totalUsedUnits").html();
  311. var ebAmount = $("#totalEBAmount").html();
  312. var formData = new FormData();
  313. formData.append('bookingId', bookingId);
  314. formData.append('ebId', ebId);
  315. formData.append('startingReading', startReading);
  316. formData.append('endingReading', endReading);
  317. formData.append('usedUnit', usedUnit);
  318. formData.append('ebAmount', ebAmount);
  319. var i = damageindex;
  320. $('#damageDetailsbody tr').each(function () {
  321. var assetName = $("#materials" + i).val();
  322. var comments = $("#comments" + i).val();
  323. var quantity = $("#quantity" + i).val();
  324. var assetAmount = $("#totalDamageAmount" + i).html();
  325. var assetImageInput = $("#damagePhoto" + i)[0].files[0]; // Assuming input type is file for assetImage
  326. formData.append('assetName', assetName);
  327. formData.append('comments', comments);
  328. formData.append('quantity', quantity);
  329. formData.append('assetAmount', assetAmount);
  330. formData.append('assetImage', assetImageInput);
  331. i = i - 1;
  332. });
  333. $.ajax({
  334. url: '/communityhall/careTaker/api/processEbDetailsAndDamage',
  335. type: 'POST',
  336. data: formData,
  337. contentType: false, // Let jQuery handle the content type
  338. processData: false, // Prevent jQuery from processing the data
  339. success: function (response) {
  340. Swal.fire({
  341. title: "Success!",
  342. text: "Form submitted successfully.",
  343. icon: "success",
  344. button: "OK",
  345. }).then((result) => {
  346. if (result.isConfirmed) {
  347. window.location.href = '/communityhall/admin/settlementlist';
  348. }
  349. });
  350. },
  351. error: function (xhr, status, error) {
  352. console.error('Error:', error);
  353. Swal.fire({
  354. title: "Error!",
  355. text: "There was an error submitting the form.",
  356. icon: "error",
  357. button: "OK",
  358. }).then(() => {
  359. window.location.href = '/communityhall/admin/settlementlist';
  360. });
  361. }
  362. });
  363. return false;
  364. }
  365. let isCompressing = false;
  366. // Attach the validation function to file input change event
  367. $(document).on('change', '.damage-image', function () {
  368. validateFile(this);
  369. });
  370. function validateFile(fileInput) {
  371. const allowedExtensions = /(\.jpg|\.jpeg|\.pdf)$/i;
  372. const file = fileInput.files[0];
  373. const filePath = fileInput.value;
  374. let errorMessage = '';
  375. // Clear any previous error message
  376. clearError(fileInput);
  377. // Check for duplicate files
  378. if (isDuplicateFile(file.name, fileInput)) {
  379. errorMessage = 'This file has already been selected. Please choose a different file.';
  380. fileInput.value = ''; // Clear the file input
  381. displayError(fileInput, errorMessage);
  382. return;
  383. }
  384. // Validate file type
  385. if (!allowedExtensions.exec(filePath)) {
  386. errorMessage = 'Only JPG, JPEG, and PDF files are allowed.';
  387. fileInput.value = ''; // Clear the file input
  388. displayError(fileInput, errorMessage);
  389. }
  390. // Handle image files separately
  391. else if (/(\.jpg|\.jpeg)$/i.test(filePath) && file.size > 2097152) {
  392. compressImage(file).then(compressedFile => {
  393. const dataTransfer = new DataTransfer();
  394. dataTransfer.items.add(compressedFile);
  395. fileInput.files = dataTransfer.files;
  396. // Validate file size after compression
  397. validateFileSize(fileInput);
  398. }).catch(error => {
  399. errorMessage = 'Error compressing image.';
  400. displayError(fileInput, errorMessage);
  401. });
  402. }
  403. // Directly validate PDF file size
  404. else if (/(\.pdf)$/i.test(filePath) && file.size > 2097152) {
  405. errorMessage = 'PDF file size exceeds the 2MB limit.';
  406. fileInput.value = ''; // Clear the file input
  407. displayError(fileInput, errorMessage);
  408. }
  409. else {
  410. // console.log('File size is within limit:', file.size, 'bytes');
  411. clearError(fileInput);
  412. }
  413. }
  414. function isDuplicateFile(fileName, currentInput) {
  415. const fileInputs = document.querySelectorAll('.file-upload-field');
  416. for (let input of fileInputs) {
  417. if (input !== currentInput && input.files.length > 0 && input.files[0].name === fileName) {
  418. return true;
  419. }
  420. }
  421. return false;
  422. }
  423. function clearError(fileInput) {
  424. const errorElements = fileInput.parentNode.getElementsByClassName('error-message');
  425. while (errorElements.length > 0) {
  426. errorElements[0].remove();
  427. }
  428. }
  429. // Function to compress image using canvas
  430. function compressImage(file) {
  431. return new Promise((resolve, reject) => {
  432. const reader = new FileReader();
  433. reader.onload = function (event) {
  434. const img = new Image();
  435. img.onload = function () {
  436. const canvas = document.createElement('canvas');
  437. const ctx = canvas.getContext('2d');
  438. const maxSize = 2097152; // 2MB in bytes
  439. let scale = 1;
  440. if (file.size > maxSize) {
  441. scale = Math.sqrt(maxSize / file.size);
  442. }
  443. canvas.width = img.width * scale;
  444. canvas.height = img.height * scale;
  445. ctx.drawImage(img, 0, 0, canvas.width, canvas.height);
  446. // Convert canvas content to blob
  447. canvas.toBlob(blob => {
  448. resolve(new File([blob], file.name, {type: 'image/jpeg', lastModified: Date.now()}));
  449. }, 'image/jpeg', 0.7); // Adjust JPEG quality as needed (0.7 is 70% quality)
  450. };
  451. img.src = event.target.result;
  452. };
  453. reader.readAsDataURL(file);
  454. });
  455. }
  456. // Function to validate file size after compression
  457. function validateFileSize(fileInput) {
  458. const file = fileInput.files[0];
  459. if (file && file.size > 2097152) {
  460. //console.log('File size is still greater than 2MB after compression.');
  461. // Handle the situation where the file size is still greater than 2MB
  462. displayError(fileInput, 'File size is still greater than 2MB after compression.');
  463. fileInput.value = ''; // Clear the file input
  464. } else {
  465. //console.log('File size is within limit after compression.');
  466. // Handle the situation where the file size is within the limit
  467. }
  468. }
  469. // Function to display error messages
  470. function displayError(fileInput, errorMessage) {
  471. // Example implementation: show error message near the file input
  472. let errorContainer = fileInput.nextElementSibling;
  473. if (!errorContainer || !errorContainer.classList.contains('error-message')) {
  474. errorContainer = document.createElement('div');
  475. errorContainer.classList.add('error-message');
  476. fileInput.parentNode.insertBefore(errorContainer, fileInput.nextSibling);
  477. }
  478. errorContainer.textContent = errorMessage;
  479. }
  480. </script>
  481. </body>
  482. </html>