diff --git a/package.json b/package.json index 29fe5570..bd2467ca 100644 --- a/package.json +++ b/package.json @@ -40,6 +40,7 @@ "vue-router": "^4.4.3" }, "devDependencies": { + "@faker-js/faker": "^9.3.0", "@iconify/vue": "^4.1.2", "@intlify/unplugin-vue-i18n": "^4.0.0", "@playwright/test": "^1.46.1", @@ -50,6 +51,7 @@ "@typescript-eslint/eslint-plugin": "^7.18.0", "@typescript-eslint/parser": "^7.18.0", "autoprefixer": "^10.4.20", + "dotenv": "^16.4.7", "eslint": "^8.57.0", "eslint-config-prettier": "^9.1.0", "eslint-plugin-vue": "^9.27.0", diff --git a/playwright.config.ts b/playwright.config.ts index 10745502..86edcc9b 100644 --- a/playwright.config.ts +++ b/playwright.config.ts @@ -1,3 +1,4 @@ +import 'dotenv/config'; import { defineConfig, devices } from '@playwright/test'; /** @@ -24,7 +25,7 @@ export default defineConfig({ /* Shared settings for all the projects below. See https://playwright.dev/docs/api/class-testoptions. */ use: { /* Base URL to use in actions like `await page.goto('/')`. */ - // baseURL: 'http://127.0.0.1:3000', + baseURL: 'http://192.168.1.62:20101', /* Collect trace when retrying the failed test. See https://playwright.dev/docs/trace-viewer */ trace: 'on-first-retry', diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 20a9899a..4cba44ed 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -78,6 +78,9 @@ importers: specifier: ^4.4.3 version: 4.4.3(vue@3.4.38(typescript@5.5.4)) devDependencies: + '@faker-js/faker': + specifier: ^9.3.0 + version: 9.3.0 '@iconify/vue': specifier: ^4.1.2 version: 4.1.2(vue@3.4.38(typescript@5.5.4)) @@ -108,6 +111,9 @@ importers: autoprefixer: specifier: ^10.4.20 version: 10.4.20(postcss@8.4.41) + dotenv: + specifier: ^16.4.7 + version: 16.4.7 eslint: specifier: ^8.57.0 version: 8.57.0 @@ -455,6 +461,10 @@ packages: resolution: {integrity: sha512-Ys+3g2TaW7gADOJzPt83SJtCDhMjndcDMFVQ/Tj9iA1BfJzFKD9mAUXT3OenpuPHbI6P/myECxRJrofUsDx/5g==} engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} + '@faker-js/faker@9.3.0': + resolution: {integrity: sha512-r0tJ3ZOkMd9xsu3VRfqlFR6cz0V/jFYRswAIpC+m/DIfAUXq7g8N7wTAlhSANySXYGKzGryfDXwtwsY8TxEIDw==} + engines: {node: '>=18.0.0', npm: '>=9.0.0'} + '@humanwhocodes/config-array@0.11.14': resolution: {integrity: sha512-3T8LkOmg45BV5FICb15QQMsyUSWrQ8AygVfC7ZG32zOalnqrilm018ZVCw0eapXux8FtA33q8PSRSstjee3jSg==} engines: {node: '>=10.10.0'} @@ -1587,8 +1597,8 @@ packages: resolution: {integrity: sha512-8NHi73otpWsZGBSZwwknTXS5pqMOrk9+Ssrna8xCaxkzEpU9OTf9R5ArQGVw03//Zmk9MOwLPng9WwndvpAJ5g==} engines: {node: '>=12'} - dotenv@16.4.5: - resolution: {integrity: sha512-ZmdL2rui+eB2YwhsWzjInR8LldtZHGDoQ1ugH85ppHKwpUHL7j7rN0Ti9NCnGiQbhaZ11FpR+7ao1dNsmduNUg==} + dotenv@16.4.7: + resolution: {integrity: sha512-47qPchRCykZC03FhkYAhrvwU4xDBFIj1QPqaarj6mdM/hgUzfPHcpkHJOn3mJAufFeeAxAzeGsr5X0M4k6fLZQ==} engines: {node: '>=12'} duplexify@3.7.1: @@ -3902,6 +3912,8 @@ snapshots: '@eslint/js@8.57.0': {} + '@faker-js/faker@9.3.0': {} + '@humanwhocodes/config-array@0.11.14': dependencies: '@humanwhocodes/object-schema': 2.0.3 @@ -4058,7 +4070,7 @@ snapshots: compression: 1.7.4 cross-spawn: 7.0.3 dot-prop: 9.0.0 - dotenv: 16.4.5 + dotenv: 16.4.7 dotenv-expand: 11.0.6 elementtree: 0.1.7 esbuild: 0.23.1 @@ -5270,9 +5282,9 @@ snapshots: dotenv-expand@11.0.6: dependencies: - dotenv: 16.4.5 + dotenv: 16.4.7 - dotenv@16.4.5: {} + dotenv@16.4.7: {} duplexify@3.7.1: dependencies: diff --git a/src/components/05_quotation/ProductItem.vue b/src/components/05_quotation/ProductItem.vue index 4c685312..a705631d 100644 --- a/src/components/05_quotation/ProductItem.vue +++ b/src/components/05_quotation/ProductItem.vue @@ -57,17 +57,13 @@ const currentBtnOpen = ref<{ title: string; opened: boolean[] }[]>([ ]); function calcPrice(c: (typeof rows.value)[number]) { - return precisionRound( - c.pricePerUnit * c.amount - - c.discount + - precisionRound( - c.product.calcVat - ? (c.pricePerUnit * (c.discount ? c.amount : 1) - c.discount) * - (config.value?.vat || 0.07) - : 0, - ) * - (!c.discount ? c.amount : 1), - ); + const price = c.pricePerUnit * c.amount; + const vat = c.product.calcVat + ? (c.pricePerUnit * (c.discount ? c.amount : 1) - c.discount) * + (config.value?.vat || 0.07) * + (!c.discount ? c.amount : 1) + : 0; + return precisionRound(price + vat); } const discount4Show = ref([]); diff --git a/src/components/05_quotation/QuotationCard.vue b/src/components/05_quotation/QuotationCard.vue index 1de73166..1cce142d 100644 --- a/src/components/05_quotation/QuotationCard.vue +++ b/src/components/05_quotation/QuotationCard.vue @@ -22,7 +22,9 @@ defineProps<{ badgeColor?: string; hideKebabView?: boolean; hideKebabEdit?: boolean; + hideKebabDelete?: boolean; hideAction?: boolean; + useCancel?: boolean; customData?: { label: string; @@ -39,6 +41,7 @@ defineEmits<{ (e: 'delete'): void; (e: 'example'): void; (e: 'preview'): void; + (e: 'cancel'): void; }>(); const rand = Math.random(); @@ -93,7 +96,8 @@ const rand = Math.random(); :idName="code" status="ACTIVE" hide-toggle - hide-delete + :use-cancel + :hide-delete="hideKebabDelete" :hide-view="hideKebabView" :hide-edit="hideKebabEdit" @view="$emit('view')" @@ -101,6 +105,7 @@ const rand = Math.random(); @link="$emit('link')" @upload="$emit('upload')" @delete="$emit('delete')" + @cancel="$emit('cancel')" /> diff --git a/src/components/11_credit-note/FormCredit.vue b/src/components/11_credit-note/FormCredit.vue new file mode 100644 index 00000000..68bb7ab2 --- /dev/null +++ b/src/components/11_credit-note/FormCredit.vue @@ -0,0 +1,39 @@ + + + diff --git a/src/components/dialog/DialogViewFile.vue b/src/components/dialog/DialogViewFile.vue index dd13f4d1..db574376 100644 --- a/src/components/dialog/DialogViewFile.vue +++ b/src/components/dialog/DialogViewFile.vue @@ -6,29 +6,57 @@ import DialogHeader from './DialogHeader.vue'; import MainButton from '../button/MainButton.vue'; import NoData from '../NoData.vue'; -defineProps<{ - title: string; +const props = defineProps<{ + title?: string; url?: string; + hideTab?: boolean; + download?: boolean; + transformUrl?: (url: string) => string | Promise; }>(); const open = defineModel({ default: false }); const state = reactive({ imageZoom: 100, + transformedUrl: props.url, }); -function openDialog() { +async function openDialog() { state.imageZoom = 100; + if (props.url && props.transformUrl) { + state.transformedUrl = await props.transformUrl(props.url); + } else { + state.transformedUrl = props.url; + } +} + +async function downloadImage(url: string | null) { + if (!url) return; + const res = await fetch(url); + const blob = await res.blob(); + + let extension = ''; + + if (blob.type === 'image/jpeg') extension = '.jpg'; + else if (blob.type === 'image/png') extension = '.png'; + else return; + + let a = document.createElement('a'); + a.download = `download${extension}`; + a.href = window.URL.createObjectURL(blob); + a.click(); + a.remove(); }