Subversion Repositories SmartDukaan

Rev

Rev 36318 | Blame | Compare with Previous | Last modification | View Log | RSS feed

<style>
    .cpr-form {
        max-width: 960px;
    }

    .cpr-form label {
        display: block;
        margin-top: 10px;
        font-weight: 600;
        font-size: 13px;
    }

    .cpr-form input[type=text], .cpr-form input[type=number], .cpr-form select, .cpr-form textarea {
        width: 100%;
        padding: 6px;
        font-size: 13px;
        box-sizing: border-box;
    }

    .cpr-form textarea {
        min-height: 60px;
        font-family: monospace;
    }

    .cpr-form .inline-row {
        display: flex;
        gap: 8px;
        margin-top: 4px;
    }

    .cpr-form .inline-row input {
        flex: 1;
    }

    .cpr-form .inline-row .remove-row {
        flex: 0 0 auto;
        padding: 4px 10px;
    }

    .cpr-form .actions {
        margin-top: 16px;
    }

    .cpr-notice {
        background: #fcf8e3;
        border: 1px solid #faebcc;
        color: #8a6d3b;
        padding: 8px 12px;
        border-radius: 3px;
        font-size: 12px;
        margin-bottom: 12px;
    }

    .cpr-panel {
        border: 1px solid #ddd;
        padding: 10px;
        border-radius: 3px;
        margin-top: 12px;
        background: #fafafa;
    }

    .cpr-panel h4 {
        margin: 0 0 8px 0;
        font-size: 14px;
    }

    .cpr-add-btn {
        margin-top: 6px;
    }

    .cpr-vendor-wrap {
        position: relative;
    }

    .cpr-vendor-search {
        margin-bottom: 0;
    }

    .cpr-vendor-list {
        position: absolute;
        z-index: 10;
        width: 100%;
        max-height: 260px;
        overflow-y: auto;
        background: #fff;
        border: 1px solid #ccc;
        border-top: none;
        display: none;
        box-shadow: 0 2px 4px rgba(0, 0, 0, 0.08);
    }

    .cpr-vendor-list .item {
        padding: 6px 10px;
        cursor: pointer;
        font-size: 13px;
    }

    .cpr-vendor-list .item:hover, .cpr-vendor-list .item.active {
        background: #f0f0f0;
    }

    .cpr-vendor-selected {
        margin-top: 4px;
        padding: 6px 10px;
        background: #eafaf1;
        border: 1px solid #b7e0c7;
        border-radius: 3px;
        font-size: 13px;
    }

    .cpr-vendor-selected .clear-vendor {
        float: right;
        cursor: pointer;
        color: #888;
    }

    .cpr-tabs {
        display: flex;
        gap: 4px;
        margin-top: 12px;
        border-bottom: 1px solid #ddd;
    }

    .cpr-tabs button {
        background: #eee;
        border: 1px solid #ccc;
        border-bottom: none;
        padding: 6px 12px;
        cursor: pointer;
        font-size: 13px;
    }

    .cpr-tabs button.active {
        background: #fff;
        font-weight: 600;
    }

    .cpr-tab-body {
        display: none;
        border: 1px solid #ddd;
        border-top: none;
        padding: 10px;
        background: #fff;
    }

    .cpr-tab-body.active {
        display: block;
    }

    #bulk-preview table {
        width: 100%;
        font-size: 12px;
        border-collapse: collapse;
        margin-top: 6px;
    }

    #bulk-preview th, #bulk-preview td {
        border: 1px solid #ddd;
        padding: 4px 6px;
    }

    #bulk-preview th {
        background: #f0f0f0;
        text-align: left;
    }

    #bulk-invalid {
        color: #a94442;
        font-size: 12px;
        margin-top: 6px;
    }

    .bulk-grand {
        font-weight: 700;
        margin-top: 6px;
        font-size: 13px;
    }
</style>

<section class="wrapper">
    <div class="row">
        <div class="col-lg-12">
            <h3 class="page-header"><i class="icon_document_alt"></i>Create Purchase Return</h3>
            <ol class="breadcrumb">
                <li><i class="fa fa-home"></i><a href="${rc.contextPath}/dashboard">Home</a></li>
                <li><a href="javascript:void(0);" class="unsettled-purchase-returns">Unsettled Purchase Returns</a></li>
                <li><i class="icon_document_alt"></i>Create</li>
            </ol>
        </div>
    </div>

    <div class="row">
        <div class="col-lg-12 cpr-form">
            ##            <div class="cpr-notice">
            ##                Amount is computed from <code>lineitem.unitPrice</code>. Bad Purchase Return is not yet supported in
            ##                fofo &mdash; use the legacy app for BAD.
            ##            </div>

            <label for="cpr-bwh">Billing Warehouse Location</label>
            <select id="cpr-bwh" name="billingWarehouseId" required>
                <option value="">-- Select Warehouse --</option>
                #foreach($entry in $warehouseMap.entrySet())
                    <option value="$entry.getKey()">$entry.getKey() &mdash; $entry.getValue()</option>
                #end
            </select>

            <label for="cpr-reason-type">Purchase Return Reason Type</label>
            <select id="cpr-reason-type" name="reasonType">
                <option value="ACTUAL_PR" selected="selected">ACTUAL_PR</option>
                <option value="REPLACEMENT">REPLACEMENT</option>
            </select>

            <label for="cpr-reason-text">Reason Text</label>
            <textarea id="cpr-reason-text" name="reasonText" maxlength="1024" placeholder="Enter the reason"></textarea>

            <div class="cpr-tabs">
                <button type="button" class="cpr-tab active" data-tab="bulk">Bulk IMEI (auto-split by invoice)</button>
                <button type="button" class="cpr-tab" data-tab="manual">Single PR (manual entry)</button>
            </div>

            <!-- ========= BULK IMEI TAB ========= -->
            <div class="cpr-tab-body active" id="tab-bulk">
                <label>Paste comma-separated IMEIs (also accepts newlines)</label>
                <textarea id="bulk-imei-text" rows="5"
                          placeholder="863477088239385, 860552089650534, 864001081655170"></textarea>

                <label>Or upload a file (.txt / .csv — comma or newline separated)</label>
                <input type="file" id="bulk-imei-file"/>

                <div class="actions">
                    <button type="button" id="bulk-verify" class="btn btn-default">Verify</button>
                    <button type="button" id="bulk-create" class="btn btn-primary" disabled>Create Returns &amp; Debit
                        Notes
                    </button>
                </div>

                <div id="bulk-preview" style="margin-top:10px;"></div>
                <div id="bulk-invalid"></div>
            </div>

            <!-- ========= MANUAL TAB ========= -->
            <div class="cpr-tab-body" id="tab-manual">
                <form id="create-pr-form" enctype="multipart/form-data" onsubmit="return false;">
                    <label for="cpr-vendor-search">Vendor</label>
                    <div class="cpr-vendor-wrap">
                        <input type="text" id="cpr-vendor-search" class="cpr-vendor-search" autocomplete="off"
                               placeholder="Type vendor name to search..."/>
                        <div class="cpr-vendor-list" id="cpr-vendor-list">
                            #foreach($s in $suppliers)
                                <div class="item" data-id="$s.getId()" data-name="$s.getName()">$s.getName()
                                    (id=$s.getId())
                                </div>
                            #end
                        </div>
                        <input type="hidden" id="cpr-vendor" name="vendorId" required/>
                        <div class="cpr-vendor-selected" id="cpr-vendor-selected" style="display:none;">
                            <span class="clear-vendor" title="Clear">&times;</span>
                            <span class="selected-label"></span>
                        </div>
                    </div>

                    <label style="margin-top: 14px;">
                        <input type="checkbox" id="cpr-bad" name="badPurchaseReturn" value="on"/> Bad Purchase Return
                    </label>

                    <div class="cpr-panel">
                        <h4>Serialized Items (IMEI)</h4>
                        <div id="imei-container">
                            <div class="inline-row">
                                <input type="text" name="returnImeiNumber" class="imei-number"
                                       placeholder="IMEI Number"/>
                                <button type="button" class="btn btn-xs btn-default remove-row">&times;</button>
                            </div>
                        </div>
                        <button type="button" id="add-imei" class="btn btn-xs btn-default cpr-add-btn">+ Add IMEI
                        </button>

                        <label style="margin-top: 10px;">Or upload IMEI file</label>
                        <input type="file" id="cpr-file" name="upload"/>
                    </div>

                    <div class="cpr-panel">
                        <h4>Non-Serialized Items</h4>
                        <div id="nonser-container">
                            <div class="inline-row">
                                <input type="number" name="returnItemId" class="return-itemId" placeholder="Item Id"
                                       min="1" step="1"/>
                                <input type="number" name="returnQty" class="return-qty" placeholder="Quantity" min="1"
                                       step="1"/>
                                <button type="button" class="btn btn-xs btn-default remove-row">&times;</button>
                            </div>
                        </div>
                        <button type="button" id="add-nonser" class="btn btn-xs btn-default cpr-add-btn">+ Add Item
                        </button>
                    </div>

                    <div class="actions">
                        <button type="button" id="cpr-submit" class="btn btn-primary">Create Purchase Return</button>
                        <a href="javascript:void(0);" class="btn btn-default unsettled-purchase-returns">Cancel</a>
                    </div>
                </form>
            </div>
        </div>
    </div>
</section>

<script type="text/javascript">
    $(document).off('.createpr');

    // Tab switch
    $(document).on('click.createpr', '.cpr-tab', function () {
        var tab = $(this).data('tab');
        $('.cpr-tab').removeClass('active');
        $(this).addClass('active');
        $('.cpr-tab-body').removeClass('active');
        $('#tab-' + tab).addClass('active');
    });

    // ===== Vendor autocomplete =====
    function filterVendorList() {
        var q = $('#cpr-vendor-search').val().toLowerCase().trim();
        var jqList = $('#cpr-vendor-list');
        if (!q) {
            jqList.hide();
            return;
        }
        var matches = 0;
        jqList.find('.item').each(function () {
            var hay = ($(this).data('name') + ' ' + $(this).data('id')).toLowerCase();
            var show = hay.indexOf(q) >= 0;
            $(this).toggle(show);
            if (show) matches++;
        });
        jqList.toggle(matches > 0);
    }

    $(document).on('focus.createpr input.createpr', '#cpr-vendor-search', filterVendorList);
    $(document).on('click.createpr', '#cpr-vendor-list .item', function () {
        var id = $(this).data('id');
        var name = $(this).data('name');
        $('#cpr-vendor').val(id);
        $('#cpr-vendor-search').val('');
        $('#cpr-vendor-list').hide();
        $('#cpr-vendor-selected .selected-label').text(name + ' (id=' + id + ')');
        $('#cpr-vendor-selected').show();
    });
    $(document).on('click.createpr', '#cpr-vendor-selected .clear-vendor', function () {
        $('#cpr-vendor').val('');
        $('#cpr-vendor-selected').hide();
        $('#cpr-vendor-search').val('').focus();
    });
    $(document).on('click.createpr', function (e) {
        if (!$(e.target).closest('.cpr-vendor-wrap').length) {
            $('#cpr-vendor-list').hide();
        }
    });

    function escapeHtml(v) {
        if (v === null || v === undefined) return '';
        return String(v).replace(/&/g, '&amp;').replace(/</g, '&lt;').replace(/>/g, '&gt;').replace(/"/g, '&quot;').replace(/'/g, '&#39;');
    }

    function readImeiInput(callback) {
        var text = $('#bulk-imei-text').val() || '';
        var fileEl = $('#bulk-imei-file')[0];
        if (fileEl.files.length > 0) {
            var reader = new FileReader();
            reader.onload = function (e) {
                var combined = (text + '\n' + (e.target.result || '')).trim();
                callback(combined);
            };
            reader.readAsText(fileEl.files[0]);
        } else {
            callback(text.trim());
        }
    }

    $(document).on('click.createpr', '#bulk-verify', function () {
        var bwh = $('#cpr-bwh').val();
        if (!bwh) {
            alert('Please select a billing warehouse first.');
            return;
        }
        readImeiInput(function (imeiStr) {
            if (!imeiStr) {
                alert('Paste IMEIs or upload a file.');
                return;
            }
            $('#bulk-preview').html('Verifying...');
            $('#bulk-invalid').html('');
            $('#bulk-create').prop('disabled', true);

            $.ajax({
                type: 'POST',
                url: context + "/return/unsettled/verify-imeis",
                data: {imeis: imeiStr, billingWarehouseId: bwh},
                success: function (resp) {
                    var r = typeof resp === 'string' ? JSON.parse(resp) : resp;
                    renderBulkPreview(r);
                },
                error: function (xhr) {
                    $('#bulk-preview').html('');
                    $('#bulk-invalid').text('Verify failed: ' + (xhr.responseText || 'server error'));
                }
            });
        });
    });

    function renderBulkPreview(r) {
        var groups = r.groups || [];
        var invalid = r.invalidImeis || [];
        var html = '';
        if (groups.length === 0) {
            html = '<div class="alert alert-warning">No valid IMEI groups found.</div>';
        } else {
            html += '<div class="bulk-grand">' + groups.length + ' purchase return(s) will be created. Grand total: ' + escapeHtml(r.grandTotal) + '</div>';
            html += '<table><thead><tr><th>#</th><th>Invoice</th><th>Vendor</th><th>Qty</th><th>Items &amp; IMEIs</th><th>Total</th></tr></thead><tbody>';
            for (var i = 0; i < groups.length; i++) {
                var g = groups[i];
                var imeis = g.imeis || [];
                // Sub-group IMEIs by product name + unit price, so repeated items show as "Product x N"
                var subMap = {};
                var subOrder = [];
                imeis.forEach(function (e) {
                    var key = (e.productName || '(unknown)') + '||' + e.unitPrice;
                    if (!subMap[key]) {
                        subMap[key] = {name: e.productName || '(unknown)', unitPrice: e.unitPrice, imeis: []};
                        subOrder.push(key);
                    }
                    subMap[key].imeis.push(e.imei);
                });
                var subHtml = subOrder.map(function (k) {
                    var s = subMap[k];
                    return '<div><strong>' + escapeHtml(s.name) + '</strong> &times; ' + s.imeis.length
                            + ' @ ' + escapeHtml(s.unitPrice)
                            + '<br/><small style="color:#666;">' + s.imeis.map(escapeHtml).join(', ') + '</small></div>';
                }).join('<hr style="margin:4px 0;"/>');

                html += '<tr>'
                        + '<td>' + (i + 1) + '</td>'
                        + '<td>' + escapeHtml(g.invoiceNumber || '-') + '</td>'
                        + '<td>' + escapeHtml(g.vendorName) + ' (id=' + escapeHtml(g.vendorId) + ')</td>'
                        + '<td>' + imeis.length + '</td>'
                        + '<td>' + subHtml + '</td>'
                        + '<td>' + escapeHtml(g.totalAmount) + '</td>'
                        + '</tr>';
            }
            html += '</tbody></table>';
        }
        $('#bulk-preview').html(html);
        if (invalid.length > 0) {
            $('#bulk-invalid').html('<strong>Invalid IMEIs:</strong><br/>' + invalid.map(escapeHtml).join('<br/>'));
        } else {
            $('#bulk-invalid').html('');
        }
        $('#bulk-create').prop('disabled', groups.length === 0 || invalid.length > 0);
    }

    $(document).on('click.createpr', '#bulk-create', function () {
        var jqBtn = $(this);
        if (jqBtn.data('submitting')) {
            return;
        }
        var bwh = $('#cpr-bwh').val();
        var reasonType = $('#cpr-reason-type').val();
        var reasonText = $('#cpr-reason-text').val() || '';
        if (!bwh) {
            alert('Select billing warehouse.');
            return;
        }

        readImeiInput(function (imeiStr) {
            if (!imeiStr) {
                alert('No IMEIs to submit.');
                return;
            }
            if (!confirm('Create purchase returns + debit notes for these groups?')) return;

            jqBtn.data('submitting', true).prop('disabled', true);
            $.ajax({
                type: 'POST',
                url: context + "/return/unsettled/create-bulk",
                data: {imeis: imeiStr, billingWarehouseId: bwh, reasonType: reasonType, reasonText: reasonText},
                success: function (resp) {
                    var r = typeof resp === 'string' ? JSON.parse(resp) : resp;
                    if (r && r.status === true) {
                        var msg = 'Created ' + (r.created || []).length + ' purchase return(s) + debit note(s).';
                        (r.created || []).forEach(function (p) {
                            msg += '\nPR #' + p.purchaseReturnId + ' / DN #' + p.debitNoteId;
                        });
                        alert(msg);
                        doAjaxRequestHandler(context + "/return/unsettled", "GET", function (lr) {
                            $('#main-content').html(lr);
                        });
                    } else {
                        alert((r && r.message) ? r.message : 'Bulk create failed.');
                        jqBtn.data('submitting', false).prop('disabled', false);
                    }
                },
                error: function (xhr) {
                    var msg = 'Bulk create failed.';
                    try {
                        var body = JSON.parse(xhr.responseText);
                        if (body && body.response && body.response.message) msg = body.response.message;
                        else if (body && body.message) msg = body.message;
                    } catch (e) {
                        if (xhr.responseText) msg = xhr.responseText;
                    }
                    alert(msg);
                    jqBtn.data('submitting', false).prop('disabled', false);
                }
            });
        });
    });

    // ===== Manual tab (existing flow) =====
    $(document).on('click.createpr', '#add-imei', function () {
        $('#imei-container').append(
                '<div class="inline-row">' +
                '<input type="text" name="returnImeiNumber" class="imei-number" placeholder="IMEI Number"/>' +
                '<button type="button" class="btn btn-xs btn-default remove-row">&times;</button>' +
                '</div>');
    });
    $(document).on('click.createpr', '#add-nonser', function () {
        $('#nonser-container').append(
                '<div class="inline-row">' +
                '<input type="number" name="returnItemId" class="return-itemId" placeholder="Item Id" min="1" step="1"/>' +
                '<input type="number" name="returnQty" class="return-qty" placeholder="Quantity" min="1" step="1"/>' +
                '<button type="button" class="btn btn-xs btn-default remove-row">&times;</button>' +
                '</div>');
    });
    $(document).on('click.createpr', '#create-pr-form .remove-row', function () {
        var jqRow = $(this).closest('.inline-row');
        if (jqRow.siblings('.inline-row').length === 0) {
            jqRow.find('input').val('');
        } else {
            jqRow.remove();
        }
    });
    $(document).on('click.createpr', '#cpr-submit', function () {
        var jqBtn = $(this);
        if (jqBtn.data('submitting')) {
            return;
        }
        var vendorId = $('#cpr-vendor').val();
        var bwh = $('#cpr-bwh').val();
        if (!vendorId) {
            alert('Please select a vendor.');
            return;
        }
        if (!bwh) {
            alert('Please select a billing warehouse.');
            return;
        }

        jqBtn.data('submitting', true).prop('disabled', true).addClass('disabled');
        var formData = new FormData(document.getElementById('create-pr-form'));
        formData.append('billingWarehouseId', bwh);
        formData.append('reasonType', $('#cpr-reason-type').val());
        formData.append('reasonText', $('#cpr-reason-text').val() || '');
        $.ajax({
            type: 'POST', url: context + "/return/unsettled/create",
            data: formData, cache: false, contentType: false, processData: false,
            success: function (response) {
                var parsed = response;
                try {
                    parsed = typeof response === 'string' ? JSON.parse(response) : response;
                } catch (e) {
                }
                if (parsed && parsed.status === true) {
                    alert('Purchase Return created. ID: ' + parsed.purchaseReturnId);
                    doAjaxRequestHandler(context + "/return/unsettled", "GET", function (resp) {
                        $('#main-content').html(resp);
                    });
                } else {
                    alert((parsed && parsed.message) ? parsed.message : 'Failed to create.');
                    jqBtn.data('submitting', false).prop('disabled', false).removeClass('disabled');
                }
            },
            error: function (xhr) {
                var msg = 'Create failed.';
                try {
                    var body = JSON.parse(xhr.responseText);
                    if (body && body.response && body.response.message) msg = body.response.message;
                    else if (body && body.message) msg = body.message;
                } catch (e) {
                    if (xhr.responseText) msg = xhr.responseText;
                }
                alert(msg);
                jqBtn.data('submitting', false).prop('disabled', false).removeClass('disabled');
            }
        });
    });
</script>