Merge branch 'develop'
This commit is contained in:
commit
257790c1ce
31 changed files with 731 additions and 263 deletions
89
.github/workflows/gitea-local.yaml
vendored
Normal file
89
.github/workflows/gitea-local.yaml
vendored
Normal file
|
|
@ -0,0 +1,89 @@
|
||||||
|
name: Gitea Action
|
||||||
|
|
||||||
|
run-name: Build ${{ github.actor }}
|
||||||
|
|
||||||
|
# Intended for local gitea instance only.
|
||||||
|
|
||||||
|
on:
|
||||||
|
workflow_dispatch:
|
||||||
|
|
||||||
|
env:
|
||||||
|
REGISTRY: ${{ vars.CONTAINER_REGISTRY }}
|
||||||
|
REGISTRY_USERNAME: ${{ vars.CONTAINER_REGISTRY_USERNAME }}
|
||||||
|
REGISTRY_PASSWORD: ${{ secrets.CONTAINER_REGISTRY_PASSWORD }}
|
||||||
|
CONTAINER_IMAGE_NAME: ${{ vars.CONTAINER_REGISTRY }}/${{ vars.CONTAINER_IMAGE_OWNER }}/${{ vars.CONTAINER_IMAGE_NAME }}:latest
|
||||||
|
jobs:
|
||||||
|
gitea-release:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- name: Checkout
|
||||||
|
uses: actions/checkout@v3
|
||||||
|
- name: Login to Docker Registry
|
||||||
|
uses: docker/login-action@v3
|
||||||
|
with:
|
||||||
|
registry: ${{ env.REGISTRY }}
|
||||||
|
username: ${{ env.REGISTRY_USERNAME }}
|
||||||
|
password: ${{ env.REGISTRY_PASSWORD }}
|
||||||
|
- name: Setup Docker Buildx
|
||||||
|
uses: docker/setup-buildx-action@v2
|
||||||
|
with:
|
||||||
|
config-inline: |
|
||||||
|
[registry."${{ env.REGISTRY }}"]
|
||||||
|
ca=["/etc/ssl/certs/ca-certificates.crt"]
|
||||||
|
- name: Build and Push Docker Image
|
||||||
|
uses: docker/build-push-action@v3
|
||||||
|
with:
|
||||||
|
context: .
|
||||||
|
platforms: linux/amd64
|
||||||
|
tags: ${{ env.CONTAINER_IMAGE_NAME }}
|
||||||
|
push: true
|
||||||
|
- name: Remote Deploy Development
|
||||||
|
uses: appleboy/ssh-action@v1.2.1
|
||||||
|
with:
|
||||||
|
host: ${{ vars.SSH_DEVELOPMENT_HOST }}
|
||||||
|
port: ${{ vars.SSH_DEVELOPMENT_PORT }}
|
||||||
|
username: ${{ secrets.SSH_DEVELOPMENT_USER }}
|
||||||
|
password: ${{ secrets.SSH_DEVELOPMENT_PASSWORD }}
|
||||||
|
script: eval "${{ secrets.SSH_DEVELOPMENT_DEPLOY_CMD }}" & wait
|
||||||
|
- name: Remote Deploy Test
|
||||||
|
uses: appleboy/ssh-action@v1.2.1
|
||||||
|
with:
|
||||||
|
host: ${{ vars.SSH_TEST_HOST }}
|
||||||
|
port: ${{ vars.SSH_TEST_PORT }}
|
||||||
|
username: ${{ secrets.SSH_TEST_USER }}
|
||||||
|
password: ${{ secrets.SSH_TEST_PASSWORD }}
|
||||||
|
script: eval "${{ secrets.SSH_TEST_DEPLOY_CMD }}" & wait
|
||||||
|
- name: Notify Discord Success
|
||||||
|
if: success()
|
||||||
|
run: |
|
||||||
|
curl -H "Content-Type: application/json" -X POST \
|
||||||
|
-d '{
|
||||||
|
"embeds": [{
|
||||||
|
"title": "✅ Gitea Local Deployment Success!",
|
||||||
|
"description": "**Details:**\n- Image: `${{ env.CONTAINER_IMAGE_NAME }}`\n- Deployed by: `${{ github.actor }}`",
|
||||||
|
"color": 3066993,
|
||||||
|
"footer": {
|
||||||
|
"text": "Gitea Local Release Notification",
|
||||||
|
"icon_url": "https://example.com/success-icon.png"
|
||||||
|
},
|
||||||
|
"timestamp": "'$(date -u +%Y-%m-%dT%H:%M:%SZ)'"
|
||||||
|
}]
|
||||||
|
}' \
|
||||||
|
${{ secrets.DISCORD_WEBHOOK }}
|
||||||
|
- name: Notify Discord Failure
|
||||||
|
if: failure()
|
||||||
|
run: |
|
||||||
|
curl -H "Content-Type: application/json" -X POST \
|
||||||
|
-d '{
|
||||||
|
"embeds": [{
|
||||||
|
"title": "❌ Gitea Local Deployment Failed!",
|
||||||
|
"description": "**Details:**\n- Image: `${{ env.CONTAINER_IMAGE_NAME }}`\n- Attempted by: `${{ github.actor }}`",
|
||||||
|
"color": 15158332,
|
||||||
|
"footer": {
|
||||||
|
"text": "Gitea Local Release Notification",
|
||||||
|
"icon_url": "https://example.com/failure-icon.png"
|
||||||
|
},
|
||||||
|
"timestamp": "'$(date -u +%Y-%m-%dT%H:%M:%SZ)'"
|
||||||
|
}]
|
||||||
|
}' \
|
||||||
|
${{ secrets.DISCORD_WEBHOOK }}
|
||||||
31
.github/workflows/local-build-release.yaml
vendored
31
.github/workflows/local-build-release.yaml
vendored
|
|
@ -1,31 +0,0 @@
|
||||||
name: local-build-release
|
|
||||||
|
|
||||||
# Intended for local network use.
|
|
||||||
# Remote access is possible if the host has a public IP address.
|
|
||||||
|
|
||||||
on:
|
|
||||||
workflow_dispatch:
|
|
||||||
|
|
||||||
env:
|
|
||||||
REGISTRY: ${{ vars.DOCKER_REGISTRY }}
|
|
||||||
|
|
||||||
jobs:
|
|
||||||
local-build-release:
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
steps:
|
|
||||||
- uses: actions/checkout@v3
|
|
||||||
- name: Setup Docker Buildx
|
|
||||||
uses: docker/setup-buildx-action@v2
|
|
||||||
with:
|
|
||||||
config-inline: |
|
|
||||||
[registry."${{ env.REGISTRY }}"]
|
|
||||||
http = true
|
|
||||||
insecure = true
|
|
||||||
- name: Build and Push Docker Image
|
|
||||||
uses: docker/build-push-action@v3
|
|
||||||
with:
|
|
||||||
context: .
|
|
||||||
platforms: linux/amd64
|
|
||||||
push: true
|
|
||||||
tags: ${{ env.REGISTRY }}/jws/jws-frontend:latest
|
|
||||||
allow: security.insecure
|
|
||||||
22
.github/workflows/local-release-demo.yml
vendored
22
.github/workflows/local-release-demo.yml
vendored
|
|
@ -1,22 +0,0 @@
|
||||||
name: local-release-demo
|
|
||||||
|
|
||||||
# Intended for local network use.
|
|
||||||
# Remote access is possible if the host has a public IP address.
|
|
||||||
|
|
||||||
on:
|
|
||||||
workflow_dispatch:
|
|
||||||
|
|
||||||
jobs:
|
|
||||||
local-release-demo:
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
steps:
|
|
||||||
- name: Remote deploy internal chamomind server
|
|
||||||
uses: appleboy/ssh-action@v1.0.3
|
|
||||||
with:
|
|
||||||
host: ${{ secrets.HOST }}
|
|
||||||
username: ${{ secrets.USERNAME }}
|
|
||||||
password: ${{ secrets.PASSWORD }}
|
|
||||||
script: |
|
|
||||||
cd ~/repositories/jws-frontend
|
|
||||||
git pull
|
|
||||||
docker compose up -d --build
|
|
||||||
22
.github/workflows/local-release-dev.yml
vendored
22
.github/workflows/local-release-dev.yml
vendored
|
|
@ -1,22 +0,0 @@
|
||||||
name: local-release-dev
|
|
||||||
|
|
||||||
# Intended for local network use.
|
|
||||||
# Remote access is possible if the host has a public IP address.
|
|
||||||
|
|
||||||
on:
|
|
||||||
workflow_dispatch:
|
|
||||||
|
|
||||||
jobs:
|
|
||||||
local-release-dev:
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
steps:
|
|
||||||
- name: Remote deploy internal chamomind server
|
|
||||||
uses: appleboy/ssh-action@v1.0.3
|
|
||||||
with:
|
|
||||||
host: ${{ secrets.HOST }}
|
|
||||||
username: ${{ secrets.USERNAME }}
|
|
||||||
password: ${{ secrets.PASSWORD }}
|
|
||||||
script: |
|
|
||||||
cd ~/repositories/jws-frontend
|
|
||||||
git pull
|
|
||||||
docker compose up -d --build
|
|
||||||
|
|
@ -34,6 +34,7 @@ const quotationId = defineModel<string>('quotationId', {
|
||||||
v-model:value="quotationId"
|
v-model:value="quotationId"
|
||||||
:label="$t('general.select', { msg: $t('quotation.title') })"
|
:label="$t('general.select', { msg: $t('quotation.title') })"
|
||||||
:params="{
|
:params="{
|
||||||
|
cancelIncludeDebitNote: true,
|
||||||
hasCancel: true,
|
hasCancel: true,
|
||||||
}"
|
}"
|
||||||
/>
|
/>
|
||||||
|
|
|
||||||
|
|
@ -779,6 +779,7 @@ export default {
|
||||||
specialCondition: 'Special Conditions',
|
specialCondition: 'Special Conditions',
|
||||||
selectInvoice: 'Select Invoice',
|
selectInvoice: 'Select Invoice',
|
||||||
approveInvoice: 'Approve the invoice',
|
approveInvoice: 'Approve the invoice',
|
||||||
|
approveDebitNote: 'Approve Debit Note',
|
||||||
paymentCondition: 'Payment Terms',
|
paymentCondition: 'Payment Terms',
|
||||||
payType: 'Payment Methods',
|
payType: 'Payment Methods',
|
||||||
bank: 'Select Payment Account',
|
bank: 'Select Payment Account',
|
||||||
|
|
@ -897,6 +898,8 @@ export default {
|
||||||
caption: 'All Request List',
|
caption: 'All Request List',
|
||||||
quotationCode: 'Quotation Code',
|
quotationCode: 'Quotation Code',
|
||||||
requestListCode: 'Request List Code',
|
requestListCode: 'Request List Code',
|
||||||
|
|
||||||
|
referenceNo: 'Reference No.',
|
||||||
invoiceCode: 'Invoice Code',
|
invoiceCode: 'Invoice Code',
|
||||||
receiptCode: 'Receipt Code',
|
receiptCode: 'Receipt Code',
|
||||||
alienIdCard: 'Alien Identification Card"',
|
alienIdCard: 'Alien Identification Card"',
|
||||||
|
|
@ -1022,6 +1025,7 @@ export default {
|
||||||
importWorker: 'Import Worker',
|
importWorker: 'Import Worker',
|
||||||
confirmLogout: 'Confirm Logout',
|
confirmLogout: 'Confirm Logout',
|
||||||
confirmQuotationAccept: 'Confirm acceptance of the quotation.',
|
confirmQuotationAccept: 'Confirm acceptance of the quotation.',
|
||||||
|
confirmDebitNoteAccept: 'Confirm acceptance of the debit note.',
|
||||||
},
|
},
|
||||||
message: {
|
message: {
|
||||||
quotationAccept: 'Once accepted, no further modifications can be made',
|
quotationAccept: 'Once accepted, no further modifications can be made',
|
||||||
|
|
@ -1241,7 +1245,8 @@ export default {
|
||||||
'The customer returned all or part of the goods because they did not meet their requirements.',
|
'The customer returned all or part of the goods because they did not meet their requirements.',
|
||||||
reasonCanceled:
|
reasonCanceled:
|
||||||
'The customer canceled certain items or services listed on the invoice.',
|
'The customer canceled certain items or services listed on the invoice.',
|
||||||
submit: 'Approve the credit note',
|
request: 'Request Credit Note Approval',
|
||||||
|
submit: 'Approve Credit Note',
|
||||||
refund: 'Refund',
|
refund: 'Refund',
|
||||||
refundMethod: 'Refund Method',
|
refundMethod: 'Refund Method',
|
||||||
totalRefund: 'Total refund amount',
|
totalRefund: 'Total refund amount',
|
||||||
|
|
@ -1252,6 +1257,7 @@ export default {
|
||||||
refundSuccess: 'Refund Success',
|
refundSuccess: 'Refund Success',
|
||||||
},
|
},
|
||||||
status: {
|
status: {
|
||||||
|
Waiting: 'Credit Note',
|
||||||
Pending: 'Pending Refund',
|
Pending: 'Pending Refund',
|
||||||
Success: 'Refund Completed',
|
Success: 'Refund Completed',
|
||||||
Canceled: 'Canceled',
|
Canceled: 'Canceled',
|
||||||
|
|
@ -1261,10 +1267,6 @@ export default {
|
||||||
Done: 'Done',
|
Done: 'Done',
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
stats: {
|
|
||||||
Pending: 'Pending Refund',
|
|
||||||
Success: 'Refund Completed',
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
|
|
||||||
invoice: {
|
invoice: {
|
||||||
|
|
@ -1303,6 +1305,7 @@ export default {
|
||||||
quotationWorkName: 'Work Name',
|
quotationWorkName: 'Work Name',
|
||||||
quotationPayment: 'Payment Method',
|
quotationPayment: 'Payment Method',
|
||||||
value: 'Net Value',
|
value: 'Net Value',
|
||||||
|
request: 'Request Debit Note Approval',
|
||||||
submit: 'Approve Debit Note',
|
submit: 'Approve Debit Note',
|
||||||
},
|
},
|
||||||
|
|
||||||
|
|
@ -1315,6 +1318,7 @@ export default {
|
||||||
},
|
},
|
||||||
|
|
||||||
viewMode: {
|
viewMode: {
|
||||||
|
accepted: 'Accept',
|
||||||
payment: 'Payment',
|
payment: 'Payment',
|
||||||
receipt: 'Receipt/Tax Invoice',
|
receipt: 'Receipt/Tax Invoice',
|
||||||
processComplete: 'Completed',
|
processComplete: 'Completed',
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,3 @@
|
||||||
import { title } from 'process';
|
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
general: {
|
general: {
|
||||||
ok: 'ตกลง',
|
ok: 'ตกลง',
|
||||||
|
|
@ -761,7 +759,7 @@ export default {
|
||||||
branch: 'สาขาที่ออกใบเสนอราคา',
|
branch: 'สาขาที่ออกใบเสนอราคา',
|
||||||
branchVirtual: 'จุดรับบริการที่ออกใบเสนอราคา',
|
branchVirtual: 'จุดรับบริการที่ออกใบเสนอราคา',
|
||||||
customer: 'ลูกค้า',
|
customer: 'ลูกค้า',
|
||||||
newCustomer: 'ลูกค้าใหม่',
|
newCustomer: 'แรงงานใหม่',
|
||||||
employeeList: 'รายชื่อแรงงาน',
|
employeeList: 'รายชื่อแรงงาน',
|
||||||
employee: 'แรงงาน',
|
employee: 'แรงงาน',
|
||||||
employeeName: 'ชื่อ-นามสกุล แรงงาน',
|
employeeName: 'ชื่อ-นามสกุล แรงงาน',
|
||||||
|
|
@ -772,6 +770,7 @@ export default {
|
||||||
specialCondition: 'เงื่อนไขพิเศษ',
|
specialCondition: 'เงื่อนไขพิเศษ',
|
||||||
selectInvoice: 'เลือกใบแจ้งหนี้',
|
selectInvoice: 'เลือกใบแจ้งหนี้',
|
||||||
approveInvoice: 'อนุมัติใบแจ้งหนี้',
|
approveInvoice: 'อนุมัติใบแจ้งหนี้',
|
||||||
|
approveDebitNote: 'อนุมัติใบเพิ่มหนี้',
|
||||||
customerAcceptance: 'ลูกค้าตอบรับ',
|
customerAcceptance: 'ลูกค้าตอบรับ',
|
||||||
additionalFile: 'ไฟล์เอกสารเพิ่มเติม',
|
additionalFile: 'ไฟล์เอกสารเพิ่มเติม',
|
||||||
|
|
||||||
|
|
@ -890,6 +889,7 @@ export default {
|
||||||
caption: 'ใบรายการคำขอทั้งหมด',
|
caption: 'ใบรายการคำขอทั้งหมด',
|
||||||
quotationCode: 'เลขที่ใบเสนอราคา',
|
quotationCode: 'เลขที่ใบเสนอราคา',
|
||||||
requestListCode: 'เลขที่ใบรายการคำขอ',
|
requestListCode: 'เลขที่ใบรายการคำขอ',
|
||||||
|
referenceNo: 'เลขที่ใบอ้างอิง',
|
||||||
invoiceCode: 'เลขที่ใบแจ้งหนี้',
|
invoiceCode: 'เลขที่ใบแจ้งหนี้',
|
||||||
receiptCode: 'เลขที่ใบเสร็จ/กำกับภาษี',
|
receiptCode: 'เลขที่ใบเสร็จ/กำกับภาษี',
|
||||||
alienIdCard: 'บัตรประจำตัวต่างด้าว',
|
alienIdCard: 'บัตรประจำตัวต่างด้าว',
|
||||||
|
|
@ -1010,6 +1010,7 @@ export default {
|
||||||
importWorker: 'นำเข้าคนงาน',
|
importWorker: 'นำเข้าคนงาน',
|
||||||
confirmLogout: 'ยืนยันการออกจากระบบ',
|
confirmLogout: 'ยืนยันการออกจากระบบ',
|
||||||
confirmQuotationAccept: 'ยืนยันการตอบรับใบเสนอราคา',
|
confirmQuotationAccept: 'ยืนยันการตอบรับใบเสนอราคา',
|
||||||
|
confirmDebitNoteAccept: 'ยืนยันการตอบรับใบเพิ่มหนี้',
|
||||||
},
|
},
|
||||||
message: {
|
message: {
|
||||||
quotationAccept: 'เมื่อตอบรับเเล้วจะไม่สามารถแก้ไขได้อีก',
|
quotationAccept: 'เมื่อตอบรับเเล้วจะไม่สามารถแก้ไขได้อีก',
|
||||||
|
|
@ -1224,6 +1225,7 @@ export default {
|
||||||
'ลูกค้าคืนสินค้าทั้งหมดหรือบางส่วน เนื่องจากสินค้าไม่ตรงตามความต้องการ',
|
'ลูกค้าคืนสินค้าทั้งหมดหรือบางส่วน เนื่องจากสินค้าไม่ตรงตามความต้องการ',
|
||||||
reasonCanceled:
|
reasonCanceled:
|
||||||
'ลูกค้ายกเลิกคำสั่งซื้อบางรายการหรือบริการที่ระบุในใบแจ้งหนี้',
|
'ลูกค้ายกเลิกคำสั่งซื้อบางรายการหรือบริการที่ระบุในใบแจ้งหนี้',
|
||||||
|
request: 'ขออนุมัติใบลดหนี้',
|
||||||
submit: 'อนุมัติใบลดหนี้',
|
submit: 'อนุมัติใบลดหนี้',
|
||||||
refund: 'การคืนเงิน',
|
refund: 'การคืนเงิน',
|
||||||
refundMethod: 'วิธีการคืนเงิน',
|
refundMethod: 'วิธีการคืนเงิน',
|
||||||
|
|
@ -1235,6 +1237,7 @@ export default {
|
||||||
refundSuccess: 'คืนเงินเสร็จเรียบร้อย',
|
refundSuccess: 'คืนเงินเสร็จเรียบร้อย',
|
||||||
},
|
},
|
||||||
status: {
|
status: {
|
||||||
|
Waiting: 'ใบลดหนี้',
|
||||||
Pending: 'รอคืนเงิน',
|
Pending: 'รอคืนเงิน',
|
||||||
Success: 'คืนเงินเสร็จสิ้น',
|
Success: 'คืนเงินเสร็จสิ้น',
|
||||||
Canceled: 'ยกเลิกรายการ',
|
Canceled: 'ยกเลิกรายการ',
|
||||||
|
|
@ -1244,10 +1247,6 @@ export default {
|
||||||
Done: 'คืนเงินเรียบร้อย',
|
Done: 'คืนเงินเรียบร้อย',
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
stats: {
|
|
||||||
Pending: 'รอคืนเงิน',
|
|
||||||
Success: 'คืนเงินเสร็จสิ้น',
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
|
|
||||||
invoice: {
|
invoice: {
|
||||||
|
|
@ -1285,6 +1284,7 @@ export default {
|
||||||
quotationWorkName: 'ชื่อใบงาน',
|
quotationWorkName: 'ชื่อใบงาน',
|
||||||
quotationPayment: 'วิธีการชำระ',
|
quotationPayment: 'วิธีการชำระ',
|
||||||
value: 'มูลค่าสุทธิ',
|
value: 'มูลค่าสุทธิ',
|
||||||
|
request: 'ขออนุมัติใบเพิ่มหนี้',
|
||||||
submit: 'อนุมัติใบเพิ่มหนี้',
|
submit: 'อนุมัติใบเพิ่มหนี้',
|
||||||
},
|
},
|
||||||
|
|
||||||
|
|
@ -1297,6 +1297,7 @@ export default {
|
||||||
},
|
},
|
||||||
|
|
||||||
viewMode: {
|
viewMode: {
|
||||||
|
accepted: 'ตอบรับ',
|
||||||
payment: 'ชำระเงิน',
|
payment: 'ชำระเงิน',
|
||||||
receipt: 'ใบเสร็จรับเงิน/ใบกำกับภาษี',
|
receipt: 'ใบเสร็จรับเงิน/ใบกำกับภาษี',
|
||||||
processComplete: 'เสร็จสิ้น',
|
processComplete: 'เสร็จสิ้น',
|
||||||
|
|
|
||||||
|
|
@ -54,7 +54,7 @@ import {
|
||||||
SaveButton,
|
SaveButton,
|
||||||
EditButton,
|
EditButton,
|
||||||
UndoButton,
|
UndoButton,
|
||||||
CloseButton,
|
CancelButton,
|
||||||
MainButton,
|
MainButton,
|
||||||
} from 'components/button';
|
} from 'components/button';
|
||||||
import QuotationFormReceipt from './QuotationFormReceipt.vue';
|
import QuotationFormReceipt from './QuotationFormReceipt.vue';
|
||||||
|
|
@ -673,6 +673,7 @@ async function triggerSelectEmployeeDialog() {
|
||||||
}
|
}
|
||||||
|
|
||||||
function triggerProductServiceDialog() {
|
function triggerProductServiceDialog() {
|
||||||
|
covertToNode();
|
||||||
pageState.productServiceModal = true;
|
pageState.productServiceModal = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -1067,11 +1068,7 @@ const productServiceNodes = ref<ProductTree>([]);
|
||||||
watch(
|
watch(
|
||||||
() => productServiceList.value,
|
() => productServiceList.value,
|
||||||
() => {
|
() => {
|
||||||
productServiceNodes.value = quotationProductTree(
|
covertToNode();
|
||||||
productServiceList.value,
|
|
||||||
agentPrice.value,
|
|
||||||
config.value?.vat,
|
|
||||||
);
|
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|
@ -1302,6 +1299,14 @@ async function formDownload() {
|
||||||
a.click();
|
a.click();
|
||||||
a.remove();
|
a.remove();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function covertToNode() {
|
||||||
|
productServiceNodes.value = quotationProductTree(
|
||||||
|
productServiceList.value,
|
||||||
|
agentPrice.value,
|
||||||
|
config.value?.vat,
|
||||||
|
);
|
||||||
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
|
|
@ -2233,14 +2238,9 @@ async function formDownload() {
|
||||||
</article>
|
</article>
|
||||||
|
|
||||||
<footer class="surface-1 q-pa-md full-width">
|
<footer class="surface-1 q-pa-md full-width">
|
||||||
<div
|
<div class="row full-width justify-end">
|
||||||
class="row full-width"
|
|
||||||
:class="{
|
|
||||||
'justify-between': view !== View.InvoicePre,
|
|
||||||
'justify-end': view === View.InvoicePre,
|
|
||||||
}"
|
|
||||||
>
|
|
||||||
<MainButton
|
<MainButton
|
||||||
|
class="q-mr-auto"
|
||||||
v-if="
|
v-if="
|
||||||
view !== View.InvoicePre &&
|
view !== View.InvoicePre &&
|
||||||
view !== View.PaymentPre &&
|
view !== View.PaymentPre &&
|
||||||
|
|
@ -2255,7 +2255,16 @@ async function formDownload() {
|
||||||
{{ $t('general.view', { msg: $t('general.example') }) }}
|
{{ $t('general.view', { msg: $t('general.example') }) }}
|
||||||
</MainButton>
|
</MainButton>
|
||||||
|
|
||||||
|
<CancelButton
|
||||||
|
outlined
|
||||||
|
id="btn-close"
|
||||||
|
@click="closeTab()"
|
||||||
|
:label="$t('dialog.action.close')"
|
||||||
|
v-if="quotationFormState.mode === 'info' && closeAble()"
|
||||||
|
/>
|
||||||
|
|
||||||
<div
|
<div
|
||||||
|
class="q-ml-sm"
|
||||||
v-if="
|
v-if="
|
||||||
view === View.Accepted &&
|
view === View.Accepted &&
|
||||||
quotationFormData.quotationStatus === 'Issued'
|
quotationFormData.quotationStatus === 'Issued'
|
||||||
|
|
@ -2293,6 +2302,7 @@ async function formDownload() {
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<div
|
<div
|
||||||
|
class="q-ml-sm"
|
||||||
v-if="
|
v-if="
|
||||||
view === View.Invoice &&
|
view === View.Invoice &&
|
||||||
((quotationFormData.quotationStatus !== 'PaymentPending' &&
|
((quotationFormData.quotationStatus !== 'PaymentPending' &&
|
||||||
|
|
@ -2317,7 +2327,7 @@ async function formDownload() {
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div
|
<div
|
||||||
class="row"
|
class="row q-ml-sm"
|
||||||
style="gap: var(--size-2)"
|
style="gap: var(--size-2)"
|
||||||
v-if="
|
v-if="
|
||||||
(view === View.Quotation &&
|
(view === View.Quotation &&
|
||||||
|
|
@ -2332,12 +2342,6 @@ async function formDownload() {
|
||||||
id="btn-undo"
|
id="btn-undo"
|
||||||
v-if="quotationFormState.mode === 'edit'"
|
v-if="quotationFormState.mode === 'edit'"
|
||||||
/>
|
/>
|
||||||
<CloseButton
|
|
||||||
outlined
|
|
||||||
id="btn-close"
|
|
||||||
@click="closeTab()"
|
|
||||||
v-if="quotationFormState.mode === 'info' && closeAble()"
|
|
||||||
/>
|
|
||||||
<SaveButton
|
<SaveButton
|
||||||
type="submit"
|
type="submit"
|
||||||
id="btn-save"
|
id="btn-save"
|
||||||
|
|
|
||||||
|
|
@ -177,7 +177,7 @@ function setDefaultFormEmployee() {
|
||||||
namePrefix: '',
|
namePrefix: '',
|
||||||
nationality: '',
|
nationality: '',
|
||||||
gender: '',
|
gender: '',
|
||||||
dateOfBirth: new Date(),
|
dateOfBirth: null,
|
||||||
attachment: [],
|
attachment: [],
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
@ -261,6 +261,49 @@ async function getWorkerFromCriteria(
|
||||||
}
|
}
|
||||||
|
|
||||||
watch(() => state.search, getWorkerList);
|
watch(() => state.search, getWorkerList);
|
||||||
|
|
||||||
|
watch(
|
||||||
|
() => formDataEmployee.value.dateOfBirth,
|
||||||
|
() => {
|
||||||
|
const age = calculateAge(formDataEmployee.value.dateOfBirth, 'year');
|
||||||
|
if (formDataEmployee.value.dateOfBirth && Number(age) < 15) {
|
||||||
|
dialog({
|
||||||
|
color: 'warning',
|
||||||
|
icon: 'mdi-alert',
|
||||||
|
title: t('dialog.title.youngWorker15'),
|
||||||
|
cancelText: t('general.edit'),
|
||||||
|
persistent: true,
|
||||||
|
message: t('dialog.message.youngWorker15'),
|
||||||
|
|
||||||
|
cancel: async () => {
|
||||||
|
formDataEmployee.value.dateOfBirth = null;
|
||||||
|
return;
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
if (
|
||||||
|
formDataEmployee.value.dateOfBirth &&
|
||||||
|
Number(age) > 15 &&
|
||||||
|
Number(age) <= 18
|
||||||
|
) {
|
||||||
|
dialog({
|
||||||
|
color: 'warning',
|
||||||
|
icon: 'mdi-alert',
|
||||||
|
title: t('dialog.title.youngWorker18'),
|
||||||
|
cancelText: t('general.cancel'),
|
||||||
|
actionText: t('general.confirm'),
|
||||||
|
persistent: true,
|
||||||
|
message: t('dialog.message.youngWorker18'),
|
||||||
|
action: () => {},
|
||||||
|
cancel: async () => {
|
||||||
|
formDataEmployee.value.dateOfBirth = null;
|
||||||
|
return;
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
},
|
||||||
|
);
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
|
|
@ -445,7 +488,7 @@ watch(() => state.search, getWorkerList);
|
||||||
class="text-weight-medium q-mr-md"
|
class="text-weight-medium q-mr-md"
|
||||||
style="font-size: 18px"
|
style="font-size: 18px"
|
||||||
>
|
>
|
||||||
{{ $t('quotation.customer') }}
|
{{ $t('quotation.employee') }}
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
</section>
|
</section>
|
||||||
|
|
|
||||||
|
|
@ -2,10 +2,11 @@
|
||||||
import { storeToRefs } from 'pinia';
|
import { storeToRefs } from 'pinia';
|
||||||
import { onMounted, nextTick, ref, watch } from 'vue';
|
import { onMounted, nextTick, ref, watch } from 'vue';
|
||||||
import { precisionRound } from 'src/utils/arithmetic';
|
import { precisionRound } from 'src/utils/arithmetic';
|
||||||
|
import { useI18n } from 'vue-i18n';
|
||||||
import ThaiBahtText from 'thai-baht-text';
|
import ThaiBahtText from 'thai-baht-text';
|
||||||
|
|
||||||
// NOTE: Import stores
|
// NOTE: Import stores
|
||||||
import { formatNumberDecimal } from 'stores/utils';
|
import { dialogWarningClose, formatNumberDecimal } from 'stores/utils';
|
||||||
import { useConfigStore } from 'stores/config';
|
import { useConfigStore } from 'stores/config';
|
||||||
import useBranchStore from 'stores/branch';
|
import useBranchStore from 'stores/branch';
|
||||||
import { baseUrl } from 'stores/utils';
|
import { baseUrl } from 'stores/utils';
|
||||||
|
|
@ -28,12 +29,14 @@ import ViewHeader from './ViewHeader.vue';
|
||||||
import ViewFooter from './ViewFooter.vue';
|
import ViewFooter from './ViewFooter.vue';
|
||||||
import BankComponents from './BankComponents.vue';
|
import BankComponents from './BankComponents.vue';
|
||||||
import PrintButton from 'src/components/button/PrintButton.vue';
|
import PrintButton from 'src/components/button/PrintButton.vue';
|
||||||
|
import { CancelButton } from 'components/button';
|
||||||
import { convertTemplate } from 'src/utils/string-template';
|
import { convertTemplate } from 'src/utils/string-template';
|
||||||
|
|
||||||
const configStore = useConfigStore();
|
const configStore = useConfigStore();
|
||||||
const branchStore = useBranchStore();
|
const branchStore = useBranchStore();
|
||||||
const customerStore = useCustomerStore();
|
const customerStore = useCustomerStore();
|
||||||
const quotationStore = useQuotationStore();
|
const quotationStore = useQuotationStore();
|
||||||
|
const { t } = useI18n();
|
||||||
const { data: config } = storeToRefs(configStore);
|
const { data: config } = storeToRefs(configStore);
|
||||||
|
|
||||||
type Product = {
|
type Product = {
|
||||||
|
|
@ -323,6 +326,20 @@ function calcPrice(c: Product) {
|
||||||
return precisionRound(price + vat);
|
return precisionRound(price + vat);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async function closeTab() {
|
||||||
|
dialogWarningClose(t, {
|
||||||
|
message: t('dialog.message.close'),
|
||||||
|
action: () => {
|
||||||
|
window.close();
|
||||||
|
},
|
||||||
|
cancel: () => {},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function closeAble() {
|
||||||
|
return window.opener !== null;
|
||||||
|
}
|
||||||
|
|
||||||
watch(elements, () => {});
|
watch(elements, () => {});
|
||||||
|
|
||||||
function print() {
|
function print() {
|
||||||
|
|
@ -333,7 +350,15 @@ function print() {
|
||||||
<template>
|
<template>
|
||||||
<div class="toolbar">
|
<div class="toolbar">
|
||||||
<PrintButton solid @click="print" />
|
<PrintButton solid @click="print" />
|
||||||
|
<CancelButton
|
||||||
|
outlined
|
||||||
|
id="btn-close"
|
||||||
|
@click="closeTab()"
|
||||||
|
:label="$t('dialog.action.close')"
|
||||||
|
v-if="closeAble()"
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div
|
<div
|
||||||
class="row justify-between container color-quotation"
|
class="row justify-between container color-quotation"
|
||||||
:class="{
|
:class="{
|
||||||
|
|
@ -641,7 +666,7 @@ function print() {
|
||||||
position: sticky;
|
position: sticky;
|
||||||
top: 0;
|
top: 0;
|
||||||
display: flex;
|
display: flex;
|
||||||
justify-content: center;
|
justify-content: space-between;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
margin-bottom: 1rem;
|
margin-bottom: 1rem;
|
||||||
padding: 1rem;
|
padding: 1rem;
|
||||||
|
|
|
||||||
|
|
@ -11,6 +11,7 @@ import PropertiesExpansion from './PropertiesExpansion.vue';
|
||||||
import FormGroupHead from './FormGroupHead.vue';
|
import FormGroupHead from './FormGroupHead.vue';
|
||||||
import AvatarGroup from 'src/components/shared/AvatarGroup.vue';
|
import AvatarGroup from 'src/components/shared/AvatarGroup.vue';
|
||||||
import { StateButton } from 'components/button';
|
import { StateButton } from 'components/button';
|
||||||
|
import { CancelButton } from 'components/button';
|
||||||
import DutyExpansion from './DutyExpansion.vue';
|
import DutyExpansion from './DutyExpansion.vue';
|
||||||
import MessengerExpansion from './MessengerExpansion.vue';
|
import MessengerExpansion from './MessengerExpansion.vue';
|
||||||
|
|
||||||
|
|
@ -20,6 +21,7 @@ import {
|
||||||
dialog,
|
dialog,
|
||||||
getEmployeeName,
|
getEmployeeName,
|
||||||
getCustomerName,
|
getCustomerName,
|
||||||
|
dialogWarningClose,
|
||||||
} from 'src/stores/utils';
|
} from 'src/stores/utils';
|
||||||
import { dateFormatJS } from 'src/utils/datetime';
|
import { dateFormatJS } from 'src/utils/datetime';
|
||||||
import { useRequestList } from 'src/stores/request-list';
|
import { useRequestList } from 'src/stores/request-list';
|
||||||
|
|
@ -37,7 +39,7 @@ import ProductExpansion from './ProductExpansion.vue';
|
||||||
import { useRoute } from 'vue-router';
|
import { useRoute } from 'vue-router';
|
||||||
import { useWorkflowTemplate } from 'src/stores/workflow-template';
|
import { useWorkflowTemplate } from 'src/stores/workflow-template';
|
||||||
import { WorkflowTemplate } from 'src/stores/workflow-template/types';
|
import { WorkflowTemplate } from 'src/stores/workflow-template/types';
|
||||||
import { initLang, initTheme, Lang } from 'src/utils/ui';
|
import { initLang, initTheme } from 'src/utils/ui';
|
||||||
import {
|
import {
|
||||||
EmployeePassportPayload,
|
EmployeePassportPayload,
|
||||||
EmployeeVisaPayload,
|
EmployeeVisaPayload,
|
||||||
|
|
@ -314,7 +316,7 @@ function goToQuotation(
|
||||||
customerBranchId: quotation.customerBranchId,
|
customerBranchId: quotation.customerBranchId,
|
||||||
agentPrice: quotation.agentPrice,
|
agentPrice: quotation.agentPrice,
|
||||||
statusDialog: 'info',
|
statusDialog: 'info',
|
||||||
quotationId: quotation.id,
|
quotationId: opt && opt.id ? opt.id : quotation.id,
|
||||||
}),
|
}),
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|
@ -330,6 +332,16 @@ function goToDebitNote(opt?: { tab?: string; id?: string }) {
|
||||||
|
|
||||||
window.open(url.toString(), '_blank');
|
window.open(url.toString(), '_blank');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function closeTab() {
|
||||||
|
dialogWarningClose(t, {
|
||||||
|
message: t('dialog.message.close'),
|
||||||
|
action: () => {
|
||||||
|
window.close();
|
||||||
|
},
|
||||||
|
cancel: () => {},
|
||||||
|
});
|
||||||
|
}
|
||||||
</script>
|
</script>
|
||||||
<template>
|
<template>
|
||||||
<div class="column surface-0 fullscreen" v-if="data">
|
<div class="column surface-0 fullscreen" v-if="data">
|
||||||
|
|
@ -565,6 +577,21 @@ function goToDebitNote(opt?: { tab?: string; id?: string }) {
|
||||||
: goToQuotation(data.quotation, { tab: 'receipt' })
|
: goToQuotation(data.quotation, { tab: 'receipt' })
|
||||||
"
|
"
|
||||||
/>
|
/>
|
||||||
|
|
||||||
|
<DataDisplay
|
||||||
|
v-if="data.quotation.isDebitNote"
|
||||||
|
clickable
|
||||||
|
class="col"
|
||||||
|
icon="mdi-file-document-outline"
|
||||||
|
:label="$t('requestList.referenceNo')"
|
||||||
|
:value="data.quotation.debitNoteQuotation.code || '-'"
|
||||||
|
@label-click="
|
||||||
|
goToQuotation(data.quotation, {
|
||||||
|
id: data.quotation.debitNoteQuotationId,
|
||||||
|
})
|
||||||
|
"
|
||||||
|
/>
|
||||||
|
|
||||||
<div v-if="$q.screen.gt.sm" class="col"></div>
|
<div v-if="$q.screen.gt.sm" class="col"></div>
|
||||||
</div>
|
</div>
|
||||||
<FormGroupHead class="col-12">
|
<FormGroupHead class="col-12">
|
||||||
|
|
@ -844,6 +871,15 @@ function goToDebitNote(opt?: { tab?: string; id?: string }) {
|
||||||
</div>
|
</div>
|
||||||
</section>
|
</section>
|
||||||
</main>
|
</main>
|
||||||
|
|
||||||
|
<!-- SEC: Footer -->
|
||||||
|
<footer class="surface-1 q-pa-md full-width text-right">
|
||||||
|
<CancelButton
|
||||||
|
@click="closeTab()"
|
||||||
|
:label="$t('dialog.action.close')"
|
||||||
|
outlined
|
||||||
|
/>
|
||||||
|
</footer>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
<style scoped></style>
|
<style scoped></style>
|
||||||
|
|
|
||||||
|
|
@ -23,7 +23,9 @@ const emit = defineEmits<{
|
||||||
|
|
||||||
const props = defineProps<{
|
const props = defineProps<{
|
||||||
taskListGroup?: {
|
taskListGroup?: {
|
||||||
product: RequestWork['productService']['product'];
|
product:
|
||||||
|
| RequestWork['productService']['product']
|
||||||
|
| RequestWork['productService'];
|
||||||
list: (RequestWork & {
|
list: (RequestWork & {
|
||||||
_template?: {
|
_template?: {
|
||||||
id: string;
|
id: string;
|
||||||
|
|
@ -135,6 +137,7 @@ async function getList() {
|
||||||
}
|
}
|
||||||
|
|
||||||
function getStep(requestWork: RequestWork) {
|
function getStep(requestWork: RequestWork) {
|
||||||
|
if (!requestWork.stepStatus) return 0;
|
||||||
const target = requestWork.stepStatus.find(
|
const target = requestWork.stepStatus.find(
|
||||||
(v) =>
|
(v) =>
|
||||||
v.workStatus === RequestWorkStatus.Ready ||
|
v.workStatus === RequestWorkStatus.Ready ||
|
||||||
|
|
@ -166,7 +169,7 @@ function submit() {
|
||||||
requestWorkStep?: Task;
|
requestWorkStep?: Task;
|
||||||
}[] = [];
|
}[] = [];
|
||||||
selectedEmployee.value.forEach((v, i) => {
|
selectedEmployee.value.forEach((v, i) => {
|
||||||
if (v.stepStatus.length === 0) {
|
if (!v.stepStatus || v.stepStatus.length === 0) {
|
||||||
selected.push({
|
selected.push({
|
||||||
step: 0,
|
step: 0,
|
||||||
requestWorkId: v.id || '',
|
requestWorkId: v.id || '',
|
||||||
|
|
@ -224,7 +227,7 @@ function close() {
|
||||||
|
|
||||||
function onDialogOpen() {
|
function onDialogOpen() {
|
||||||
// assign selected to group
|
// assign selected to group
|
||||||
!props.creditNote && assignTempGroup();
|
assignTempGroup();
|
||||||
|
|
||||||
// match group to check
|
// match group to check
|
||||||
selectedEmployee.value = [];
|
selectedEmployee.value = [];
|
||||||
|
|
@ -232,7 +235,7 @@ function onDialogOpen() {
|
||||||
const matchingItems = tempGroupEdit.value
|
const matchingItems = tempGroupEdit.value
|
||||||
.flatMap((g) => g.list)
|
.flatMap((g) => g.list)
|
||||||
.filter((l) => {
|
.filter((l) => {
|
||||||
if (l.stepStatus.length === 0) {
|
if (!l.stepStatus || l.stepStatus.length === 0) {
|
||||||
return taskList.value.some(
|
return taskList.value.some(
|
||||||
(t) => t.requestWorkStep?.requestWork.id === l.id,
|
(t) => t.requestWorkStep?.requestWork.id === l.id,
|
||||||
);
|
);
|
||||||
|
|
@ -248,8 +251,12 @@ function onDialogOpen() {
|
||||||
function assignTempGroup() {
|
function assignTempGroup() {
|
||||||
if (!props.taskListGroup) return;
|
if (!props.taskListGroup) return;
|
||||||
props.taskListGroup.forEach((newGroup) => {
|
props.taskListGroup.forEach((newGroup) => {
|
||||||
|
const productId = props.creditNote
|
||||||
|
? (newGroup.product as RequestWork['productService']).productId
|
||||||
|
: (newGroup.product as RequestWork['productService']['product']).id;
|
||||||
|
|
||||||
const existingGroup = tempGroupEdit.value.find(
|
const existingGroup = tempGroupEdit.value.find(
|
||||||
(g) => g.product.id === newGroup.product.id,
|
(g) => g.product.id === productId,
|
||||||
);
|
);
|
||||||
|
|
||||||
if (existingGroup) {
|
if (existingGroup) {
|
||||||
|
|
@ -260,7 +267,9 @@ function assignTempGroup() {
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
tempGroupEdit.value.push({
|
tempGroupEdit.value.push({
|
||||||
...newGroup,
|
product: props.creditNote
|
||||||
|
? (newGroup.product as RequestWork['productService']).product
|
||||||
|
: (newGroup.product as RequestWork['productService']['product']),
|
||||||
list: [...newGroup.list], // Ensure a new reference
|
list: [...newGroup.list], // Ensure a new reference
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -232,14 +232,14 @@ function disableCheckAll() {
|
||||||
:columns="
|
:columns="
|
||||||
stepOn
|
stepOn
|
||||||
? [
|
? [
|
||||||
...employeeColumn.slice(0, 2),
|
...employeeColumn.slice(0, 3),
|
||||||
{
|
{
|
||||||
name: 'periodNo',
|
name: 'periodNo',
|
||||||
align: 'center',
|
align: 'center',
|
||||||
label: 'flow.step',
|
label: 'flow.step',
|
||||||
field: (v) => v.product.code,
|
field: (v) => v.product.code,
|
||||||
},
|
},
|
||||||
...employeeColumn.slice(2),
|
...employeeColumn.slice(3),
|
||||||
]
|
]
|
||||||
: statusOn
|
: statusOn
|
||||||
? [
|
? [
|
||||||
|
|
@ -463,6 +463,16 @@ function disableCheckAll() {
|
||||||
{{ props.row.request.code }}
|
{{ props.row.request.code }}
|
||||||
</span>
|
</span>
|
||||||
</q-td>
|
</q-td>
|
||||||
|
|
||||||
|
<q-td>
|
||||||
|
<span
|
||||||
|
class="cursor-pointer link"
|
||||||
|
@click="goToQuotation(props.row.request.quotation)"
|
||||||
|
>
|
||||||
|
{{ props.row.request.quotation?.code }}
|
||||||
|
</span>
|
||||||
|
</q-td>
|
||||||
|
|
||||||
<q-td v-if="stepOn" class="text-left">
|
<q-td v-if="stepOn" class="text-left">
|
||||||
<div v-if="props.row._template" class="column text-left">
|
<div v-if="props.row._template" class="column text-left">
|
||||||
<span>{{ props.row._template.templateName }}</span>
|
<span>{{ props.row._template.templateName }}</span>
|
||||||
|
|
@ -528,14 +538,7 @@ function disableCheckAll() {
|
||||||
:expiration-date="new Date(props.row.request.quotation?.dueDate)"
|
:expiration-date="new Date(props.row.request.quotation?.dueDate)"
|
||||||
/>
|
/>
|
||||||
</q-td>
|
</q-td>
|
||||||
<q-td>
|
|
||||||
<span
|
|
||||||
class="cursor-pointer link"
|
|
||||||
@click="goToQuotation(props.row.request.quotation)"
|
|
||||||
>
|
|
||||||
{{ props.row.request.quotation?.code }}
|
|
||||||
</span>
|
|
||||||
</q-td>
|
|
||||||
<q-td>
|
<q-td>
|
||||||
<BadgeComponent
|
<BadgeComponent
|
||||||
v-if="props.row.request.quotation?.urgent"
|
v-if="props.row.request.quotation?.urgent"
|
||||||
|
|
|
||||||
|
|
@ -170,6 +170,12 @@ export const employeeColumn = [
|
||||||
label: 'requestList.requestListCode',
|
label: 'requestList.requestListCode',
|
||||||
field: 'code',
|
field: 'code',
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
name: 'quotationCode',
|
||||||
|
align: 'center',
|
||||||
|
label: 'requestList.quotationCode',
|
||||||
|
field: 'quotationCode',
|
||||||
|
},
|
||||||
{
|
{
|
||||||
name: 'fullName',
|
name: 'fullName',
|
||||||
align: 'center',
|
align: 'center',
|
||||||
|
|
@ -200,12 +206,6 @@ export const employeeColumn = [
|
||||||
label: 'general.numberOfDay',
|
label: 'general.numberOfDay',
|
||||||
field: 'day',
|
field: 'day',
|
||||||
},
|
},
|
||||||
{
|
|
||||||
name: 'quotationCode',
|
|
||||||
align: 'center',
|
|
||||||
label: 'requestList.quotationCode',
|
|
||||||
field: 'quotationCode',
|
|
||||||
},
|
|
||||||
] as const satisfies QTableProps['columns'];
|
] as const satisfies QTableProps['columns'];
|
||||||
|
|
||||||
export const productColumn = [
|
export const productColumn = [
|
||||||
|
|
|
||||||
|
|
@ -1,10 +1,11 @@
|
||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
import { storeToRefs } from 'pinia';
|
import { storeToRefs } from 'pinia';
|
||||||
import { onMounted, nextTick, ref } from 'vue';
|
import { onMounted, nextTick, ref } from 'vue';
|
||||||
|
import { useI18n } from 'vue-i18n';
|
||||||
import ThaiBahtText from 'thai-baht-text';
|
import ThaiBahtText from 'thai-baht-text';
|
||||||
|
|
||||||
// NOTE: Import stores
|
// NOTE: Import stores
|
||||||
import { formatNumberDecimal } from 'stores/utils';
|
import { dialogWarningClose, formatNumberDecimal } from 'stores/utils';
|
||||||
import { useConfigStore } from 'stores/config';
|
import { useConfigStore } from 'stores/config';
|
||||||
import { precisionRound } from 'src/utils/arithmetic';
|
import { precisionRound } from 'src/utils/arithmetic';
|
||||||
|
|
||||||
|
|
@ -12,6 +13,7 @@ import { precisionRound } from 'src/utils/arithmetic';
|
||||||
import { Branch } from 'stores/branch/types';
|
import { Branch } from 'stores/branch/types';
|
||||||
|
|
||||||
// NOTE: Import Components
|
// NOTE: Import Components
|
||||||
|
import { CancelButton } from 'components/button';
|
||||||
import ViewHeader from './ViewHeader.vue';
|
import ViewHeader from './ViewHeader.vue';
|
||||||
import ViewFooter from './ViewFooter.vue';
|
import ViewFooter from './ViewFooter.vue';
|
||||||
import PrintButton from 'src/components/button/PrintButton.vue';
|
import PrintButton from 'src/components/button/PrintButton.vue';
|
||||||
|
|
@ -29,6 +31,7 @@ const route = useRoute();
|
||||||
const taskOrder = useTaskOrderStore();
|
const taskOrder = useTaskOrderStore();
|
||||||
const configStore = useConfigStore();
|
const configStore = useConfigStore();
|
||||||
const config = storeToRefs(configStore).data;
|
const config = storeToRefs(configStore).data;
|
||||||
|
const { t } = useI18n();
|
||||||
const viewType = ref<'docOrder' | 'docReceive'>('docOrder');
|
const viewType = ref<'docOrder' | 'docReceive'>('docOrder');
|
||||||
|
|
||||||
type Data = TaskOrder;
|
type Data = TaskOrder;
|
||||||
|
|
@ -250,11 +253,32 @@ onMounted(async () => {
|
||||||
function print() {
|
function print() {
|
||||||
window.print();
|
window.print();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async function closeTab() {
|
||||||
|
dialogWarningClose(t, {
|
||||||
|
message: t('dialog.message.close'),
|
||||||
|
action: () => {
|
||||||
|
window.close();
|
||||||
|
},
|
||||||
|
cancel: () => {},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function closeAble() {
|
||||||
|
return window.opener !== null;
|
||||||
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<div class="toolbar">
|
<div class="toolbar">
|
||||||
<PrintButton solid @click="print" />
|
<PrintButton solid @click="print" />
|
||||||
|
<CancelButton
|
||||||
|
outlined
|
||||||
|
id="btn-close"
|
||||||
|
@click="closeTab()"
|
||||||
|
:label="$t('dialog.action.close')"
|
||||||
|
v-if="closeAble()"
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div
|
<div
|
||||||
class="row justify-between container"
|
class="row justify-between container"
|
||||||
|
|
@ -489,7 +513,7 @@ function print() {
|
||||||
position: sticky;
|
position: sticky;
|
||||||
top: 0;
|
top: 0;
|
||||||
display: flex;
|
display: flex;
|
||||||
justify-content: center;
|
justify-content: space-between;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
margin-bottom: 1rem;
|
margin-bottom: 1rem;
|
||||||
padding: 1rem;
|
padding: 1rem;
|
||||||
|
|
|
||||||
|
|
@ -1209,19 +1209,19 @@ watch(
|
||||||
{{ $t('general.view', { msg: $t('general.example') }) }}
|
{{ $t('general.view', { msg: $t('general.example') }) }}
|
||||||
</MainButton>
|
</MainButton>
|
||||||
<div class="row" style="gap: var(--size-2)">
|
<div class="row" style="gap: var(--size-2)">
|
||||||
|
<UndoButton outlined @click="undo()" v-if="state.mode === 'edit'" />
|
||||||
|
<CancelButton
|
||||||
|
v-if="state.mode !== 'edit'"
|
||||||
|
:label="$t('dialog.action.close')"
|
||||||
|
outlined
|
||||||
|
@click="closeTab()"
|
||||||
|
/>
|
||||||
<template
|
<template
|
||||||
v-if="
|
v-if="
|
||||||
fullTaskOrder?.taskOrderStatus === TaskOrderStatus.Pending ||
|
fullTaskOrder?.taskOrderStatus === TaskOrderStatus.Pending ||
|
||||||
state.mode === 'create'
|
state.mode === 'create'
|
||||||
"
|
"
|
||||||
>
|
>
|
||||||
<UndoButton outlined @click="undo()" v-if="state.mode === 'edit'" />
|
|
||||||
<CancelButton
|
|
||||||
@click="closeTab()"
|
|
||||||
:label="$t('dialog.action.close')"
|
|
||||||
v-if="state.mode === 'info'"
|
|
||||||
outlined
|
|
||||||
/>
|
|
||||||
<SaveButton
|
<SaveButton
|
||||||
v-if="state.mode && ['create', 'edit'].includes(state.mode)"
|
v-if="state.mode && ['create', 'edit'].includes(state.mode)"
|
||||||
@click="() => formDocument.submit()"
|
@click="() => formDocument.submit()"
|
||||||
|
|
|
||||||
|
|
@ -3,9 +3,10 @@ import { storeToRefs } from 'pinia';
|
||||||
import { useRoute } from 'vue-router';
|
import { useRoute } from 'vue-router';
|
||||||
import { computed, onMounted, ref, watch } from 'vue';
|
import { computed, onMounted, ref, watch } from 'vue';
|
||||||
import { getUserId } from 'src/services/keycloak';
|
import { getUserId } from 'src/services/keycloak';
|
||||||
|
import { useI18n } from 'vue-i18n';
|
||||||
|
|
||||||
// NOTE: Import Components
|
// NOTE: Import Components
|
||||||
import { SaveButton } from 'src/components/button';
|
import { SaveButton, CancelButton } from 'src/components/button';
|
||||||
import { StateButton } from 'components/button';
|
import { StateButton } from 'components/button';
|
||||||
import InfoMessengerExpansion from '../expansion/receive/InfoMessengerExpansion.vue';
|
import InfoMessengerExpansion from '../expansion/receive/InfoMessengerExpansion.vue';
|
||||||
import InfoProductExpansion from '../expansion/receive/InfoProductExpansion.vue';
|
import InfoProductExpansion from '../expansion/receive/InfoProductExpansion.vue';
|
||||||
|
|
@ -31,6 +32,7 @@ import { useTaskOrderStore } from 'src/stores/task-order';
|
||||||
|
|
||||||
const route = useRoute();
|
const route = useRoute();
|
||||||
const taskOrderFormStore = useTaskOrderForm();
|
const taskOrderFormStore = useTaskOrderForm();
|
||||||
|
const { t } = useI18n();
|
||||||
|
|
||||||
const { currentFormData, state, fullTaskOrder } =
|
const { currentFormData, state, fullTaskOrder } =
|
||||||
storeToRefs(taskOrderFormStore);
|
storeToRefs(taskOrderFormStore);
|
||||||
|
|
@ -342,6 +344,16 @@ function sortList(
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async function closeTab() {
|
||||||
|
dialogWarningClose(t, {
|
||||||
|
message: t('dialog.message.close'),
|
||||||
|
action: () => {
|
||||||
|
window.close();
|
||||||
|
},
|
||||||
|
cancel: () => {},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
onMounted(async () => {
|
onMounted(async () => {
|
||||||
initTheme();
|
initTheme();
|
||||||
initLang();
|
initLang();
|
||||||
|
|
@ -756,12 +768,15 @@ watch([currentFormData.value.taskStatus], () => {
|
||||||
|
|
||||||
<!-- SEC: footer -->
|
<!-- SEC: footer -->
|
||||||
|
|
||||||
<footer
|
<footer class="surface-1 q-pa-md full-width">
|
||||||
v-if="fullTaskOrder.taskOrderStatus !== TaskOrderStatus.Pending"
|
<nav class="row justify-end" style="gap: var(--size-2)">
|
||||||
class="surface-1 q-pa-md full-width"
|
<CancelButton
|
||||||
>
|
:label="$t('dialog.action.close')"
|
||||||
<nav class="row justify-end">
|
outlined
|
||||||
|
@click="closeTab()"
|
||||||
|
/>
|
||||||
<SaveButton
|
<SaveButton
|
||||||
|
v-if="fullTaskOrder.taskOrderStatus !== TaskOrderStatus.Pending"
|
||||||
:disabled="
|
:disabled="
|
||||||
fullTaskOrder.taskList.some(
|
fullTaskOrder.taskList.some(
|
||||||
(t) =>
|
(t) =>
|
||||||
|
|
|
||||||
|
|
@ -24,7 +24,13 @@ import SelectReadyRequestWork from '../09_task-order/SelectReadyRequestWork.vue'
|
||||||
import RefundInformation from './RefundInformation.vue';
|
import RefundInformation from './RefundInformation.vue';
|
||||||
import QuotationFormReceipt from '../05_quotation/QuotationFormReceipt.vue';
|
import QuotationFormReceipt from '../05_quotation/QuotationFormReceipt.vue';
|
||||||
import DialogViewFile from 'src/components/dialog/DialogViewFile.vue';
|
import DialogViewFile from 'src/components/dialog/DialogViewFile.vue';
|
||||||
import { MainButton, SaveButton } from 'src/components/button';
|
import {
|
||||||
|
MainButton,
|
||||||
|
SaveButton,
|
||||||
|
CancelButton,
|
||||||
|
EditButton,
|
||||||
|
UndoButton,
|
||||||
|
} from 'src/components/button';
|
||||||
import { RequestWork } from 'src/stores/request-list/types';
|
import { RequestWork } from 'src/stores/request-list/types';
|
||||||
import { storeToRefs } from 'pinia';
|
import { storeToRefs } from 'pinia';
|
||||||
import useOptionStore from 'src/stores/options';
|
import useOptionStore from 'src/stores/options';
|
||||||
|
|
@ -76,15 +82,16 @@ const statusTabForm = ref<
|
||||||
}[]
|
}[]
|
||||||
>([]);
|
>([]);
|
||||||
|
|
||||||
const readonly = computed(
|
// const readonly = computed(
|
||||||
() =>
|
// () =>
|
||||||
creditNoteData.value?.creditNoteStatus === CreditNoteStatus.Pending ||
|
// creditNoteData.value?.creditNoteStatus === CreditNoteStatus.Pending ||
|
||||||
creditNoteData.value?.creditNoteStatus === CreditNoteStatus.Success,
|
// creditNoteData.value?.creditNoteStatus === CreditNoteStatus.Success,
|
||||||
);
|
// );
|
||||||
|
|
||||||
const pageState = reactive({
|
const pageState = reactive({
|
||||||
productDialog: false,
|
productDialog: false,
|
||||||
fileDialog: false,
|
fileDialog: false,
|
||||||
|
mode: 'view' as 'view' | 'edit' | 'info',
|
||||||
});
|
});
|
||||||
|
|
||||||
const defaultRemark = '#[quotation-labor]<br/><br/>#[quotation-payment]';
|
const defaultRemark = '#[quotation-labor]<br/><br/>#[quotation-payment]';
|
||||||
|
|
@ -161,9 +168,11 @@ async function initStatus() {
|
||||||
{
|
{
|
||||||
title: 'Pending',
|
title: 'Pending',
|
||||||
status: creditNoteData.value?.id
|
status: creditNoteData.value?.id
|
||||||
? creditNoteData.value.creditNoteStatus === CreditNoteStatus.Success
|
? creditNoteData.value.creditNoteStatus === CreditNoteStatus.Waiting
|
||||||
? 'done'
|
? 'waiting'
|
||||||
: 'doing'
|
: creditNoteData.value.creditNoteStatus === CreditNoteStatus.Success
|
||||||
|
? 'done'
|
||||||
|
: 'doing'
|
||||||
: 'waiting',
|
: 'waiting',
|
||||||
active: () => view.value === CreditNoteStatus.Pending,
|
active: () => view.value === CreditNoteStatus.Pending,
|
||||||
handler: async () => {
|
handler: async () => {
|
||||||
|
|
@ -253,8 +262,9 @@ function assignFormData() {
|
||||||
requestWorkStep: {
|
requestWorkStep: {
|
||||||
requestWork: {
|
requestWork: {
|
||||||
...v,
|
...v,
|
||||||
|
stepStatus: v.stepStatus || [],
|
||||||
request: { ...v.request, quotation: current.quotation },
|
request: { ...v.request, quotation: current.quotation },
|
||||||
},
|
} as RequestWork,
|
||||||
},
|
},
|
||||||
}));
|
}));
|
||||||
}
|
}
|
||||||
|
|
@ -285,12 +295,24 @@ async function getQuotation() {
|
||||||
|
|
||||||
async function submit() {
|
async function submit() {
|
||||||
const payload = formData.value;
|
const payload = formData.value;
|
||||||
|
|
||||||
payload.requestWorkId = formTaskList.value.map((v) => v.requestWorkId);
|
payload.requestWorkId = formTaskList.value.map((v) => v.requestWorkId);
|
||||||
payload.quotationId =
|
payload.quotationId =
|
||||||
typeof route.query['quotationId'] === 'string'
|
(pageState.mode === 'edit'
|
||||||
? route.query['quotationId']
|
? creditNoteData.value?.quotationId
|
||||||
: '';
|
: typeof route.query['quotationId'] === 'string'
|
||||||
const res = await creditNote.createCreditNote(payload);
|
? route.query['quotationId']
|
||||||
|
: '') || '';
|
||||||
|
|
||||||
|
const res =
|
||||||
|
pageState.mode === 'edit'
|
||||||
|
? await creditNote.updateCreditNote(
|
||||||
|
creditNoteData.value?.id || '',
|
||||||
|
payload,
|
||||||
|
)
|
||||||
|
: creditNoteData.value
|
||||||
|
? await creditNote.acceptCreditNote(creditNoteData.value.id)
|
||||||
|
: await creditNote.createCreditNote(payload);
|
||||||
|
|
||||||
if (res) {
|
if (res) {
|
||||||
await router.push(`/credit-note/${res.id}`);
|
await router.push(`/credit-note/${res.id}`);
|
||||||
|
|
@ -302,6 +324,7 @@ async function submit() {
|
||||||
}
|
}
|
||||||
|
|
||||||
initStatus();
|
initStatus();
|
||||||
|
pageState.mode = 'info';
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -526,13 +549,31 @@ function storeDataLocal() {
|
||||||
window.open(url, '_blank');
|
window.open(url, '_blank');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function closeTab() {
|
||||||
|
dialogWarningClose(t, {
|
||||||
|
message: t('dialog.message.close'),
|
||||||
|
action: () => {
|
||||||
|
window.close();
|
||||||
|
},
|
||||||
|
cancel: () => {},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function undo() {
|
||||||
|
assignFormData();
|
||||||
|
pageState.mode = 'info';
|
||||||
|
}
|
||||||
|
|
||||||
onMounted(async () => {
|
onMounted(async () => {
|
||||||
initTheme();
|
initTheme();
|
||||||
initLang();
|
initLang();
|
||||||
await useConfigStore().getConfig();
|
await useConfigStore().getConfig();
|
||||||
await getCreditNote();
|
await getCreditNote();
|
||||||
await getQuotation();
|
await getQuotation();
|
||||||
creditNoteData.value && (await getFileList(creditNoteData.value.id, true));
|
if (creditNoteData.value) {
|
||||||
|
pageState.mode = 'info';
|
||||||
|
await getFileList(creditNoteData.value.id, true);
|
||||||
|
}
|
||||||
initStatus();
|
initStatus();
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|
@ -587,7 +628,7 @@ onMounted(async () => {
|
||||||
:key="i.title"
|
:key="i.title"
|
||||||
:label="
|
:label="
|
||||||
$t(
|
$t(
|
||||||
`creditNote${i.title === 'title' ? '' : '.stats'}.${i.title}`,
|
`creditNote${i.title === 'title' ? '' : '.status'}.${i.title}`,
|
||||||
)
|
)
|
||||||
"
|
"
|
||||||
:status-active="i.active?.()"
|
:status-active="i.active?.()"
|
||||||
|
|
@ -616,7 +657,7 @@ onMounted(async () => {
|
||||||
>
|
>
|
||||||
<CreditNoteExpansion
|
<CreditNoteExpansion
|
||||||
v-if="view === null"
|
v-if="view === null"
|
||||||
:readonly="readonly"
|
:readonly="pageState.mode === 'info'"
|
||||||
v-model:reason="formData.reason"
|
v-model:reason="formData.reason"
|
||||||
v-model:detail="formData.detail"
|
v-model:detail="formData.detail"
|
||||||
/>
|
/>
|
||||||
|
|
@ -625,7 +666,7 @@ onMounted(async () => {
|
||||||
<ProductExpansion
|
<ProductExpansion
|
||||||
v-if="view === null"
|
v-if="view === null"
|
||||||
creditNote
|
creditNote
|
||||||
:readonly="readonly"
|
:readonly="pageState.mode === 'info'"
|
||||||
:agentPrice="quotationData?.agentPrice"
|
:agentPrice="quotationData?.agentPrice"
|
||||||
:task-list="taskListGroup"
|
:task-list="taskListGroup"
|
||||||
@add-product="openProductDialog"
|
@add-product="openProductDialog"
|
||||||
|
|
@ -633,7 +674,7 @@ onMounted(async () => {
|
||||||
|
|
||||||
<PaymentExpansion
|
<PaymentExpansion
|
||||||
v-if="view === null"
|
v-if="view === null"
|
||||||
:readonly="readonly"
|
:readonly="pageState.mode === 'info'"
|
||||||
:total-price="summaryPrice.finalPrice"
|
:total-price="summaryPrice.finalPrice"
|
||||||
v-model:payback-type="formData.paybackType"
|
v-model:payback-type="formData.paybackType"
|
||||||
v-model:payback-bank="formData.paybackBank"
|
v-model:payback-bank="formData.paybackBank"
|
||||||
|
|
@ -746,7 +787,7 @@ onMounted(async () => {
|
||||||
v-model:remark="formData.remark"
|
v-model:remark="formData.remark"
|
||||||
:default-remark="defaultRemark"
|
:default-remark="defaultRemark"
|
||||||
:items="[]"
|
:items="[]"
|
||||||
:readonly
|
:readonly="pageState.mode === 'info'"
|
||||||
>
|
>
|
||||||
<template #hint>
|
<template #hint>
|
||||||
{{ $t('general.hintRemark') }}
|
{{ $t('general.hintRemark') }}
|
||||||
|
|
@ -793,7 +834,7 @@ onMounted(async () => {
|
||||||
|
|
||||||
<!-- SEC: footer -->
|
<!-- SEC: footer -->
|
||||||
<footer class="surface-1 q-pa-md full-width">
|
<footer class="surface-1 q-pa-md full-width">
|
||||||
<nav class="row justify-end">
|
<nav class="row justify-end" style="gap: var(--size-2)">
|
||||||
<!-- TODO: view example -->
|
<!-- TODO: view example -->
|
||||||
<MainButton
|
<MainButton
|
||||||
class="q-mr-auto"
|
class="q-mr-auto"
|
||||||
|
|
@ -804,16 +845,45 @@ onMounted(async () => {
|
||||||
>
|
>
|
||||||
{{ $t('general.view', { msg: $t('general.example') }) }}
|
{{ $t('general.view', { msg: $t('general.example') }) }}
|
||||||
</MainButton>
|
</MainButton>
|
||||||
<!-- @click="submit" -->
|
|
||||||
|
<UndoButton v-if="pageState.mode === 'edit'" outlined @click="undo()" />
|
||||||
|
<CancelButton
|
||||||
|
v-if="pageState.mode !== 'edit'"
|
||||||
|
@click="closeTab()"
|
||||||
|
:label="$t('dialog.action.close')"
|
||||||
|
outlined
|
||||||
|
/>
|
||||||
<SaveButton
|
<SaveButton
|
||||||
v-if="!readonly"
|
v-if="
|
||||||
:disabled="taskListGroup.length === 0"
|
!creditNoteData ||
|
||||||
|
creditNoteData?.creditNoteStatus === CreditNoteStatus.Waiting
|
||||||
|
"
|
||||||
|
:disabled="taskListGroup.length === 0 || pageState.mode === 'edit'"
|
||||||
type="submit"
|
type="submit"
|
||||||
@click.stop="(e) => refForm?.submit(e)"
|
@click.stop="(e) => refForm?.submit(e)"
|
||||||
:label="$t('creditNote.label.submit')"
|
:label="
|
||||||
|
$t(
|
||||||
|
`creditNote.label.${creditNoteData?.creditNoteStatus ? 'submit' : 'request'}`,
|
||||||
|
)
|
||||||
|
"
|
||||||
icon="mdi-account-multiple-check-outline"
|
icon="mdi-account-multiple-check-outline"
|
||||||
solid
|
solid
|
||||||
></SaveButton>
|
/>
|
||||||
|
<SaveButton
|
||||||
|
v-if="pageState.mode === 'edit'"
|
||||||
|
:disabled="taskListGroup.length === 0"
|
||||||
|
@click="(e) => refForm?.submit(e)"
|
||||||
|
solid
|
||||||
|
/>
|
||||||
|
<EditButton
|
||||||
|
v-if="
|
||||||
|
pageState.mode === 'info' &&
|
||||||
|
creditNoteData?.creditNoteStatus === CreditNoteStatus.Waiting
|
||||||
|
"
|
||||||
|
class="no-print"
|
||||||
|
@click="pageState.mode = 'edit'"
|
||||||
|
solid
|
||||||
|
/>
|
||||||
</nav>
|
</nav>
|
||||||
</footer>
|
</footer>
|
||||||
</div>
|
</div>
|
||||||
|
|
@ -822,6 +892,7 @@ onMounted(async () => {
|
||||||
<SelectReadyRequestWork
|
<SelectReadyRequestWork
|
||||||
v-if="quotationData"
|
v-if="quotationData"
|
||||||
creditNote
|
creditNote
|
||||||
|
:task-list-group="taskListGroup"
|
||||||
:fetch-params="{ cancelOnly: true, quotationId: quotationData.id }"
|
:fetch-params="{ cancelOnly: true, quotationId: quotationData.id }"
|
||||||
v-model:open="pageState.productDialog"
|
v-model:open="pageState.productDialog"
|
||||||
v-model:task-list="formTaskList"
|
v-model:task-list="formTaskList"
|
||||||
|
|
|
||||||
|
|
@ -35,7 +35,7 @@ const { stats, pageMax, page, data, pageSize } = storeToRefs(creditNote);
|
||||||
// NOTE: Variable
|
// NOTE: Variable
|
||||||
const pageState = reactive({
|
const pageState = reactive({
|
||||||
quotationId: '',
|
quotationId: '',
|
||||||
currentTab: CreditNoteStatus.Pending,
|
currentTab: CreditNoteStatus.Waiting,
|
||||||
hideStat: false,
|
hideStat: false,
|
||||||
statusFilter: 'None',
|
statusFilter: 'None',
|
||||||
inputSearch: '',
|
inputSearch: '',
|
||||||
|
|
@ -158,7 +158,7 @@ watch(
|
||||||
color: hsl(var(--info-bg));
|
color: hsl(var(--info-bg));
|
||||||
"
|
"
|
||||||
>
|
>
|
||||||
{{ stats.Pending + stats.Success || 0 }}
|
{{ Object.values(stats).reduce((sum, value) => sum + value, 0) || 0 }}
|
||||||
</q-badge>
|
</q-badge>
|
||||||
<q-btn
|
<q-btn
|
||||||
class="q-ml-sm"
|
class="q-ml-sm"
|
||||||
|
|
@ -180,16 +180,22 @@ watch(
|
||||||
<StatCardComponent
|
<StatCardComponent
|
||||||
labelI18n
|
labelI18n
|
||||||
:branch="[
|
:branch="[
|
||||||
|
{
|
||||||
|
icon: 'icon-park-outline:loading-one',
|
||||||
|
count: stats[CreditNoteStatus.Pending] || 0,
|
||||||
|
label: `creditNote.status.${CreditNoteStatus.Waiting}`,
|
||||||
|
color: 'light-yellow',
|
||||||
|
},
|
||||||
{
|
{
|
||||||
icon: 'material-symbols-light:receipt-long',
|
icon: 'material-symbols-light:receipt-long',
|
||||||
count: stats[CreditNoteStatus.Pending] || 0,
|
count: stats[CreditNoteStatus.Pending] || 0,
|
||||||
label: `creditNote.stats.${CreditNoteStatus.Pending}`,
|
label: `creditNote.status.${CreditNoteStatus.Pending}`,
|
||||||
color: 'orange',
|
color: 'orange',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
icon: 'mdi-check-decagram-outline',
|
icon: 'mdi-check-decagram-outline',
|
||||||
count: stats[CreditNoteStatus.Success] || 0,
|
count: stats[CreditNoteStatus.Success] || 0,
|
||||||
label: `creditNote.stats.${CreditNoteStatus.Success}`,
|
label: `creditNote.status.${CreditNoteStatus.Success}`,
|
||||||
color: 'blue',
|
color: 'blue',
|
||||||
},
|
},
|
||||||
]"
|
]"
|
||||||
|
|
@ -345,7 +351,7 @@ watch(
|
||||||
<TableCreditNote
|
<TableCreditNote
|
||||||
:grid="pageState.gridView"
|
:grid="pageState.gridView"
|
||||||
:visible-columns="pageState.fieldSelected"
|
:visible-columns="pageState.fieldSelected"
|
||||||
:hide-delete="pageState.currentTab === CreditNoteStatus.Success"
|
:hide-delete="pageState.currentTab !== CreditNoteStatus.Waiting"
|
||||||
@view="(v) => navigateTo({ statusDialog: 'info', creditId: v.id })"
|
@view="(v) => navigateTo({ statusDialog: 'info', creditId: v.id })"
|
||||||
@delete="(v) => triggerDelete(v.id)"
|
@delete="(v) => triggerDelete(v.id)"
|
||||||
>
|
>
|
||||||
|
|
|
||||||
|
|
@ -14,6 +14,7 @@ export const taskStatusOpts = [
|
||||||
];
|
];
|
||||||
|
|
||||||
export const pageTabs = [
|
export const pageTabs = [
|
||||||
|
{ label: CreditNoteStatus.Waiting, value: CreditNoteStatus.Waiting },
|
||||||
{ label: CreditNoteStatus.Pending, value: CreditNoteStatus.Pending },
|
{ label: CreditNoteStatus.Pending, value: CreditNoteStatus.Pending },
|
||||||
{ label: CreditNoteStatus.Success, value: CreditNoteStatus.Success },
|
{ label: CreditNoteStatus.Success, value: CreditNoteStatus.Success },
|
||||||
];
|
];
|
||||||
|
|
|
||||||
|
|
@ -3,9 +3,10 @@ import { storeToRefs } from 'pinia';
|
||||||
import { onMounted, nextTick, ref, watch } from 'vue';
|
import { onMounted, nextTick, ref, watch } from 'vue';
|
||||||
import { precisionRound } from 'src/utils/arithmetic';
|
import { precisionRound } from 'src/utils/arithmetic';
|
||||||
import ThaiBahtText from 'thai-baht-text';
|
import ThaiBahtText from 'thai-baht-text';
|
||||||
|
import { useI18n } from 'vue-i18n';
|
||||||
|
|
||||||
// NOTE: Import stores
|
// NOTE: Import stores
|
||||||
import { formatNumberDecimal } from 'stores/utils';
|
import { dialogWarningClose, formatNumberDecimal } from 'stores/utils';
|
||||||
|
|
||||||
import { useConfigStore } from 'stores/config';
|
import { useConfigStore } from 'stores/config';
|
||||||
import useBranchStore from 'stores/branch';
|
import useBranchStore from 'stores/branch';
|
||||||
|
|
@ -28,12 +29,14 @@ import PrintButton from 'src/components/button/PrintButton.vue';
|
||||||
import { convertTemplate } from 'src/utils/string-template';
|
import { convertTemplate } from 'src/utils/string-template';
|
||||||
import { RequestWork } from 'src/stores/request-list';
|
import { RequestWork } from 'src/stores/request-list';
|
||||||
import { Employee } from 'src/stores/employee/types';
|
import { Employee } from 'src/stores/employee/types';
|
||||||
|
import { CancelButton } from 'components/button';
|
||||||
|
|
||||||
const configStore = useConfigStore();
|
const configStore = useConfigStore();
|
||||||
const branchStore = useBranchStore();
|
const branchStore = useBranchStore();
|
||||||
const customerStore = useCustomerStore();
|
const customerStore = useCustomerStore();
|
||||||
const creditNoteStore = useCreditNote();
|
const creditNoteStore = useCreditNote();
|
||||||
const { data: config } = storeToRefs(configStore);
|
const { data: config } = storeToRefs(configStore);
|
||||||
|
const { t } = useI18n();
|
||||||
|
|
||||||
const agentPrice = ref<boolean>(false);
|
const agentPrice = ref<boolean>(false);
|
||||||
|
|
||||||
|
|
@ -263,11 +266,32 @@ watch(elements, () => {});
|
||||||
function print() {
|
function print() {
|
||||||
window.print();
|
window.print();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async function closeTab() {
|
||||||
|
dialogWarningClose(t, {
|
||||||
|
message: t('dialog.message.close'),
|
||||||
|
action: () => {
|
||||||
|
window.close();
|
||||||
|
},
|
||||||
|
cancel: () => {},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function closeAble() {
|
||||||
|
return window.opener !== null;
|
||||||
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<div class="toolbar">
|
<div class="toolbar">
|
||||||
<PrintButton solid @click="print" />
|
<PrintButton solid @click="print" />
|
||||||
|
<CancelButton
|
||||||
|
outlined
|
||||||
|
id="btn-close"
|
||||||
|
@click="closeTab()"
|
||||||
|
:label="$t('dialog.action.close')"
|
||||||
|
v-if="closeAble()"
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div class="row justify-between container color-debit-note">
|
<div class="row justify-between container color-debit-note">
|
||||||
<section class="content" v-for="(chunk, i) in chunks" :key="i">
|
<section class="content" v-for="(chunk, i) in chunks" :key="i">
|
||||||
|
|
@ -548,7 +572,7 @@ function print() {
|
||||||
position: sticky;
|
position: sticky;
|
||||||
top: 0;
|
top: 0;
|
||||||
display: flex;
|
display: flex;
|
||||||
justify-content: center;
|
justify-content: space-between;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
margin-bottom: 1rem;
|
margin-bottom: 1rem;
|
||||||
padding: 1rem;
|
padding: 1rem;
|
||||||
|
|
|
||||||
|
|
@ -7,6 +7,7 @@ import { dateFormatJS, calculateAge } from 'src/utils/datetime';
|
||||||
import { initLang, initTheme, Lang } from 'src/utils/ui';
|
import { initLang, initTheme, Lang } from 'src/utils/ui';
|
||||||
import { useQuotationStore } from 'src/stores/quotations';
|
import { useQuotationStore } from 'src/stores/quotations';
|
||||||
import { useQuotationForm } from 'src/pages/05_quotation/form';
|
import { useQuotationForm } from 'src/pages/05_quotation/form';
|
||||||
|
import { useDebitNoteForm } from './form';
|
||||||
import { useReceipt, usePayment } from 'stores/payment';
|
import { useReceipt, usePayment } from 'stores/payment';
|
||||||
import useProductServiceStore from 'stores/product-service';
|
import useProductServiceStore from 'stores/product-service';
|
||||||
import {
|
import {
|
||||||
|
|
@ -40,16 +41,17 @@ import {
|
||||||
SaveButton,
|
SaveButton,
|
||||||
UndoButton,
|
UndoButton,
|
||||||
EditButton,
|
EditButton,
|
||||||
|
CancelButton,
|
||||||
} from 'src/components/button';
|
} from 'src/components/button';
|
||||||
import { RequestWork } from 'src/stores/request-list/types';
|
import { RequestWork } from 'src/stores/request-list/types';
|
||||||
import { storeToRefs } from 'pinia';
|
import { storeToRefs } from 'pinia';
|
||||||
import useOptionStore from 'src/stores/options';
|
import useOptionStore from 'src/stores/options';
|
||||||
import { deleteItem, dialogWarningClose } from 'src/stores/utils';
|
import { deleteItem, dialog, dialogWarningClose } from 'src/stores/utils';
|
||||||
import { useI18n } from 'vue-i18n';
|
import { useI18n } from 'vue-i18n';
|
||||||
import { Employee } from 'src/stores/employee/types';
|
import { Employee } from 'src/stores/employee/types';
|
||||||
import QuotationFormWorkerSelect from '../05_quotation/QuotationFormWorkerSelect.vue';
|
import QuotationFormWorkerSelect from '../05_quotation/QuotationFormWorkerSelect.vue';
|
||||||
import { watch } from 'vue';
|
import { watch } from 'vue';
|
||||||
import { ProductTree } from '../05_quotation/utils';
|
import { ProductTree, quotationProductTree } from '../05_quotation/utils';
|
||||||
import TableRequest from '../05_quotation/TableRequest.vue';
|
import TableRequest from '../05_quotation/TableRequest.vue';
|
||||||
import { RequestData, RequestDataStatus } from 'src/stores/request-list/types';
|
import { RequestData, RequestDataStatus } from 'src/stores/request-list/types';
|
||||||
import { useRequestList } from 'src/stores/request-list';
|
import { useRequestList } from 'src/stores/request-list';
|
||||||
|
|
@ -63,6 +65,7 @@ import { precisionRound } from 'src/utils/arithmetic';
|
||||||
import QuotationFormProductSelect from '../05_quotation/QuotationFormProductSelect.vue';
|
import QuotationFormProductSelect from '../05_quotation/QuotationFormProductSelect.vue';
|
||||||
import { getName } from 'src/services/keycloak';
|
import { getName } from 'src/services/keycloak';
|
||||||
|
|
||||||
|
const debitNoteForm = useDebitNoteForm();
|
||||||
const route = useRoute();
|
const route = useRoute();
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
const debitNote = useDebitNote();
|
const debitNote = useDebitNote();
|
||||||
|
|
@ -76,6 +79,7 @@ const $q = useQuasar();
|
||||||
const paymentStore = usePayment();
|
const paymentStore = usePayment();
|
||||||
const { data: config } = storeToRefs(configStore);
|
const { data: config } = storeToRefs(configStore);
|
||||||
const { newWorkerList } = storeToRefs(quotationForm);
|
const { newWorkerList } = storeToRefs(quotationForm);
|
||||||
|
const { currentFormData } = storeToRefs(debitNoteForm);
|
||||||
const { t, locale } = useI18n();
|
const { t, locale } = useI18n();
|
||||||
const requestStore = useRequestList();
|
const requestStore = useRequestList();
|
||||||
|
|
||||||
|
|
@ -101,9 +105,10 @@ const productGroup = ref<ProductGroup[]>([]);
|
||||||
const agentPrice = ref(false);
|
const agentPrice = ref(false);
|
||||||
const debitNoteData = ref<DebitNote>();
|
const debitNoteData = ref<DebitNote>();
|
||||||
const quotationData = ref<DebitNote['debitNoteQuotation']>();
|
const quotationData = ref<DebitNote['debitNoteQuotation']>();
|
||||||
const view = ref<QuotationStatus | null>(null);
|
const view = ref<QuotationStatus>(QuotationStatus.Issued);
|
||||||
const fileList = ref<FileList>();
|
const fileList = ref<FileList>();
|
||||||
const receiptList = ref<Receipt[]>([]);
|
const receiptList = ref<Receipt[]>([]);
|
||||||
|
let previousValue: DebitNotePayload | undefined = undefined;
|
||||||
|
|
||||||
const rowsRequestList = ref<RequestData[]>([]);
|
const rowsRequestList = ref<RequestData[]>([]);
|
||||||
|
|
||||||
|
|
@ -112,20 +117,6 @@ const productServiceList = ref<
|
||||||
>([]);
|
>([]);
|
||||||
const productServiceNodes = ref<ProductTree>([]);
|
const productServiceNodes = ref<ProductTree>([]);
|
||||||
|
|
||||||
const currentFormData = ref<DebitNotePayload>({
|
|
||||||
productServiceList: [],
|
|
||||||
debitNoteQuotationId: '',
|
|
||||||
worker: [],
|
|
||||||
payBillDate: new Date(),
|
|
||||||
paySplitCount: 0,
|
|
||||||
payCondition: PayCondition.Full,
|
|
||||||
dueDate: new Date(Date.now() + 86400000),
|
|
||||||
discount: 0,
|
|
||||||
status: 'CREATED',
|
|
||||||
remark: '#[quotation-labor]<br/><br/>#[quotation-payment]',
|
|
||||||
quotationId: '',
|
|
||||||
agentPrice: false,
|
|
||||||
});
|
|
||||||
const tempPaySplitCount = ref(0);
|
const tempPaySplitCount = ref(0);
|
||||||
const tempPaySplit = ref<
|
const tempPaySplit = ref<
|
||||||
{ no: number; amount: number; name?: string; invoice?: boolean }[]
|
{ no: number; amount: number; name?: string; invoice?: boolean }[]
|
||||||
|
|
@ -258,11 +249,27 @@ const selectedInstallmentNo = ref<number[]>([]);
|
||||||
const installmentAmount = ref<number>(0);
|
const installmentAmount = ref<number>(0);
|
||||||
|
|
||||||
const QUOTATION_STATUS = [
|
const QUOTATION_STATUS = [
|
||||||
|
'Accepted',
|
||||||
'PaymentPending',
|
'PaymentPending',
|
||||||
'PaymentSuccess',
|
'PaymentSuccess',
|
||||||
'ProcessComplete',
|
'ProcessComplete',
|
||||||
];
|
];
|
||||||
|
|
||||||
|
function covertToNode() {
|
||||||
|
productServiceNodes.value = quotationProductTree(
|
||||||
|
productServiceList.value,
|
||||||
|
agentPrice.value,
|
||||||
|
config.value?.vat,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
watch(
|
||||||
|
() => productServiceList.value,
|
||||||
|
() => {
|
||||||
|
covertToNode();
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
function toggleDeleteProduct(index: number) {
|
function toggleDeleteProduct(index: number) {
|
||||||
// product display
|
// product display
|
||||||
productServiceList.value.splice(index, 1);
|
productServiceList.value.splice(index, 1);
|
||||||
|
|
@ -324,6 +331,46 @@ async function assignToProductServiceList() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function undo() {
|
||||||
|
if (!previousValue) return;
|
||||||
|
|
||||||
|
currentFormData.value = structuredClone(previousValue);
|
||||||
|
assignProductServiceList();
|
||||||
|
assignSelectedWorker();
|
||||||
|
pageState.mode = 'info';
|
||||||
|
}
|
||||||
|
|
||||||
|
function assignProductServiceList() {
|
||||||
|
if (!debitNoteData.value) return;
|
||||||
|
productServiceList.value = debitNoteData.value.productServiceList.map(
|
||||||
|
(v, i) => ({
|
||||||
|
id: v.id!,
|
||||||
|
installmentNo: v.installmentNo || 0,
|
||||||
|
workerIndex: v.worker
|
||||||
|
.map((a) =>
|
||||||
|
debitNoteData.value!.worker.findIndex(
|
||||||
|
(b) => b.employeeId === a.employeeId,
|
||||||
|
),
|
||||||
|
)
|
||||||
|
.filter(
|
||||||
|
(index): index is number => index !== -1 && index !== undefined,
|
||||||
|
),
|
||||||
|
vat: v.vat || 0,
|
||||||
|
pricePerUnit: v.pricePerUnit || 0,
|
||||||
|
discount: v.discount || 0,
|
||||||
|
amount: v.amount || 0,
|
||||||
|
product: v.product,
|
||||||
|
work: v.work || null,
|
||||||
|
service: v.service || null,
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
function assignSelectedWorker() {
|
||||||
|
if (debitNoteData.value)
|
||||||
|
selectedWorker.value = debitNoteData.value.worker.map((v) => v.employee);
|
||||||
|
}
|
||||||
|
|
||||||
async function assignFormData(id: string) {
|
async function assignFormData(id: string) {
|
||||||
const data = await debitNote.getDebitNote(id);
|
const data = await debitNote.getDebitNote(id);
|
||||||
if (!data) return;
|
if (!data) return;
|
||||||
|
|
@ -333,7 +380,7 @@ async function assignFormData(id: string) {
|
||||||
selectedProductGroup.value =
|
selectedProductGroup.value =
|
||||||
data.productServiceList[0]?.product.productGroup?.id || '';
|
data.productServiceList[0]?.product.productGroup?.id || '';
|
||||||
|
|
||||||
currentFormData.value = {
|
(previousValue = {
|
||||||
id: data.id || undefined,
|
id: data.id || undefined,
|
||||||
debitNoteQuotationId: data.debitNoteQuotationId || undefined,
|
debitNoteQuotationId: data.debitNoteQuotationId || undefined,
|
||||||
productServiceList: structuredClone(
|
productServiceList: structuredClone(
|
||||||
|
|
@ -358,24 +405,11 @@ async function assignFormData(id: string) {
|
||||||
agentPrice: data.agentPrice,
|
agentPrice: data.agentPrice,
|
||||||
quotationId: data.debitNoteQuotationId,
|
quotationId: data.debitNoteQuotationId,
|
||||||
remark: data.remark || undefined,
|
remark: data.remark || undefined,
|
||||||
};
|
}),
|
||||||
|
(currentFormData.value = structuredClone(previousValue));
|
||||||
|
|
||||||
productServiceList.value = data.productServiceList.map((v, i) => ({
|
assignProductServiceList();
|
||||||
id: v.id!,
|
assignSelectedWorker();
|
||||||
installmentNo: v.installmentNo || 0,
|
|
||||||
workerIndex: v.worker.map((a) =>
|
|
||||||
data.worker.findIndex((b) => b.employeeId === a.employeeId),
|
|
||||||
) || [0],
|
|
||||||
vat: v.vat || 0,
|
|
||||||
pricePerUnit: v.pricePerUnit || 0,
|
|
||||||
discount: v.discount || 0,
|
|
||||||
amount: v.amount || 0,
|
|
||||||
product: v.product,
|
|
||||||
work: v.work || null,
|
|
||||||
service: v.service || null,
|
|
||||||
}));
|
|
||||||
|
|
||||||
selectedWorker.value = data.worker.map((v) => v.employee);
|
|
||||||
await getQuotation(data.debitNoteQuotation?.id);
|
await getQuotation(data.debitNoteQuotation?.id);
|
||||||
await assignToProductServiceList();
|
await assignToProductServiceList();
|
||||||
await fetchRequest();
|
await fetchRequest();
|
||||||
|
|
@ -419,16 +453,29 @@ async function initStatus() {
|
||||||
{
|
{
|
||||||
title: 'title',
|
title: 'title',
|
||||||
status: debitNoteData.value?.id !== undefined ? 'done' : 'doing',
|
status: debitNoteData.value?.id !== undefined ? 'done' : 'doing',
|
||||||
active: () => view.value === null,
|
active: () => view.value === QuotationStatus.Issued,
|
||||||
handler: () => {
|
handler: () => {
|
||||||
view.value = null;
|
pageState.mode = 'info';
|
||||||
|
view.value = QuotationStatus.Issued;
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: 'accepted',
|
||||||
|
status:
|
||||||
|
debitNoteData.value?.id !== undefined
|
||||||
|
? getStatus(debitNoteData.value.quotationStatus, 1, -1)
|
||||||
|
: 'waiting',
|
||||||
|
active: () => view.value === QuotationStatus.Accepted,
|
||||||
|
handler: () => {
|
||||||
|
pageState.mode = 'info';
|
||||||
|
view.value = QuotationStatus.Accepted;
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: 'payment',
|
title: 'payment',
|
||||||
status:
|
status:
|
||||||
debitNoteData.value?.id !== undefined
|
debitNoteData.value?.id !== undefined
|
||||||
? getStatus(debitNoteData.value.quotationStatus, 1, -1)
|
? getStatus(debitNoteData.value.quotationStatus, 2, 1)
|
||||||
: 'waiting',
|
: 'waiting',
|
||||||
active: () => view.value === QuotationStatus.PaymentPending,
|
active: () => view.value === QuotationStatus.PaymentPending,
|
||||||
handler: async () => {
|
handler: async () => {
|
||||||
|
|
@ -438,18 +485,20 @@ async function initStatus() {
|
||||||
|
|
||||||
{
|
{
|
||||||
title: 'receipt',
|
title: 'receipt',
|
||||||
status: getStatus(debitNoteData.value?.quotationStatus, 1, 1),
|
status: getStatus(debitNoteData.value?.quotationStatus, 2, 2),
|
||||||
active: () => view.value === QuotationStatus.PaymentSuccess,
|
active: () => view.value === QuotationStatus.PaymentSuccess,
|
||||||
handler: () => {
|
handler: () => {
|
||||||
|
pageState.mode = 'info';
|
||||||
view.value = QuotationStatus.PaymentSuccess;
|
view.value = QuotationStatus.PaymentSuccess;
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|
||||||
{
|
{
|
||||||
title: 'processComplete',
|
title: 'processComplete',
|
||||||
status: getStatus(debitNoteData.value?.quotationStatus, 2, 1),
|
status: getStatus(debitNoteData.value?.quotationStatus, 3, 2),
|
||||||
active: () => view.value === QuotationStatus.ProcessComplete,
|
active: () => view.value === QuotationStatus.ProcessComplete,
|
||||||
handler: () => {
|
handler: () => {
|
||||||
|
pageState.mode = 'info';
|
||||||
view.value = QuotationStatus.ProcessComplete;
|
view.value = QuotationStatus.ProcessComplete;
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|
@ -778,6 +827,7 @@ async function submit() {
|
||||||
: await debitNote.createDebitNote(payload);
|
: await debitNote.createDebitNote(payload);
|
||||||
|
|
||||||
if (res) {
|
if (res) {
|
||||||
|
newWorkerList.value = [];
|
||||||
await router.push(`/debit-note/${res.id}/?mode=info`);
|
await router.push(`/debit-note/${res.id}/?mode=info`);
|
||||||
|
|
||||||
if (attachmentList.value && pageState.mode === 'create') {
|
if (attachmentList.value && pageState.mode === 'create') {
|
||||||
|
|
@ -785,7 +835,6 @@ async function submit() {
|
||||||
}
|
}
|
||||||
|
|
||||||
pageState.mode = 'info';
|
pageState.mode = 'info';
|
||||||
|
|
||||||
assignFormData(res.id);
|
assignFormData(res.id);
|
||||||
|
|
||||||
initStatus();
|
initStatus();
|
||||||
|
|
@ -849,6 +898,20 @@ function storeDataLocal() {
|
||||||
window.open(url, '_blank');
|
window.open(url, '_blank');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async function closeTab() {
|
||||||
|
dialogWarningClose(t, {
|
||||||
|
message: t('dialog.message.close'),
|
||||||
|
action: () => {
|
||||||
|
window.close();
|
||||||
|
},
|
||||||
|
cancel: () => {},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function closeAble() {
|
||||||
|
return window.opener !== null;
|
||||||
|
}
|
||||||
|
|
||||||
watch(
|
watch(
|
||||||
() => pageState.mode,
|
() => pageState.mode,
|
||||||
() => toggleMode(pageState.mode),
|
() => toggleMode(pageState.mode),
|
||||||
|
|
@ -876,7 +939,8 @@ onMounted(async () => {
|
||||||
}
|
}
|
||||||
|
|
||||||
if (typeof route.query['mode'] === 'string') {
|
if (typeof route.query['mode'] === 'string') {
|
||||||
pageState.mode = route.query['mode'] as 'create' | 'edit' | 'info';
|
pageState.mode =
|
||||||
|
(route.query['mode'] as 'create' | 'edit' | 'info') || 'info';
|
||||||
}
|
}
|
||||||
|
|
||||||
if (typeof route.query['tab'] === 'string') {
|
if (typeof route.query['tab'] === 'string') {
|
||||||
|
|
@ -884,11 +948,40 @@ onMounted(async () => {
|
||||||
{
|
{
|
||||||
payment: QuotationStatus.PaymentPending,
|
payment: QuotationStatus.PaymentPending,
|
||||||
receipt: QuotationStatus.PaymentSuccess,
|
receipt: QuotationStatus.PaymentSuccess,
|
||||||
}[route.query['tab']] || null;
|
}[route.query['tab']] || QuotationStatus.Issued;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (
|
||||||
|
pageState.mode === 'edit' &&
|
||||||
|
debitNoteData.value?.quotationStatus !== QuotationStatus.Issued
|
||||||
|
) {
|
||||||
|
pageState.mode = 'info';
|
||||||
|
}
|
||||||
await useConfigStore().getConfig();
|
await useConfigStore().getConfig();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
async function submitAccepted() {
|
||||||
|
dialog({
|
||||||
|
color: 'info',
|
||||||
|
icon: 'mdi-account-check',
|
||||||
|
title: t('dialog.title.confirmDebitNoteAccept'),
|
||||||
|
actionText: t('general.confirm'),
|
||||||
|
persistent: true,
|
||||||
|
message: t('dialog.message.quotationAccept'),
|
||||||
|
action: async () => {
|
||||||
|
if (!currentFormData.value.id) return;
|
||||||
|
|
||||||
|
const res = await debitNote.action.acceptDebitNote(
|
||||||
|
currentFormData.value.id,
|
||||||
|
);
|
||||||
|
|
||||||
|
if (res && typeof route.params['id'] === 'string') {
|
||||||
|
await assignFormData(route.params['id']);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
cancel: () => {},
|
||||||
|
});
|
||||||
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
|
|
@ -953,7 +1046,10 @@ onMounted(async () => {
|
||||||
<!-- #TODO add goToQuotation as @goto-quotation-->
|
<!-- #TODO add goToQuotation as @goto-quotation-->
|
||||||
|
|
||||||
<DocumentExpansion
|
<DocumentExpansion
|
||||||
v-if="view === null"
|
v-if="
|
||||||
|
view === QuotationStatus.Issued ||
|
||||||
|
view === QuotationStatus.Accepted
|
||||||
|
"
|
||||||
:readonly
|
:readonly
|
||||||
:registered-branch-id="quotationData?.registeredBranchId"
|
:registered-branch-id="quotationData?.registeredBranchId"
|
||||||
:customer-id="quotationData?.customerBranchId"
|
:customer-id="quotationData?.customerBranchId"
|
||||||
|
|
@ -984,7 +1080,10 @@ onMounted(async () => {
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<WorkerItemExpansion
|
<WorkerItemExpansion
|
||||||
v-if="view === null"
|
v-if="
|
||||||
|
view === QuotationStatus.Issued ||
|
||||||
|
view === QuotationStatus.Accepted
|
||||||
|
"
|
||||||
:readonly
|
:readonly
|
||||||
:hide-btn-add-worker="
|
:hide-btn-add-worker="
|
||||||
!readonly &&
|
!readonly &&
|
||||||
|
|
@ -997,14 +1096,22 @@ onMounted(async () => {
|
||||||
|
|
||||||
<!-- #TODO add openProductDialog at @add-product-->
|
<!-- #TODO add openProductDialog at @add-product-->
|
||||||
<ProductExpansion
|
<ProductExpansion
|
||||||
v-if="view === null"
|
v-if="
|
||||||
|
view === QuotationStatus.Issued ||
|
||||||
|
view === QuotationStatus.Accepted
|
||||||
|
"
|
||||||
:readonly
|
:readonly
|
||||||
:installment-input="currentFormData.payCondition === 'SplitCustom'"
|
:installment-input="currentFormData.payCondition === 'SplitCustom'"
|
||||||
:max-installment="currentFormData.paySplitCount"
|
:max-installment="currentFormData.paySplitCount"
|
||||||
:agent-price="agentPrice"
|
:agent-price="agentPrice"
|
||||||
:employee-rows="selectedWorkerItem"
|
:employee-rows="selectedWorkerItem"
|
||||||
:rows="productService"
|
:rows="productService"
|
||||||
@add-product="() => (pageState.productServiceModal = true)"
|
@add-product="
|
||||||
|
() => {
|
||||||
|
pageState.productServiceModal = true;
|
||||||
|
covertToNode();
|
||||||
|
}
|
||||||
|
"
|
||||||
@update-rows="
|
@update-rows="
|
||||||
(v) => {
|
(v) => {
|
||||||
view === null && (productServiceList = v);
|
view === null && (productServiceList = v);
|
||||||
|
|
@ -1015,7 +1122,10 @@ onMounted(async () => {
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<PaymentExpansion
|
<PaymentExpansion
|
||||||
v-if="view === null"
|
v-if="
|
||||||
|
view === QuotationStatus.Issued ||
|
||||||
|
view === QuotationStatus.Accepted
|
||||||
|
"
|
||||||
readonly
|
readonly
|
||||||
:total-price="summaryPrice.finalPrice"
|
:total-price="summaryPrice.finalPrice"
|
||||||
class="q-mb-md"
|
class="q-mb-md"
|
||||||
|
|
@ -1029,7 +1139,11 @@ onMounted(async () => {
|
||||||
|
|
||||||
<!-- TODO: bind additional file -->
|
<!-- TODO: bind additional file -->
|
||||||
<AdditionalFileExpansion
|
<AdditionalFileExpansion
|
||||||
v-if="view === null || view === QuotationStatus.PaymentPending"
|
v-if="
|
||||||
|
view === QuotationStatus.Issued ||
|
||||||
|
view === QuotationStatus.Accepted ||
|
||||||
|
view === QuotationStatus.PaymentPending
|
||||||
|
"
|
||||||
:readonly
|
:readonly
|
||||||
v-model:file-data="attachmentData"
|
v-model:file-data="attachmentData"
|
||||||
:transform-url="
|
:transform-url="
|
||||||
|
|
@ -1074,7 +1188,11 @@ onMounted(async () => {
|
||||||
|
|
||||||
<!-- TODO: bind remark -->
|
<!-- TODO: bind remark -->
|
||||||
<RemarkExpansion
|
<RemarkExpansion
|
||||||
v-if="view === null || view === QuotationStatus.PaymentPending"
|
v-if="
|
||||||
|
view === QuotationStatus.Issued ||
|
||||||
|
view === QuotationStatus.Accepted ||
|
||||||
|
view === QuotationStatus.PaymentPending
|
||||||
|
"
|
||||||
:readonly="readonly"
|
:readonly="readonly"
|
||||||
v-model:remark="currentFormData.remark"
|
v-model:remark="currentFormData.remark"
|
||||||
/>
|
/>
|
||||||
|
|
@ -1149,7 +1267,7 @@ onMounted(async () => {
|
||||||
installmentAmount = v.invoice.amount;
|
installmentAmount = v.invoice.amount;
|
||||||
}
|
}
|
||||||
|
|
||||||
view = null;
|
view = QuotationStatus.Issued;
|
||||||
}
|
}
|
||||||
"
|
"
|
||||||
@example="() => exampleReceipt(v.id)"
|
@example="() => exampleReceipt(v.id)"
|
||||||
|
|
@ -1173,38 +1291,59 @@ onMounted(async () => {
|
||||||
{{ $t('general.view', { msg: $t('general.example') }) }}
|
{{ $t('general.view', { msg: $t('general.example') }) }}
|
||||||
</MainButton>
|
</MainButton>
|
||||||
|
|
||||||
<div class="row q-gutter-x-sm">
|
<CancelButton
|
||||||
|
outlined
|
||||||
|
id="btn-close"
|
||||||
|
@click="closeTab()"
|
||||||
|
:label="$t('dialog.action.close')"
|
||||||
|
v-if="pageState.mode === 'info' && closeAble()"
|
||||||
|
/>
|
||||||
|
|
||||||
|
<div class="row q-gutter-x-sm q-ml-xs">
|
||||||
<UndoButton
|
<UndoButton
|
||||||
outlined
|
outlined
|
||||||
@click="
|
v-if="pageState.mode === 'edit'"
|
||||||
() => {
|
@click="undo()"
|
||||||
pageState.mode = 'info';
|
|
||||||
}
|
|
||||||
"
|
|
||||||
v-if="false"
|
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<SaveButton
|
<SaveButton
|
||||||
v-if="!readonly && pageState.mode === 'create'"
|
v-if="pageState.mode !== 'info'"
|
||||||
:disabled="
|
:disabled="
|
||||||
selectedWorkerItem.length === 0 && productService.length === 0
|
selectedWorkerItem.length === 0 && productService.length === 0
|
||||||
"
|
"
|
||||||
@click="submit"
|
@click="submit"
|
||||||
:label="true ? $t('debitNote.label.submit') : $t('general.save')"
|
|
||||||
:icon="
|
|
||||||
true
|
|
||||||
? 'mdi-account-multiple-check-outline'
|
|
||||||
: 'mdi-content-save-outline'
|
|
||||||
"
|
|
||||||
solid
|
|
||||||
></SaveButton>
|
|
||||||
|
|
||||||
<EditButton
|
|
||||||
v-if="false"
|
|
||||||
class="no-print"
|
|
||||||
@click="pageState.mode = 'edit'"
|
|
||||||
solid
|
solid
|
||||||
/>
|
/>
|
||||||
|
|
||||||
|
<template v-if="debitNoteData">
|
||||||
|
<EditButton
|
||||||
|
v-if="
|
||||||
|
pageState.mode === 'info' &&
|
||||||
|
view === QuotationStatus.Issued &&
|
||||||
|
debitNoteData.quotationStatus === QuotationStatus.Issued
|
||||||
|
"
|
||||||
|
class="no-print"
|
||||||
|
solid
|
||||||
|
@click="pageState.mode = 'edit'"
|
||||||
|
/>
|
||||||
|
|
||||||
|
<MainButton
|
||||||
|
v-if="
|
||||||
|
view === QuotationStatus.Accepted &&
|
||||||
|
debitNoteData.quotationStatus === QuotationStatus.Issued
|
||||||
|
"
|
||||||
|
solid
|
||||||
|
icon="mdi-account-multiple-check-outline"
|
||||||
|
color="207 96% 32%"
|
||||||
|
id="btn-submit-accepted"
|
||||||
|
@click="
|
||||||
|
() => {
|
||||||
|
submitAccepted();
|
||||||
|
}
|
||||||
|
"
|
||||||
|
>
|
||||||
|
{{ $t('quotation.customerAcceptance') }}
|
||||||
|
</MainButton>
|
||||||
|
</template>
|
||||||
</div>
|
</div>
|
||||||
</nav>
|
</nav>
|
||||||
</footer>
|
</footer>
|
||||||
|
|
|
||||||
|
|
@ -3,9 +3,10 @@ import { storeToRefs } from 'pinia';
|
||||||
import { onMounted, nextTick, ref, watch } from 'vue';
|
import { onMounted, nextTick, ref, watch } from 'vue';
|
||||||
import { precisionRound } from 'src/utils/arithmetic';
|
import { precisionRound } from 'src/utils/arithmetic';
|
||||||
import ThaiBahtText from 'thai-baht-text';
|
import ThaiBahtText from 'thai-baht-text';
|
||||||
|
import { useI18n } from 'vue-i18n';
|
||||||
|
|
||||||
// NOTE: Import stores
|
// NOTE: Import stores
|
||||||
import { formatNumberDecimal } from 'stores/utils';
|
import { dialogWarningClose, formatNumberDecimal } from 'stores/utils';
|
||||||
|
|
||||||
import { useConfigStore } from 'stores/config';
|
import { useConfigStore } from 'stores/config';
|
||||||
import useBranchStore from 'stores/branch';
|
import useBranchStore from 'stores/branch';
|
||||||
|
|
@ -29,6 +30,7 @@ import ViewHeader from './ViewHeader.vue';
|
||||||
import ViewFooter from './ViewFooter.vue';
|
import ViewFooter from './ViewFooter.vue';
|
||||||
import BankComponents from './BankComponents.vue';
|
import BankComponents from './BankComponents.vue';
|
||||||
import PrintButton from 'src/components/button/PrintButton.vue';
|
import PrintButton from 'src/components/button/PrintButton.vue';
|
||||||
|
import { CancelButton } from 'components/button';
|
||||||
import { convertTemplate } from 'src/utils/string-template';
|
import { convertTemplate } from 'src/utils/string-template';
|
||||||
|
|
||||||
const configStore = useConfigStore();
|
const configStore = useConfigStore();
|
||||||
|
|
@ -36,6 +38,7 @@ const branchStore = useBranchStore();
|
||||||
const customerStore = useCustomerStore();
|
const customerStore = useCustomerStore();
|
||||||
const debitNoteStore = useDebitNote();
|
const debitNoteStore = useDebitNote();
|
||||||
const { data: config } = storeToRefs(configStore);
|
const { data: config } = storeToRefs(configStore);
|
||||||
|
const { t } = useI18n();
|
||||||
|
|
||||||
type Product = {
|
type Product = {
|
||||||
id: string;
|
id: string;
|
||||||
|
|
@ -272,6 +275,20 @@ onMounted(async () => {
|
||||||
|
|
||||||
watch(elements, () => {});
|
watch(elements, () => {});
|
||||||
|
|
||||||
|
async function closeTab() {
|
||||||
|
dialogWarningClose(t, {
|
||||||
|
message: t('dialog.message.close'),
|
||||||
|
action: () => {
|
||||||
|
window.close();
|
||||||
|
},
|
||||||
|
cancel: () => {},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function closeAble() {
|
||||||
|
return window.opener !== null;
|
||||||
|
}
|
||||||
|
|
||||||
function print() {
|
function print() {
|
||||||
window.print();
|
window.print();
|
||||||
}
|
}
|
||||||
|
|
@ -280,6 +297,13 @@ function print() {
|
||||||
<template>
|
<template>
|
||||||
<div class="toolbar">
|
<div class="toolbar">
|
||||||
<PrintButton solid @click="print" />
|
<PrintButton solid @click="print" />
|
||||||
|
<CancelButton
|
||||||
|
outlined
|
||||||
|
id="btn-close"
|
||||||
|
@click="closeTab()"
|
||||||
|
:label="$t('dialog.action.close')"
|
||||||
|
v-if="closeAble()"
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div class="row justify-between container color-debit-note">
|
<div class="row justify-between container color-debit-note">
|
||||||
<section class="content" v-for="chunk in chunks">
|
<section class="content" v-for="chunk in chunks">
|
||||||
|
|
@ -553,7 +577,7 @@ function print() {
|
||||||
position: sticky;
|
position: sticky;
|
||||||
top: 0;
|
top: 0;
|
||||||
display: flex;
|
display: flex;
|
||||||
justify-content: center;
|
justify-content: space-between;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
margin-bottom: 1rem;
|
margin-bottom: 1rem;
|
||||||
padding: 1rem;
|
padding: 1rem;
|
||||||
|
|
|
||||||
|
|
@ -54,7 +54,7 @@ const toggleWorker = defineModel<boolean>('toggleWorker');
|
||||||
</div>
|
</div>
|
||||||
<nav class="q-ml-auto">
|
<nav class="q-ml-auto">
|
||||||
<AddButton
|
<AddButton
|
||||||
v-if="!hideBtnAddWorker"
|
v-if="!readonly"
|
||||||
icon-only
|
icon-only
|
||||||
@click.stop="$emit('addWorker')"
|
@click.stop="$emit('addWorker')"
|
||||||
/>
|
/>
|
||||||
|
|
|
||||||
|
|
@ -14,10 +14,9 @@ const DEFAULT_DATA: DebitNotePayload = {
|
||||||
debitNoteQuotationId: '',
|
debitNoteQuotationId: '',
|
||||||
worker: [],
|
worker: [],
|
||||||
payBillDate: new Date(),
|
payBillDate: new Date(),
|
||||||
paySplit: [],
|
|
||||||
paySplitCount: 0,
|
paySplitCount: 0,
|
||||||
payCondition: PayCondition.Full,
|
payCondition: PayCondition.Full,
|
||||||
dueDate: new Date(),
|
dueDate: new Date(Date.now() + 86400000),
|
||||||
discount: 0,
|
discount: 0,
|
||||||
status: 'CREATED',
|
status: 'CREATED',
|
||||||
remark: '#[quotation-labor]<br/><br/>#[quotation-payment]',
|
remark: '#[quotation-labor]<br/><br/>#[quotation-payment]',
|
||||||
|
|
@ -58,6 +57,7 @@ export const useDebitNoteForm = defineStore('form-debit-note', () => {
|
||||||
}
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
|
currentFormData,
|
||||||
isFormDataDifferent,
|
isFormDataDifferent,
|
||||||
resetForm,
|
resetForm,
|
||||||
};
|
};
|
||||||
|
|
|
||||||
|
|
@ -71,12 +71,19 @@ export async function updatePaybackStatus(
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export async function acceptCreditNote(id: string) {
|
||||||
|
const res = await api.post<Data>(`/${ENDPOINT}/${id}/accept`);
|
||||||
|
if (res.status < 400) return res.data;
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
export const useCreditNote = defineStore('credit-note-store', () => {
|
export const useCreditNote = defineStore('credit-note-store', () => {
|
||||||
const data = ref<Data[]>([]);
|
const data = ref<Data[]>([]);
|
||||||
const page = ref<number>(1);
|
const page = ref<number>(1);
|
||||||
const pageMax = ref<number>(1);
|
const pageMax = ref<number>(1);
|
||||||
const pageSize = ref<number>(30);
|
const pageSize = ref<number>(30);
|
||||||
const stats = ref<Record<Status, number>>({
|
const stats = ref<Record<Status, number>>({
|
||||||
|
[Status.Waiting]: 0,
|
||||||
[Status.Pending]: 0,
|
[Status.Pending]: 0,
|
||||||
[Status.Success]: 0,
|
[Status.Success]: 0,
|
||||||
});
|
});
|
||||||
|
|
@ -94,6 +101,7 @@ export const useCreditNote = defineStore('credit-note-store', () => {
|
||||||
createCreditNote,
|
createCreditNote,
|
||||||
updateCreditNote,
|
updateCreditNote,
|
||||||
deleteCreditNote,
|
deleteCreditNote,
|
||||||
|
acceptCreditNote,
|
||||||
|
|
||||||
...manageAttachment(api, ENDPOINT),
|
...manageAttachment(api, ENDPOINT),
|
||||||
...manageFile<'slip'>(api, ENDPOINT),
|
...manageFile<'slip'>(api, ENDPOINT),
|
||||||
|
|
|
||||||
|
|
@ -42,6 +42,7 @@ export type CreditNote = {
|
||||||
};
|
};
|
||||||
|
|
||||||
export enum CreditNoteStatus {
|
export enum CreditNoteStatus {
|
||||||
|
Waiting = 'Waiting',
|
||||||
Pending = 'Pending',
|
Pending = 'Pending',
|
||||||
Success = 'Success',
|
Success = 'Success',
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -61,6 +61,12 @@ export async function deleteDebitNote(id: string) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export async function acceptDebitNote(id: string) {
|
||||||
|
const res = await api.post<Data>(`/${ENDPOINT}/${id}/accept`);
|
||||||
|
if (res.status < 400) return res.data;
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
export const useDebitNote = defineStore('debit-note-store', () => {
|
export const useDebitNote = defineStore('debit-note-store', () => {
|
||||||
const data = ref<Data[]>([]);
|
const data = ref<Data[]>([]);
|
||||||
const page = ref<number>(1);
|
const page = ref<number>(1);
|
||||||
|
|
@ -87,6 +93,8 @@ export const useDebitNote = defineStore('debit-note-store', () => {
|
||||||
updateDebitNote,
|
updateDebitNote,
|
||||||
deleteDebitNote,
|
deleteDebitNote,
|
||||||
|
|
||||||
|
action: { acceptDebitNote },
|
||||||
|
|
||||||
...manageAttachment(api, ENDPOINT),
|
...manageAttachment(api, ENDPOINT),
|
||||||
...manageFile<'slip'>(api, ENDPOINT),
|
...manageFile<'slip'>(api, ENDPOINT),
|
||||||
};
|
};
|
||||||
|
|
|
||||||
|
|
@ -72,6 +72,7 @@ export const useQuotationStore = defineStore('quotation-store', () => {
|
||||||
hasCancel?: boolean;
|
hasCancel?: boolean;
|
||||||
includeRegisteredBranch?: boolean;
|
includeRegisteredBranch?: boolean;
|
||||||
forDebitNote?: boolean;
|
forDebitNote?: boolean;
|
||||||
|
cancelIncludeDebitNote?: boolean;
|
||||||
}) {
|
}) {
|
||||||
const res = await api.get<PaginationResult<Quotation>>('/quotation', {
|
const res = await api.get<PaginationResult<Quotation>>('/quotation', {
|
||||||
params,
|
params,
|
||||||
|
|
|
||||||
|
|
@ -343,6 +343,7 @@ export type QuotationFull = {
|
||||||
updatedBy: UpdatedBy;
|
updatedBy: UpdatedBy;
|
||||||
|
|
||||||
agentPrice?: boolean;
|
agentPrice?: boolean;
|
||||||
|
isDebitNote?: boolean;
|
||||||
};
|
};
|
||||||
|
|
||||||
export type QuotationPayload = {
|
export type QuotationPayload = {
|
||||||
|
|
@ -387,7 +388,7 @@ export type EmployeeWorker = {
|
||||||
namePrefix: string;
|
namePrefix: string;
|
||||||
nationality: string;
|
nationality: string;
|
||||||
gender: string;
|
gender: string;
|
||||||
dateOfBirth: Date;
|
dateOfBirth: Date | null;
|
||||||
};
|
};
|
||||||
|
|
||||||
export type ProductGroup = {
|
export type ProductGroup = {
|
||||||
|
|
|
||||||
|
|
@ -9,7 +9,12 @@ export type RequestData = {
|
||||||
createdAt: string;
|
createdAt: string;
|
||||||
updatedAt: string;
|
updatedAt: string;
|
||||||
|
|
||||||
quotation: QuotationFull & { isDebitNote: boolean };
|
quotation: QuotationFull & {
|
||||||
|
debitNoteQuotationId: string;
|
||||||
|
isDebitNote: boolean;
|
||||||
|
debitNoteQuotation: { code: string };
|
||||||
|
};
|
||||||
|
|
||||||
quotationId: string;
|
quotationId: string;
|
||||||
|
|
||||||
flow: Record<string, any>;
|
flow: Record<string, any>;
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue