Files
exchange/resources/assets/js/components/NewInvoice.vue
T
2020-09-25 16:04:02 +08:00

805 lines
26 KiB
Vue

/* eslint-disable camelcase */
<template>
<div class="invoice">
<div class="header-container">
<h3 v-if="currentStatus === 'pending'" class="title">
Purchase Order (Pending Approval)
</h3>
<h3 v-else-if="currentStatus === 'approve'" class="title" >
Purchase Order (Approved)
</h3>
<h3 v-else-if="currentStatus === 'request_change'" class="title">
Purchase Order (Please Change)
<div class="error">
{{ statuses[statuses.length - 1].comment }}
</div>
</h3>
<h3 v-else class="title">
Purchase Order
</h3>
</div>
<div class="invoice-container">
<form @submit.prevent="submitForm">
<div class="buyer-detail card">
<div class="card-body">
<div class="card-title">
<h5>
My Infomation
</h5>
</div>
<div class="form-group">
<label for="marking">Marking</label>
<input id="marking" v-model="$v.form.formData.marking.$model" type="text" class="form-control" disabled>
</div>
<div class="form-group">
<label for="buyer-company">Buyer's Company</label>
<input id="buyer-company"
v-model="$v.form.formData.buyer_company.$model"
:class="{
'is-invalid': $v.form.formData.buyer_company.$invalid && $v.form.formData.buyer_company.$dirty,
'is-valid': !$v.form.formData.buyer_company.$invalid && $v.form.formData.buyer_company.$dirty
}"
:disabled="!editable"
type="text"
class="form-control"
@blur="$v.form.formData.buyer_company.$touch()">
<div class="invalid-feedback">
Buyer Company Required
</div>
</div>
<!-- <div class="form-group">
<label for="gst-id">GST ID</label>
<input id="gst-id" type="text" class="form-control" disabled>
</div> -->
<div class="form-group">
<label for="contact-number">Contact Number</label>
<input id="contact-number"
v-model.trim="$v.form.formData.contact_no.$model"
:class="{
'is-invalid': $v.form.formData.contact_no.$invalid && $v.form.formData.contact_no.$dirty,
'is-valid': !$v.form.formData.contact_no.$invalid && $v.form.formData.contact_no.$dirty
}"
:disabled="!editable"
type="tel"
class="form-control"
@blur="$v.form.formData.contact_no.$touch()">
<div class="invalid-feedback">
Contact Number Required
</div>
</div>
<div class="form-group">
<label for="reg-no">Company Registration Number</label>
<input id="reg-no"
v-model.trim="$v.form.formData.reg_no.$model"
:class="{
'is-invalid': $v.form.formData.reg_no.$invalid && $v.form.formData.reg_no.$dirty,
'is-valid': !$v.form.formData.reg_no.$invalid && $v.form.formData.reg_no.$dirty
}"
:disabled="!editable"
type="text" class="form-control"
@blur="$v.form.formData.reg_no.$touch()">
<div class="invalid-feedback">
Company Registration Number Required
</div>
</div>
<div class="form-group">
<label for="address">Address</label>
<textarea id="address"
v-model="$v.form.formData.address.$model"
:class="{
'is-invalid': $v.form.formData.address.$invalid && $v.form.formData.address.$dirty,
'is-valid': !$v.form.formData.address.$invalid && $v.form.formData.address.$dirty
}"
:disabled="!editable"
class="form-control"
cols="30"
rows="4"
@blur="$v.form.formData.address.$touch()"/>
<div class="invalid-feedback">
Address Required
</div>
</div>
</div>
</div>
<div class="conversion-rate">
<h5>
Conversion: {{ rate }}
</h5>
</div>
<div class="billing-detail-title">
<h5>Billing Detail</h5>
</div>
<div class="billing-detail-lines">
<table class="table">
<thead>
<tr>
<th class="align-middle">#</th>
<th class="align-middle">Stock Code</th>
<th class="align-middle">Description</th>
<th class="text-center align-middle">Qty</th>
<th class="text-center align-middle">Unit Price (RMB)</th>
<th class="text-center align-middle">Unit Price (RM)</th>
<th class="text-center align-middle">Total Amount (RM)</th>
<th>&nbsp;</th>
</tr>
</thead>
<tbody>
<tr>
<td>&nbsp;</td>
<td>
<div class="form-group">
<input v-model="$v.form.addLine.stock_code.$model"
:class="{
'is-invalid': $v.form.addLine.stock_code.$invalid && $v.form.addLine.stock_code.$dirty,
'is-valid': !$v.form.addLine.stock_code.$invalid && $v.form.addLine.stock_code.$dirty
}"
:disabled="!editable"
name="stockcode" type="text"
class="form-control"
@blur="$v.form.addLine.stock_code.$touch()">
<div class="invalid-feedback">
Stock Code Required
</div>
</div>
</td>
<td>
<div class="form-group">
<textarea id="description"
v-model="$v.form.addLine.description.$model"
:class="{
'is-invalid': $v.form.addLine.description.$invalid && $v.form.addLine.description.$dirty,
'is-valid': !$v.form.addLine.description.$invalid && $v.form.addLine.description.$dirty
}"
:disabled="!editable"
name="description" cols="20" rows="2"
@blur="$v.form.addLine.description.$touch()"/>
<div class="invalid-feedback">
Maximum length for description is 256 characters
</div>
</div>
</td>
<td>
<div class="form-group">
<input id="quantity"
v-model="$v.form.addLine.quantity.$model"
:class="{
'is-invalid': $v.form.addLine.quantity.$invalid && $v.form.addLine.quantity.$dirty,
'is-valid': !$v.form.addLine.quantity.$invalid && $v.form.addLine.quantity.$dirty
}"
:disabled="!editable"
type="number"
min="1"
class="form-control"
name="quantity"
@change="onUnitPriceRM()"
@blur="$v.form.addLine.quantity.$touch()">
<div class="invalid-feedback">
Quantity Required
</div>
</div>
</td>
<td>
<div class="form-group">
<input id="rmb-unit-price"
v-model="$v.form.addLine.unit_price_rmb.$model"
:class="{
'is-invalid': $v.form.addLine.unit_price_rmb.$invalid && $v.form.addLine.unit_price_rmb.$dirty,
'is-valid': !$v.form.addLine.unit_price_rmb.$invalid && $v.form.addLine.unit_price_rmb.$dirty
}"
:disabled="!editable"
type="number"
min="0"
name="rmb-unit-price"
class="form-control"
@change="onUnitPriceRMB()"
@blur="$v.form.addLine.unit_price_rmb.$touch()">
<div class="invalid-feedback">
Unit Price Required
</div>
</div>
</td>
<td>
<div class="form-group">
<input id="rm-unit-price"
v-model="$v.form.addLine.unit_price_rm.$model"
:class="{
'is-invalid': $v.form.addLine.unit_price_rm.$invalid && $v.form.addLine.unit_price_rm.$dirty,
'is-valid': !$v.form.addLine.unit_price_rm.$invalid && $v.form.addLine.unit_price_rm.$dirty
}"
:disabled="!editable"
type="number"
min="0"
name="rm-unit-price"
class="form-control"
@change="onUnitPriceRM()"
@blur="$v.form.addLine.unit_price_rm.$touch()">
<div class="invalid-feedback">
Unit Price Required
</div>
</div>
</td>
<td>
<div class="form-group">
<input id="total"
v-model="$v.form.addLine.total.$model"
type="number"
min="0"
name="subtotal"
class="form-control"
disabled>
</div>
</td>
<td>
<button :disabled="!editable" class="btn btn-success" type="button" @click="onAddLine()">
Add
</button>
</td>
</tr>
<tr v-for="(line, index) in form.formData.lines" :key="index">
<td class="line-number text-center align-middle">
{{ index + 1 }}
</td>
<td class="stock-code text-center align-middle">
{{ line.stock_code }}
</td>
<td class="description align-middle">
{{ line.description }}
</td>
<td class="quantity text-center align-middle">
{{ line.quantity }}
</td>
<td class="unit-price-rmb text-center align-middle">
{{ line.unit_price_rmb }}
</td>
<td class="unit-price-rm text-center align-middle">
{{ line.unit_price_rm }}
</td>
<td class="line-subtotal text-center align-middle">
{{ line.total }}
</td>
<td>
<button :disabled="!editable" type="button" class="btn btn-danger" @click="onRemoveLine(index)">
Remove
</button>
</td>
</tr>
</tbody>
<tfoot>
<tr class="subtotal-line">
<td colspan="5"/>
<td class="subtotal-title text-center">
Rounding
</td>
<td class="subtotal text-center">
plus or minus some number
</td>
<td>
&nbsp;
</td>
</tr>
<tr class="subtotal-line">
<td colspan="5"/>
<td class="subtotal-title text-center">
Subtotal
</td>
<td class="subtotal text-center">
{{ subtotal }}
</td>
<td>
&nbsp;
</td>
</tr>
<tr class="service-charge">
<td colspan="5"/>
<td class="subtotal-title text-center">
Service Charge
</td>
<td class="subtotal text-center">
{{ service_charge }}
</td>
<td>
&nbsp;
</td>
</tr>
<tr v-if="tax > 0" class="gst-line">
<td colspan="5"/>
<td class="gst-title text-center">
GST
</td>
<td class="gst-total text-center">
{{ tax }}
</td>
<td>
&nbsp;
</td>
</tr>
<tr class="total-line">
<td colspan="5"/>
<td class="total-title text-center">
Total
</td>
<td class="total text-center">
{{ total }}
</td>
<td>
&nbsp;
</td>
</tr>
<tr>
<td colspan="5">&nbsp;</td>
<td colspan="2">
<button :disabled="!editable" class="btn btn-success btn-block" type="submit">
Create PO
</button>
</td>
</tr>
</tfoot>
</table>
</div>
</form>
</div> <!-- invoice-container -->
<div v-if="role === 'admin'" class="admin-panel">
<div v-if="statuses.length > 0" class="comments">
<h5>Log</h5>
<table class="table comment-table">
<thead>
<tr>
<th>Date Time</th>
<th>Status</th>
<th>Comment</th>
</tr>
</thead>
<tbody>
<tr v-for="(comment, index) in statuses" :key="index">
<td>{{ comment.created_at }}</td>
<td>{{ comment.status }}</td>
<td>{{ comment.comment }}</td>
</tr>
</tbody>
</table>
</div>
<br>
<form @submit.prevent="onSubmitStatus">
<h5>Admin Panel</h5>
<div class="form-group">
<label for="action">Action</label>
<select id="action"
v-model="$v.form.postStatus.status.$model"
:class="{
'is-invalid': $v.form.postStatus.status.$invalid && $v.form.postStatus.status.$dirty,
'is-valid': !$v.form.postStatus.status.$invalid && $v.form.postStatus.status.$dirty
}"
name="action"
class="form-control">
<option value="approve">Approve</option>
<option value="request_change">Request Change</option>
</select>
<div class="invalid-feedback">
Action Required
</div>
</div>
<div class="form-group">
<label for="comment">Comment</label>
<textarea id="comment"
v-model="$v.form.postStatus.comment.$model"
:class="{
'is-invalid': $v.form.postStatus.comment.$invalid && $v.form.postStatus.comment.$dirty,
'is-valid': !$v.form.postStatus.comment.$invalid && $v.form.postStatus.comment.$dirty
}"
name="comment"
cols="30" rows="3" class="form-control" />
<div class="invalid-feedback">
Comment required if you are requesting change from customer.
</div>
</div>
<button type="submit" class="btn btn-success">Submit</button>
</form>
</div>
</div>
</template>
<style>
input {
min-width: 6em;
}
.container {
margin-left: 10% !important;
margin-right: 10% !important;
}
.el-main {
padding-left: 0 !important;
padding-right: 0 !important;
}
.title {
margin-top: 1em;
padding-left: 1.7em;
padding-bottom: 0 !important;
}
.buyer-detail {
grid-area: buyer-detail;
width: 100%;
margin-bottom: 3em;
}
.card-body {
display: flex !important;
flex-direction: row !important;
flex-wrap: wrap !important;
justify-content: space-around !important;
}
.buyer-detail .form-group {
width: 45%;
}
.card-title {
width: 100%;
}
.conversion-rate {
grid-area: conversion-rate;
}
.conversion-rate h5 {
float: right;
background-color: lightblue;
padding-left: 1em;
padding-right: 1em;
}
.billing-detail-title {
width: 100%;
grid-area: billing-detail-title;
}
.billing-detail-lines {
width: 100%;
grid-area: billing-detail-lines;
}
.invoice-container {
padding: 2em 3em;
display: grid;
grid-template-areas:
'buyer-detail buyer-detail'
'billing-detail-title conversion-rate'
'billing-detail-lines billing-detail-lines';
}
.header-container {
display: flex;
flex-direction: row;
justify-content: space-between;
align-items: center;
}
.admin-panel {
margin-left: 3em;
margin-right: 3em;
}
.error {
color: red;
}
</style>
<script>
import Vue from 'vue'
import Vuelidate from 'vuelidate'
import { required, numeric, decimal, requiredIf, maxLength } from 'vuelidate/lib/validators'
import axios from 'axios'
Vue.use(Vuelidate)
export default {
name: 'NewComponent',
props: {
trackerConfig: {
type: Object,
default: function () {
return {
status: 5,
term: null
}
}
},
booking_id: {
type: String,
default: null
},
booking_details: {
type: Object,
default: function () {
return {
id: null,
bia: null,
amount: null,
user_bankslip_path: null,
china_bankslip_path: null,
marking: null
}
}
}
},
data: function () {
return {
form: {
formData: {
buyer_company: '',
address: '',
contact_no: '',
marking: this.booking_details.marking,
gst_id: '',
reg_no: '',
lines: []
},
addLine: {
order: null,
description: null,
quantity: null,
unit_price_rmb: null,
unit_price_rm: null,
stock_code: null,
total: null
},
postStatus: {
status: null,
comment: null
}
},
rate: 0,
service_charge: 0,
amount: 0,
bia: 0,
invoice_total: 0,
tax: 0,
billing_fee: 0,
subtotal: 0,
total: 0,
role: null,
currentStatus: null,
statuses: []
}
},
computed: {
editable: function () {
// Not editable if created and not admin
return this.role === 'admin' || this.currentStatus === null || this.currentStatus === 'request_change'
}
},
validations: {
form: {
formData: {
buyer_company: {
required
},
address: {
required
},
contact_no: {
required
},
marking: {
required
},
reg_no: {
required
},
gst_id: {
}
},
addLine: {
stock_code: {
required
},
description: {
maxLength: maxLength(256)
},
quantity: {
required,
numeric
},
unit_price_rmb: {
required,
decimal
},
unit_price_rm: {
required,
decimal
},
total: {
required,
decimal
}
},
postStatus: {
status: {
required
},
comment: {
required: requiredIf(function () {
return this.form.postStatus.status === 'request_change'
})
}
}
}
},
watch: {
booking_details: function () {
const {
marking,
tax,
bia,
amount,
rate
} = this.booking_details
this.form.formData.marking = marking
this.tax = tax
this.amount = amount
this.bia = bia
this.rate = rate
this.service_charge = parseFloat(this.bia - (this.amount / this.rate)).toFixed(2)
this.total = this.service_charge // Will not work if there is tax. Temporary fix.
}
},
mounted () {
this.getInvoice()
this.getUser()
},
methods: {
onAddLine: function () {
if (this.$v.form.addLine.$invalid) {
this.$v.form.addLine.$touch()
return
}
const {
order,
description,
quantity,
stock_code,
unit_price_rmb,
unit_price_rm,
total
} = this.form.addLine
const data = {
order: order,
description,
quantity,
stock_code,
unit_price_rmb,
unit_price_rm,
total
}
this.form.formData.lines.push(data)
this.setTotal()
this.clearAddLine()
},
onUnitPriceRMB: function () {
this.form.addLine.unit_price_rm = (+this.form.addLine.unit_price_rmb / +this.rate).toFixed(2)
this.form.addLine.total = (+this.form.addLine.unit_price_rm * +this.form.addLine.quantity).toFixed(2)
},
onUnitPriceRM: function () {
this.form.addLine.unit_price_rmb = (this.form.addLine.unit_price_rm * this.rate).toFixed(2)
this.form.addLine.total = (+this.form.addLine.unit_price_rm * +this.form.addLine.quantity).toFixed(2)
},
onRemoveLine: function (line) {
this.form.formData.lines.splice(line, 1)
this.setTotal()
},
setTotal: function () {
if (this.form.formData.lines.length < 1) {
this.subtotal = 0
this.total = this.service_charge // Temporary fix. Will return error if GST is implemented.
return
}
this.subtotal = parseFloat(this.form.formData.lines.map(line => line.total).reduce((a, b) => parseFloat(a) + parseFloat(b))).toFixed(2)
this.total = parseFloat(+this.subtotal + (+this.subtotal * (+this.tax || 0) / 100) + +this.service_charge).toFixed(2)
},
clearAddLine: function () {
this.form.addLine.order = null
this.form.addLine.description = null
this.form.addLine.quantity = null
this.form.addLine.unit_price_rmb = null
this.form.addLine.unit_price_rm = null
this.form.addLine.stock_code = null
this.form.addLine.total = null
this.$v.form.addLine.$reset()
},
submitForm () {
if (this.$v.form.formData.$invalid) {
this.$v.form.formData.$touch()
console.log('form not valid')
return
}
if (this.bia != this.total) {
console.log('amount not matched')
alert(`PO Total RM${this.total} Does Not Match Expected RM${this.bia}`)
return
}
// create order in line
this.form.formData.lines
.forEach((el, i) => {
this.form.formData.lines[i].order = (i + 1)
})
if (this.currentStatus) {
console.log('edit invoice')
this.editInvoice()
} else {
console.log('create invoice')
this.createInvoice()
}
},
createInvoice () {
const url = '/api/invoice/' + this.booking_id
axios
.post(url, this.form.formData)
.then(resp => {
alert('PO Created. Pending Approval.')
this.$router.push({ name: 'home' })
})
.catch(err => {
alert(JSON.stringify(err.response.data.message))
console.error(err.response.data.message)
})
},
editInvoice () {
const url = '/api/invoice/' + this.booking_id
axios
.put(url, this.form.formData)
.then(resp => {
alert(`PO have been edited${this.role === 'admin' ? ' by admin.' : '. Pending Approval'}`)
this.$router.push({ name: 'home' })
})
.catch(err => {
alert(JSON.stringify(err.response.data.message))
console.error(err.response.data.message)
})
},
getInvoice () {
const url = '/api/invoice/' + this.booking_id
axios
.get(url)
.then(resp => {
this.form.formData.buyer_company = resp.data.buyer_company
this.form.formData.contact_no = resp.data.contact_no
this.form.formData.address = resp.data.address
this.form.formData.reg_no = resp.data.reg_no
this.form.formData.lines = []
this.statuses = []
resp.data.lines.forEach((el, i) => {
this.form.formData.lines.push(el)
})
resp.data.status.forEach((el, i) => {
this.statuses.push(el)
})
this.currentStatus = resp.data.status[resp.data.status.length - 1].status
this.setTotal()
})
},
getUser () {
const url = '/api/user'
axios
.get(url)
.then(resp => {
this.role = resp.data.role
})
},
onSubmitStatus () {
if (this.$v.form.postStatus.$invalid) {
this.$v.form.postStatus.$touch()
return
}
const url = '/api/invoice/' + this.booking_id + '/comment'
axios
.post(url, this.form.postStatus)
.then(resp => {
this.getInvoice()
this.form.postStatus.status = null
this.form.postStatus.comment = null
this.$v.form.postStatus.$reset()
alert('Status Updated')
})
}
}
}
</script>