/** * Utility functions for the ACC One Purchase Order application */ // /** // * Groups an array of objects by a specified key // * @param {Array} array - The array of objects to group // * @param {String} key - The key to group by // * @returns {Object} - An object with keys representing the grouped values and arrays as values // */ // export const groupBy = (array, key) => { // return array.reduce((result, item) => { // const groupKey = item[key]; // if (!result[groupKey]) { // result[groupKey] = []; // } // result[groupKey].push(item); // return result; // }, {}); // }; /** * Groups an array of objects by one or multiple keys * @param {Array} array - The array of objects to group * @param {Function|String|Array} key - The key(s) to group by. Can be a string, array of strings, or a function. * @returns {Object} - An object with keys representing the grouped values and arrays as values */ export const groupBy = (array, key) => { return array.reduce((result, item) => { let groupKey; // Handle single key (string), multiple keys (array), or custom function if (typeof key === 'function') { groupKey = key(item); } else if (Array.isArray(key)) { groupKey = key.map(k => item[k]).join('_'); // Combine multiple keys with underscore } else { groupKey = item[key]; } if (!result[groupKey]) { result[groupKey] = []; } result[groupKey].push(item); return result; }, {}); }; /** * Groups purchase request data by M_ItemID * Terjadi jika ada beberapa item yang sama, maka akan digabungkan berdasarkan M_ItemID dan PoItemUnitID * @param {Array} purchaseRequestData - Array of purchase request objects * @returns {Array} - Array of objects with M_ItemID, M_ItemCode, M_ItemDesc, unit information, converted PoQty, and Details array */ export const groupPurchaseRequestsByItemId = (purchaseRequestData) => { const grouped = groupBy(purchaseRequestData, ['M_ItemID', 'PoItemUnitID']); // console.log("grouped"); // console.log(grouped); return Object.keys(grouped).map(itemId => { const items = grouped[itemId]; const firstItem = items[0]; /* Sebelum Kalkulasi Konversi Item di BE */ // // Calculate total PoQty from all items // const totalPoQty = calculateTotalQuantity(items, 'PoQty'); // // Get the conversion factor from the first item // const conversionFactor = Number(firstItem.DefaultPurchase.UnitConvertAmount) || 1; // // Convert the total quantity and round up if it's a decimal // const convertedPoQty = Math.ceil(convertUnits(totalPoQty, conversionFactor)); /* Sesudah Kalkulasi Konversi Item di BE getItem() */ const totalPoQty = calculateTotalQuantity(items, 'PoQty') // console.log("Total PoQty for itemId", itemId, " : ", totalPoQty); // get price real or default supplier price const realPrice = Number(firstItem.RealPrice ?? firstItem.DefaultPurchase.SupplierPricePrice) || 0; // let price = 0; // calculagte price after discount let priceAfterDiscount = realPrice; if (parseFloat(firstItem.discount) > 0) { if (firstItem.discountType === 'P') { priceAfterDiscount = realPrice - (Number(firstItem.discount) * (realPrice / 100)); } if (firstItem.discountType === 'R') { priceAfterDiscount = realPrice - Number(firstItem.discount); } } else { priceAfterDiscount = realPrice || 0; } // Get the price from the first item // const realPrice = Number(firstItem.DefaultPurchase.SupplierPricePrice) || 0; // Calculate the total (PoQty * priceAfterDiscount) const total = totalPoQty * priceAfterDiscount; const itemParts = itemId.split('_'); const M_ItemID = itemParts[0]; return { keyID: itemId, M_ItemID: M_ItemID, PurchaseOrderID: firstItem.PurchaseOrderID ?? 0, PurchaseOrderSummaryID: firstItem.PurchaseOrderSummaryID ?? 0, M_ItemCode: firstItem.M_ItemCode, M_ItemDesc: firstItem.M_ItemDesc, discount: firstItem.discount, discountType: firstItem.discountType, PoItemUnitID: firstItem.DefaultPurchase.ItemUnitID, ItemUnitCode: firstItem.DefaultPurchase.ItemUnitCode, PoItemUnitName: firstItem.DefaultPurchase.ItemUnitName, TotalPoQty: totalPoQty, Price: priceAfterDiscount, RealPrice: realPrice, PriceAfterDiscount: priceAfterDiscount, Total: total, Details: items }; }); }; /** * Formats a number as currency (IDR) * @param {Number} amount - The amount to format * @returns {String} - Formatted currency string */ export const formatCurrency = (amount) => { return new Intl.NumberFormat('id-ID', { style: 'currency', currency: 'IDR', minimumFractionDigits: 0 }).format(amount); }; /** * Formats a date to a localized string * @param {Date|String} date - The date to format * @param {Object} options - Intl.DateTimeFormat options * @returns {String} - Formatted date string */ export const formatDate = (date, options = { year: 'numeric', month: 'long', day: 'numeric' }) => { if (!date) return ''; const dateObj = typeof date === 'string' ? new Date(date) : date; return new Intl.DateTimeFormat('id-ID', options).format(dateObj); }; /** * Calculates the total value of purchase requests * @param {Array} items - Array of purchase request items * @returns {Number} - Total value */ export const calculateTotal = (items) => { return items.reduce((sum, item) => sum + (Number(item.Total) || 0), 0); }; /** * Calculates the total quantity of items * @param {Array} items - Array of purchase request items * @param {String} qtyField - The field name containing quantity (e.g., 'PoQty') * @returns {Number} - Total quantity */ export const calculateTotalQuantity = (items, qtyField = 'PoQty') => { // console.log("Items in calculateTotalQuantity:", items); return items.reduce((sum, item) => sum + (Number(item[qtyField]) || 0), 0); }; /** * Converts units based on the conversion factor * @param {Number} quantity - The quantity to convert * @param {Number} conversionFactor - The conversion factor * @returns {Number} - Converted quantity */ export const convertUnits = (quantity, conversionFactor) => { return quantity / conversionFactor; }; /** * Filters purchase requests by branch * @param {Array} items - Array of purchase request items * @param {String|Array} branchCodes - Branch code(s) to filter by * @returns {Array} - Filtered items */ export const filterByBranch = (items, branchCodes) => { if (!branchCodes) return items; const codes = Array.isArray(branchCodes) ? branchCodes : [branchCodes]; return items.filter(item => codes.includes(item.BranchCode)); }; /** * Filters purchase requests by regional * @param {Array} items - Array of purchase request items * @param {String|Array} regionalIds - Regional ID(s) to filter by * @returns {Array} - Filtered items */ export const filterByRegional = (items, regionalIds) => { if (!regionalIds) return items; const ids = Array.isArray(regionalIds) ? regionalIds : [regionalIds]; return items.filter(item => ids.includes(item.S_RegionalID)); }; /** * Validates if a purchase request item has all required fields * @param {Object} item - Purchase request item * @returns {Boolean} - True if valid, false otherwise */ export const validatePurchaseRequestItem = (item) => { const requiredFields = [ 'M_ItemID', 'M_ItemCode', 'M_ItemDesc', 'Price', 'RequestQty' ]; return requiredFields.every(field => item[field] !== undefined && item[field] !== null); }; /** * Sorts purchase request items by a specified field * @param {Array} items - Array of purchase request items * @param {String} field - Field to sort by * @param {Boolean} ascending - Sort direction (true for ascending, false for descending) * @returns {Array} - Sorted items */ export const sortPurchaseRequests = (items, field = 'M_ItemDesc', ascending = true) => { return [...items].sort((a, b) => { const valueA = a[field]; const valueB = b[field]; if (typeof valueA === 'string' && typeof valueB === 'string') { return ascending ? valueA.localeCompare(valueB) : valueB.localeCompare(valueA); } return ascending ? (valueA - valueB) : (valueB - valueA); }); }; /** * Validates purchase order data before saving * @param {Object} context - Vuex context object * @returns {Boolean} - Returns true if validation passes, false otherwise */ export const validatePurchaseOrder = (context) => { if (context.state.loading) { let snackbar = { state: true, type: "warning", message: 'Loading sedang berlangsung harap menunggu ...', }; context.commit("update_snackbar", snackbar); return false; } let warningMsg = []; if (context.state.selectedItem.length == 0) { warningMsg.push('Belum memilih item'); } if (!context.state.refNumber || context.state.refNumber.trim() === '') { warningMsg.push('Nomor Referensi belum diisi'); } if (!context.state.selectedWarehouse || !context.state.selectedWarehouse.WarehouseID && context.state.warehouseType !== 'Multiple') { warningMsg.push('Tipe Gudang Single Harus memilih Gudang'); } if (!context.state.selectedSupplier || !context.state.selectedSupplier.SupplierID) { warningMsg.push('Supplier belum dipilih'); } if (context.state.discountType === 'Percentage') { const discountValue = (context.state.itemTotal.total * context.state.discountAmount) / 100; if (discountValue > context.state.itemTotal.total) { warningMsg.push('Diskon tidak boleh melebihi total'); } } else if (context.state.discountType === 'Absolute') { if (context.state.discountAmount > context.state.itemTotal.total) { warningMsg.push('Diskon tidak boleh melebihi total'); } } context.state.summary.forEach(element => { if (Number(element.discount) < 0) { warningMsg.push('Diskon ' + element.M_ItemDesc + ' tidak boleh kurang dari 0'); } if (element.discountType === 'P') { if (Number(element.discount) > 100) { warningMsg.push('Diskon per item ' + element.M_ItemDesc + ' tidak boleh melebihi 100'); } } if (element.discountType === 'R') { if (Number(element.discount) > Number(element.RealPrice)) { warningMsg.push('Diskon per item ' + element.M_ItemDesc + ' tidak boleh melebihi dari harga'); } } }); if (warningMsg.length > 0) { let snackbar = { state: true, type: "warning", message: warningMsg.join(", "), }; context.commit("update_snackbar", snackbar); return false; } return true; }; /** * Menghitung total summary untuk purchase order * @param {Array} summary - Array data summary * @param {Number} taxPpn - Persentase pajak PPN * @param {Number} taxPph - Persentase pajak PPH * @param {String} discountType - Tipe diskon ('Percentage' atau 'Absolute') * @param {Number} discountAmount - Jumlah diskon * @returns {Object} - Object summaryTotal */ export const calculateSummaryTotal = (summary, typePpnSelected = 'Percentage', taxPpn = 0, taxPph = 0, discountType = 'Percentage', discountAmount = 0) => { if (!summary || summary.length === 0) { return { total: 0, tax: 0, ppn: 0, pph: 0, discount: 0, finalTotal: 0, }; } // Hitung total dari item summary const total = summary.reduce((acc, item) => acc + (Number(item.Total) || 0), 0); // Hitung diskon berdasarkan tipe let discount = discountAmount; if (discountType === 'Percentage') { discount = (discountAmount / 100) * total; } // Hitung pajak const newTotal = total - discount; let ppn = taxPpn; if (typePpnSelected === 'Percentage' && taxPpn > 0) { ppn = (taxPpn / 100) * newTotal; } const pph = (taxPph / 100) * newTotal; const tax = ppn + pph; const finalTotal = newTotal + tax; // Return object summaryTotal return { total: total, tax: tax, ppn: ppn, pph: pph, discount: discount, finalTotal: finalTotal, }; }; /** * Menghapus item dari summary dan selectedItem berdasarkan itemId atau detailId * @param {Array} summary - Array data summary * @param {Array} selectedItem - Array data selectedItem * @param {String} itemId - ID item yang akan dihapus (opsional) * @param {String} detailId - ID detail yang akan dihapus (opsional) * @returns {Object} - Object berisi summary dan selectedItem yang sudah diupdate */ export const deleteItemFromSummary = (summary, selectedItem, itemId = null, detailId = null) => { // Jika itemId ada, hapus seluruh item dan detailsnya if (itemId) { const newSummary = summary.filter(item => item.M_ItemID !== itemId); const newSelectedItem = selectedItem.filter(item => item.M_ItemID !== itemId); return { summary: newSummary, selectedItem: newSelectedItem }; } // Jika detailId ada, hapus detail spesifik if (detailId) { // Hapus dari selectedItem const newSelectedItem = selectedItem.filter(item => item.PurchaseRequestFlagID !== detailId); // Regenerate summary dari selectedItem yang baru const newSummary = groupPurchaseRequestsByItemId(newSelectedItem); return { summary: newSummary, selectedItem: newSelectedItem }; } return { summary, selectedItem }; };