first commit + add fe component vue accone

This commit is contained in:
2026-05-07 17:10:10 +07:00
parent fd8e4d694b
commit 38a182641a
327 changed files with 263946 additions and 0 deletions

View File

@@ -0,0 +1,103 @@
<template>
<div>
<v-dialog v-model="dialog_attachment" persistent max-width="60vw">
<v-card>
<v-card-title class="headline grey lighten-2" primary-title>
BUKTI PENERIMAAN
</v-card-title>
<v-card-text>
<v-layout row wrap>
<v-flex v-for="(docs) in preview_attachment" xs4 class="pa-2" :key="docs.attach_id">
<div style="position: relative; padding-bottom: 35px;">
<v-img :src="makeImgUrl(docs)" aspect-ratio="1" contain class="grey lighten-2"
@click="fullview(docs)"></v-img>
</div>
</v-flex>
</v-layout>
</v-card-text>
<v-card-actions>
<v-spacer></v-spacer>
<v-btn @click="dialog_attachment = false" color="error" class="white--text">Tutup</v-btn>
</v-card-actions>
</v-card>
</v-dialog>
<v-dialog v-model="dialog_fullview" persistent max-width="50vw">
<v-card>
<v-card-title class="headline grey lighten-2" primary-title>
{{ doctitle }}
<v-spacer></v-spacer>
<v-btn color="red" fab small outline @click="dialog_fullview = false">
<v-icon color="red">close</v-icon>
</v-btn>
</v-card-title>
<v-card-text style="max-height: 80vh; overflow-y: auto;">
<div class="pa-2">
<v-img :src="docurl" contain class="grey lighten-2"></v-img>
</div>
</v-card-text>
<v-card-actions>
<v-spacer></v-spacer>
<v-btn class="mr-2" color="red" flat outline @click="dialog_fullview = false">
Tutup
</v-btn>
</v-card-actions>
</v-card>
</v-dialog>
</div>
</template>
<script>
module.exports = {
data() {
return {
dialog_fullview: false,
doctitle: "",
docurl: ""
}
},
computed: {
dialog_attachment: {
get() {
return this.$store.state.data.dialog_attachment
},
set(val) {
this.$store.commit("data/update_dialog_attachment", val)
}
},
preview_attachment: {
get() {
return this.$store.state.data.preview_attachment
},
set(val) {
this.$store.commit("data/update_preview_attachment", val)
}
}
},
methods: {
fullview(docs) {
this.doctitle = docs.img_url
this.docurl = this.makeImgUrl(docs)
this.dialog_fullview = true
},
makeImgUrl(docs) {
const year = new Date(docs.created).getFullYear()
let baseURL = BASE_URL;
switch (docs.category) {
case "4":
baseURL += `/one-media/order-jasa/${year}/`
return baseURL + docs.img_url
case "2":
baseURL += `/one-media/order-inventaris/${year}/`
return baseURL + docs.img_url
default:
return null
}
}
},
watch: {
},
}
</script>

View File

