Files
fe-accone/test/vuex/acc-one-approve-po/components/poForm.vue

1510 lines
51 KiB
Vue

<template>
<div>
<!-- DIALOG TAMBAH ITEM -->
<v-dialog v-model="dialogItem" persistent width="1000">
<v-card>
<v-card-title class="headline grey lighten-2" primary-title>
TAMBAH ITEM
</v-card-title>
<v-card-text>
<v-text-field v-model="searchItem" label="Cari Item" class="mb-2" outline hide-details></v-text-field>
<v-data-table v-model="selectedItemTemp" :headers="HeadersItem" :items="itemList" :loading="loading"
hide-actions select-all item-key="PurchaseRequestFlagID" class="elevation-1">
<template v-slot:headers="props">
<tr>
<th width="5%" class="blue lighten-3 white--text">
<v-checkbox :input-value="props.all" :indeterminate="props.indeterminate" primary
hide-details></v-checkbox>
</th>
<th v-for="header in props.headers" :key="header.text" :width="header.width" :class="header.class">
{{ header.text }}
</th>
</tr>
</template>
<template v-slot:items="props">
<tr
:active="props.selected"
@click="props.item.UnitReqPoConvertStatus !== 'failed' ? props.selected = !props.selected : null"
:class="{'grey lighten-3': props.item.UnitReqPoConvertStatus === 'failed'}" >
<td>
<v-checkbox
:input-value="props.selected"
:disabled="props.item.UnitReqPoConvertStatus === 'failed'"
primary
hide-details
></v-checkbox>
</td>
<td>{{ props.item.PurchaseRequestNumber }}</td>
<td>{{ props.item.M_ItemCode }}</td>
<td class="">{{ props.item.M_ItemDesc }}</td>
<td class="">
{{ props.item.M_BranchName ?? props.item.S_RegionalName }}
</td>
<td class="text-xs-center">
{{ props.item.PoItemUnitName }}
</td>
<td class="text-xs-center">
{{ props.item.RequestQty }} {{ props.item.ReqItemUnitName }}
</td>
</tr>
<!-- Error message card for failed unit conversion -->
<tr v-if="props.item.UnitReqPoConvertStatus === 'failed'" class="red lighten-5">
<td colspan="7" class="pa-0">
<v-card flat class="red lighten-5 ma-0 py-1 px-2">
<span class="red--text text--darken-2">
<v-icon small color="red darken-2">warning</v-icon>
{{ props.item.UnitReqPoConvertMsg }}
</span>
</v-card>
</td>
</tr>
</template>
</v-data-table>
<!-- CARD ITEM YANG SEBELUMNYA DIHAPUS -->
<v-card class="text-xs-left pa-2 mb-3">
<v-pagination v-model="currentPageItem" :length="totalPageItem"></v-pagination>
<p style="font-weight: bold;" class="subheader" v-if="deletedItem.length > 0">
ITEM YANG SEBELUMNYA DIHAPUS
</p>
<v-data-table v-model="selectedItemTemp" :headers="HeadersItem" v-if="deletedItem.length > 0"
:items="deletedItem" :loading="loading" hide-actions select-all item-key="PurchaseRequestFlagID"
class="elevation-1">
<template v-slot:headers="props">
<tr>
<th width="5%" class="blue lighten-3 white--text">
<v-checkbox :input-value="props.all" :indeterminate="props.indeterminate" primary
hide-details></v-checkbox>
<!-- @click.stop="toggleAll" -->
</th>
<th v-for="header in props.headers" :key="header.text" :width="header.width" :class="header.class">
{{ header.text }}
</th>
</tr>
</template>
<template v-slot:items="props">
<tr :active="props.selected" @click="props.selected = !props.selected">
<td>
<v-checkbox :input-value="props.selected" primary hide-details></v-checkbox>
</td>
<td>{{ props.item.PurchaseRequestNumber }}</td>
<td>{{ props.item.M_ItemCode }}</td>
<td class="">{{ props.item.M_ItemDesc }}</td>
<td class="">
{{ props.item.M_BranchName ?? props.item.S_RegionalName }}
</td>
<td class="text-xs-center">
{{ props.item.PoItemUnitName }}
</td>
<td class="text-xs-center">
{{ props.item.RequestQty }} {{ props.item.ReqItemUnitName }}
</td>
</tr>
</template>
</v-data-table>
</v-card>
</v-card-text>
<v-divider></v-divider>
<v-card-actions>
<v-spacer></v-spacer>
<v-btn color="error" flat @click="dialogItem = false">
Tutup
</v-btn>
<v-btn color="green" flat @click="addItem()">
Tambahkan
</v-btn>
</v-card-actions>
</v-card>
</v-dialog>
<!-- DIALOG FORM UTAMA -->
<v-dialog v-model="dialogForm" persistent width="98vw">
<v-card>
<v-card-title class="headline grey lighten-2" primary-title>
FORM PURCHASE ORDER
<v-spacer></v-spacer>
<div v-if="dialogFormAct === 'EDIT'">
<kbd>{{ selectedPO.PurchaseOrderNumber ?? "" }}</kbd>
</div>
</v-card-title>
<v-progress-linear :indeterminate="true" v-if="loading"></v-progress-linear>
<v-card-text>
<v-layout row wrap class="px-2 py-2">
<!-- HEADER FORM PO -->
<v-layout row wrap class="pa-0 ma-0">
<v-flex xs6>
<v-menu v-model="menuselectedDateForm" :close-on-content-click="false" transition="scale-transition"
:nudge-right="40" lazy offset-y max-width="290px" min-width="290px">
<template v-slot:activator="{ on }">
<v-text-field :value="selectedDateFormFormatted" label="Tanggal" readonly :readonly="loading"
hide-details class="mr-2" outline hide-details v-on="on"></v-text-field>
</template>
<v-date-picker no-title hide-details v-model="selectedDateForm"
@input="menuselectedDateForm = false"></v-date-picker>
</v-menu>
</v-flex>
<v-flex xs6>
<v-text-field v-model="refNumber" label="Nomor Referensi *" class="mr-2" :readonly="loading" outline
required hide-details>
</v-text-field>
</v-flex>
<v-flex xs3>
<v-text-field type="number" v-model="taxPpn" label="Pajak Pendapatan (PPN)" class="mr-2"
:readonly="loading" outline suffix="%" hide-details>
</v-text-field>
</v-flex>
<v-flex xs3>
<v-text-field type="number" v-model="taxPph" label="Pajak Penghasilan (PPH)" class="mr-2" outline
:readonly="loading" suffix="%" hide-details>
</v-text-field>
</v-flex>
<v-flex xs3>
<v-autocomplete v-model="selectedSupplier" :loading="loadingAutocomplete" :items="supplierList"
:search-input.sync="searchSupplier" cache-items class="mr-2" return-object :readonly="loading"
item-text="SupplierName" flat hide-no-data hide-details label="Vendor" outline></v-autocomplete>
</v-flex>
<v-flex xs3>
<v-autocomplete v-model="selectedItemCategory" :loading="loadingAutocomplete" :items="itemCategoryList"
cache-items class="mr-2" return-object :readonly="loading" :disabled="selectedItem.length > 0"
item-text="itemCategoryName" flat hide-no-data hide-details label="Kategori Item"
outline></v-autocomplete>
</v-flex>
<v-flex xs6>
<v-text-field v-model="formattedPaymentTerm" :readonly="loading" label="Jangka Waktu Pembayaran"
class="mr-2 mt-3" outline suffix="Hari" hide-details>
</v-text-field>
</v-flex>
<v-flex xs2>
<v-select :items="discountTypeList" label="Tipe Diskon" hide-details :readonly="loading"
class="mini-select mr-2 mt-3" v-model="discountType" outline></v-select>
</v-flex>
<v-flex xs4>
<v-text-field :prefix="discountType === 'Absolute' ? 'Rp' : ''"
:suffix="discountType === 'Percentage' ? '%' : ''"
:type="discountType === 'Absolute' ? 'text' : 'number'" v-model="formattedDiscountAmount"
:label="discountType === 'Percentage' ? 'Diskon' : ''" :readonly="loading" class="mt-3" outline
hide-details>
</v-text-field>
</v-flex>
<v-flex xs3>
<v-select :items="warehouseTypeList" label="Tipe Gudang"
:readonly="selectedItem.length > 0 && loading"
:disabled="selectedItem.length > 0 && !canHaveMultipleWarehouses"
class="mini-select mb-0 mr-2 mt-3" v-model="warehouseType" outline></v-select>
</v-flex>
<v-flex xs3>
<v-autocomplete v-model="selectedWarehouse" :loading="loadingAutocomplete" :items="warehouseList"
return-object :readonly="loading || warehouseType === 'Multiple'" :disabled="warehouseType === 'Multiple'"
class="mini-select mr-2 mt-3" item-text="WarehouseName" flat outline hide-no-data
hide-details label="Gudang"></v-autocomplete>
</v-flex>
<v-flex xs4>
<v-text-field prefix="Rp" v-model="formattedShippingCost" label="Biaya Ekspedisi"
:readonly="loading" class="mr-2 mt-3" outline hide-details>
</v-text-field>
</v-flex>
<v-flex xs2 class="text-xs-center">
<v-checkbox v-model="shippingCostStatus" class="mr-2 mt-3" label="Biaya ekspedisi di bayar di muka"
:readonly="loading" hide-details></v-checkbox>
</v-flex>
<v-flex xs-12 class="mt-0 pt-0">
<div v-if="selectedItem.length > 0 && !canHaveMultipleWarehouses" class="caption info--text mt-0 mb-2 pt-0 text-small">
{{ isValidMultipleWarehouse.message }}
</div>
</v-flex>
<v-flex xs12>
<v-textarea outline v-model="note" auto-grow hide-details :readonly="loading" rows="2"
label="Keterangan"></v-textarea>
<v-divider class="my-3"></v-divider>
</v-flex>
<v-flex xs3 class="mb-3">
<v-btn block color="info"
:disabled="isObjectEmpty(selectedSupplier) || isObjectEmpty(selectedItemCategory)"
@click="openDialogAddItem()">TAMBAHKAN ITEM</v-btn>
</v-flex>
</v-layout>
<!-- SUMMARY ITEM PO -->
<v-flex xs12>
<v-data-table v-if="summary.length > 0" :headers="summaryHeaders" :items="summary" :loading="loading"
hide-actions class="elevation-1 mt-4" expand item-key="keyID">
<template slot="items" slot-scope="props">
<tr>
<td class="text-xs-center" @click="props.expanded = !props.expanded">
<v-icon>{{
props.expanded
? "keyboard_arrow_down"
: "keyboard_arrow_right"
}}</v-icon>
</td>
<td class="" @click="props.expanded = !props.expanded">
<kbd>{{ props.item.M_ItemCode }}</kbd>
{{ props.item.M_ItemDesc }}
</td>
<td class="text-xs-center" @click="props.expanded = !props.expanded">
{{ props.item.TotalPoQty }}
{{ props.item.PoItemUnitName }}
</td>
<td class="text-xs-right" @click="props.expanded = !props.expanded">
{{ oneMoney(props.item.RealPrice) }}
</td>
<td class="text-xs-right">
<div>
<v-text-field :messages="oneMoney(getDiscountAmountSummaryPerItem(props.item))"
@input="handleChangeDiscountPerItemSummary(props.item)" v-model="props.item.discount"
type="number" solo>
<template v-slot:prepend>
<v-menu offset-y>
<template v-slot:activator="{ on }">
<v-btn small color="primary" dark v-on="on">
<div v-if="props.item.discountType == 'R'">
Rp
</div>
<div v-if="props.item.discountType == 'P'">
%
</div>
</v-btn>
</template>
<v-list>
<v-list-tile @click="handleChangeDiscountSummary('R', props.item)">
<v-list-tile-title>Rupiah</v-list-tile-title>
</v-list-tile>
<v-list-tile @click="handleChangeDiscountSummary('P', props.item)">
<v-list-tile-title>Percent</v-list-tile-title>
</v-list-tile>
</v-list>
</v-menu>
</template>
</v-text-field>
</div>
</td>
<td class="text-xs-right" @click="props.expanded = !props.expanded">
{{ oneMoney(props.item.Price) }}
</td>
<td class="text-xs-right" @click="props.expanded = !props.expanded">
{{ oneMoney(props.item.Total) }}
</td>
<td class="text-xs-right">
<v-btn icon small :disabled="loading" @click="deleteEntireItem(props.item.keyID)">
<!-- @click="tryDeleteItem(props.item, 'S')" -->
<v-icon color="red">delete</v-icon>
</v-btn>
</td>
</tr>
</template>
<!-- DETAIL LIST ITEM SUMMARY KALAU ADA ITEM YANG SAMA -->
<template slot="expand" slot-scope="props">
<v-card flat>
<v-card-text class="pl-3">
<div class="ml-3 mb-3">
<v-data-table :headers="detailHeaders" :items="props.item.Details" hide-actions
class="elevation-0">
<template slot="items" slot-scope="detailProps">
<td class="">
<kbd>{{ detailProps.item.M_ItemCode }}</kbd>
{{ detailProps.item.M_ItemDesc }}
</td>
<td class="text-xs-center">
{{
detailProps.item.PurchaseRequestNumber
}}
</td>
<td class="text-xs-center">
{{
detailProps.item.RequesterBranchName
}}
</td>
<td class="text-xs-center">
{{
detailProps.item.M_BranchName ??
detailProps.item.S_RegionalName
}}
</td>
<td class="text-xs-center py-1">
<div v-if="warehouseType === 'Single'">
{{ detailProps.item.warehouse.WarehouseName }}
</div>
<div v-if="warehouseType === 'Multiple'">
<v-autocomplete :disabled="loading" v-model="detailProps.item.warehouse"
:loading="loadingAutocomplete" :items="warehouseList" return-object
item-text="WarehouseName" solo hide-no-data hide-details
class="text-xs-center caption"></v-autocomplete>
</div>
</td>
<td class="text-xs-center py-1">
<v-text-field
v-model.number="detailProps.item.PoQty"
type="number" min="1" step="1"
:disabled="loading"
@input="handlePoQtyChange(detailProps.item)"
@blur="validatePoQty(detailProps.item)"
class="text-xs-center caption"
solo hide-details dense style="max-width: 80px;"
></v-text-field>
</td>
<td class="text-xs-center">
{{ detailProps.item.PoItemUnitName }}
</td>
<td class="text-xs-center">
<v-btn icon small :disabled="loading"
@click="deleteItem(detailProps.item.PurchaseRequestFlagID)">
<v-icon color="red">delete</v-icon>
</v-btn>
</td>
</template>
</v-data-table>
</div>
</v-card-text>
</v-card>
</template>
<template v-slot:footer>
<tr>
<td :colspan="4">
<strong class="ml-2">JUMLAH</strong>
</td>
<td :colspan="3" class="text-xs-right">
<strong>{{ oneMoney(summaryTotal.total) }}</strong>
</td>
</tr>
<tr>
<td :colspan="4">
<strong class="ml-2">DISKON</strong>
</td>
<td :colspan="3" class="text-xs-right">
<strong>{{ oneMoney(summaryTotal.discount) }}</strong>
</td>
</tr>
<tr>
<td :colspan="4">
<strong class="ml-5">PAJAK PENDAPATAN(PPN) {{ taxPpn }}%</strong>
</td>
<td :colspan="3" class="text-xs-right">
<strong>{{ oneMoney(summaryTotal.ppn) }}</strong>
</td>
</tr>
<tr>
<td :colspan="4">
<strong class="ml-5">PAJAK PENGHASILAN(PPH) {{ taxPph }}%</strong>
</td>
<td :colspan="3" class="text-xs-right">
<strong>{{ oneMoney(summaryTotal.pph) }}</strong>
</td>
</tr>
<tr>
<td :colspan="4">
<strong class="ml-2">PAJAK</strong>
</td>
<td :colspan="3" class="text-xs-right">
<strong>{{ oneMoney(summaryTotal.tax) }}</strong>
</td>
</tr>
<tr>
<td :colspan="4">
<strong>TOTAL</strong>
</td>
<td :colspan="3" class="text-xs-right">
<strong>{{
oneMoney(summaryTotal.finalTotal)
}}</strong>
</td>
</tr>
</template>
</v-data-table>
</v-flex>
</v-layout>
</v-card-text>
<v-divider v-if="!loading"></v-divider>
<v-progress-linear :indeterminate="true" v-if="loading"></v-progress-linear>
<v-card-actions>
<v-spacer></v-spacer>
<v-btn color="error" :disabled="loading" flat @click="dialogForm = false">
BATAL
</v-btn>
<v-btn color="green" :disabled="loading" flat @click="saveOrder()">
SIMPAN
</v-btn>
</v-card-actions>
</v-card>
</v-dialog>
</div>
</template>
<style scoped>
.date-type-table {
display: flex;
flex-direction: column;
gap: 0.5rem;
}
.flex-items-center {
display: flex;
align-items: center;
gap: 0.25rem;
}
.table.v-table tbody td,
.table.v-table tbody tr {
height: 40px;
}
.table.v-table thead tr {
height: 40px;
}
</style>
<script>
module.exports = {
components: {},
data() {
return {
menuselectedDateForm: false,
msgAlertDeleteOrReject: "",
dialogAlertRejectRequest: false,
dialogAlertDeleteDetail: false,
amountRules: [(v) => v >= 0 || "Jumlah harus diisi"],
validationDetail: false,
detailHeaders: [
{
text: "NAMA PRODUK",
align: "center",
sortable: false,
value: "M_ItemDesc",
width: "20%",
class: "blue lighten-4 black--text",
},
{
text: "NO. PR",
align: "center",
sortable: false,
value: "M_ItemDesc",
width: "10%",
class: "blue lighten-4 black--text",
},
{
text: "REQUESTER",
align: "center",
sortable: false,
value: "M_ItemDesc",
width: "15%",
class: "blue lighten-4 black--text",
},
{
text: "CABANG",
align: "center",
sortable: false,
value: "M_BranchName",
width: "15%",
class: "blue lighten-4 black--text",
},
{
text: "GUDANG",
align: "center",
sortable: false,
value: "",
width: "20%",
class: "blue lighten-4 black--text",
},
{
text: "QTY",
align: "center",
sortable: false,
value: "",
width: "8%",
class: "blue lighten-4 black--text",
},
{
text: "UNIT PURCHASE",
align: "center",
sortable: false,
value: "",
width: "7%",
class: "blue lighten-4 black--text",
},
{
text: "AKSI",
align: "center",
sortable: false,
value: "",
width: "5%",
class: "blue lighten-4 black--text",
},
],
summaryHeaders: [
{
text: "",
align: "center",
sortable: false,
value: "expand",
width: "5%",
class: "blue lighten-3 white--text",
},
{
text: "ITEM",
align: "center",
sortable: false,
value: "",
width: "10%",
class: "blue lighten-3 white--text",
},
{
text: "QTY",
align: "center",
sortable: false,
value: "",
width: "10%",
class: "blue lighten-3 white--text",
},
{
text: "HARGA",
align: "center",
sortable: false,
value: "",
width: "15%",
class: "blue lighten-3 white--text",
},
{
text: "DISKON",
align: "center",
sortable: false,
value: "",
width: "20%",
class: "blue lighten-3 white--text",
},
{
text: "HARGA SETELAH DISKON",
align: "center",
sortable: false,
value: "",
width: "15%",
class: "blue lighten-3 white--text",
},
{
text: "TOTAL",
align: "center",
sortable: false,
value: "",
width: "20%",
class: "blue lighten-3 white--text",
},
{
text: "AKSI",
align: "center",
sortable: false,
value: "",
width: "5%",
class: "blue lighten-3 white--text",
},
],
headers: [
{
text: "NO ITEM",
align: "center",
sortable: false,
value: "",
width: "5%",
class: "blue lighten-3 white--text",
},
{
text: "NAMA PRODUK",
align: "center",
sortable: false,
value: "",
width: "15%",
class: "blue lighten-3 white--text",
},
{
text: "GUDANG",
align: "center",
sortable: false,
value: "",
width: "15%",
class: "blue lighten-3 white--text",
},
{
text: "UNIT",
align: "center",
sortable: false,
value: "",
width: "10%",
class: "blue lighten-3 white--text",
},
{
text: "QTY",
align: "center",
sortable: false,
value: "",
width: "10%",
class: "blue lighten-3 white--text",
},
{
text: "PO QTY",
align: "center",
sortable: false,
value: "",
width: "15%",
class: "blue lighten-3 white--text",
},
{
text: "Harga",
align: "center",
sortable: false,
value: "",
width: "10%",
class: "blue lighten-3 white--text",
},
{
text: "TOTAL",
align: "center",
sortable: false,
value: "",
width: "15%",
class: "blue lighten-3 white--text",
},
{
text: "AKSI",
align: "center",
sortable: false,
value: "",
width: "5%",
class: "blue lighten-3 white--text",
},
],
HeadersItem: [
{
text: "No PurchaseReq",
align: "center",
sortable: false,
value: "",
width: "10%",
class: "blue lighten-3 white--text",
},
{
text: "KODE ITEM",
align: "center",
sortable: false,
value: "",
width: "10%",
class: "blue lighten-3 white--text",
},
{
text: "NAMA ITEM",
align: "left",
sortable: false,
value: "",
width: "35%",
class: "blue lighten-3 white--text",
},
{
text: "CABANG",
align: "left",
sortable: false,
value: "",
width: "20%",
class: "blue lighten-3 white--text",
},
{
text: "UNIT PO",
align: "left",
sortable: false,
value: "",
width: "10%",
class: "blue lighten-3 white--text",
},
{
text: "REQ QTY",
align: "left",
sortable: false,
value: "",
width: "10%",
class: "blue lighten-3 white--text",
},
],
};
},
mounted() {
this.$store.dispatch("po/getWarehouse");
},
computed: {
formattedDiscountAmount: {
get() {
if (this.discountType === 'Absolute') {
// Format without decimal places - only show thousands separators
if (typeof this.discountAmount !== 'number') {
return '0';
}
// Format number with thousand separators but no decimal places
return Math.round(this.discountAmount).toString().replace(/\B(?=(\d{3})+(?!\d))/g, ".");
} else {
// For percentage, show the raw number
return this.discountAmount.toString();
}
},
set(value) {
if (this.discountType === 'Absolute') {
// Remove all non-digit characters (dots, commas, etc.) and convert to number
const raw = value.replace(/[^\d]/g, '');
this.discountAmount = parseInt(raw) || 0;
} else {
this.discountAmount = parseFloat(value) || 0;
}
}
},
formattedShippingCost: {
get() {
const cost = parseFloat(this.shippingCost);
// Check if it's a valid number
if (isNaN(cost)) {
return '0';
}
// Format number with thousand separators but no decimal places
return Math.round(cost).toString().replace(/\B(?=(\d{3})+(?!\d))/g, ".");
},
set(value) {
// Remove all non-digit characters (dots, commas, etc.) and convert to number
const raw = value.replace(/[^\d]/g, '');
this.shippingCost = parseInt(raw) || 0;
}
},
selectedPO() {
return this.$store.state.po.selectedPO;
},
statusOptions() {
return this.$store.state.po.statusOptions;
},
dialogFormAct() {
return this.$store.state.po.dialogFormAct;
},
totalPageItem: {
get() {
return this.$store.state.po.totalPageItem;
},
set(val) {
this.$store.commit("po/update_totalPageItem", val);
},
},
shippingCost: {
get() {
return this.$store.state.po.shippingCost;
},
set(val) {
this.$store.commit("po/update_shippingCost", val);
},
},
shippingCostStatus: {
get() {
return this.$store.state.po.shippingCostStatus;
},
set(val) {
this.$store.commit("po/update_shippingCostStatus", val);
},
},
currentPageItem: {
get() {
return this.$store.state.po.currentPageItem;
},
set(val) {
this.$store.commit("po/update_currentPageItem", val);
this.$store.dispatch("po/getItem");
},
},
dialogItem: {
get() {
return this.$store.state.po.dialogItem;
},
set(val) {
this.$store.commit("po/update_dialogItem", val);
},
},
itemTotal: {
get() {
return this.$store.state.po.itemTotal;
},
set(val) {
this.$store.commit("po/update_itemTotal", val);
},
},
searchItem: {
get() {
return this.$store.state.po.searchItem;
},
set(val) {
this.$store.commit("po/update_searchItem", val);
},
},
selectedItem: {
get() {
return this.$store.state.po.selectedItem;
},
set(val) {
this.$store.commit("po/update_selectedItem", val);
},
},
deletedItem: {
get() {
return this.$store.state.po.deletedItem;
},
set(val) {
this.$store.commit("po/update_deletedItem", val);
},
},
itemList: {
get() {
return this.$store.state.po.itemList;
},
set(val) {
this.$store.commit("po/update_itemList", val);
},
},
warehouseList: {
get() {
return this.$store.state.po.warehouseList;
},
set(val) {
this.$store.commit("po/update_warehouseList", val);
},
},
selectedWarehouse: {
get() {
return this.$store.state.po.selectedWarehouse;
},
set(val) {
this.$store.commit("po/update_selectedWarehouse", val);
if (this.warehouseType === "Single") {
// this.$store.dispatch("po/generateSummary");
this.changeWarehouse();
}
},
},
warehouseTypeList: {
get() {
return this.$store.state.po.warehouseTypeList;
},
set(val) {
this.$store.commit("po/update_warehouseTypeList", val);
},
},
warehouseType: {
get() {
return this.$store.state.po.warehouseType;
},
set(val) {
this.$store.commit("po/update_warehouseType", val);
},
},
canHaveMultipleWarehouses: {
get() {
return this.$store.state.po.isValidMultipleWarehouse.status;
}
},
isValidMultipleWarehouse(){
return this.$store.state.po.isValidMultipleWarehouse;
},
note: {
get() {
return this.$store.state.po.note;
},
set(val) {
this.$store.commit("po/update_note", val);
},
},
loadingAutocomplete: {
get() {
return this.$store.state.po.loadingAutocomplete;
},
set(val) {
this.$store.commit("po/update_loadingAutocomplete", val);
},
},
selectedSupplier: {
get() {
return this.$store.state.po.selectedSupplier;
},
set(val) {
this.$store.commit("po/update_selectedSupplier", val);
},
},
supplierList: {
get() {
return this.$store.state.po.supplierList;
},
set(val) {
this.$store.commit("po/update_supplierList", val);
},
},
searchSupplier: {
get() {
return this.$store.state.po.searchSupplier;
},
set(val) {
this.$store.commit("po/update_searchSupplier", val);
},
},
discountAmount: {
get() {
return this.$store.state.po.discountAmount;
},
set(val) {
this.$store.commit("po/update_discountAmount", val);
this.$store.dispatch("po/countItemTotal");
this.$store.dispatch("po/generateSummaryTotal");
},
},
discountTypeList: {
get() {
return this.$store.state.po.discountTypeList;
},
set(val) {
this.$store.commit("po/update_discountTypeList", val);
},
},
discountType: {
get() {
return this.$store.state.po.discountType;
},
set(val) {
this.$store.commit("po/update_discountType", val);
this.$store.dispatch("po/countItemTotal");
this.$store.dispatch("po/generateSummary");
},
},
paymentTerm: {
get() {
return this.$store.state.po.paymentTerm;
},
set(val) {
this.$store.commit("po/update_paymentTerm", val);
},
},
formattedPaymentTerm: {
get() {
return this.paymentTerm.toString();
},
set(value) {
// Remove any non-digit characters
const raw = value.replace(/[^\d]/g, '');
// Convert to integer
this.paymentTerm = parseInt(raw) || 0;
}
},
taxPpn: {
get() {
return this.$store.state.po.taxPpn;
},
set(val) {
this.$store.commit("po/update_taxPpn", val);
this.$store.dispatch("po/countItemTotal");
this.$store.dispatch("po/generateSummaryTotal");
},
},
taxPph: {
get() {
return this.$store.state.po.taxPph;
},
set(val) {
this.$store.commit("po/update_taxPph", val);
this.$store.dispatch("po/countItemTotal");
this.$store.dispatch("po/generateSummaryTotal");
},
},
refNumber: {
get() {
return this.$store.state.po.refNumber;
},
set(val) {
this.$store.commit("po/update_refNumber", val);
},
},
selectedDateForm: {
get() {
return this.$store.state.po.selectedDateForm;
},
set(val) {
this.$store.commit("po/update_selectedDateForm", val);
},
},
selectedDateFormFormatted() {
return this.formatDate(this.selectedDateForm);
},
endDateFormatted() {
return this.formatDate(this.fEndDate);
},
poList() {
return this.$store.state.po.poList;
},
selectedItemTemp: {
get() {
return this.$store.state.po.selectedItemTemp;
},
set(val) {
this.$store.commit("po/update_selectedItemTemp", val);
},
},
loading: {
get() {
return this.$store.state.po.loading;
},
set(val) {
this.$store.commit("po/update_loading", val);
},
},
dialogForm: {
get() {
return this.$store.state.po.dialogForm;
},
set(val) {
this.$store.commit("po/update_dialogForm", val);
},
},
snackbar: {
get() {
return this.$store.state.po.snackbar;
},
set(val) {
this.$store.commit("po/update_snackbar", val);
},
},
defaultWarehouse: {
get() {
return this.$store.state.po.defaultWarehouse;
},
set(val) {
this.$store.commit("po/update_defaultWarehouse", val);
},
},
summary: {
get() {
return this.$store.state.po.summary;
},
set(val) {
this.$store.commit("po/update_summary", val);
},
},
summaryTotal: {
get() {
return this.$store.state.po.summaryTotal;
},
set(val) {
this.$store.commit("po/update_summaryTotal", val);
},
},
selectedItemCategory: {
get() {
return this.$store.state.po.selectedItemCategory;
},
set(val) {
this.$store.commit("po/update_selectedItemCategory", val);
},
},
itemCategoryList: {
get() {
return this.$store.state.po.itemCategoryList;
},
set(val) {
this.$store.commit("po/update_itemCategoryList", val);
},
},
},
methods: {
roundToThreeSignificantDigits(num) {
if (num === 0) return 0;
// Hitung jumlah digit
const absNum = Math.abs(num);
const magnitude = Math.floor(Math.log10(absNum)) + 1;
// Jika kurang dari 3 digit, tidak perlu dibulatkan
if (magnitude <= 3) return num;
// Hitung faktor pembulatan
const factor = Math.pow(10, magnitude - 3);
// Bulatkan
return Math.round(num / factor) * factor;
},
oneMoney(value) {
//return one_money(value);
// let splitValue = value.toString().split(".");
// if (splitValue.length > 1) {
// let val = splitValue[0]
// return val.toString().replace(/\B(?=(\d{3})+(?!\d))/g, ".") + "," + this.roundToThreeSignificantDigits(splitValue[1]);
// } else {
// return value.toString().replace(/\B(?=(\d{3})+(?!\d))/g, ".");
// }
// Ubah ke float jika belum angka
if (typeof value !== 'number') {
value = parseFloat(value);
}
if (isNaN(value)) return '0,00';
// Bulatkan dulu ke 2 desimal
const fixed = value.toFixed(2); // hasil string, misal "16200000.00" atau "123456.70"
const splitValue = fixed.split(".");
const val = splitValue[0]; // bagian ribuan
const decimal = splitValue[1]; // bagian desimal
const formatted = val.replace(/\B(?=(\d{3})+(?!\d))/g, ".");
return `${formatted},${decimal}`;
},
getDiscountAmountSummaryPerItem(data) {
let discountAmount = 0;
if (data.discountType === "R") {
discountAmount = parseFloat(data.discount);
}
if (data.discountType === "P") {
discountAmount =
(parseFloat(data.discount) / 100) * parseFloat(data.RealPrice);
}
if (isNaN(discountAmount)) {
discountAmount = 0;
}
return discountAmount;
},
handleChangeDiscountPerItemSummary(data) {
console.log("Data Diskon", JSON.parse(JSON.stringify(data)));
// Convert discount to a number and back to string to remove leading zeros
if (data.discount !== "") {
data.discount = Number(data.discount).toString();
} else {
data.discount = "0";
}
let tmpSelectedItem = JSON.parse(JSON.stringify(this.selectedItem));
let dataDetail = {};
this.summary.forEach((element) => {
if (element.keyID == data.keyID) {
dataDetail = element.Details[0];
}
});
tmpSelectedItem.forEach((element) => {
if (
element.M_ItemID === dataDetail.M_ItemID &&
element.PoItemUnitID === dataDetail.PoItemUnitID
) {
element.discount = data.discount;
}
});
this.selectedItem = tmpSelectedItem;
let newPrice = 0;
if (data.discountType === "R") {
newPrice = parseFloat(data.RealPrice) - parseFloat(data.discount);
}
if (data.discountType === "P") {
newPrice =
parseFloat(data.RealPrice) -
(parseFloat(data.discount) / 100) * parseFloat(data.RealPrice);
}
if (isNaN(newPrice)) {
newPrice = 0;
}
if (newPrice <= 0) {
this.snackbar = {
state: true,
type: "warning",
message: "Diskon tidak boleh melebihi harga !",
};
}
data.Price = newPrice;
let newTotal = parseInt(data.TotalPoQty) * newPrice;
data.Total = newTotal;
this.$store.dispatch("po/generateSummaryTotal");
},
handleChangeDiscountSummary(typeDisc, data) {
console.debug("Data Diskon HandleChangeDiscountSummary", data.discount);
if (!data.discount || data.discount == "") {
data.discount = 0
}
// debugger;
let tmpSummary = JSON.parse(JSON.stringify(this.summary));
let tmpSelectedItem = JSON.parse(JSON.stringify(this.selectedItem));
let dataDetail = {};
tmpSummary.forEach((element) => {
if (element.keyID == data.keyID) {
element.discountType = typeDisc;
element.Price = element.RealPrice;
element.discount = 0;
element.Total = element.TotalPoQty * element.RealPrice;
dataDetail = element.Details[0];
}
});
tmpSelectedItem.forEach((element) => {
if (
element.M_ItemID === dataDetail.M_ItemID &&
element.PoItemUnitID === dataDetail.PoItemUnitID
) {
element.discount = 0;
element.discountType = typeDisc;
}
});
this.selectedItem = tmpSelectedItem;
this.summary = tmpSummary;
this.$store.dispatch("po/countItemTotal");
this.$store.dispatch("po/generateSummaryTotal");
},
tryDeleteItem(data, type) {
},
deleteItem(itemID) {
// Menghapus item berdasarkan PurchaseRequestFlagID (detail)
console.log("Delete an Item from Summary, itemID:", itemID);
this.$store.dispatch("po/deleteItemFromSummary", { detailId: itemID });
},
// Jika ingin menghapus seluruh item dan detailsnya
deleteEntireItem(keyID) {
console.log("Delete Entire Item form Summary, keyID: ", keyID);
this.$store.dispatch("po/deleteItemFromSummary", { itemId: keyID });
},
saveOrder() {
if (this.loading) {
this.snackbar = {
state: true,
type: "warning",
message: "Proses sedang berjalan, harap tunggu hingga selesai.",
};
return;
}
if (this.dialogFormAct === "ADD") {
this.$store.dispatch("po/saveOrder");
} else if (this.dialogFormAct === "EDIT") {
this.$store.dispatch("po/updateOrder");
}
},
isObjectEmpty(obj) {
return Object.keys(obj).length === 0 && obj.constructor === Object;
},
openDialogAddItem() {
if (this.loading) {
this.snackbar = {
state: true,
type: "warning",
message: "Proses sedang berjalan, harap tunggu hingga selesai.",
};
return;
}
this.selectedItemTemp = JSON.parse(JSON.stringify(this.selectedItem));
this.itemList = [];
this.dialogItem = true;
},
addItem() {
if (this.selectedItemTemp.length === 0) {
this.snackbar = {
state: true,
type: "warning",
message: "Belum ada item yang di pilih",
};
}
let newItem = JSON.parse(JSON.stringify(this.selectedItemTemp));
this.selectedItem = newItem;
this.$store.dispatch("po/countItemTotal");
this.$store.dispatch("po/generateSummary");
this.selectedItemTemp = [];
this.dialogItem = false;
},
changeWarehouse() {
if (this.warehouseType === "Single") {
this.selectedItem.forEach((item) => {
item.warehouse = this.selectedWarehouse;
});
}
this.$store.dispatch("po/generateSummary");
},
formatRupiah(angka) {
const format = new Intl.NumberFormat("id-ID", {
style: "currency",
currency: "IDR",
minimumFractionDigits: 0,
});
return format.format(angka).trim();
},
formatDate(date) {
if (!date) return null;
const [year, month, day] = date.split("-");
return `${day}-${month}-${year}`;
},
deFormatedDate(date) {
if (!date) return null;
const [day, month, year] = date.split("-");
return `${year}-${month.padStart(2, "0")}-${day.padStart(2, "0")}`;
},
async handlePoQtyChange(item) {
if (item.PoQty < 1) {
item.PoQty = 1;
this.snackbar = {
state: true,
type: "warning",
message: "Quantity tidak boleh kurang dari 0",
};
}
item.PoQty = Math.floor(item.PoQty || 1);
await this.updateSelectedItemPoQty(item);
// Recalculate summary and totals
await Promise.all([
this.$store.dispatch("po/generateSummary"),
this.$store.dispatch("po/countItemTotal"),
this.$store.dispatch("po/generateSummaryTotal")
]);
},
validatePoQty(item) {
// Final validation on blur
if (!item.PoQty || item.PoQty < 1) {
item.PoQty = 1;
}
item.PoQty = Math.floor(item.PoQty);
},
async updateSelectedItemPoQty(changedItem) {
let tmpSelectedItem = JSON.parse(JSON.stringify(this.selectedItem));
// Group per M_ItemID dan jumlahkan total PoQty
const groupedPoQty = tmpSelectedItem.reduce((acc, item) => {
const itemID = item.M_ItemID;
const qty = item.PurchaseRequestFlagID === changedItem.PurchaseRequestFlagID
? Number(changedItem.PoQty || 0)
: Number(item.PoQty || 0);
if (!acc[itemID]) acc[itemID] = qty;
else acc[itemID] += qty;
return acc;
}, {});
// Simpan hasil validasi supplier price dalam Map
const supplierPriceMap = new Map();
// Validasi supplier price untuk setiap item
const validationPromises = Object.entries(groupedPoQty).map(async ([M_ItemID, totalPoQty]) => {
try {
const supplierPrice = await this.$store.dispatch("po/getDefaultSupplierPrice", {
itemID: M_ItemID,
qty: totalPoQty,
});
// Simpan hasil ke Map dengan key M_ItemID
supplierPriceMap.set(M_ItemID, supplierPrice.SupplierPricePrice);
} catch (error) {
this.snackbar = {
state: true,
type: "error",
message: `Error fetching supplier price for item ${M_ItemID}`,
};
}
});
// Tunggu semua validasi selesai
await Promise.all(validationPromises);
// Update item dengan harga baru dari validasi
tmpSelectedItem.forEach((element) => {
if (element.PurchaseRequestFlagID === changedItem.PurchaseRequestFlagID) {
element.PoQty = changedItem.PoQty;
}
// Gunakan harga dari validasi jika ada, fallback ke DefaultPurchase
let basePrice = supplierPriceMap.get(element.M_ItemID)
|| (element.DefaultPurchase && element.DefaultPurchase.SupplierPricePrice)
|| 0;
if (basePrice > 0) {
// Update element.Price dengan harga base sebelum discount
element.Price = basePrice;
let price = basePrice;
// Apply discount if exists
if (element.discount > 0) {
if (element.discountType === 'P') {
price = price - (element.discount * price / 100);
} else if (element.discountType === 'R') {
price = price - element.discount;
}
}
element.Total = element.PoQty * price;
}
});
this.selectedItem = tmpSelectedItem;
}
},
watch: {
searchSupplier(val, old) {
if (val == old) return;
if (!val) return;
if (val.length < 2) return;
this.$store.dispatch("po/getSupplier");
},
searchItem(val, old) {
if (val == old) return;
// if (!val) return;
// if (val.length < 2) return;
this.$store.dispatch("po/getItem");
},
warehouseType(val, old) {
if (this.selectedItem.length == 0) return;
if (val !== old) {
if (val === "Multiple" && old === "Single") {
// Check if multiple warehouses are allowed
if (!this.canHaveMultipleWarehouses) {
this.$store.commit("po/update_warehouseType", "Single");
this.snackbar = {
state: true,
type: "error",
message: "Item terpilih hanya mendukung tipe gudang Single"
};
return;
}
this.snackbar = {
state: true,
type: "info",
message: "Tipe gudang berubah dari Single ke Multiple, silahkan pilih gudang tiap item.",
};
} else if (val === "Single" && old === "Multiple") {
this.snackbar = {
state: true,
type: "warning",
message: "Tipe gudang berubah dari Multiple ke Single, tiap item akan dikirim ke gudang default yang sudah dipilih.",
};
}
}
},
dialogItem(val, old) {
if (val) this.$store.dispatch("po/getItem");
},
},
};
</script>