1797 lines
60 KiB
Vue
1797 lines
60 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"
|
|
@click.stop="toggleAll"
|
|
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>
|
|
<td class="text-xs-center">
|
|
{{ props.item.RemainingQty }} {{ 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"
|
|
@click.stop="toggleAllDeleted"
|
|
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.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 INVENTARIS
|
|
<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 xs4>
|
|
<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"
|
|
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 xs4>
|
|
<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 xs4>
|
|
<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 xs2>
|
|
<v-select :items="typePpn" label="Tipe PPN" hide-details :readonly="loading"
|
|
class="mini-select mr-2" v-model="typePpnSelected" outline></v-select>
|
|
</v-flex>
|
|
<v-flex xs4>
|
|
<v-text-field :prefix="typePpnSelected === 'Absolute' ? 'Rp' : ''"
|
|
:suffix="typePpnSelected === 'Percentage' ? '%' : ''"
|
|
v-model="formattedPpn"
|
|
label="Pajak Pendapatan (PPN)" :readonly="loading" class="mr-2"
|
|
type="number" min="0" step="1"
|
|
outline hide-details>
|
|
</v-text-field>
|
|
</v-flex>
|
|
<v-flex xs6>
|
|
<v-text-field type="number" min="0" step="1" v-model="taxPph" label="Pajak Penghasilan (PPH)" class="mr-2" outline
|
|
:readonly="loading" suffix="%" hide-details>
|
|
</v-text-field>
|
|
</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' ? '%' : ''"
|
|
v-model="formattedDiscountAmount"
|
|
:label="discountType === 'Percentage' ? 'Diskon' : ''" :readonly="loading" class="mt-3"
|
|
type="number" min="0" step="1"
|
|
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="selectedItemCategory" :loading="loadingAutocomplete" :items="itemCategoryList"
|
|
cache-items class="mr-2 mt-3" 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 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
|
|
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">
|
|
<v-text-field
|
|
prefix="Rp"
|
|
:value="formatRealPrice(props.item.RealPrice)"
|
|
@input="setRealPrice($event, props.item)"
|
|
type="text"
|
|
solo
|
|
hide-details
|
|
inputmode="numeric"
|
|
pattern="[0-9]*"
|
|
@keypress="onlyNumber"
|
|
@blur="ensureMinZero(props.item)"
|
|
/>
|
|
</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" min="0" 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">
|
|
<div>
|
|
<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
|
|
>
|
|
<template v-slot:prepend>
|
|
<v-menu offset-y :disabled="dialogFormAct === 'EDIT' && detailProps.item.qty_mode === 'SPLIT_PR'">
|
|
<template v-slot:activator="{ on }">
|
|
<v-btn :class="{ 'btn-disabled-ui': dialogFormAct === 'EDIT' }" color="primary" small dark v-on="on">
|
|
<div v-if="detailProps.item.qty_mode == 'EDIT_PR'">
|
|
EDIT QTY
|
|
</div>
|
|
<div v-if="detailProps.item.qty_mode == 'SPLIT_PR'">
|
|
SPLIT PR
|
|
</div>
|
|
</v-btn>
|
|
</template>
|
|
<v-list>
|
|
<v-list-tile @click="handleChangeMode('EDIT_PR', detailProps.item)">
|
|
<v-list-tile-title>Edit QTY</v-list-tile-title>
|
|
</v-list-tile>
|
|
<v-list-tile @click="handleChangeMode('SPLIT_PR', detailProps.item)">
|
|
<v-list-tile-title>Split PR</v-list-tile-title>
|
|
</v-list-tile>
|
|
</v-list>
|
|
</v-menu>
|
|
</template>
|
|
</v-text-field>
|
|
</div>
|
|
</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">
|
|
<div v-if="typePpnSelected === 'Percentage'">
|
|
<strong class="ml-5">PAJAK PENDAPATAN(PPN) {{ taxPpn }}%</strong>
|
|
</div>
|
|
<div v-else>
|
|
<strong class="ml-5">PAJAK PENDAPATAN(PPN)</strong>
|
|
</div>
|
|
</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="batalForm()">
|
|
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: "18%",
|
|
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: "15%",
|
|
class: "blue lighten-4 black--text",
|
|
},
|
|
{
|
|
text: "QTY",
|
|
align: "center",
|
|
sortable: false,
|
|
value: "",
|
|
width: "15%",
|
|
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: "30%",
|
|
class: "blue lighten-3 white--text",
|
|
},
|
|
{
|
|
text: "CABANG",
|
|
align: "left",
|
|
sortable: false,
|
|
value: "",
|
|
width: "15%",
|
|
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",
|
|
},
|
|
{
|
|
text: "SISA SPLIT QTY",
|
|
align: "left",
|
|
sortable: false,
|
|
value: "",
|
|
width: "10%",
|
|
class: "blue lighten-3 white--text",
|
|
},
|
|
],
|
|
};
|
|
},
|
|
mounted() {
|
|
// this.$store.dispatch("po/getWarehouse");
|
|
},
|
|
|
|
computed: {
|
|
formattedPpn: {
|
|
get() {
|
|
if (this.typePpnSelected === 'Absolute') {
|
|
// Format without decimal places - only show thousands separators
|
|
if (typeof this.taxPpn !== 'number') {
|
|
return '0';
|
|
}
|
|
|
|
return Math.round(this.taxPpn).toString().replace(/\B(?=(\d{3})+(?!\d))/g, ".");
|
|
} else {
|
|
// For percentage, show the raw number
|
|
return this.taxPpn.toString();
|
|
}
|
|
},
|
|
set(value) {
|
|
const raw = value.replace(/[^\d]/g, '');
|
|
if (this.typePpnSelected === 'Absolute') {
|
|
this.taxPpn = parseInt(raw) || 0;
|
|
} else {
|
|
this.taxPpn = parseFloat(raw) || 0;
|
|
}
|
|
}
|
|
},
|
|
formattedDiscountAmount: {
|
|
get() {
|
|
if (this.discountType === 'Absolute') {
|
|
// Format without decimal places - only show thousands separators
|
|
if (typeof this.discountAmount !== 'number') {
|
|
return '0';
|
|
}
|
|
|
|
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) {
|
|
const raw = value.replace(/[^\d]/g, '');
|
|
if (this.discountType === 'Absolute') {
|
|
this.discountAmount = parseInt(raw) || 0;
|
|
} else {
|
|
this.discountAmount = parseFloat(raw) || 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) {
|
|
const validVal = this.validateNumber(val);
|
|
this.$store.commit("po/update_taxPpn", validVal);
|
|
this.$store.dispatch("po/countItemTotal");
|
|
this.$store.dispatch("po/generateSummaryTotal");
|
|
},
|
|
},
|
|
taxPph: {
|
|
get() {
|
|
return this.$store.state.po.taxPph;
|
|
},
|
|
set(val) {
|
|
const validVal = this.validateNumber(val);
|
|
this.$store.commit("po/update_taxPph", validVal);
|
|
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);
|
|
},
|
|
},
|
|
dialogRiwayat: {
|
|
get() {
|
|
return this.$store.state.po.dialogRiwayat;
|
|
},
|
|
set(val) {
|
|
this.$store.commit("po/update_dialogRiwayat", val);
|
|
},
|
|
},
|
|
typePpn: {
|
|
get() {
|
|
return this.$store.state.po.typePpn;
|
|
},
|
|
set(val) {
|
|
this.$store.commit("po/update_typePpn", val);
|
|
},
|
|
},
|
|
typePpnSelected: {
|
|
get() {
|
|
return this.$store.state.po.typePpnSelected;
|
|
},
|
|
set(val) {
|
|
this.$store.commit("po/update_typePpnSelected", val);
|
|
this.$store.dispatch("po/countItemTotal");
|
|
this.$store.dispatch("po/generateSummary");
|
|
},
|
|
},
|
|
},
|
|
methods: {
|
|
formatRealPrice(value) {
|
|
if (typeof value !== 'number' || value < 0) return '0';
|
|
return Math.round(value)
|
|
.toString()
|
|
.replace(/\B(?=(\d{3})+(?!\d))/g, ".");
|
|
},
|
|
|
|
setRealPrice(val, item) {
|
|
const raw = val.replace(/[^\d]/g, '');
|
|
const num = parseInt(raw) || 0;
|
|
|
|
item.RealPrice = num < 0 ? 0 : num;
|
|
this.updatePrice(item);
|
|
},
|
|
|
|
onlyNumber(e) {
|
|
if (!/[0-9]/.test(e.key)) {
|
|
e.preventDefault();
|
|
}
|
|
},
|
|
|
|
ensureMinZero(item) {
|
|
if (!item.RealPrice || item.RealPrice < 0) {
|
|
item.RealPrice = 0;
|
|
}
|
|
},
|
|
openRiwayatPo() {
|
|
this.$store.dispatch("po/getPoBySupplier");
|
|
this.dialogRiwayat = true
|
|
},
|
|
// searchWarehouse() {
|
|
// this.$store.dispatch("po/getWarehouse");
|
|
// },
|
|
async batalForm() {
|
|
await this.$store.dispatch("po/resetForm");
|
|
this.dialogForm = false
|
|
},
|
|
validateNumber(value){
|
|
const cleanValue = value.toString().replace(/[^0-9]/g, '');
|
|
const numValue = parseInt(cleanValue) || 0;
|
|
|
|
return Math.max(0, numValue);
|
|
},
|
|
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");
|
|
},
|
|
updatePrice(item) {
|
|
if (item.Price <= 0) {
|
|
this.snackbar = {
|
|
state: true,
|
|
type: "warning",
|
|
message: "Harga tidak boleh kosong !",
|
|
};
|
|
}
|
|
|
|
this.updtaeSelectedItemPoPrice(item);
|
|
|
|
// Recalculate summary and totals
|
|
this.$store.dispatch("po/generateSummary");
|
|
this.$store.dispatch("po/countItemTotal");
|
|
this.$store.dispatch("po/generateSummaryTotal");
|
|
},
|
|
updtaeSelectedItemPoPrice(changedItem) {
|
|
this.selectedItem = this.selectedItem.map(detail => {
|
|
if (detail.keyID !== changedItem.keyID ) {
|
|
return detail;
|
|
}
|
|
|
|
const basePrice = Math.max(0, Number(changedItem.RealPrice) || 0);
|
|
|
|
let finalPrice = basePrice;
|
|
|
|
if (parseFloat(detail.discount) > 0) {
|
|
if (detail.discountType === 'P') {
|
|
finalPrice = basePrice - (detail.discount * basePrice / 100);
|
|
}
|
|
if (detail.discountType === 'R') {
|
|
finalPrice = basePrice - detail.discount;
|
|
}
|
|
}
|
|
|
|
return {
|
|
...detail,
|
|
RealPrice: basePrice,
|
|
Price: finalPrice, // price FINAL
|
|
Total: detail.PoQty * finalPrice
|
|
};
|
|
});
|
|
},
|
|
toggleAll() {
|
|
// Ambil item yang bisa dipilih (tidak failed)
|
|
const selectableItems = this.itemList.filter(item => item.UnitReqPoConvertStatus !== 'failed');
|
|
|
|
const allSelected = selectableItems.every(item =>
|
|
this.selectedItemTemp.some(selected => selected.PurchaseRequestFlagID === item.PurchaseRequestFlagID)
|
|
);
|
|
|
|
// Hapus semua pilihan - simpan hanya item yang tidak bisa dipilih
|
|
if (allSelected) {
|
|
this.selectedItemTemp = this.selectedItemTemp.filter(selected =>
|
|
!selectableItems.some(item => item.PurchaseRequestFlagID === selected.PurchaseRequestFlagID)
|
|
);
|
|
} else {
|
|
// Pilih semua item yang bisa dipilih
|
|
const newSelection = [...this.selectedItemTemp];
|
|
|
|
selectableItems.forEach(item => {
|
|
if (!newSelection.some(selected => selected.PurchaseRequestFlagID === item.PurchaseRequestFlagID)) {
|
|
newSelection.push(item);
|
|
}
|
|
});
|
|
|
|
this.selectedItemTemp = newSelection;
|
|
}
|
|
},
|
|
toggleAllDeleted() {
|
|
// Cek apakah semua item terhapus sudah dipilih
|
|
const allSelected = this.deletedItem.every(item =>
|
|
this.selectedItemTemp.some(selected => selected.PurchaseRequestFlagID === item.PurchaseRequestFlagID)
|
|
);
|
|
|
|
// Hapus semua pilihan item terhapus
|
|
if (allSelected) {
|
|
this.selectedItemTemp = this.selectedItemTemp.filter(selected =>
|
|
!this.deletedItem.some(item => item.PurchaseRequestFlagID === selected.PurchaseRequestFlagID)
|
|
);
|
|
} else {
|
|
const newSelection = [...this.selectedItemTemp];
|
|
|
|
this.deletedItem.forEach(item => {
|
|
// Tambah hanya jika belum dipilih
|
|
if (!newSelection.some(selected => selected.PurchaseRequestFlagID === item.PurchaseRequestFlagID)) {
|
|
newSelection.push(item);
|
|
}
|
|
});
|
|
this.selectedItemTemp = newSelection;
|
|
}
|
|
},
|
|
|
|
handleChangeMode(qtymode, data) {
|
|
const item = this.selectedItem.find(el =>
|
|
el.M_ItemID === data.M_ItemID &&
|
|
el.PoItemUnitID === data.PoItemUnitID
|
|
)
|
|
|
|
if (!item) return
|
|
|
|
item.qty_mode = qtymode
|
|
|
|
if (qtymode === 'EDIT_PR') {
|
|
item.PoQty = data.RequestQty
|
|
}
|
|
|
|
if (qtymode === 'SPLIT_PR') {
|
|
item.PoQty = data.RemainingQty
|
|
}
|
|
|
|
this.$store.dispatch("po/generateSummary");
|
|
this.$store.dispatch("po/countItemTotal");
|
|
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;
|
|
},
|
|
|
|
updateSelectedItemPrice(selectedItem) {
|
|
const tempSummary = JSON.parse(JSON.stringify(this.summary));
|
|
|
|
selectedItem.forEach(detail => {
|
|
const summary = tempSummary.find(
|
|
s => s.keyID === detail.keyID
|
|
);
|
|
|
|
if (!summary) return;
|
|
|
|
// DETAIL IKUT SUMMARY
|
|
detail.RealPrice = summary.RealPrice;
|
|
|
|
detail.Price = summary.Price; // kalau FE lama pakai Price
|
|
// maksud price di sini adalah harga setelah diskon dari summary
|
|
detail.Total = detail.PoQty * summary.Price;
|
|
});
|
|
|
|
this.selectedItem = selectedItem;
|
|
},
|
|
|
|
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.updateSelectedItemPrice(newItem);
|
|
// this.selectedItem = newItem;
|
|
let ref = newItem.map(obj => obj.PurchaseRequestNumber).join(", ")
|
|
|
|
this.$store.dispatch("po/countItemTotal");
|
|
this.$store.dispatch("po/generateSummary");
|
|
this.refNumber = ref
|
|
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")}`;
|
|
},
|
|
|
|
handlePoQtyChange(item) {
|
|
qty = Math.floor(item.PoQty || 1);
|
|
if (qty < 1) {
|
|
qty = 1;
|
|
this.snackbar = {
|
|
state: true,
|
|
type: "warning",
|
|
message: "Quantity tidak boleh kurang dari 0",
|
|
};
|
|
}
|
|
|
|
if (this.dialogFormAct == 'EDIT') {
|
|
if (item.qty_mode === 'SPLIT_PR') {
|
|
const maxQty = Number(item.RequestQty)
|
|
const RemainingQty = Number(item.RemainingQty)
|
|
|
|
if (qty > maxQty) {
|
|
// qty = maxQty
|
|
this.snackbar = {
|
|
state: true,
|
|
type: "warning",
|
|
message: `Qty split tidak boleh melebihi sisa PURCHASE REQUEST (${RemainingQty})`,
|
|
};
|
|
}
|
|
}
|
|
|
|
item.PoQty = qty;
|
|
|
|
this.updateSelectedItemPoQty(item);
|
|
} else {
|
|
if (item.qty_mode === 'SPLIT_PR') {
|
|
const maxQty = Number(item.RemainingQty)
|
|
|
|
if (qty > maxQty) {
|
|
// qty = maxQty
|
|
this.snackbar = {
|
|
state: true,
|
|
type: "warning",
|
|
message: `Qty split tidak boleh melebihi sisa PURCHASE REQUEST (${maxQty})`,
|
|
};
|
|
}
|
|
}
|
|
|
|
item.PoQty = qty;
|
|
|
|
this.updateSelectedItemPoQty(item);
|
|
}
|
|
|
|
|
|
// Recalculate summary and totals
|
|
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;
|
|
}
|
|
|
|
// if (item.qty_mode === 'SPLIT_PR' && item.PoQty > item.RemainingQty) {
|
|
// item.PoQty = item.RemainingQty
|
|
// }
|
|
|
|
item.PoQty = Math.floor(item.PoQty);
|
|
},
|
|
|
|
updateSelectedItemPoQty(changedItem) {
|
|
this.selectedItem = this.selectedItem.map(detail => {
|
|
if (detail.PurchaseRequestFlagID !== changedItem.PurchaseRequestFlagID) {
|
|
return detail;
|
|
}
|
|
|
|
const qty = Math.max(1, Number(changedItem.PoQty) || 1);
|
|
|
|
return {
|
|
...detail,
|
|
PoQty: qty,
|
|
Total: qty * detail.Price // price SUDAH FINAL
|
|
};
|
|
});
|
|
},
|
|
},
|
|
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");
|
|
},
|
|
dialogForm(val) {
|
|
if (val && this.selectedItemCategory && Object.keys(this.selectedItemCategory).length > 0) {
|
|
this.$store.dispatch("po/getWarehouse");
|
|
}
|
|
}
|
|
},
|
|
};
|
|
</script>
|