Subversion Repositories SmartDukaan

Rev

Rev 37723 | Rev 37770 | Go to most recent revision | Show entire file | Ignore whitespace | Details | Blame | Last modification | View Log | RSS feed

Rev 37723 Rev 37750
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, '&amp;').replace(/</g, '&lt;').replace(/>/g, '&gt;')
-
 
1024
        .replace(/"/g, '&quot;').replace(/'/g, '&#39;');
-
 
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">&#8627; 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 = {}