| 1 | @model StockMaster.ViewModels.PurchaseOrderCreateViewModel
|
|---|
| 2 | @{
|
|---|
| 3 | ViewData["Title"] = "New Purchase Order";
|
|---|
| 4 | }
|
|---|
| 5 |
|
|---|
| 6 | <div class="row mb-4">
|
|---|
| 7 | <div class="col-12">
|
|---|
| 8 | <h2><i class="fas fa-truck"></i> Create New Purchase Order</h2>
|
|---|
| 9 | <nav aria-label="breadcrumb">
|
|---|
| 10 | <ol class="breadcrumb">
|
|---|
| 11 | <li class="breadcrumb-item"><a href="/">Home</a></li>
|
|---|
| 12 | <li class="breadcrumb-item"><a href="/PurchaseOrder/Index">Purchase Orders</a></li>
|
|---|
| 13 | <li class="breadcrumb-item active">New Order</li>
|
|---|
| 14 | </ol>
|
|---|
| 15 | </nav>
|
|---|
| 16 | </div>
|
|---|
| 17 | </div>
|
|---|
| 18 |
|
|---|
| 19 | <form id="poForm" method="post" action="/PurchaseOrder/Create">
|
|---|
| 20 | @Html.AntiForgeryToken()
|
|---|
| 21 | <div class="row">
|
|---|
| 22 | <div class="col-md-8">
|
|---|
| 23 | <div class="card mb-3">
|
|---|
| 24 | <div class="card-header">
|
|---|
| 25 | <i class="fas fa-info-circle"></i> Order Information
|
|---|
| 26 | </div>
|
|---|
| 27 | <div class="card-body">
|
|---|
| 28 | <div class="row">
|
|---|
| 29 | <div class="col-md-4 mb-3">
|
|---|
| 30 | <label class="form-label">Supplier</label>
|
|---|
| 31 | <select name="SupplierId" class="form-select">
|
|---|
| 32 | <option value="">Select supplier</option>
|
|---|
| 33 | @foreach (var supplier in ViewBag.Suppliers)
|
|---|
| 34 | {
|
|---|
| 35 | <option value="@supplier.SupplierId">@supplier.Name</option>
|
|---|
| 36 | }
|
|---|
| 37 | </select>
|
|---|
| 38 | </div>
|
|---|
| 39 |
|
|---|
| 40 | <div class="col-md-4 mb-3">
|
|---|
| 41 | <label class="form-label">Warehouse *</label>
|
|---|
| 42 | <select name="WarehouseId" class="form-select" required>
|
|---|
| 43 | <option value="">Select warehouse</option>
|
|---|
| 44 | @foreach (var warehouse in ViewBag.Warehouses)
|
|---|
| 45 | {
|
|---|
| 46 | <option value="@warehouse.WarehouseId">@warehouse.Name</option>
|
|---|
| 47 | }
|
|---|
| 48 | </select>
|
|---|
| 49 | </div>
|
|---|
| 50 |
|
|---|
| 51 | <div class="col-md-4 mb-3">
|
|---|
| 52 | <label class="form-label">Expected Delivery Date *</label>
|
|---|
| 53 | <input type="date" name="ExpectedDeliveryDate" class="form-control"
|
|---|
| 54 | min="@DateTime.Now.ToString("yyyy-MM-dd")"
|
|---|
| 55 | value="@DateTime.Now.AddDays(7).ToString("yyyy-MM-dd")" required />
|
|---|
| 56 | </div>
|
|---|
| 57 | </div>
|
|---|
| 58 | </div>
|
|---|
| 59 | </div>
|
|---|
| 60 |
|
|---|
| 61 | <div class="card">
|
|---|
| 62 | <div class="card-header d-flex justify-content-between align-items-center">
|
|---|
| 63 | <span><i class="fas fa-list"></i> Order Items</span>
|
|---|
| 64 | <button type="button" class="btn btn-sm btn-success" onclick="addItem()">
|
|---|
| 65 | <i class="fas fa-plus"></i> Add Product
|
|---|
| 66 | </button>
|
|---|
| 67 | </div>
|
|---|
| 68 | <div class="card-body">
|
|---|
| 69 | <div id="itemsContainer">
|
|---|
| 70 |
|
|---|
| 71 | </div>
|
|---|
| 72 |
|
|---|
| 73 |
|
|---|
| 74 | @if (ViewBag.Products == null || ViewBag.Products.Count == 0)
|
|---|
| 75 | {
|
|---|
| 76 | <div class="alert alert-warning">
|
|---|
| 77 | <i class="fas fa-exclamation-triangle"></i>
|
|---|
| 78 | No products available. Please add products first.
|
|---|
| 79 | </div>
|
|---|
| 80 | }
|
|---|
| 81 | </div>
|
|---|
| 82 | </div>
|
|---|
| 83 | </div>
|
|---|
| 84 |
|
|---|
| 85 | <div class="col-md-4">
|
|---|
| 86 | <div class="card mb-3">
|
|---|
| 87 | <div class="card-header">
|
|---|
| 88 | <i class="fas fa-calculator"></i> Order Summary
|
|---|
| 89 | </div>
|
|---|
| 90 | <div class="card-body">
|
|---|
| 91 | <div class="mb-3">
|
|---|
| 92 | <div class="d-flex justify-content-between mb-2">
|
|---|
| 93 | <span>Product Types:</span>
|
|---|
| 94 | <strong id="itemCount">0</strong>
|
|---|
| 95 | </div>
|
|---|
| 96 | <div class="d-flex justify-content-between mb-2">
|
|---|
| 97 | <span>Total Quantity:</span>
|
|---|
| 98 | <strong id="totalQuantity">0</strong>
|
|---|
| 99 | </div>
|
|---|
| 100 | <hr>
|
|---|
| 101 | <div class="d-flex justify-content-between">
|
|---|
| 102 | <h5>Total Cost:</h5>
|
|---|
| 103 | <h5 class="text-primary">
|
|---|
| 104 | <strong id="totalCost">0.00 MKD</strong>
|
|---|
| 105 | </h5>
|
|---|
| 106 | </div>
|
|---|
| 107 | </div>
|
|---|
| 108 | </div>
|
|---|
| 109 | </div>
|
|---|
| 110 |
|
|---|
| 111 | <div class="card mb-3">
|
|---|
| 112 | <div class="card-header">
|
|---|
| 113 | <i class="fas fa-lightbulb"></i> Tips
|
|---|
| 114 | </div>
|
|---|
| 115 | <div class="card-body">
|
|---|
| 116 | <ul class="list-unstyled mb-0">
|
|---|
| 117 | <li class="mb-2">
|
|---|
| 118 | <i class="fas fa-check text-success me-2"></i>
|
|---|
| 119 | Check supplier information
|
|---|
| 120 | </li>
|
|---|
| 121 | <li class="mb-2">
|
|---|
| 122 | <i class="fas fa-check text-success me-2"></i>
|
|---|
| 123 | Set correct delivery date
|
|---|
| 124 | </li>
|
|---|
| 125 | <li class="mb-2">
|
|---|
| 126 | <i class="fas fa-check text-success me-2"></i>
|
|---|
| 127 | Review unit costs
|
|---|
| 128 | </li>
|
|---|
| 129 | <li>
|
|---|
| 130 | <i class="fas fa-check text-success me-2"></i>
|
|---|
| 131 | Verify stock levels
|
|---|
| 132 | </li>
|
|---|
| 133 | </ul>
|
|---|
| 134 | </div>
|
|---|
| 135 | </div>
|
|---|
| 136 |
|
|---|
| 137 | <div class="card">
|
|---|
| 138 | <div class="card-body">
|
|---|
| 139 | <div class="d-grid gap-2">
|
|---|
| 140 | <button type="submit" class="btn btn-primary btn-lg">
|
|---|
| 141 | <i class="fas fa-check"></i> Create Order
|
|---|
| 142 | </button>
|
|---|
| 143 | <a href="/PurchaseOrder/Index" class="btn btn-secondary">
|
|---|
| 144 | <i class="fas fa-times"></i> Cancel
|
|---|
| 145 | </a>
|
|---|
| 146 | </div>
|
|---|
| 147 | </div>
|
|---|
| 148 | </div>
|
|---|
| 149 | </div>
|
|---|
| 150 | </div>
|
|---|
| 151 | </form>
|
|---|
| 152 |
|
|---|
| 153 |
|
|---|
| 154 | <div id="itemTemplate" style="display: none;">
|
|---|
| 155 | <div class="card mb-3 item-row">
|
|---|
| 156 | <div class="card-body">
|
|---|
| 157 | <div class="row">
|
|---|
| 158 | <div class="col-md-5">
|
|---|
| 159 | <label class="form-label">Product *</label>
|
|---|
| 160 | <select class="form-select product-select" required>
|
|---|
| 161 | <option value="">Select product</option>
|
|---|
| 162 | @foreach (var product in ViewBag.Products)
|
|---|
| 163 | {
|
|---|
| 164 | <option value="@product.ProductId" data-sku="@product.Sku">
|
|---|
| 165 | @product.Name (SKU: @product.Sku)
|
|---|
| 166 | </option>
|
|---|
| 167 | }
|
|---|
| 168 | </select>
|
|---|
| 169 | </div>
|
|---|
| 170 | <div class="col-md-3">
|
|---|
| 171 | <label class="form-label">Quantity *</label>
|
|---|
| 172 | <input type="number" class="form-control quantity-input" min="1" value="1" required />
|
|---|
| 173 | </div>
|
|---|
| 174 | <div class="col-md-3">
|
|---|
| 175 | <label class="form-label">Unit Cost (MKD) *</label>
|
|---|
| 176 | <input type="number" class="form-control cost-input" step="0.01" min="0" required />
|
|---|
| 177 | </div>
|
|---|
| 178 | <div class="col-md-1">
|
|---|
| 179 | <label class="form-label"> </label>
|
|---|
| 180 | <button type="button" class="btn btn-danger w-100" onclick="removeItem(this)">
|
|---|
| 181 | <i class="fas fa-trash"></i>
|
|---|
| 182 | </button>
|
|---|
| 183 | </div>
|
|---|
| 184 | </div>
|
|---|
| 185 | <div class="row mt-2">
|
|---|
| 186 | <div class="col-12">
|
|---|
| 187 | <small class="text-muted">
|
|---|
| 188 | Total: <span class="item-total">0.00</span> MKD
|
|---|
| 189 | </small>
|
|---|
| 190 | </div>
|
|---|
| 191 | </div>
|
|---|
| 192 | </div>
|
|---|
| 193 | </div>
|
|---|
| 194 | </div>
|
|---|
| 195 |
|
|---|
| 196 | @section Scripts {
|
|---|
| 197 | <script>
|
|---|
| 198 | function addItem() {
|
|---|
| 199 | const template = document.getElementById('itemTemplate').innerHTML;
|
|---|
| 200 | const container = document.getElementById('itemsContainer');
|
|---|
| 201 | const itemDiv = document.createElement('div');
|
|---|
| 202 |
|
|---|
| 203 | itemDiv.innerHTML = template;
|
|---|
| 204 |
|
|---|
| 205 | const row = itemDiv.firstElementChild;
|
|---|
| 206 | container.appendChild(row);
|
|---|
| 207 |
|
|---|
| 208 | const productSelect = row.querySelector('.product-select');
|
|---|
| 209 | const quantityInput = row.querySelector('.quantity-input');
|
|---|
| 210 | const costInput = row.querySelector('.cost-input');
|
|---|
| 211 |
|
|---|
| 212 |
|
|---|
| 213 | productSelect.addEventListener('change', function() {
|
|---|
| 214 | calculateItemTotal.call(this);
|
|---|
| 215 | });
|
|---|
| 216 | quantityInput.addEventListener('input', function() {
|
|---|
| 217 | calculateItemTotal.call(this);
|
|---|
| 218 | calculateSummary();
|
|---|
| 219 | });
|
|---|
| 220 | costInput.addEventListener('input', function() {
|
|---|
| 221 | calculateItemTotal.call(this);
|
|---|
| 222 | calculateSummary();
|
|---|
| 223 | });
|
|---|
| 224 |
|
|---|
| 225 |
|
|---|
| 226 | updateIndices();
|
|---|
| 227 | calculateSummary();
|
|---|
| 228 | }
|
|---|
| 229 |
|
|---|
| 230 | function removeItem(button) {
|
|---|
| 231 | button.closest('.item-row').remove();
|
|---|
| 232 | updateIndices();
|
|---|
| 233 | calculateSummary();
|
|---|
| 234 | }
|
|---|
| 235 |
|
|---|
| 236 |
|
|---|
| 237 | function updateIndices() {
|
|---|
| 238 | document.querySelectorAll('#itemsContainer .item-row').forEach((row, index) => {
|
|---|
| 239 | const productSelect = row.querySelector('.product-select');
|
|---|
| 240 | const quantityInput = row.querySelector('.quantity-input');
|
|---|
| 241 | const costInput = row.querySelector('.cost-input');
|
|---|
| 242 |
|
|---|
| 243 | productSelect.name = `Items[${index}].ProductId`;
|
|---|
| 244 | quantityInput.name = `Items[${index}].Quantity`;
|
|---|
| 245 | costInput.name = `Items[${index}].UnitCost`;
|
|---|
| 246 | });
|
|---|
| 247 | }
|
|---|
| 248 |
|
|---|
| 249 | function calculateItemTotal() {
|
|---|
| 250 | const row = this.closest('.item-row');
|
|---|
| 251 | const quantity = parseFloat(row.querySelector('.quantity-input').value) || 0;
|
|---|
| 252 | const cost = parseFloat(row.querySelector('.cost-input').value) || 0;
|
|---|
| 253 | const total = quantity * cost;
|
|---|
| 254 | row.querySelector('.item-total').textContent = total.toFixed(2);
|
|---|
| 255 | }
|
|---|
| 256 |
|
|---|
| 257 | function calculateSummary() {
|
|---|
| 258 | const items = document.querySelectorAll('#itemsContainer .item-row');
|
|---|
| 259 | let totalCost = 0;
|
|---|
| 260 | let totalQuantity = 0;
|
|---|
| 261 |
|
|---|
| 262 | items.forEach(row => {
|
|---|
| 263 | const quantity = parseFloat(row.querySelector('.quantity-input').value) || 0;
|
|---|
| 264 | const cost = parseFloat(row.querySelector('.cost-input').value) || 0;
|
|---|
| 265 | totalCost += quantity * cost;
|
|---|
| 266 | totalQuantity += quantity;
|
|---|
| 267 | });
|
|---|
| 268 |
|
|---|
| 269 | document.getElementById('itemCount').textContent = items.length;
|
|---|
| 270 | document.getElementById('totalQuantity').textContent = totalQuantity;
|
|---|
| 271 | document.getElementById('totalCost').textContent = totalCost.toFixed(2) + ' MKD';
|
|---|
| 272 | }
|
|---|
| 273 |
|
|---|
| 274 | window.addEventListener('DOMContentLoaded', function() {
|
|---|
| 275 | addItem();
|
|---|
| 276 | });
|
|---|
| 277 |
|
|---|
| 278 | document.getElementById('poForm').addEventListener('submit', function(e) {
|
|---|
| 279 | const itemsCount = document.querySelectorAll('#itemsContainer .item-row').length;
|
|---|
| 280 | if (itemsCount === 0) {
|
|---|
| 281 | e.preventDefault();
|
|---|
| 282 | alert('You must add at least one product!');
|
|---|
| 283 | return false;
|
|---|
| 284 | }
|
|---|
| 285 |
|
|---|
| 286 | let allValid = true;
|
|---|
| 287 | document.querySelectorAll('#itemsContainer .item-row').forEach(row => {
|
|---|
| 288 | const productSelect = row.querySelector('.product-select');
|
|---|
| 289 | if (!productSelect.value) {
|
|---|
| 290 | allValid = false;
|
|---|
| 291 | productSelect.classList.add('is-invalid');
|
|---|
| 292 | } else {
|
|---|
| 293 | productSelect.classList.remove('is-invalid');
|
|---|
| 294 | }
|
|---|
| 295 | });
|
|---|
| 296 |
|
|---|
| 297 | if (!allValid) {
|
|---|
| 298 | e.preventDefault();
|
|---|
| 299 | alert('Please select products for all items!');
|
|---|
| 300 | return false;
|
|---|
| 301 | }
|
|---|
| 302 |
|
|---|
| 303 | return true;
|
|---|
| 304 | });
|
|---|
| 305 | </script>
|
|---|
| 306 | } |
|---|