Files
fe-accone/test/vuex/acc-one-approve-po-inventaris/components/poForm.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>