| Line 20... |
Line 20... |
| 20 |
|
20 |
|
| 21 |
$(document).on('click', ".warehouse-invoices", function () {
|
21 |
$(document).on('click', ".warehouse-invoices", function () {
|
| 22 |
loadWarehouseInvoices("main-content");
|
22 |
loadWarehouseInvoices("main-content");
|
| 23 |
});
|
23 |
});
|
| 24 |
|
24 |
|
| - |
|
25 |
// Sidebar entry for the PO stock-cover approval queue (Niranjan Kala's queue).
|
| - |
|
26 |
// Add this <li> in your sidebar template to expose the entry:
|
| - |
|
27 |
// <li><a class="warehouse-po-stock-approvals" href="javascript:void(0);">Stock Approvals</a></li>
|
| - |
|
28 |
// Server-side access is gated to APPROVER_EMAILS in PoStockApprovalController.
|
| - |
|
29 |
$(document).on('click', ".warehouse-po-stock-approvals", function () {
|
| - |
|
30 |
loadPoStockApprovals("main-content");
|
| - |
|
31 |
});
|
| - |
|
32 |
|
| 25 |
|
33 |
|
| 26 |
$(document).on('click', '.dateWiseInvoices', function () {
|
34 |
$(document).on('click', '.dateWiseInvoices', function () {
|
| 27 |
|
35 |
|
| 28 |
var startDate = getDatesFromPicker('input[name="invoice-duration"]').startDate;
|
36 |
var startDate = getDatesFromPicker('input[name="invoice-duration"]').startDate;
|
| 29 |
var endDate = getDatesFromPicker('input[name="invoice-duration"]').endDate
|
37 |
var endDate = getDatesFromPicker('input[name="invoice-duration"]').endDate
|
| Line 283... |
Line 291... |
| 283 |
<td><input class="form-control btn btn-primary removeInPurchaseOrder" tabindex="-1" type="button" value="Remove"></td>
|
291 |
<td><input class="form-control btn btn-primary removeInPurchaseOrder" tabindex="-1" type="button" value="Remove"></td>
|
| 284 |
</tr>`);
|
292 |
</tr>`);
|
| 285 |
|
293 |
|
| 286 |
var vendorId = $('#vendorId').val();
|
294 |
var vendorId = $('#vendorId').val();
|
| 287 |
|
295 |
|
| - |
|
296 |
// Scoped to the PO items table's tbody — bare $('tbody') would also match
|
| - |
|
297 |
// the modal's flagged-items tbody, and jQuery would move $html into the
|
| - |
|
298 |
// modal instead of the visible table, breaking the autocomplete wiring.
|
| 288 |
$('tbody').append($html);
|
299 |
$('#purchase-order-table tbody').append($html);
|
| 289 |
getVendorItemAheadOptions($html.find('.typeaheaditem'), vendorId, function (selectedItem) {
|
300 |
getVendorItemAheadOptions($html.find('.typeaheaditem'), vendorId, function (selectedItem) {
|
| 290 |
let poDate = $('#poDate').val();
|
301 |
let poDate = $('#poDate').val();
|
| 291 |
var itemId = selectedItem.itemId;
|
302 |
var itemId = selectedItem.itemId;
|
| 292 |
var description = selectedItem.itemDescription;
|
303 |
var description = selectedItem.itemDescription;
|
| 293 |
if (itemId) {
|
304 |
if (itemId) {
|
| Line 890... |
Line 901... |
| 890 |
doGetAjaxRequestHandler(context + "/getWarehouseInvoices", function (response) {
|
901 |
doGetAjaxRequestHandler(context + "/getWarehouseInvoices", function (response) {
|
| 891 |
$('#' + domId).html(response);
|
902 |
$('#' + domId).html(response);
|
| 892 |
});
|
903 |
});
|
| 893 |
}
|
904 |
}
|
| 894 |
|
905 |
|
| - |
|
906 |
// Loads the PO stock-cover approvals queue into the main content pane.
|
| - |
|
907 |
// Access is server-gated (403-ish empty view if the caller isn't an approver).
|
| - |
|
908 |
function loadPoStockApprovals(domId) {
|
| - |
|
909 |
doGetAjaxRequestHandler(context + "/po/stock-approvals", function (response) {
|
| - |
|
910 |
$('#' + domId).html(response);
|
| - |
|
911 |
});
|
| - |
|
912 |
}
|
| - |
|
913 |
|
| 895 |
function createPO(sendPo) {
|
914 |
function createPO(sendPo) {
|
| 896 |
var purchaseOrder = {};
|
915 |
var purchaseOrder = {};
|
| 897 |
|
916 |
|
| 898 |
var vendorId = $('#vendorId').val();
|
917 |
var vendorId = $('#vendorId').val();
|
| 899 |
|
918 |
|
| Line 979... |
Line 998... |
| 979 |
purchaseOrder['poDate'] = poDate;
|
998 |
purchaseOrder['poDate'] = poDate;
|
| 980 |
purchaseOrder['items'] = items
|
999 |
purchaseOrder['items'] = items
|
| 981 |
|
1000 |
|
| 982 |
console.log(purchaseOrder)
|
1001 |
console.log(purchaseOrder)
|
| 983 |
|
1002 |
|
| - |
|
1003 |
// Modal 1 (pmscp) replaces the old native `confirm`. Populate the summary
|
| - |
|
1004 |
// tiles from what the user actually entered, then hand off to the button
|
| - |
|
1005 |
// handler wired in $(document).on('click', '#pmscpConfirmCreate', ...).
|
| - |
|
1006 |
openPmscpCreateConfirm(purchaseOrder);
|
| - |
|
1007 |
}
|
| - |
|
1008 |
|
| - |
|
1009 |
// ─────────────────────────────────────────────────────────────────────────────
|
| - |
|
1010 |
// PO-create modal stack — three sequenced modals replace the old native
|
| - |
|
1011 |
// confirm/prompt/alert popups:
|
| - |
|
1012 |
// 1) #pmscpConfirmModal — "Are you sure you want to create this PO?"
|
| - |
|
1013 |
// 2) #pmscpReasonModal — reason textarea + live 50-char counter
|
| - |
|
1014 |
// (only when preflight says approval is needed)
|
| - |
|
1015 |
// 3) #pmscpApprovalModal — final "Sending to Niranjan Kala · L4" confirm
|
| - |
|
1016 |
// The pending purchaseOrder object rides on window.__pmscpPO between modals.
|
| - |
|
1017 |
// ─────────────────────────────────────────────────────────────────────────────
|
| - |
|
1018 |
var PMSCP_MIN_REASON = 50;
|
| - |
|
1019 |
|
| - |
|
1020 |
// Minimal HTML escape for values injected into modal markup (item names from server).
|
| - |
|
1021 |
function escapePmscp(s) {
|
| - |
|
1022 |
if (s === null || s === undefined) return '';
|
| - |
|
1023 |
return String(s).replace(/&/g, '&').replace(/</g, '<').replace(/>/g, '>')
|
| - |
|
1024 |
.replace(/"/g, '"').replace(/'/g, ''');
|
| - |
|
1025 |
}
|
| - |
|
1026 |
|
| - |
|
1027 |
function openPmscpCreateConfirm(purchaseOrder) {
|
| - |
|
1028 |
window.__pmscpPO = purchaseOrder;
|
| - |
|
1029 |
|
| - |
|
1030 |
// Populate summary tiles from the form + purchaseOrder payload.
|
| - |
|
1031 |
var vendorName = $('#vendorId option:selected').text() || purchaseOrder.vendorId;
|
| - |
|
1032 |
var warehouseName = $('#warehouseMap option:selected').text() || purchaseOrder.warehouseId;
|
| - |
|
1033 |
var totalQty = $('#totalQuantity').text() || (purchaseOrder.items || []).length;
|
| - |
|
1034 |
var grandTotal = $('#grandTotal').text() || '';
|
| - |
|
1035 |
|
| - |
|
1036 |
$('#pmscpSumVendor').text(vendorName);
|
| - |
|
1037 |
$('#pmscpSumWarehouse').text(warehouseName);
|
| - |
|
1038 |
$('#pmscpSumItems').text((purchaseOrder.items || []).length + ' line item(s)');
|
| - |
|
1039 |
$('#pmscpSumQty').text(totalQty);
|
| - |
|
1040 |
$('#pmscpSumTotal').text(grandTotal ? '₹ ' + grandTotal : '—');
|
| - |
|
1041 |
|
| - |
|
1042 |
$('#pmscpConfirmModal').modal('show');
|
| - |
|
1043 |
}
|
| - |
|
1044 |
|
| - |
|
1045 |
// Handlers — bind once (safe with delegated `on`).
|
| - |
|
1046 |
$(document).on('click', '#pmscpConfirmCreate', function () {
|
| - |
|
1047 |
$('#pmscpConfirmModal').modal('hide');
|
| - |
|
1048 |
// Give Bootstrap a moment to finish the fade-out before firing the AJAX so
|
| - |
|
1049 |
// the loading overlay renders cleanly.
|
| - |
|
1050 |
setTimeout(function () {
|
| - |
|
1051 |
submitCreatePO(window.__pmscpPO);
|
| - |
|
1052 |
}, 200);
|
| - |
|
1053 |
});
|
| - |
|
1054 |
|
| - |
|
1055 |
// Live counter on the reason textarea — gates the Continue button.
|
| - |
|
1056 |
$(document).on('input change', '#pmscpReasonText', function () {
|
| - |
|
1057 |
var len = $(this).val().trim().length;
|
| - |
|
1058 |
$('#pmscpReasonCount').text(len);
|
| - |
|
1059 |
var ok = len >= PMSCP_MIN_REASON;
|
| - |
|
1060 |
$('#pmscpReasonCounter').toggleClass('pmscp-ok', ok);
|
| - |
|
1061 |
$('#pmscpReasonContinue').prop('disabled', !ok);
|
| - |
|
1062 |
});
|
| - |
|
1063 |
|
| - |
|
1064 |
// Continue from reason modal → open approval-target confirm modal.
|
| - |
|
1065 |
$(document).on('click', '#pmscpReasonContinue', function () {
|
| - |
|
1066 |
var reason = $('#pmscpReasonText').val().trim();
|
| - |
|
1067 |
if (reason.length < PMSCP_MIN_REASON) return; // defensive
|
| - |
|
1068 |
window.__pmscpPO.requesterReason = reason;
|
| - |
|
1069 |
$('#pmscpApprovalReasonPreview').text(reason);
|
| - |
|
1070 |
$('#pmscpReasonModal').modal('hide');
|
| - |
|
1071 |
setTimeout(function () {
|
| - |
|
1072 |
$('#pmscpApprovalModal').modal('show');
|
| - |
|
1073 |
}, 250);
|
| - |
|
1074 |
});
|
| - |
|
1075 |
|
| - |
|
1076 |
// Back from approval modal → return to reason modal so user can tweak text.
|
| - |
|
1077 |
$(document).on('click', '#pmscpApprovalBack', function () {
|
| - |
|
1078 |
$('#pmscpApprovalModal').modal('hide');
|
| - |
|
1079 |
setTimeout(function () {
|
| - |
|
1080 |
$('#pmscpReasonModal').modal('show');
|
| - |
|
1081 |
}, 250);
|
| - |
|
1082 |
});
|
| - |
|
1083 |
|
| - |
|
1084 |
// Final confirm → fire the real create request.
|
| - |
|
1085 |
$(document).on('click', '#pmscpApprovalConfirm', function () {
|
| - |
|
1086 |
$('#pmscpApprovalModal').modal('hide');
|
| - |
|
1087 |
setTimeout(function () {
|
| - |
|
1088 |
actuallyCreatePO(window.__pmscpPO);
|
| - |
|
1089 |
}, 200);
|
| - |
|
1090 |
});
|
| - |
|
1091 |
|
| - |
|
1092 |
// Reset counter every time reason modal opens so a fresh session starts at 0.
|
| - |
|
1093 |
$(document).on('shown.bs.modal', '#pmscpReasonModal', function () {
|
| - |
|
1094 |
// Only reset text if we don't already have a saved reason (e.g. user
|
| - |
|
1095 |
// came back via the "Back" button — preserve what they typed).
|
| - |
|
1096 |
if (!window.__pmscpPO || !window.__pmscpPO.requesterReason) {
|
| - |
|
1097 |
$('#pmscpReasonText').val('');
|
| - |
|
1098 |
} else {
|
| - |
|
1099 |
$('#pmscpReasonText').val(window.__pmscpPO.requesterReason);
|
| - |
|
1100 |
}
|
| - |
|
1101 |
$('#pmscpReasonText').trigger('input').focus();
|
| - |
|
1102 |
});
|
| 984 |
|
1103 |
|
| - |
|
1104 |
// Submits the PO create request. Runs a preflight stock-cover check first
|
| - |
|
1105 |
// (single always-200 call, no side effects): if the rule fires, opens the
|
| - |
|
1106 |
// reason modal → approval-target modal → hits /createPurchaseOrder once with
|
| - |
|
1107 |
// the reason included. This avoids the old "submit → 400 → retry" pattern
|
| - |
|
1108 |
// that triggered the global "Error occurred" popup on the first response.
|
| - |
|
1109 |
function submitCreatePO(purchaseOrder) {
|
| - |
|
1110 |
$.ajax({
|
| - |
|
1111 |
url: context + "/createPurchaseOrder/preflight-stock-check",
|
| - |
|
1112 |
type: "POST",
|
| - |
|
1113 |
contentType: "application/json",
|
| - |
|
1114 |
data: JSON.stringify(purchaseOrder),
|
| - |
|
1115 |
dataType: "json",
|
| - |
|
1116 |
global: false, // suppress global handlers just in case preflight fails
|
| - |
|
1117 |
success: function (preflight) {
|
| - |
|
1118 |
// Duplicate-guard: same vendor + item already in a pending approval.
|
| - |
|
1119 |
if (preflight && preflight.alreadyInReview) {
|
| - |
|
1120 |
var lines = [];
|
| - |
|
1121 |
for (var k in preflight.conflicts) {
|
| - |
|
1122 |
if (Object.prototype.hasOwnProperty.call(preflight.conflicts, k)) {
|
| - |
|
1123 |
var c = preflight.conflicts[k];
|
| - |
|
1124 |
// Server sends { itemId: { modelName, poNumber } } — support the
|
| - |
|
1125 |
// old shape { itemId: poNumber } as fallback in case of stale server.
|
| - |
|
1126 |
var poNumber = (c && typeof c === 'object') ? c.poNumber : c;
|
| - |
|
1127 |
var modelName = (c && typeof c === 'object' && c.modelName) ? c.modelName : "";
|
| - |
|
1128 |
var itemLabel = modelName ? (modelName + " #" + k) : ("Item #" + k);
|
| - |
|
1129 |
lines.push(" " + itemLabel + " → PO " + poNumber);
|
| - |
|
1130 |
}
|
| - |
|
1131 |
}
|
| - |
|
1132 |
alert(
|
| - |
|
1133 |
"Cannot create this PO — one or more items are already under review for the same vendor:\n\n" +
|
| - |
|
1134 |
lines.join("\n") +
|
| - |
|
1135 |
"\n\nWait for those POs to be approved or rejected first, then try again."
|
| - |
|
1136 |
);
|
| - |
|
1137 |
return;
|
| - |
|
1138 |
}
|
| 985 |
if (confirm("Are you sure you want to create purchase order") == true) {
|
1139 |
if (preflight && preflight.needsApproval && !purchaseOrder.requesterReason) {
|
| - |
|
1140 |
// Populate the "Items requiring approval" table from preflight.flaggedItems
|
| - |
|
1141 |
// BEFORE opening the modal so the requester sees exactly which SKUs and how
|
| - |
|
1142 |
// much aging vs 15d-sold when they compose their reason. Mixed POs (some
|
| - |
|
1143 |
// clean, some aging) show only the aging items here — clean items are not
|
| - |
|
1144 |
// held and not displayed.
|
| - |
|
1145 |
var flagged = (preflight && preflight.flaggedItems) || [];
|
| - |
|
1146 |
var $rows = $('#pmscpFlaggedRows').empty();
|
| - |
|
1147 |
if (flagged.length > 0) {
|
| - |
|
1148 |
for (var i = 0; i < flagged.length; i++) {
|
| - |
|
1149 |
var f = flagged[i];
|
| - |
|
1150 |
var name = f.modelName || ('#' + f.itemId);
|
| - |
|
1151 |
// If server left "#itemId" as the model name (item lookup failed),
|
| - |
|
1152 |
// skip the redundant "#itemId" span since the name already carries it.
|
| - |
|
1153 |
var idSpan = (name.indexOf('#') === 0) ? '' :
|
| - |
|
1154 |
' <span style="color:#7b8199;font-size:11px">#' + f.itemId + '</span>';
|
| - |
|
1155 |
// Excess turns green only when NO aging anywhere in the network.
|
| - |
|
1156 |
var hasAgingHere = (f.agingQty || 0) > 0;
|
| - |
|
1157 |
var hasAgingElsewhere = f.agingBreakdown && f.agingBreakdown.length > 0;
|
| - |
|
1158 |
var excessColor = (!hasAgingHere && !hasAgingElsewhere) ? '#177a3a' : '#c62828';
|
| - |
|
1159 |
$rows.append(
|
| - |
|
1160 |
'<tr>' +
|
| - |
|
1161 |
'<td style="padding:6px 10px;border-bottom:1px solid #f0f2f7">' + escapePmscp(name) + idSpan + '</td>' +
|
| - |
|
1162 |
'<td style="padding:6px 10px;text-align:right;border-bottom:1px solid #f0f2f7">' + (f.agingQty || 0) + '</td>' +
|
| - |
|
1163 |
'<td style="padding:6px 10px;text-align:right;border-bottom:1px solid #f0f2f7">' + (f.sold15dQty || 0) + '</td>' +
|
| - |
|
1164 |
'<td style="padding:6px 10px;text-align:right;border-bottom:1px solid #f0f2f7">' + (f.poQty || 0) + '</td>' +
|
| - |
|
1165 |
'<td style="padding:6px 10px;text-align:right;border-bottom:1px solid #f0f2f7;color:' + excessColor + ';font-weight:600">+' + (f.excess || 0) + '</td>' +
|
| - |
|
1166 |
'</tr>'
|
| - |
|
1167 |
);
|
| - |
|
1168 |
// Mirror the detail-modal pattern: when this vendor+warehouse has no
|
| - |
|
1169 |
// aging AND the item has aging elsewhere, render one amber sub-row per
|
| - |
|
1170 |
// (warehouse, vendor). Shortens "DL-NSSPL/Delhi/BSB Marketing/G" +
|
| - |
|
1171 |
// "BSB Marketing Private Limited" to "DL-NSSPL - (BSB)" for readability.
|
| - |
|
1172 |
if (!hasAgingHere && hasAgingElsewhere) {
|
| - |
|
1173 |
for (var j = 0; j < f.agingBreakdown.length; j++) {
|
| - |
|
1174 |
var b = f.agingBreakdown[j];
|
| - |
|
1175 |
var whFull = b.warehouseName || '';
|
| - |
|
1176 |
var vFull = b.vendorName || '';
|
| - |
|
1177 |
var whShort = whFull.indexOf('/') >= 0 ? whFull.substring(0, whFull.indexOf('/')) : whFull;
|
| - |
|
1178 |
var vShort = vFull.indexOf(' ') >= 0 ? vFull.substring(0, vFull.indexOf(' ')) : vFull;
|
| - |
|
1179 |
$rows.append(
|
| - |
|
1180 |
'<tr style="background:#fff9e6;color:#8a6d3b">' +
|
| - |
|
1181 |
'<td style="padding:4px 10px 4px 26px;border-bottom:1px solid #f1e0b0;font-size:11px">' +
|
| - |
|
1182 |
'<span style="color:#c19c00">↳ aging elsewhere:</span> ' +
|
| - |
|
1183 |
escapePmscp(whShort) + ' - (' + escapePmscp(vShort) + ')' +
|
| - |
|
1184 |
'</td>' +
|
| - |
|
1185 |
'<td style="padding:4px 10px;text-align:right;border-bottom:1px solid #f1e0b0;font-size:11px">' + (b.qty || 0) + '</td>' +
|
| - |
|
1186 |
'<td colspan="3" style="border-bottom:1px solid #f1e0b0"></td>' +
|
| - |
|
1187 |
'</tr>'
|
| - |
|
1188 |
);
|
| - |
|
1189 |
}
|
| - |
|
1190 |
}
|
| - |
|
1191 |
}
|
| - |
|
1192 |
$('#pmscpFlaggedCount').text(flagged.length);
|
| - |
|
1193 |
$('#pmscpFlaggedBox').show();
|
| - |
|
1194 |
} else {
|
| - |
|
1195 |
// Empty flaggedItems is unexpected when needsApproval=true, but hide
|
| - |
|
1196 |
// the table gracefully rather than showing an empty box.
|
| - |
|
1197 |
$('#pmscpFlaggedBox').hide();
|
| - |
|
1198 |
}
|
| - |
|
1199 |
$('#pmscpReasonModal').modal('show');
|
| - |
|
1200 |
return;
|
| - |
|
1201 |
}
|
| - |
|
1202 |
actuallyCreatePO(purchaseOrder);
|
| - |
|
1203 |
},
|
| - |
|
1204 |
error: function () {
|
| - |
|
1205 |
// Preflight failed for a reason unrelated to the rule — attempt
|
| - |
|
1206 |
// the create anyway; server will still enforce the rule if needed.
|
| - |
|
1207 |
actuallyCreatePO(purchaseOrder);
|
| - |
|
1208 |
}
|
| - |
|
1209 |
});
|
| - |
|
1210 |
}
|
| 986 |
|
1211 |
|
| 987 |
doPostAjaxRequestWithJsonHandler(context
|
1212 |
// The single, actual create request — fires once whether or not approval is needed.
|
| 988 |
+ "/createPurchaseOrder", JSON
|
1213 |
function actuallyCreatePO(purchaseOrder) {
|
| - |
|
1214 |
doPostAjaxRequestWithJsonHandler(context + "/createPurchaseOrder",
|
| 989 |
.stringify(purchaseOrder), function (response) {
|
1215 |
JSON.stringify(purchaseOrder), function (response) {
|
| 990 |
if (response == 'true') {
|
1216 |
if (response == 'true') {
|
| 991 |
alert("successfully created");
|
1217 |
alert("Successfully created");
|
| 992 |
loadCreatePurchase("main-content");
|
1218 |
loadCreatePurchase("main-content");
|
| 993 |
}
|
1219 |
}
|
| 994 |
});
|
1220 |
});
|
| 995 |
}
|
- |
|
| 996 |
|
- |
|
| 997 |
}
|
1221 |
}
|
| 998 |
|
1222 |
|
| 999 |
|
1223 |
|
| 1000 |
function editPurchase(sendPo, poid) {
|
1224 |
function editPurchase(sendPo, poid) {
|
| 1001 |
var editpurchaseOrder = {}
|
1225 |
var editpurchaseOrder = {}
|