@@ -0,0 +1,487 @@
<template>
<v-dialog v-model="dialogCreate" persistent max-width="85%">
<v-card>
<v-card-title class="headline grey lighten-2" primary-title>
FORM FAKTUR PO
</v-card-title>
<v-card-text class="pt-0 pb-0 mt-4">
<v-form ref="formCreate" v-model="validationForm" lazy-validation>
<v-layout row wrap>
<v-flex pr-2 xs2>
<v-menu
v-model="menuDateForm"
transition="scale-transition"
:close-on-content-click="false"
:nudge-right="40" lazy
full-width offset-y
max-width="290px" min-width="290px"
>
<template v-slot:activator="{on}">
<v-text-field
label="Tanggal Faktur" readonly
:value="DateFormatted" v-on="on"
outline hide-details
></v-text-field>
</template>
<v-date-picker
no-title v-model="form_date"
@input="menuDateForm = false"
></v-date-picker>
</v-menu>
</v-flex>
<v-flex pr-2 xs2>
<v-menu
v-model="menuDueDateForm"
transition="scale-transition"
:close-on-content-click="false"
:nudge-right="40" lazy
full-width offset-y
max-width="290px" min-width="290px"
>
<template v-slot:activator="{on}">
<v-text-field
label="Tanggal Jatuh Tempo" readonly
:value="DueDateFormatted" v-on="on"
hide-details outline
></v-text-field>
</template>
<v-date-picker
no-title v-model="form_duedate"
@input="menuDueDateForm = false"
></v-date-picker>
</v-menu>
</v-flex>
<v-flex pr-2 xs2>
<v-autocomplete readonly
v-model="form_supplier"
label="Supplier" menu-icon="mdi-chevron-down"
item-text="SupplierName"
:items="list_supplier.records"
:rules="rules_text" outline
return-object required
></v-autocomplete>
</v-flex>
<v-flex pr-2 xs2>
<v-text-field readonly
v-model="form_refnumber"
label="Nomor Referensi"
:rules="rules_text"
outline required
></v-text-field>
</v-flex>
<v-flex pr-2 xs2>
<v-text-field
v-model="form_tax_invoicenum"
label="Nomor Faktur Pajak"
:rules="rules_text"
required outline
></v-text-field>
</v-flex>
<v-flex xs2>
<v-menu
v-model="menuTaxInvoiceDateForm"
transition="scale-transition"
:close-on-content-click="false"
:nudge-right="40" lazy
full-width offset-y
max-width="290px" min-width="290px"
>
<template v-slot:activator="{on}">
<v-text-field
label="Tanggal Faktur Pajak" readonly
:value="TaxInvDateFormatted" v-on="on"
outline hide-details
></v-text-field>
</template>
<v-date-picker
no-title v-model="form_tax_invoicedate"
@input="menuTaxInvoiceDateForm = false"
></v-date-picker>
</v-menu>
</v-flex>
</v-layout>
<v-layout row wrap>
<v-flex pr-2 xs2>
<v-autocomplete
v-model="form_disc_type"
label="Tipe Diskon" menu-icon="mdi-chevron-down"
item-text="SupplierName"
:items="['Percentage', 'Absolute']"
outline required :rules="rules_text"
></v-autocomplete>
</v-flex>
<v-flex pr-2 xs2>
<v-text-field
v-if="this.form_disc_type == 'Percentage'"
v-model="form_disc_percent"
label="Diskon (%)" suffix="%"
:rules="rules_number"
outline type="number"
></v-text-field>
<v-text-field
v-else
v-model="form_disc_amount"
label="Nilai diskon"
:rules="rules_number"
outline type="number"
></v-text-field>
</v-flex>
<v-flex pr-2 xs2>
<v-text-field
v-model="form_tax_percent_pph"
label="Pajak Penghasilan (%)" suffix="%"
:rules="rules_number"
outline type="number"
></v-text-field>
</v-flex>
<v-flex pr-2 xs2>
<v-text-field
v-model="form_tax_percent_ppn"
label="Pajak Pendapatan (%)" suffix="%"
:rules="rules_number"
outline type="number"
></v-text-field>
</v-flex>
<v-flex pr-2 xs2>
<v-text-field
v-model="form_shipping_cost"
label="Biaya Pengiriman"
:rules="rules_number"
outline type="number"
></v-text-field>
</v-flex>
<v-flex xs2>
<v-text-field
v-model="form_adjust_amount"
label="Jumlah Penyesuaian"
:rules="rules_number"
outline type="number"
></v-text-field>
</v-flex>
<v-flex xs12>
<v-textarea
label="Catatan Penyesuaian"
v-model="form_adjust_note"
outline rows="2"
></v-textarea>
</v-flex>
</v-layout>
<v-layout row wrap>
<v-flex pr-2 xs3>
<v-autocomplete
v-model="form_po"
label="Purchase Order" menu-icon="mdi-chevron-down"
item-text="PurchaseOrderNumber"
:items="list_po.records" return-object
outline required :rules="rules_text"
></v-autocomplete>
</v-flex>
<v-flex pr-2 xs2>
<v-text-field
v-model="form_grandtotal"
label="Jumlah total"
:rules="rules_number"
type="number" required outline
></v-text-field>
</v-flex>
<v-flex pr-2 xs2>
<v-text-field
v-model="form_paidamount"
label="Jumlah dibayar"
:rules="rules_number"
type="number" required outline
></v-text-field>
</v-flex>
<v-flex pr-2 xs2>
<v-menu
v-model="menuReceivedDateForm"
transition="scale-transition"
:close-on-content-click="false"
:nudge-right="40" lazy
full-width offset-y
max-width="290px" min-width="290px"
>
<template v-slot:activator="{on}">
<v-text-field
label="Tanggal Diterima" readonly outline
:value="ReceivedDateFormatted" v-on="on"
></v-text-field>
</template>
<v-date-picker
no-title v-model="form_receiveddate"
@input="menuReceivedDateForm = false"
></v-date-picker>
</v-menu>
</v-flex>
<v-flex xs3>
<v-autocomplete
v-model="form_receivedby"
label="Diterima Oleh" menu-icon="mdi-chevron-down"
item-text="M_StaffName"
:items="list_staff.records"
:rules="rules_text" outline
return-object required
></v-autocomplete>
</v-flex>
<v-flex xs12>
<v-textarea
label="Catatan Faktur"
v-model="form_note"
outline rows="2"
></v-textarea>
</v-flex>
</v-layout>
</v-form>
<v-flex xs2>
<v-btn
block color="info"
:disabled="isObjectEmpty(this.form_supplier)"
@click="dialogAddItem()"
>
TAMBAHKAN ITEM
</v-btn>
</v-flex>
</v-card-text>
<v-card-actions>
<v-spacer></v-spacer>
<v-btn color="error" flat @click="closeDialog()">
Tutup
</v-btn>
<v-btn color="primary" dark @click="">
Simpan
</v-btn>
</v-card-actions>
</v-card>
</v-dialog>
</template>
<script>
module.exports = {
components: {},
mounted() {
},
data() {
return {
validationForm: false,
menuDateForm: false,
menuDueDateForm: false,
menuTaxInvoiceDateForm: false,
menuReceivedDateForm: false,
rules_text: [(v) => !!v || "Tidak boleh kosong"],
rules_number: [(v) => !isNaN(v) || "Tidak boleh kosong"],
}
},
computed: {
dialogCreate: {
get() {
return this.$store.state.form.opendialog;
},
set(val) {
this.$store.commit("form/update_opendialog", val);
}
},
form_date: {
get() {
return this.$store.state.form.form_date;
},
set(val) {
this.$store.commit("form/update_form_date", val);
}
},
DateFormatted() {
return this.formatDate(this.form_date);
},
form_duedate: {
get() {
return this.$store.state.form.form_duedate
},
set(val) {
this.$store.commit("form/update_form_duedate", val)
}
},
DueDateFormatted() {
return this.formatDate(this.form_duedate)
},
form_supplier: {
get() {
return this.$store.state.form.form_supplier;
},
set(val) {
this.$store.commit("form/update_form_supplier", val);
}
},
form_refnumber: {
get() {
return this.$store.state.form.form_refnumber;
},
set(val) {
this.$store.commit("form/update_form_refnumber", val);
}
},
form_tax_invoicenum: {
get() {
return this.$store.state.form.form_tax_invoicenum;
},
set(val) {
this.$store.commit("form/update_form_tax_invoicenum", val);
}
},
form_tax_invoicedate: {
get() {
return this.$store.state.form.form_tax_invoicedate;
},
set(val) {
this.$store.commit("form/update_form_tax_invoicedate", val);
}
},
TaxInvDateFormatted() {
return this.formatDate(this.form_tax_invoicedate);
},
form_disc_type: {
get() {
return this.$store.state.form.form_disc_type;
},
set(val) {
this.$store.commit("form/update_form_disc_type", val);
}
},
form_disc_percent: {
get() {
return this.$store.state.form.form_disc_percent;
},
set(val) {
this.$store.commit("form/update_form_disc_percent", val);
}
},
form_disc_amount: {
get() {
return this.$store.state.form.form_disc_amount;
},
set(val) {
this.$store.commit("form/update_form_disc_amount", val);
}
},
form_tax_percent_pph: {
get() {
return this.$store.state.form.form_tax_percent_pph;
},
set(val) {
this.$store.commit("form/update_form_tax_percent_pph", val);
}
},
form_tax_percent_ppn: {
get() {
return this.$store.state.form.form_tax_percent_ppn;
},
set(val) {
this.$store.commit("form/update_form_tax_percent_ppn", val);
}
},
form_shipping_cost: {
get() {
return this.$store.state.form.form_shipping_cost;
},
set(val) {
this.$store.commit("form/update_form_shipping_cost", val);
}
},
form_adjust_amount: {
get() {
return this.$store.state.form.form_adjust_amount;
},
set(val) {
this.$store.commit("form/update_form_adjust_amount", val);
}
},
form_adjust_note: {
get() {
return this.$store.state.form.form_adjust_note;
},
set(val) {
this.$store.commit("form/update_form_adjust_note", val);
}
},
form_po: {
get() {
return this.$store.state.form.form_po;
},
set(val) {
this.$store.commit("form/update_form_po", val)
}
},
form_grandtotal: {
get() {
return this.$store.state.form.form_grandtotal;
},
set(val) {
this.$store.commit("form/update_form_grandtotal", val);
}
},
form_paidamount: {
get() {
return this.$store.state.form.form_paidamount;
},
set(val) {
this.$store.commit("form/update_form_paidamount", val);
}
},
form_note: {
get() {
return this.$store.state.form.form_note;
},
set(val) {
this.$store.commit("form/update_form_note", val);
}
},
form_receiveddate: {
get() {
return this.$store.state.form.form_receiveddate;
},
set(val) {
this.$store.commit("form/update_form_receiveddate", val);
}
},
ReceivedDateFormatted() {
return this.formatDate(this.form_receiveddate);
},
form_receivedby: {
get() {
return this.$store.state.form.form_receivedby;
},
set(val) {
this.$store.commit("form/update_form_receivedby", val);
}
},
list_supplier() {
return this.$store.state.data.list_supplier;
},
list_staff() {
return this.$store.state.data.list_staff;
},
list_po() {
return this.$store.state.data.list_po;
}
},
methods: {
closeDialog() {
this.dialogCreate = false
},
formatDate(date) {
if (!date) return null;
const [year, month, day] = date.split("-");
return `${day}-${month}-${year}`;
},
dialogAddItem() {
},
isObjectEmpty(obj) {
return Object.keys(obj).length === 0 && obj.constructor === Object;
}
},
watch: {
}
}
</script>

View File

@@ -0,0 +1,263 @@
<template>
<v-dialog persistent max-width="60%" v-model="dialog_item">
<v-card>
<v-card-title class="headline grey lighten-2" primary-title>
TAMBAH ITEM
</v-card-title>
<v-card-text>
<v-layout row wrap>
<v-flex pr-2 xs6>
<v-autocomplete
v-model="form_ro"
label="Cari Receive Order"
return-object outline @input="lookupItem"
:items="list_ro.records"
item-text="ReceiveOrderPoNumber"
></v-autocomplete>
</v-flex>
<v-flex xs6>
<v-text-field
v-model="search_item"
label="Cari Item"
outline hide-details
></v-text-field>
</v-flex>
<v-flex xs12>
<v-data-table
v-model="selected_item"
:headers="header"
:items="list_item.records" select-all
:loading="false" item-key="ReceiveOrderPoDetailID"
hide-actions class="elevation-1"
>
<template v-slot:headers="props">
<tr>
<th width="5%" class="blue lighten-3 white--text">
<v-checkbox
:input-value="props.all" primary hide-details
:indeterminate="props.indeterminate" @click.stop="checkItemAll"
></v-checkbox>
</th>
<th
v-for="header in props.headers" :class="header.class"
:key="header.text" :width="header.width">
{{ header.text }}
</th>
</tr>
</template>
<template v-slot:items="props">
<tr :active="props.selected" @click="">
<td>
<v-checkbox
:input-value="props.selected"
primary hide-details
@click.stop="checkPerItem(props.item)"
></v-checkbox>
</td>
<td class="text-xs-center">{{ props.item.M_ItemCode }}</td>
<td class="text-xs-center">{{ props.item.M_ItemDesc }}</td>
<td class="text-xs-center">Rp {{ toRupiah(props.item.ReceiveOrderPoDetailPrice) }}</td>
<td class="text-xs-center">{{ props.item.ReceiveOrderPoDetailQty }} {{ props.item.ReceiveOrderPoItemUnitName }}</td>
</tr>
</template>
</v-data-table>
<div class="text-xs-center my-2">
<v-pagination v-model="page_item" :length="length_page" :total-visible="5"></v-pagination>
</div>
</v-flex>
</v-layout>
</v-card-text>
<v-card-actions>
<v-spacer></v-spacer>
<v-btn color="error" flat @click="closeDialogItem()">Tutup</v-btn>
<v-btn color="success" flat @click="tambahItem()">Tambahkan</v-btn>
</v-card-actions>
</v-card>
</v-dialog>
</template>
<script>
module.exports = {
mounted() {
},
data() {
return {
temp: [],
header: [
{
text: "KODE ITEM",
align: "center",
sortable: false,
value: "no",
width: "15%",
class: "blue lighten-3 white--text",
},
{
text: "NAMA ITEM",
align: "center",
sortable: false,
value: "no",
width: "35%",
class: "blue lighten-3 white--text",
},
{
text: "PRICE",
align: "center",
sortable: false,
value: "no",
width: "30%",
class: "blue lighten-3 white--text",
},
{
text: "QTY",
align: "center",
sortable: false,
value: "no",
width: "15%",
class: "blue lighten-3 white--text",
},
]
}
},
computed: {
search_item: {
get() {
return this.$store.state.data.search_item;
},
set(val) {
this.$store.commit("data/update_search_item", val)
}
},
dialog_item: {
get() {
return this.$store.state.form.dialog_item
},
set(val) {
this.$store.commit("form/update_dialog_item", val)
}
},
form_ro: {
get() {
return this.$store.state.form.form_ro
},
set(val) {
this.$store.commit("form/update_form_ro", val)
}
},
selected_item: {
get() {
return this.$store.state.data.selected_item
},
set(val) {
this.$store.commit("data/update_selected_item", val)
}
},
page_item: {
get() {
return this.$store.state.data.page_item
},
set(val) {
this.$store.commit("data/update_page_item", val)
this.$store.dispatch("data/getListItemRO")
}
},
length_page() {
let total = this.list_item.total
if (total == undefined || total == 0) return 1
return Math.ceil(total/5)
},
list_ro() {
return this.$store.state.data.list_ro;
},
list_item() {
return this.$store.state.data.list_item;
}
},
methods: {
closeDialogItem() {
this.dialog_item = false
this.selected_item = []
},
lookupItem() {
this.$store.dispatch("data/getListItemRO")
},
toRupiah(data) {
// Input validation
if (!data) return '0,00'
// Ensure we're working with a number
if (typeof data !== 'number') {
data = parseFloat(data)
}
// Handle invalid numbers
if (isNaN(data)) return '0,00'
// Convert to string with exactly 2 decimal places without rounding
// This preserves the exact value for display
const parts = data.toString().split('.')
const wholePart = parts[0]
let decimalPart = parts.length > 1 ? parts[1] : '00'
// Ensure we have exactly 2 decimal digits for display
if (decimalPart.length > 2) {
decimalPart = decimalPart.substring(0, 2)
} else if (decimalPart.length < 2) {
decimalPart = decimalPart.padEnd(2, '0')
}
// Format the whole number part with dots as thousand separators
const formatted = wholePart.replace(/\B(?=(\d{3})+(?!\d))/g, ".")
// Return the formatted number
return `${formatted},${decimalPart}`
},
tambahItem() {
let item = this.selected_item
this.$store.commit("form/update_form_item", item)
this.dialog_item = false
this.selected_item = []
},
checkPerItem(item){
const currentSelected = [...this.selected_item]
const index = currentSelected.findIndex(selected => selected.ReceiveOrderPoDetailID === item.ReceiveOrderPoDetailID)
if (index > -1) {
currentSelected.splice(index, 1) // Item is selected, remove it
} else {
currentSelected.push(item) // Item is not selected, add it
}
this.selected_item = currentSelected
},
checkItemAll() {
const allItems = this.list_item.records
const currentSelected = this.selected_item
const allSelected = allItems.length === currentSelected.length
if (allSelected) {
this.selected_item = []
} else {
this.selected_item = [...allItems]
}
},
fn_searchItemRO: _.debounce(function () {
this.$store.dispatch("data/getListItemRO")
}, 1000)
},
watch: {
search_item(val, old) {
if (val === old) return;
if (!val) return
console.log("search");
this.fn_searchItemRO()
}
}
}
</script>

View File

@@ -0,0 +1,476 @@
<template>
<v-dialog v-model="dialogCreate" persistent max-width="90%">
<v-card>
<v-card-title class="headline grey lighten-2" primary-title>
{{ this.is_create ? "FORM PURCHASE INVOICE" : "EDIT FORM PURCHASE INVOICE" }}
</v-card-title>
<v-card-text class="pt-0 pb-0 mt-4">
<v-form ref="formCreate" v-model="validationForm" lazy-validation>
<v-layout row wrap>
<v-flex pr-2 xs3>
<v-menu readonly
v-model="menuDateForm" transition="scale-transition"
full-width offset-y lazy :nudge-right="40"
max-width="290px" min-width="290px"
:close-on-content-click="false"
>
<template v-slot:activator="{on}">
<v-text-field
label="Tanggal" readonly v-on="on"
outline hide-details :value="DateFormated"
></v-text-field>
</template>
<v-date-picker readonly
no-title v-model="form_faktur.form_date" @input="menuDateForm = false"
></v-date-picker>
</v-menu>
</v-flex>
<v-flex pr-2 xs3>
<v-menu readonly
v-model="menuDueDateForm" transition="scale-transition"
full-width offset-y lazy :nudge-right="40"
max-width="290px" min-width="290px"
:close-on-content-click="false"
>
<template v-slot:activator="{on}">
<v-text-field
label="Tanggal Jatuh Tempo" readonly v-on="on"
outline hide-details :value="DueDateFormated"
></v-text-field>
</template>
<v-date-picker readonly
no-title v-model="form_faktur.form_duedate"
@input="menuDueDateForm = false"
></v-date-picker>
</v-menu>
</v-flex>
<v-flex pr-2 xs3>
<v-menu
v-model="menuDraftDateForm" transition="scale-transition"
full-width offset-y lazy :nudge-right="40"
max-width="290px" min-width="290px"
:close-on-content-click="false"
>
<template v-slot:activator="{on}">
<v-text-field
label="Tanggal Rencana Bayar" readonly v-on="on"
:value="DraftDateFormated" outline :rules="rules_text"
></v-text-field>
</template>
<v-date-picker
no-title v-model="form_faktur.form_draftdate"
@input="menuDraftDateForm = false"
></v-date-picker>
</v-menu>
</v-flex>
<v-flex pr-2 xs3>
<v-autocomplete
v-model="form_faktur.form_supplier"
label="Supplier" menu-icon="mdi-chevron-down"
item-text="SupplierName" @input="searchPO"
:items="list_supplier.records" readonly
:rules="rules_text" outline
return-object required
></v-autocomplete>
</v-flex>
<!-- 2nd row -->
<v-flex pr-2 xs3>
<v-text-field
v-model="form_faktur.form_po.ReceiveOrderPoNumber"
label="Nomor GRN" outline :rules="rules_text" readonly
></v-text-field>
</v-flex>
<v-flex pr-2 xs3>
<v-text-field
v-model="form_faktur.form_refnumber" readonly
label="Nomor Referensi Penerimaan" outline :rules="rules_text"
></v-text-field>
</v-flex>
<v-flex pr-2 xs3>
<v-text-field
v-model="form_faktur.form_supplier_invoicenum"
label="Nomor Invoice Supplier" outline :rules="rules_text"
></v-text-field>
</v-flex>
<v-flex pr-2 xs3>
<v-menu
v-model="menuSupplierDateForm" transition="scale-transition"
full-width offset-y lazy :nudge-right="40"
max-width="290px" min-width="290px"
:close-on-content-click="false"
>
<template v-slot:activator="{on}">
<v-text-field
label="Tanggal Invoice Supplir" readonly v-on="on"
:value="SupplierInvoiceDateFormated" outline :rules="rules_text"
></v-text-field>
</template>
<v-date-picker
no-title v-model="form_faktur.form_supplier_invoicedate"
@input="menuSupplierDateForm = false"
></v-date-picker>
</v-menu>
</v-flex>
<!-- 5th row -->
<v-flex xs12>
<v-textarea
v-model="form_faktur.form_note"
label="Catatan" outline rows="2"
></v-textarea>
</v-flex>
</v-layout>
</v-form>
<v-data-table
v-if="form_item.length > 0"
:headers="headers" hide-actions
:items="form_item" class="elevation-1 mt-2"
item-key="ReceiveOrderPoDetailID"
>
<template slot="items" slot-scope="props">
<tr>
<td class="text-xs-center">{{ props.item.PurchaseOrderNumber }}</td>
<td class="text-xs-center">{{ props.item.M_ItemDesc }}</td>
<td class="text-xs-center">{{ props.item.ReceiveOrderPoDetailQty }} {{ props.item.ReceiveOrderPoItemUnitName }} </td>
<td class="text-xs-right">Rp. {{ toRupiah(props.item.ReceiveOrderPoDetailPrice) }}</td>
<td class="text-xs-right">
<kbd v-if="props.item.PurchaseOrderSummaryDiscountType == 'P'">{{ props.item.Discount }}%</kbd>
Rp. {{ calcItemDisc(props.item) }}
</td>
<td class="text-xs-right">Rp. {{ calcSubtal(props.item) }}</td>
<td class="text-xs-right">Rp. {{ calcTotal(props.item) }}</td>
</tr>
</template>
<template v-slot:footer>
<tr>
<td :colspan="6">
<strong class="ml-2">SUBTOTAL</strong>
</td>
<td :colspan="1" class="text-xs-right">
<strong>Rp. {{ toRupiah(form_faktur.form_subtotal) }}</strong>
</td>
</tr>
<tr v-if="form_faktur.form_shipping_cost > 0.00">
<td :colspan="6">
<strong class="ml-2">BIAYA PENGIRIMAN</strong>
</td>
<td :colspan="1" class="text-xs-right">
<strong>Rp. {{ toRupiah(form_faktur.form_shipping_cost) }}</strong>
</td>
</tr>
<tr>
<td :colspan="6">
<strong class="ml-2">DISKON</strong>
</td>
<td :colspan="1" class="text-xs-right">
<strong>Rp. {{ toRupiah(form_faktur.form_disc_amount) }}</strong>
</td>
</tr>
<!-- <tr>
<td :colspan="6" class="text-xs-right">
<strong class="ml-2">PAJAK (PPN)</strong>
</td>
<td :colspan="1" class="text-xs-right">
<strong>Rp. {{ toRupiah(form_faktur.form_tax_amount_ppn) }}</strong>
</td>
</tr> -->
<!-- <tr>
<td :colspan="6" class="text-xs-right">
<strong class="ml-2">PAJAK PENGHASILAN (PPH)</strong>
</td>
<td :colspan="1" class="text-xs-right">
<strong>Rp. {{ toRupiah(form_faktur.form_tax_amount_pph) }}</strong>
</td>
</tr> -->
<tr>
<td :colspan="6">
<strong class="ml-2">PAJAK</strong>
</td>
<td :colspan="1" class="text-xs-right">
<strong>Rp. {{ toRupiah(form_faktur.form_tax_amount_ppn + form_faktur.form_tax_amount_pph) }}</strong>
</td>
</tr>
<tr>
<td :colspan="6">
<strong class="ml-2">GRAND TOTAL</strong>
</td>
<td :colspan="1" class="text-xs-right">
<strong>Rp. {{ toRupiah(form_faktur.form_grandtotal) }}</strong>
</td>
</tr>
</template>
</v-data-table>
</v-card-text>
<v-card-actions>
<v-spacer></v-spacer>
<v-btn color="error" flat @click="closeDialog()" :disabled="isSubmitting">
Tutup
</v-btn>
<v-btn color="primary" dark @click="simpanFaktur()" :disabled="isSubmitting" :loading="isSubmitting">
Simpan
</v-btn>
</v-card-actions>
</v-card>
</v-dialog>
</template>
<script>
module.exports = {
components: {},
mounted() {},
data() {
return {
isSubmitting: false,
validationForm: false,
menuDateForm: false,
menuDueDateForm: false,
menuDraftDateForm: false,
menuTaxDateForm: false,
menuSupplierDateForm: false,
rules_text: [(v) => !!v || "Tidak boleh kosong"],
rules_number: [(v) => !isNaN(v) || "Tidak boleh kosong"],
headers: [
{
text: 'NOMOR PO', align: 'center', sortable: false, value: 'item',
width: '15%', class: 'blue lighten-3 white--text'
},
{
text: 'ITEM', align: 'center', sortable: false, value: 'item',
width: '15%', class: 'blue lighten-3 white--text'
},
{
text: 'QTY', align: 'center', sortable: false, value: 'qty',
width: '10%', class: 'blue lighten-3 white--text'
},
{
text: 'HARGA', align: 'center', sortable: false, value: 'price',
width: '15%', class: 'blue lighten-3 white--text'
},
{
text: 'DISKON', align: 'center', sortable: false, value: 'disc',
width: '15%', class: 'blue lighten-3 white--text'
},
{
text: 'HARGA DISKON', align: 'center', sortable: false, value: 'pricedisc',
width: '15%', class: 'blue lighten-3 white--text'
},
{
text: 'JUMLAH', align: 'center', sortable: false, value: 'total',
width: '15%', class: 'blue lighten-3 white--text'
}
]
}
},
computed: {
dialogCreate: {
get() {
return this.$store.state.form.opendialog;
},
set(val) {
this.$store.commit("form/update_opendialog", val)
}
},
form_faktur: {
get() {
return this.$store.state.form.form_faktur
},
set(val) {
this.$store.commit("form/update_form_faktur", val)
}
},
dialog_item: {
get() {
return this.$store.state.form.dialog_item;
},
set(val) {
this.$store.commit("form/update_dialog_item", val)
}
},
form_item: {
get() {
return this.$store.state.form.form_item
},
set(val) {
this.$store.commit("form/update_form_item", val)
}
},
is_create: {
get() {
return this.$store.state.form.is_create
},
set(val) {
this.$store.commit("form/update_is_create", val)
}
},
DateFormated() {
return this.formatDate(this.form_faktur.form_date);
},
DueDateFormated() {
return this.formatDate(this.form_faktur.form_duedate);
},
SupplierInvoiceDateFormated() {
return this.formatDate(this.form_faktur.form_supplier_invoicedate);
},
DraftDateFormated() {
return this.formatDate(this.form_faktur.form_draftdate);
},
list_supplier() {
return this.$store.state.data.list_supplier;
},
list_po() {
return this.$store.state.data.list_po;
},
rupiah() {
return this.toRupiah(this.form_faktur.form_grandtotal);
}
},
methods: {
simpanFaktur() {
if (this.$refs.formCreate.validate()) {
this.isSubmitting = true;
console.log("isSubmitting should be true=> ", this.isSubmitting)
if (this.is_create) {
this.$store.dispatch("form/createFaktur")
this.isSubmitting=false;
console.log("isSubmitting should be false=> ", this.isSubmitting)
} else {
this.$store.dispatch("form/updateFaktur")
this.isSubmitting=false;
}
}
},
closeDialog() {
this.dialogCreate = false
this.$store.commit("data/update_list_item", {records: [], total: 0})
this.$store.dispatch("form/resetForm")
this.$refs.formCreate.resetValidation()
},
formatDate(date) {
if (!date) return null
const [year, month, day] = date.split("-")
return `${day}-${month}-${year}`;
},
searchPO() {
/* add tanggal jatuh tempo */
let faktur = JSON.parse(JSON.stringify(this.form_faktur));
let supplier_top = faktur.form_supplier.SupplierTermOfPayment;
let tempdate = new Date(faktur.form_date);
tempdate.setDate(tempdate.getDate() + Number(supplier_top));
faktur.form_duedate = tempdate.toISOString().split('T')[0];
this.form_faktur = faktur;
/* get listting po */
this.$store.dispatch("data/getListPO");
},
detailPO() {
this.$store.dispatch("data/getDetailPO");
this.form_item = []
this.$store.commit("form/update_form_ro", {})
this.$store.commit("data/update_list_item", {records: [], total: 0})
},
isObjectEmpty(obj) {
return Object.keys(obj).length === 0 && obj.constructor === Object;
},
dialogAddItem() {
this.dialog_item = true
this.$store.dispatch("data/getListRO");
this.$store.commit("data/update_selected_item", this.form_item)
},
deleteItemFaktur(item) {
let arr = this.form_item
if (!arr) {
return
}
let newArr = arr.filter(x => x.ReceiveOrderPoDetailID !== item.ReceiveOrderPoDetailID)
this.form_item = newArr
},
toRupiah(data) {
// Input validation
if (!data) return '0,00'
// Ensure we're working with a number
if (typeof data !== 'number') {
data = parseFloat(data)
}
// Handle invalid numbers
if (isNaN(data)) return '0,00'
// Convert to string with exactly 2 decimal places without rounding
// This preserves the exact value for display
const parts = data.toString().split('.')
const wholePart = parts[0]
let decimalPart = parts.length > 1 ? parts[1] : '00'
// Ensure we have exactly 2 decimal digits for display
if (decimalPart.length > 2) {
decimalPart = decimalPart.substring(0, 2)
} else if (decimalPart.length < 2) {
decimalPart = decimalPart.padEnd(2, '0')
}
// Format the whole number part with dots as thousand separators
const formatted = wholePart.replace(/\B(?=(\d{3})+(?!\d))/g, ".")
// Return the formatted number
return `${formatted},${decimalPart}`
},
calcItemDisc(item) {
if (item.PurchaseOrderSummaryDiscountType == 'R') {
this.form_item.find(a => a.ReceiveOrderPoDetailID === item.ReceiveOrderPoDetailID).disc_amount = item.Discount
return this.toRupiah(item.Discount)
}
let d = (Number(item.ReceiveOrderPoDetailPrice) * Number(item.Discount)) / 100
this.form_item.find(a => a.ReceiveOrderPoDetailID === item.ReceiveOrderPoDetailID).disc_amount = d
return this.toRupiah(d.toString())
},
calcSubtal(item) {
price = Number(item.ReceiveOrderPoDetailPrice)
disc = Number(item.Discount)
if (item.PurchaseOrderSummaryDiscountType == 'R') {
return this.toRupiah((price - disc).toString())
}
let d = (price * disc) / 100
return this.toRupiah((price - d).toString())
},
calcTotal(item) {
qty = Number(item.ReceiveOrderPoDetailQty)
price = Number(item.ReceiveOrderPoDetailPrice)
disc = Number(item.Discount)
let tl = 0
if (item.PurchaseOrderSummaryDiscountType == 'R') {
tl = qty * (price - disc)
this.form_item.find(a => a.ReceiveOrderPoDetailID === item.ReceiveOrderPoDetailID).subtotal = tl
return this.toRupiah((tl).toString())
}
let d = (price * disc) / 100
tl = qty * (price - d)
this.form_item.find(a => a.ReceiveOrderPoDetailID === item.ReceiveOrderPoDetailID).subtotal = tl
return this.toRupiah((tl).toString())
},
fn_calcSummary: _.debounce(function () {
this.$store.dispatch("form/calcSummary")
}, 1000),
fn_adjust: _.debounce(function () {
this.$store.dispatch("form/calcSummary")
}, 1000)
},
watch: {
form_item(newValue, oldValue) {
this.fn_calcSummary();
},
}
}
</script>

View File

@@ -0,0 +1,477 @@
<template>
<div style="width: 100%;">
<v-toolbar color="primary" dark>
<v-toolbar-title class="white--text">PURCHASE INVOICE</v-toolbar-title>
<v-spacer></v-spacer>
<!-- <v-btn icon @click="newFaktur()">
<v-icon large>add_box</v-icon>
</v-btn> -->
</v-toolbar>
<v-card class="pa-2">
<v-layout row wrap>
<v-flex xs2 pr-2>
<v-menu
v-model="filterDateForm" transition="scale-transition"
full-width offset-y lazy :nudge-right="40"
max-width="290px" min-width="290px"
:close-on-content-click="false"
>
<template v-slot:activator="{on}">
<v-text-field
label="Tanggal Awal" readonly v-on="on"
outline hide-details :value="DateFormated"
></v-text-field>
</template>
<v-date-picker
no-title v-model="filter.date" @input="filterDateForm = false"
></v-date-picker>
</v-menu>
</v-flex>
<v-flex xs2 pr-2>
<v-menu
v-model="filterEndDateForm" transition="scale-transition"
full-width offset-y lazy :nudge-right="40"
max-width="290px" min-width="290px"
:close-on-content-click="false"
>
<template v-slot:activator="{on}">
<v-text-field
label="Tanggal Akhir" readonly v-on="on"
outline hide-details :value="EndDateFormated"
></v-text-field>
</template>
<v-date-picker
no-title v-model="filter.enddate" @input="filterEndDateForm = false"
></v-date-picker>
</v-menu>
</v-flex>
<v-flex xs3 pr-2>
<v-autocomplete
v-model="filter.supplier" label="Supplier" hide-details item-value="SupplierID"
:items="list_supplier.records" outline item-text="SupplierName" clearable
></v-autocomplete>
</v-flex>
<v-flex xs2 pr-2>
<v-autocomplete
v-model="filter.status" label="Status" hide-details
:items="statusFaktur" outline
></v-autocomplete>
</v-flex>
<v-flex xs3>
<v-layout row justify-space-between>
<v-flex xs10 pr-2>
<v-text-field
v-model="filter.nomor" hide-details outline
label="Nomor Faktur / Delivery / Supplier Invoice"
></v-text-field>
</v-flex>
<v-flex xs2>
<v-btn
dark color="primary" @click="searchFaktur()"
style="height: 40px; min-width: 40px;"
>
<v-icon>search</v-icon>
</v-btn>
</v-flex>
</v-layout>
</v-flex>
</v-layout>
</v-card>
<v-card class="pa-2 mt-2">
<v-data-table
:headers="headers" :items="list_faktur.records"
hide-actions class="elevation-1 mb-2"
>
<template v-slot:items="props">
<tr>
<td class="text-xs-center pa-2">{{ props.index + 1}}</td>
<td class="text-xs-center pa-2">{{ props.item.SupplierInvoiceNumber }}</td>
<td class="text-xs-center pa-2">{{ deFormatedDate(props.item.SupplierInvoiceDate) }}</td>
<td class="text-xs-center pa-2">{{ deFormatedDate(props.item.SupplierInvoiceDueDate) }}</td>
<td class="text-xs-center pa-2">{{ props.item.SupplierName }}</td>
<td class="text-xs-center pa-2">{{ props.item.WarehouseName }}</td>
<td class="text-xs-center pa-2 text-truncate">{{ props.item.SupplierInvoiceNote }}</td>
<td class="text-xs-center pa-2">Rp. {{ toRupiah(props.item.SupplierInvoiceGrandTotal) }}</td>
<td class="text-xs-center pa-2">{{ props.item.SupplierInvoiceStatus }}</td>
<td class="text-xs-center pa-2">
<v-layout align-center justify-center row fill-height>
<v-tooltip bottom class="pr-1">
<template v-slot:activator="{ on }">
<v-btn
color="info" dark small v-on="on"
style="min-width: 40px; margin: 0;"
@click="viewAttachment(props.item)"
>
<v-icon small>attachment</v-icon>
</v-btn>
</template>
<span>Bukti Penerimaan</span>
</v-tooltip>
<div v-show="props.item.SupplierInvoiceStatus == 'Draft'">
<v-tooltip bottom pr-2>
<template v-slot:activator="{on}">
<v-btn
color="warning" dark small v-on="on"
style="min-width: 40px; margin: 0;"
@click="editFaktur(props.item)"
>
<v-icon small>edit</v-icon>
</v-btn>
</template>
<span>Edit Draft</span>
</v-tooltip>
<v-tooltip bottom pr-2>
<template v-slot:activator="{on}">
<v-btn
color="success" dark small v-on="on"
style="min-width: 40px; margin: 0;"
@click="fakturVerify(props.item)"
>
<v-icon small>send</v-icon>
</v-btn>
</template>
<span>Verifikasi Draft</span>
</v-tooltip>
</div>
<div v-show="props.item.SupplierInvoiceStatus == 'Verified'">
<v-tooltip bottom pr-2>
<template v-slot:activator="{on}">
<v-btn
color="success" dark small v-on="on"
style="min-width: 40px; margin: 0;"
@click="fakturApprove(props.item)"
>
<v-icon small>done_all</v-icon>
</v-btn>
</template>
<span>Approve Faktur</span>
</v-tooltip>
<v-tooltip bottom pr-2>
<template v-slot:activator="{on}">
<v-btn
color="error" dark small v-on="on"
style="min-width: 40px; margin: 0;"
@click="fakturReject(props.item)"
>
<v-icon small>block</v-icon>
</v-btn>
</template>
<span>Tolak Faktur</span>
</v-tooltip>
</div>
<div v-show="props.item.SupplierInvoiceStatus == 'Approved'">
<v-tooltip bottom>
<template v-slot:activator="{on}">
<v-btn
style="min-width: 40px; margin: 0;"
color="blue" small v-on="on" dark
@click="viewFaktur(props.item)"
>
<v-icon small>visibility</v-icon>
</v-btn>
</template>
<span>Detail Faktur</span>
</v-tooltip>
</div>
</v-layout>
</td>
</tr>
</template>
</v-data-table>
<v-divider></v-divider>
<div class="pa-2 mt-2 text-xs-center">
<v-pagination v-model="page_list" :length="list_length" :total-visible="10"></v-pagination>
</div>
</v-card>
<dialog-form></dialog-form>
<dialog-item></dialog-item>
<dialog-view></dialog-view>
<dialog-attachment></dialog-attachment>
<v-snackbar
v-model="snack.isOpen" :timeout="3000" :multi-line="false"
:vertical="false" :top="true" :color="snack.color"
>
{{ snack.message }}
<v-btn class="pl-2" flat @click="closeSnackbar()">Tutup</v-btn>
</v-snackbar>
<v-dialog v-model="confirmDialog" persistent max-width="50%">
<v-card>
<v-card-title class="headline grey lighten-2" primary-title>
KONFIRMASI HAPUS FAKTUR
</v-card-title>
<v-card-text class="pt-0 pb-0 mt-4">
<h4>Apakah anda yakin ingin menghapus faktur no: {{ this.fakturdelete.SupplierInvoiceNumber }}</h4>
</v-card-text>
<v-card-actions>
<v-spacer></v-spacer>
<v-btn color="error" flat @click="confirmDialog = false">
Batal
</v-btn>
<v-btn color="primary" dark @click="deleteFaktur()">
Hapus
</v-btn>
</v-card-actions>
</v-card>
</v-dialog>
</div>
</template>
<style scoped>
.text-truncate {
max-width: 150px;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
</style>
<script>
module.exports = {
components: {
"dialog-form": httpVueLoader("./dialogform.vue"),
"dialog-item": httpVueLoader("./dialogItem.vue"),
"dialog-view": httpVueLoader("./viewFaktur.vue"),
"dialog-attachment": httpVueLoader("./dialog-attachment.vue"),
},
mounted() {
this.$store.dispatch("data/getListSupplier")
this.$store.dispatch("data/getListFaktur")
},
data() {
return {
filterDateForm: false,
filterEndDateForm: false,
confirmDialog: false,
fakturdelete: {},
statusFaktur: ['All', 'Draft', 'Verified', 'Approved', 'Rejected', 'Scheduled', 'Paid', 'Partially Paid', 'Canceled'],
headers: [
{
text: 'NO', align: 'center', sortable: false, value: 'no',
width: '5%', class: 'blue lighten-3 white--text'
},
{
text: 'NOMOR FAKTUR', align: 'center', sortable: false, value: 'nomor',
width: '10%', class: 'blue lighten-3 white--text'
},
{
text: 'TANGGAL', align: 'center', sortable: false, value: 'tanggal',
width: '10%', class: 'blue lighten-3 white--text'
},
{
text: 'JATUH TEMPO', align: 'center', sortable: false, value: 'jatuhtempo',
width: '10%', class: 'blue lighten-3 white--text'
},
{
text: 'SUPPLIER', align: 'center', sortable: false, value: 'supplier',
width: '15%', class: 'blue lighten-3 white--text'
},
{
text: 'GUDANG PENERIMA', align: 'center', sortable: false, value: 'warehouse',
width: '10%', class: 'blue lighten-3 white--text'
},
{
text: 'CATATAN', align: 'center', sortable: false, value: 'supplier',
width: '15%', class: 'blue lighten-3 white--text'
},
{
text: 'TOTAL', align: 'center', sortable: false, value: 'status',
width: '10%', class: 'blue lighten-3 white--text'
},
{
text: 'STATUS', align: 'center', sortable: false, value: 'status',
width: '5%', class: 'blue lighten-3 white--text'
},
{
text: 'AKSI', align: 'center', sortable: false, value: 'aksi',
width: '15%', class: 'blue lighten-3 white--text'
},
]
}
},
computed: {
dialogCreate: {
get() {
return this.$store.state.form.opendialog;
},
set(val) {
this.$store.commit("form/update_opendialog", val);
}
},
snack: {
get() {
return this.$store.state.data.snack;
},
set(val) {
this.$store.commit("data/update_snack", val)
}
},
page_list: {
get() {
return this.$store.state.data.page_list
},
set(val) {
this.$store.commit("data/update_page_list", val)
this.$store.dispatch("data/getListFaktur")
}
},
list_length() {
let total = this.list_faktur.total
if (total == undefined || total == 0) return 1
return Math.ceil(total/10)
},
list_faktur() {
return this.$store.state.data.list_faktur
},
filter: {
get() {
return this.$store.state.data.filter
},
set(val) {
this.$store.commit("data/update_filter", val)
}
},
list_supplier() {
return this.$store.state.data.list_supplier;
},
DateFormated() {
return this.formatDate(this.filter.date)
},
EndDateFormated() {
return this.formatDate(this.filter.enddate)
},
is_create: {
get() {
return this.$store.state.form.is_create
},
set(val) {
this.$store.commit("form/update_is_create", val)
}
},
viewFakturDialog: {
get() {
return this.$store.state.data.viewfaktur
},
set(val) {
this.$store.commit("data/update_viewfaktur", val)
}
}
},
methods: {
newFaktur() {
this.is_create = true;
this.dialogCreate = true;
this.$store.dispatch("data/getListSupplier");
this.$store.dispatch("data/getListStaff");
this.$store.dispatch("form/resetForm");
},
viewAttachment(item) {
let param = {
roID: item.ReceiveOrderPoID,
category: item.PurchaseOrderItemCategoryID
}
this.$store.dispatch('data/getListAttachment', param)
this.$store.commit('data/update_dialog_attachment', true)
},
editFaktur(item) {
this.is_create = false
this.dialogCreate = true;
this.$store.dispatch("data/getListSupplier");
this.$store.dispatch("data/getDetailFaktur", {SInvoiceID: item.SupplierInvoiceID});
},
viewFaktur(item) {
this.viewFakturDialog = true;
this.$store.dispatch("data/getListSupplier");
this.$store.dispatch("data/getDetailFaktur", {SInvoiceID: item.SupplierInvoiceID});
},
konfirmDelete(item) {
this.confirmDialog = true
this.fakturdelete = item
},
toRupiah(data) {
// Input validation
if (!data) return '0,00'
// Ensure we're working with a number
if (typeof data !== 'number') {
data = parseFloat(data)
}
// Handle invalid numbers
if (isNaN(data)) return '0,00'
// Convert to string with exactly 2 decimal places without rounding
// This preserves the exact value for display
const parts = data.toString().split('.')
const wholePart = parts[0]
let decimalPart = parts.length > 1 ? parts[1] : '00'
// Ensure we have exactly 2 decimal digits for display
if (decimalPart.length > 2) {
decimalPart = decimalPart.substring(0, 2)
} else if (decimalPart.length < 2) {
decimalPart = decimalPart.padEnd(2, '0')
}
// Format the whole number part with dots as thousand separators
const formatted = wholePart.replace(/\B(?=(\d{3})+(?!\d))/g, ".")
// Return the formatted number
return `${formatted},${decimalPart}`
},
deleteFaktur() {
this.$store.dispatch("form/deleteFaktur", {SInvoiceID: this.fakturdelete.SupplierInvoiceID})
this.fakturdelete = {}
this.confirmDialog = false
},
closeSnackbar() {
let snacc = { isOpen: false, message: "", color: "error" }
this.snack = snacc
},
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")}`;
},
searchFaktur() {
this.$store.dispatch("data/getListFaktur")
},
fakturApprove(item) {
if (item.SupplierInvoiceID != '') {
this.$store.dispatch("form/approveFaktur", {fakturID: item.SupplierInvoiceID});
}
},
fakturReject(item) {
if (item.SupplierInvoiceID != '') {
this.$store.dispatch("form/rejectFaktur", {fakturID: item.SupplierInvoiceID});
}
},
fakturVerify(item) {
// console.log(item)
// Validation before verify
if (item.SupplierInvoiceSupplierInvoiceNumber == '' || item.SupplierInvoiceSupplierInvoiceNumber == null) {
this.snack = { isOpen: true, message: "Nomor invoice supplier tidak boleh kosong", color: "error" }
return
}
if (item.SupplierInvoiceID != '') {
this.$store.dispatch("form/verifyFaktur", {fakturID: item.SupplierInvoiceID});
}
}
},
watch: {
}
}
</script>

View File

@@ -0,0 +1,412 @@
<template>
<v-dialog v-model="viewfakturDialog" persistent max-width="90%">
<v-card>
<v-card-title class="headline grey lighten-2" primary-title>
FAKTUR PO
</v-card-title>
<v-card-text>
<v-layout row wrap>
<!-- 1st row -->
<v-flex pr-2 xs3>
<v-menu
v-model="menuDateForm" transition="scale-transition"
full-width offset-y lazy :nudge-right="40"
max-width="290px" min-width="290px"
:close-on-content-click="false"
>
<template v-slot:activator="{on}">
<v-text-field
label="Tanggal" readonly disabled
outline hide-details :value="DateFormated"
></v-text-field>
</template>
<v-date-picker
no-title v-model="form_faktur.form_date"
readonly disabled
></v-date-picker>
</v-menu>
</v-flex>
<v-flex pr-2 xs3>
<v-menu
v-model="menuDueDateForm" transition="scale-transition"
full-width offset-y lazy :nudge-right="40"
max-width="290px" min-width="290px"
:close-on-content-click="false"
>
<template v-slot:activator="{on}">
<v-text-field
label="Tanggal Jatuh Tempo" readonly disabled
outline hide-details :value="DueDateFormated"
></v-text-field>
</template>
<v-date-picker
no-title v-model="form_faktur.form_duedate"
readonly disabled
></v-date-picker>
</v-menu>
</v-flex>
<v-flex pr-2 xs3>
<v-menu
v-model="menuDraftDateForm" transition="scale-transition"
full-width offset-y lazy :nudge-right="40"
max-width="290px" min-width="290px"
:close-on-content-click="false"
>
<template v-slot:activator="{on}">
<v-text-field
label="Tanggal Rencana Bayar" readonly disabled v-on="on"
:value="DraftDateFormated" outline :rules="rules_text"
></v-text-field>
</template>
<v-date-picker
no-title v-model="form_faktur.form_draftdate"
readonly disabled
></v-date-picker>
</v-menu>
</v-flex>
<v-flex pr-2 xs3>
<v-text-field
:value="form_faktur.form_supplier ? form_faktur.form_supplier.SupplierName : ''"
label="Supplier" readonly disabled outline
></v-text-field>
</v-flex>
<!-- 2nd row -->
<v-flex pr-2 xs3>
<v-text-field
:value="form_faktur.form_po
? form_faktur.form_po.ReceiveOrderPoNumber
: ''"
label="Nomor GRN" readonly disabled outline
></v-text-field>
</v-flex>
<v-flex pr-2 xs3>
<v-text-field
v-model="form_faktur.form_refnumber" readonly disabled
label="Nomor Referensi" outline
></v-text-field>
</v-flex>
<v-flex pr-2 xs3>
<v-text-field
v-model="form_faktur.form_supplier_invoicenum" readonly disabled
label="Nomor Invoice Supplier" outline
></v-text-field>
</v-flex>
<v-flex pr-2 xs3>
<v-menu
v-model="menuSupplierDateForm" transition="scale-transition"
full-width offset-y lazy :nudge-right="40"
max-width="290px" min-width="290px"
:close-on-content-click="false"
>
<template v-slot:activator="{on}">
<v-text-field
label="Tanggal Invoice Supplier" readonly disabled
:value="SupplierInvoiceDateFormated" outline hide-details
></v-text-field>
</template>
<v-date-picker
no-title v-model="form_faktur.form_supplier_invoicedate"
@input="menuSupplierDateForm = false" readonly disabled
></v-date-picker>
</v-menu>
</v-flex>
<!-- 3rd row -->
<v-flex xs12>
<v-textarea
v-model="form_faktur.form_note" readonly disabled
label="Catatan" outline rows="2"
></v-textarea>
</v-flex>
</v-layout>
<v-data-table
v-if="form_item.length > 0"
:headers="headers" hide-actions
:items="form_item" class="elevation-1 mt-2"
item-key="ReceiveOrderPoDetailID"
>
<template slot="items" slot-scope="props">
<tr>
<td class="text-xs-center">{{ props.item.PurchaseOrderNumber }}</td>
<td class="text-xs-center">{{ props.item.M_ItemDesc }}</td>
<td class="text-xs-center">{{ props.item.ReceiveOrderPoDetailQty }}</td>
<td class="text-xs-center">Rp. {{ toRupiah(props.item.ReceiveOrderPoDetailPrice) }}</td>
<td class="text-xs-center">
<kbd v-if="props.item.PurchaseOrderSummaryDiscountType == 'P'">{{ props.item.Discount }}%</kbd>
Rp. {{ calcItemDisc(props.item) }}
</td>
<td class="text-xs-center">Rp. {{ calcSubtal(props.item) }}</td>
<td class="text-xs-right">Rp. {{ calcTotal(props.item) }}</td>
</tr>
</template>
<template v-slot:footer>
<tr>
<td :colspan="6">
<strong class="ml-2">SUBTOTAL</strong>
</td>
<td :colspan="1" class="text-xs-right">
<strong>Rp. {{ toRupiah(form_faktur.form_subtotal) }}</strong>
</td>
</tr>
<tr v-if="form_faktur.form_shipping_cost > 0.00">
<td :colspan="6">
<strong class="ml-2">BIAYA PENGIRIMAN</strong>
</td>
<td :colspan="1" class="text-xs-right">
<strong>Rp. {{ toRupiah(form_faktur.form_shipping_cost) }}</strong>
</td>
</tr>
<tr>
<td :colspan="6">
<strong class="ml-2">DISKON</strong>
</td>
<td :colspan="1" class="text-xs-right">
<!-- <kbd v-if="form_faktur.form_disc_type == 'P'">{{ parseFloat(form_faktur.form_disc_po).toFixed(2) }}%</kbd> -->
<strong>Rp. {{ toRupiah(form_faktur.form_disc_amount) }}</strong>
</td>
</tr>
<!-- <tr>
<td :colspan="5" class="text-xs-left">
<strong class="ml-4">PAJAK PENDAPATAN (PPN)</strong>
</td>
<td :colspan="1" class="text-xs-center">
<strong>Rp. {{ toRupiah(form_faktur.form_tax_amount_ppn) }}</strong>
</td>
</tr> -->
<!-- <tr>
<td :colspan="5" class="text-xs-left">
<strong class="ml-4">PAJAK PENGHASILAN (PPH)</strong>
</td>
<td :colspan="1" class="text-xs-center">
<strong>Rp. {{ toRupiah(form_faktur.form_tax_amount_pph) }}</strong>
</td>
</tr> -->
<tr>
<td :colspan="6">
<strong class="ml-2">PAJAK</strong>
</td>
<td :colspan="1" class="text-xs-right">
<strong>Rp. {{ toRupiah(form_faktur.form_tax_amount_ppn + form_faktur.form_tax_amount_pph) }}</strong>
</td>
</tr>
<tr>
<td :colspan="6">
<strong class="ml-2">GRAND TOTAL</strong>
</td>
<td :colspan="1" class="text-xs-right">
<strong>Rp. {{ toRupiah(form_faktur.form_grandtotal) }}</strong>
</td>
</tr>
</template>
</v-data-table>
</v-card-text>
<v-card-actions>
<v-spacer></v-spacer>
<v-btn color="error" flat @click="closeDialog()">
Tutup
</v-btn>
</v-card-actions>
</v-card>
</v-dialog>
</template>
<script>
module.exports = {
components: {},
mounted() {},
data() {
return {
menuDateForm: false,
menuDueDateForm: false,
menuDraftDateForm: false,
menuTaxDateForm: false,
menuSupplierDateForm: false,
rules_text: [(v) => !!v || "Tidak boleh kosong"],
rules_number: [(v) => !isNaN(v) || "Tidak boleh kosong"],
headers: [
{
text: 'NOMOR PO', align: 'center', sortable: false, value: 'item',
width: '15%', class: 'blue lighten-3 white--text'
},
{
text: 'ITEM', align: 'center', sortable: false, value: 'item',
width: '15%', class: 'blue lighten-3 white--text'
},
{
text: 'QTY', align: 'center', sortable: false, value: 'qty',
width: '10%', class: 'blue lighten-3 white--text'
},
{
text: 'HARGA', align: 'center', sortable: false, value: 'price',
width: '15%', class: 'blue lighten-3 white--text'
},
{
text: 'DISKON', align: 'center', sortable: false, value: 'disc',
width: '15%', class: 'blue lighten-3 white--text'
},
{
text: 'HARGA DISKON', align: 'center', sortable: false, value: 'pricedisc',
width: '15%', class: 'blue lighten-3 white--text'
},
{
text: 'JUMLAH', align: 'center', sortable: false, value: 'total',
width: '15%', class: 'blue lighten-3 white--text'
}
]
}
},
computed: {
viewfakturDialog: {
get() {
return this.$store.state.data.viewfaktur
},
set(val) {
this.$store.commit("data/update_viewfaktur", val)
}
},
form_faktur: {
get() {
return this.$store.state.form.form_faktur
},
set(val) {
this.$store.commit("form/update_form_faktur", val)
}
},
form_item: {
get() {
return this.$store.state.form.form_item
},
set(val) {
this.$store.commit("form/update_form_item", val)
}
},
DateFormated() {
return this.formatDate(this.form_faktur.form_date);
},
DueDateFormated() {
return this.formatDate(this.form_faktur.form_duedate);
},
SupplierInvoiceDateFormated() {
return this.formatDate(this.form_faktur.form_supplier_invoicedate);
},
DraftDateFormated() {
return this.formatDate(this.form_faktur.form_draftdate);
},
list_supplier() {
return this.$store.state.data.list_supplier;
},
list_po() {
return this.$store.state.data.list_po;
},
rupiah() {
return this.toRupiah(this.form_faktur.form_grandtotal);
}
},
methods: {
closeDialog() {
this.viewfakturDialog = false
this.$store.commit("data/update_list_item", {records: [], total: 0})
this.$store.dispatch("form/resetForm")
},
formatDate(date) {
if (!date) return null
const [year, month, day] = date.split("-")
return `${day}-${month}-${year}`;
},
searchPO() {
this.$store.dispatch("data/getListPO");
},
detailPO() {
this.$store.dispatch("data/getDetailPO");
this.form_item = []
this.$store.commit("form/update_form_ro", {})
this.$store.commit("data/update_list_item", {records: [], total: 0})
},
toRupiah(data) {
// Input validation
if (!data) return '0,00'
// Ensure we're working with a number
if (typeof data !== 'number') {
data = parseFloat(data)
}
// Handle invalid numbers
if (isNaN(data)) return '0,00'
// Convert to string with exactly 2 decimal places without rounding
// This preserves the exact value for display
const parts = data.toString().split('.')
const wholePart = parts[0]
let decimalPart = parts.length > 1 ? parts[1] : '00'
// Ensure we have exactly 2 decimal digits for display
if (decimalPart.length > 2) {
decimalPart = decimalPart.substring(0, 2)
} else if (decimalPart.length < 2) {
decimalPart = decimalPart.padEnd(2, '0')
}
// Format the whole number part with dots as thousand separators
const formatted = wholePart.replace(/\B(?=(\d{3})+(?!\d))/g, ".")
// Return the formatted number
return `${formatted},${decimalPart}`
},
calcItemDisc(item) {
if (item.PurchaseOrderSummaryDiscountType == 'R') {
this.form_item.find(a => a.ReceiveOrderPoDetailID === item.ReceiveOrderPoDetailID).disc_amount = item.Discount
return this.toRupiah(item.Discount)
}
let d = (Number(item.ReceiveOrderPoDetailPrice) * Number(item.Discount)) / 100
this.form_item.find(a => a.ReceiveOrderPoDetailID === item.ReceiveOrderPoDetailID).disc_amount = d
return this.toRupiah(d.toString())
},
calcSubtal(item) {
price = Number(item.ReceiveOrderPoDetailPrice)
disc = Number(item.Discount)
if (item.PurchaseOrderSummaryDiscountType == 'R') {
return this.toRupiah((price - disc).toString())
}
let d = (price * disc) / 100
return this.toRupiah((price - d).toString())
},
calcTotal(item) {
qty = Number(item.ReceiveOrderPoDetailQty)
price = Number(item.ReceiveOrderPoDetailPrice)
disc = Number(item.Discount)
let tl = 0
if (item.PurchaseOrderSummaryDiscountType == 'R') {
tl = qty * (price - disc)
this.form_item.find(a => a.ReceiveOrderPoDetailID === item.ReceiveOrderPoDetailID).subtotal = tl
return this.toRupiah((tl).toString())
}
let d = (price * disc) / 100
tl = qty * (price - d)
this.form_item.find(a => a.ReceiveOrderPoDetailID === item.ReceiveOrderPoDetailID).subtotal = tl
return this.toRupiah((tl).toString())
},
fn_calcSummary: _.debounce(function () {
this.$store.dispatch("form/calcSummary")
}, 1000),
fn_adjust: _.debounce(function () {
this.$store.dispatch("form/calcSummary")
}, 1000)
},
watch: {
form_item(newValue, oldValue) {
this.fn_calcSummary();
},
}
}
</script>