Merge branch 'dev' into 8409-VnSelectSupplier
gitea/salix-front/pipeline/pr-dev This commit looks good
Details
gitea/salix-front/pipeline/pr-dev This commit looks good
Details
This commit is contained in:
commit
788c6e8b61
|
@ -31,3 +31,7 @@ yarn-error.log*
|
||||||
# Cypress directories and files
|
# Cypress directories and files
|
||||||
/test/cypress/videos
|
/test/cypress/videos
|
||||||
/test/cypress/screenshots
|
/test/cypress/screenshots
|
||||||
|
|
||||||
|
# VitePress directories and files
|
||||||
|
/docs/.vitepress/cache
|
||||||
|
/docs/.vuepress
|
||||||
|
|
|
@ -0,0 +1,38 @@
|
||||||
|
import { defineConfig } from 'vitepress';
|
||||||
|
|
||||||
|
// https://vitepress.dev/reference/site-config
|
||||||
|
export default defineConfig({
|
||||||
|
title: 'Lilium',
|
||||||
|
description: 'Lilium docs',
|
||||||
|
themeConfig: {
|
||||||
|
// https://vitepress.dev/reference/default-theme-config
|
||||||
|
nav: [
|
||||||
|
{ text: 'Home', link: '/' },
|
||||||
|
{ text: 'Components', link: '/components/vnInput' },
|
||||||
|
{ text: 'Composables', link: '/composables/useArrayData' },
|
||||||
|
],
|
||||||
|
|
||||||
|
sidebar: [
|
||||||
|
{
|
||||||
|
items: [
|
||||||
|
{
|
||||||
|
text: 'Components',
|
||||||
|
collapsible: true,
|
||||||
|
collapsed: true,
|
||||||
|
items: [{ text: 'VnInput', link: '/components/vnInput' }],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
text: 'Composables',
|
||||||
|
collapsible: true,
|
||||||
|
collapsed: true,
|
||||||
|
items: [
|
||||||
|
{ text: 'useArrayData', link: '/composables/useArrayData' },
|
||||||
|
],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
|
||||||
|
socialLinks: [{ icon: 'github', link: 'https://github.com/vuejs/vitepress' }],
|
||||||
|
},
|
||||||
|
});
|
|
@ -0,0 +1,136 @@
|
||||||
|
# VnInput
|
||||||
|
|
||||||
|
`VnInput` is a custom input component that provides various useful features such as validation, input clearing, and more.
|
||||||
|
|
||||||
|
## Props
|
||||||
|
|
||||||
|
### `modelValue`
|
||||||
|
|
||||||
|
- **Type:** `String | Number`
|
||||||
|
- **Default:** `null`
|
||||||
|
- **Description:** The value of the model bound to the component.
|
||||||
|
|
||||||
|
### `isOutlined`
|
||||||
|
|
||||||
|
- **Type:** `Boolean`
|
||||||
|
- **Default:** `false`
|
||||||
|
- **Description:** If `true`, the component is rendered with an outlined style.
|
||||||
|
|
||||||
|
### `info`
|
||||||
|
|
||||||
|
- **Type:** `String`
|
||||||
|
- **Default:** `''`
|
||||||
|
- **Description:** Additional information displayed alongside the component.
|
||||||
|
|
||||||
|
### `clearable`
|
||||||
|
|
||||||
|
- **Type:** `Boolean`
|
||||||
|
- **Default:** `true`
|
||||||
|
- **Description:** If `true`, the component shows a button to clear the input.
|
||||||
|
|
||||||
|
### `emptyToNull`
|
||||||
|
|
||||||
|
- **Type:** `Boolean`
|
||||||
|
- **Default:** `true`
|
||||||
|
- **Description:** If `true`, converts empty inputs to `null`.
|
||||||
|
|
||||||
|
### `insertable`
|
||||||
|
|
||||||
|
- **Type:** `Boolean`
|
||||||
|
- **Default:** `false`
|
||||||
|
- **Description:** If `true`, allows the insertion of new values.
|
||||||
|
|
||||||
|
### `maxlength`
|
||||||
|
|
||||||
|
- **Type:** `Number`
|
||||||
|
- **Default:** `null`
|
||||||
|
- **Description:** The maximum number of characters allowed in the input.
|
||||||
|
|
||||||
|
### `uppercase`
|
||||||
|
|
||||||
|
- **Type:** `Boolean`
|
||||||
|
- **Default:** `false`
|
||||||
|
- **Description:** If `true`, converts the input text to uppercase.
|
||||||
|
|
||||||
|
## Emits
|
||||||
|
|
||||||
|
### `update:modelValue`
|
||||||
|
|
||||||
|
- **Description:** Emits the updated model value.
|
||||||
|
- **Behavior:** This event is emitted whenever the input value changes. It is used to update the model value bound to the component.
|
||||||
|
|
||||||
|
### `update:options`
|
||||||
|
|
||||||
|
- **Description:** Emits the updated options.
|
||||||
|
- **Behavior:** This event is emitted when the component's options change. It is useful for components with dynamic options.
|
||||||
|
|
||||||
|
### `keyup.enter`
|
||||||
|
|
||||||
|
- **Description:** Emits an event when the Enter key is pressed.
|
||||||
|
- **Behavior:** This event is emitted whenever the Enter key is pressed while the input is focused. It can be used to handle specific actions when the input is confirmed.
|
||||||
|
|
||||||
|
### `remove`
|
||||||
|
|
||||||
|
- **Description:** Emits an event to remove the current value.
|
||||||
|
- **Behavior:** This event is emitted when the clear button (close icon) is clicked. It is used to handle the removal of the current input value.
|
||||||
|
|
||||||
|
## Functions
|
||||||
|
|
||||||
|
### `focus`
|
||||||
|
|
||||||
|
- **Description:** Focuses the input.
|
||||||
|
- **Behavior:** This function is exposed so it can be called from outside the component. It uses `vnInputRef.value.focus()` to focus the input.
|
||||||
|
|
||||||
|
### `handleKeydown`
|
||||||
|
|
||||||
|
- **Description:** Handles the `keydown` event of the input.
|
||||||
|
- **Behavior:** This function is called whenever a key is pressed while the input is focused. If the pressed key is `Backspace`, it does nothing. If `insertable` is `true` and the pressed key is a number, it calls `handleInsertMode`.
|
||||||
|
|
||||||
|
### `handleInsertMode`
|
||||||
|
|
||||||
|
- **Description:** Handles the insertion mode of values.
|
||||||
|
- **Behavior:** This function is called when `insertable` is `true` and a numeric key is pressed. It inserts the value at the cursor position and updates the input value. Then, it moves the cursor to the correct position.
|
||||||
|
|
||||||
|
### `handleUppercase`
|
||||||
|
|
||||||
|
- **Description:** Converts the input value to uppercase.
|
||||||
|
- **Behavior:** This function is called when the uppercase icon is clicked. It converts the current input value to uppercase.
|
||||||
|
|
||||||
|
## Usage
|
||||||
|
|
||||||
|
```vue
|
||||||
|
<template>
|
||||||
|
<VnInput
|
||||||
|
v-model="inputValue"
|
||||||
|
:isOutlined="true"
|
||||||
|
info="Additional information"
|
||||||
|
:clearable="true"
|
||||||
|
:emptyToNull="true"
|
||||||
|
:insertable="false"
|
||||||
|
:maxlength="50"
|
||||||
|
:uppercase="true"
|
||||||
|
@update:modelValue="handleUpdate"
|
||||||
|
@keyup.enter="handleEnter"
|
||||||
|
@remove="handleRemove"
|
||||||
|
/>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup>
|
||||||
|
import { ref } from 'vue';
|
||||||
|
import VnInput from 'src/components/common/VnInput.vue';
|
||||||
|
|
||||||
|
const inputValue = ref('');
|
||||||
|
|
||||||
|
const handleUpdate = (value) => {
|
||||||
|
console.log('Updated value:', value);
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleEnter = () => {
|
||||||
|
console.log('Enter pressed');
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleRemove = () => {
|
||||||
|
console.log('Value removed');
|
||||||
|
};
|
||||||
|
</script>
|
||||||
|
```
|
|
@ -0,0 +1,215 @@
|
||||||
|
# useArrayData
|
||||||
|
|
||||||
|
`useArrayData` is a composable function that provides a set of utilities for managing array data in a Vue component. It leverages Pinia for state management and provides various methods for fetching, filtering, and manipulating data.
|
||||||
|
|
||||||
|
## Usage
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
import { useArrayData } from 'src/composables/useArrayData';
|
||||||
|
|
||||||
|
const {
|
||||||
|
fetch,
|
||||||
|
applyFilter,
|
||||||
|
addFilter,
|
||||||
|
getCurrentFilter,
|
||||||
|
setCurrentFilter,
|
||||||
|
addFilterWhere,
|
||||||
|
addOrder,
|
||||||
|
deleteOrder,
|
||||||
|
refresh,
|
||||||
|
destroy,
|
||||||
|
loadMore,
|
||||||
|
store,
|
||||||
|
totalRows,
|
||||||
|
updateStateParams,
|
||||||
|
isLoading,
|
||||||
|
deleteOption,
|
||||||
|
reset,
|
||||||
|
resetPagination,
|
||||||
|
} = useArrayData('myKey', userOptions);
|
||||||
|
```
|
||||||
|
|
||||||
|
## Parameters
|
||||||
|
|
||||||
|
### `key`
|
||||||
|
|
||||||
|
- **Type:** `String`
|
||||||
|
- **Description:** A unique key to identify the data store.
|
||||||
|
|
||||||
|
### `userOptions`
|
||||||
|
|
||||||
|
- **Type:** `Object`
|
||||||
|
- **Description:** An object containing user-defined options for configuring the data store.
|
||||||
|
|
||||||
|
## Methods
|
||||||
|
|
||||||
|
### `fetch`
|
||||||
|
|
||||||
|
Fetches data from the server.
|
||||||
|
|
||||||
|
#### Parameters
|
||||||
|
|
||||||
|
- **`options`** : An object with the following properties:
|
||||||
|
- `append` (Boolean): Whether to append the fetched data to the existing data.
|
||||||
|
- `updateRouter` (Boolean): Whether to update the router with the current filter.
|
||||||
|
|
||||||
|
#### Returns
|
||||||
|
|
||||||
|
- **`Promise`** : A promise that resolves with the fetched data.
|
||||||
|
|
||||||
|
### `applyFilter`
|
||||||
|
|
||||||
|
Applies a filter to the data.
|
||||||
|
|
||||||
|
#### Parameters
|
||||||
|
|
||||||
|
- **`filter`** : An object containing the filter criteria.
|
||||||
|
- **`params`** : Additional parameters for the filter.
|
||||||
|
- **`fetchOptions`** : Options for the fetch method.
|
||||||
|
|
||||||
|
#### Returns
|
||||||
|
|
||||||
|
- **`Promise`** : A promise that resolves with the filtered data.
|
||||||
|
|
||||||
|
### `addFilter`
|
||||||
|
|
||||||
|
Adds a filter to the existing filters.
|
||||||
|
|
||||||
|
#### Parameters
|
||||||
|
|
||||||
|
- **`filter`** : An object containing the filter criteria.
|
||||||
|
- **`params`** : Additional parameters for the filter.
|
||||||
|
|
||||||
|
#### Returns
|
||||||
|
|
||||||
|
- **`Promise`** : A promise that resolves with the updated filter and parameters.
|
||||||
|
|
||||||
|
### `getCurrentFilter`
|
||||||
|
|
||||||
|
Gets the current filter applied to the data.
|
||||||
|
|
||||||
|
#### Returns
|
||||||
|
|
||||||
|
- **`Object`** : The current filter and parameters.
|
||||||
|
|
||||||
|
### `setCurrentFilter`
|
||||||
|
|
||||||
|
Sets the current filter for the data.
|
||||||
|
|
||||||
|
#### Returns
|
||||||
|
|
||||||
|
- **`Object`** : The current filter and parameters.
|
||||||
|
|
||||||
|
### `addFilterWhere`
|
||||||
|
|
||||||
|
Adds a `where` clause to the existing filters.
|
||||||
|
|
||||||
|
#### Parameters
|
||||||
|
|
||||||
|
- **`where`** : An object containing the `where` clause.
|
||||||
|
|
||||||
|
#### Returns
|
||||||
|
|
||||||
|
- **`Promise`** : A promise that resolves when the filter is applied.
|
||||||
|
|
||||||
|
### `addOrder`
|
||||||
|
|
||||||
|
Adds an order to the existing orders.
|
||||||
|
|
||||||
|
#### Parameters
|
||||||
|
|
||||||
|
- **`field`** : The field to order by.
|
||||||
|
- **`direction`** : The direction of the order (`ASC` or `DESC`).
|
||||||
|
|
||||||
|
#### Returns
|
||||||
|
|
||||||
|
- **`Promise`** : A promise that resolves with the updated order.
|
||||||
|
|
||||||
|
### `deleteOrder`
|
||||||
|
|
||||||
|
Deletes an order from the existing orders.
|
||||||
|
|
||||||
|
#### Parameters
|
||||||
|
|
||||||
|
- **`field`** : The field to delete the order for.
|
||||||
|
|
||||||
|
#### Returns
|
||||||
|
|
||||||
|
- **`Promise`** : A promise that resolves when the order is deleted.
|
||||||
|
|
||||||
|
### `refresh`
|
||||||
|
|
||||||
|
Refreshes the data by re-fetching it from the server.
|
||||||
|
|
||||||
|
#### Returns
|
||||||
|
|
||||||
|
- **`Promise`** : A promise that resolves with the refreshed data.
|
||||||
|
|
||||||
|
### `destroy`
|
||||||
|
|
||||||
|
Destroys the data store for the given key.
|
||||||
|
|
||||||
|
### `loadMore`
|
||||||
|
|
||||||
|
Loads more data by incrementing the pagination.
|
||||||
|
|
||||||
|
#### Returns
|
||||||
|
|
||||||
|
- **`Promise`** : A promise that resolves with the additional data.
|
||||||
|
|
||||||
|
### `updateStateParams`
|
||||||
|
|
||||||
|
Updates the state parameters with the given data.
|
||||||
|
|
||||||
|
#### Parameters
|
||||||
|
|
||||||
|
- **`data`** : The data to update the state parameters with.
|
||||||
|
|
||||||
|
### `deleteOption`
|
||||||
|
|
||||||
|
Deletes an option from the store.
|
||||||
|
|
||||||
|
#### Parameters
|
||||||
|
|
||||||
|
- **`option`** : The option to delete.
|
||||||
|
|
||||||
|
### `reset`
|
||||||
|
|
||||||
|
Resets the store to its default state.
|
||||||
|
|
||||||
|
#### Parameters
|
||||||
|
|
||||||
|
- **`opts`** : An array of options to reset.
|
||||||
|
|
||||||
|
### `resetPagination`
|
||||||
|
|
||||||
|
Resets the pagination for the store.
|
||||||
|
|
||||||
|
## Computed Properties
|
||||||
|
|
||||||
|
### `totalRows`
|
||||||
|
|
||||||
|
- **Description:** The total number of rows in the data.
|
||||||
|
- **Type:** `Number`
|
||||||
|
|
||||||
|
### `isLoading`
|
||||||
|
|
||||||
|
- **Description:** Whether the data is currently being loaded.
|
||||||
|
- **Type:** `Boolean`
|
||||||
|
|
||||||
|
```vue
|
||||||
|
<script setup>
|
||||||
|
import { useArrayData } from 'src/composables/useArrayData';
|
||||||
|
|
||||||
|
const userOptions = {
|
||||||
|
url: '/api/data',
|
||||||
|
limit: 10,
|
||||||
|
};
|
||||||
|
|
||||||
|
const arrayData = useArrayData('myKey', userOptions);
|
||||||
|
</script>
|
||||||
|
```
|
||||||
|
|
||||||
|
```
|
||||||
|
|
||||||
|
```
|
|
@ -0,0 +1,13 @@
|
||||||
|
---
|
||||||
|
# https://vitepress.dev/reference/default-theme-home-page
|
||||||
|
layout: home
|
||||||
|
|
||||||
|
hero:
|
||||||
|
name: 'Lilium'
|
||||||
|
text: 'Lilium docs'
|
||||||
|
tagline: Powered by Verdnatura
|
||||||
|
actions:
|
||||||
|
- theme: brand
|
||||||
|
text: Docs
|
||||||
|
link: /components/vnInput
|
||||||
|
---
|
140
package.json
140
package.json
|
@ -1,70 +1,74 @@
|
||||||
{
|
{
|
||||||
"name": "salix-front",
|
"name": "salix-front",
|
||||||
"version": "25.06.0",
|
"version": "25.06.0",
|
||||||
"description": "Salix frontend",
|
"description": "Salix frontend",
|
||||||
"productName": "Salix",
|
"productName": "Salix",
|
||||||
"author": "Verdnatura",
|
"author": "Verdnatura",
|
||||||
"private": true,
|
"private": true,
|
||||||
"packageManager": "pnpm@8.15.1",
|
"packageManager": "pnpm@8.15.1",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"resetDatabase": "cd ../salix && gulp docker",
|
"resetDatabase": "cd ../salix && gulp docker",
|
||||||
"lint": "eslint --ext .js,.vue ./",
|
"lint": "eslint --ext .js,.vue ./",
|
||||||
"format": "prettier --write \"**/*.{js,vue,scss,html,md,json}\" --ignore-path .gitignore",
|
"format": "prettier --write \"**/*.{js,vue,scss,html,md,json}\" --ignore-path .gitignore",
|
||||||
"test:e2e": "cypress open",
|
"test:e2e": "cypress open",
|
||||||
"test:e2e:ci": "npm run resetDatabase && cd ../salix-front && cypress run",
|
"test:e2e:ci": "npm run resetDatabase && cd ../salix-front && cypress run",
|
||||||
"test": "echo \"See package.json => scripts for available tests.\" && exit 0",
|
"test": "echo \"See package.json => scripts for available tests.\" && exit 0",
|
||||||
"test:unit": "vitest",
|
"test:unit": "vitest",
|
||||||
"test:unit:ci": "vitest run",
|
"test:unit:ci": "vitest run",
|
||||||
"commitlint": "commitlint --edit",
|
"commitlint": "commitlint --edit",
|
||||||
"prepare": "npx husky install",
|
"prepare": "npx husky install",
|
||||||
"addReferenceTag": "node .husky/addReferenceTag.js"
|
"addReferenceTag": "node .husky/addReferenceTag.js",
|
||||||
},
|
"docs:dev": "vitepress dev docs",
|
||||||
"dependencies": {
|
"docs:build": "vitepress build docs",
|
||||||
"@quasar/cli": "^2.4.1",
|
"docs:preview": "vitepress preview docs"
|
||||||
"@quasar/extras": "^1.16.16",
|
},
|
||||||
"axios": "^1.4.0",
|
"dependencies": {
|
||||||
"chromium": "^3.0.3",
|
"@quasar/cli": "^2.4.1",
|
||||||
"croppie": "^2.6.5",
|
"@quasar/extras": "^1.16.16",
|
||||||
"moment": "^2.30.1",
|
"axios": "^1.4.0",
|
||||||
"pinia": "^2.1.3",
|
"chromium": "^3.0.3",
|
||||||
"quasar": "^2.17.7",
|
"croppie": "^2.6.5",
|
||||||
"validator": "^13.9.0",
|
"moment": "^2.30.1",
|
||||||
"vue": "^3.5.13",
|
"pinia": "^2.1.3",
|
||||||
"vue-i18n": "^9.3.0",
|
"quasar": "^2.17.7",
|
||||||
"vue-router": "^4.2.5"
|
"validator": "^13.9.0",
|
||||||
},
|
"vue": "^3.5.13",
|
||||||
"devDependencies": {
|
"vue-i18n": "^9.3.0",
|
||||||
"@commitlint/cli": "^19.2.1",
|
"vue-router": "^4.2.5"
|
||||||
"@commitlint/config-conventional": "^19.1.0",
|
},
|
||||||
"@intlify/unplugin-vue-i18n": "^0.8.2",
|
"devDependencies": {
|
||||||
"@pinia/testing": "^0.1.2",
|
"@commitlint/cli": "^19.2.1",
|
||||||
"@quasar/app-vite": "^2.0.8",
|
"@commitlint/config-conventional": "^19.1.0",
|
||||||
"@quasar/quasar-app-extension-qcalendar": "^4.0.2",
|
"@intlify/unplugin-vue-i18n": "^0.8.2",
|
||||||
"@quasar/quasar-app-extension-testing-unit-vitest": "^0.4.0",
|
"@pinia/testing": "^0.1.2",
|
||||||
"@vue/test-utils": "^2.4.4",
|
"@quasar/app-vite": "^2.0.8",
|
||||||
"autoprefixer": "^10.4.14",
|
"@quasar/quasar-app-extension-qcalendar": "^4.0.2",
|
||||||
"cypress": "^13.6.6",
|
"@quasar/quasar-app-extension-testing-unit-vitest": "^0.4.0",
|
||||||
"cypress-mochawesome-reporter": "^3.8.2",
|
"@vue/test-utils": "^2.4.4",
|
||||||
"eslint": "^9.18.0",
|
"autoprefixer": "^10.4.14",
|
||||||
"eslint-config-prettier": "^10.0.1",
|
"cypress": "^13.6.6",
|
||||||
"eslint-plugin-cypress": "^4.1.0",
|
"cypress-mochawesome-reporter": "^3.8.2",
|
||||||
"eslint-plugin-vue": "^9.32.0",
|
"eslint": "^9.18.0",
|
||||||
"husky": "^8.0.0",
|
"eslint-config-prettier": "^10.0.1",
|
||||||
"postcss": "^8.4.23",
|
"eslint-plugin-cypress": "^4.1.0",
|
||||||
"prettier": "^3.4.2",
|
"eslint-plugin-vue": "^9.32.0",
|
||||||
"sass": "^1.83.4",
|
"husky": "^8.0.0",
|
||||||
"vitest": "^0.34.0"
|
"postcss": "^8.4.23",
|
||||||
},
|
"prettier": "^3.4.2",
|
||||||
"engines": {
|
"sass": "^1.83.4",
|
||||||
"node": "^20 || ^18 || ^16",
|
"vitepress": "^1.6.3",
|
||||||
"npm": ">= 8.1.2",
|
"vitest": "^0.34.0"
|
||||||
"yarn": ">= 1.21.1",
|
},
|
||||||
"bun": ">= 1.0.25"
|
"engines": {
|
||||||
},
|
"node": "^20 || ^18 || ^16",
|
||||||
"overrides": {
|
"npm": ">= 8.1.2",
|
||||||
"@vitejs/plugin-vue": "^5.2.1",
|
"yarn": ">= 1.21.1",
|
||||||
"vite": "^6.0.11",
|
"bun": ">= 1.0.25"
|
||||||
"vitest": "^0.31.1"
|
},
|
||||||
}
|
"overrides": {
|
||||||
|
"@vitejs/plugin-vue": "^5.2.1",
|
||||||
|
"vite": "^6.0.11",
|
||||||
|
"vitest": "^0.31.1"
|
||||||
|
}
|
||||||
}
|
}
|
772
pnpm-lock.yaml
772
pnpm-lock.yaml
File diff suppressed because it is too large
Load Diff
|
@ -0,0 +1,6 @@
|
||||||
|
export default [
|
||||||
|
{
|
||||||
|
path: '/api',
|
||||||
|
rule: { target: 'http://0.0.0.0:3000' },
|
||||||
|
},
|
||||||
|
];
|
|
@ -179,7 +179,6 @@ export default configure(function (/* ctx */) {
|
||||||
'render', // keep this as last one
|
'render', // keep this as last one
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
|
|
||||||
// https://v2.quasar.dev/quasar-cli/developing-pwa/configuring-pwa
|
// https://v2.quasar.dev/quasar-cli/developing-pwa/configuring-pwa
|
||||||
pwa: {
|
pwa: {
|
||||||
workboxMode: 'generateSW', // or 'injectManifest'
|
workboxMode: 'generateSW', // or 'injectManifest'
|
||||||
|
|
|
@ -20,6 +20,7 @@ const appName = 'Lilium';
|
||||||
const pinnedModulesRef = ref();
|
const pinnedModulesRef = ref();
|
||||||
|
|
||||||
onMounted(() => stateStore.setMounted());
|
onMounted(() => stateStore.setMounted());
|
||||||
|
const refresh = () => window.location.reload();
|
||||||
</script>
|
</script>
|
||||||
<template>
|
<template>
|
||||||
<QHeader color="white" elevated>
|
<QHeader color="white" elevated>
|
||||||
|
@ -64,6 +65,13 @@ onMounted(() => stateStore.setMounted());
|
||||||
<QSpace />
|
<QSpace />
|
||||||
<div class="q-pl-sm q-gutter-sm row items-center no-wrap">
|
<div class="q-pl-sm q-gutter-sm row items-center no-wrap">
|
||||||
<div id="actions-prepend"></div>
|
<div id="actions-prepend"></div>
|
||||||
|
<QIcon
|
||||||
|
name="refresh"
|
||||||
|
size="md"
|
||||||
|
color="red"
|
||||||
|
v-if="state.get('error')"
|
||||||
|
@click="refresh"
|
||||||
|
/>
|
||||||
<QBtn
|
<QBtn
|
||||||
:class="{ 'q-pa-none': quasar.platform.is.mobile }"
|
:class="{ 'q-pa-none': quasar.platform.is.mobile }"
|
||||||
id="pinnedModules"
|
id="pinnedModules"
|
||||||
|
|
|
@ -0,0 +1,12 @@
|
||||||
|
export default function (initialFooter, data) {
|
||||||
|
const footer = data.reduce(
|
||||||
|
(acc, row) => {
|
||||||
|
Object.entries(initialFooter).forEach(([key, initialValue]) => {
|
||||||
|
acc[key] += row?.[key] !== undefined ? row[key] : initialValue;
|
||||||
|
});
|
||||||
|
return acc;
|
||||||
|
},
|
||||||
|
{ ...initialFooter }
|
||||||
|
);
|
||||||
|
return footer;
|
||||||
|
}
|
|
@ -98,8 +98,8 @@ function checkIsMain() {
|
||||||
/>
|
/>
|
||||||
<div :id="searchbarId"></div>
|
<div :id="searchbarId"></div>
|
||||||
</slot>
|
</slot>
|
||||||
<RightAdvancedMenu :is-main-section="isMainSection && rightFilter">
|
<RightAdvancedMenu :is-main-section="isMainSection">
|
||||||
<template #advanced-menu v-if="$slots[advancedMenuSlot]">
|
<template #advanced-menu v-if="$slots[advancedMenuSlot] || rightFilter">
|
||||||
<slot :name="advancedMenuSlot">
|
<slot :name="advancedMenuSlot">
|
||||||
<VnTableFilter
|
<VnTableFilter
|
||||||
v-if="rightFilter && columns"
|
v-if="rightFilter && columns"
|
||||||
|
|
|
@ -368,7 +368,7 @@ watchEffect(selectedRows);
|
||||||
<VnSelect
|
<VnSelect
|
||||||
url="InvoiceOutSerials"
|
url="InvoiceOutSerials"
|
||||||
v-model="data.serial"
|
v-model="data.serial"
|
||||||
:label="t('invoicein.serial')"
|
:label="t('invoiceOutModule.serial')"
|
||||||
:options="invoiceOutSerialsOptions"
|
:options="invoiceOutSerialsOptions"
|
||||||
option-label="description"
|
option-label="description"
|
||||||
option-value="code"
|
option-value="code"
|
||||||
|
|
|
@ -60,6 +60,7 @@ invoiceOutModule:
|
||||||
amount: Amount
|
amount: Amount
|
||||||
company: Company
|
company: Company
|
||||||
address: Address
|
address: Address
|
||||||
|
serial: Serial
|
||||||
invoiceOutList:
|
invoiceOutList:
|
||||||
tableVisibleColumns:
|
tableVisibleColumns:
|
||||||
id: ID
|
id: ID
|
||||||
|
|
|
@ -60,6 +60,7 @@ invoiceOutModule:
|
||||||
amount: Importe
|
amount: Importe
|
||||||
company: Empresa
|
company: Empresa
|
||||||
address: Consignatario
|
address: Consignatario
|
||||||
|
serial: Serie
|
||||||
invoiceOutList:
|
invoiceOutList:
|
||||||
tableVisibleColumns:
|
tableVisibleColumns:
|
||||||
id: ID
|
id: ID
|
||||||
|
|
|
@ -3,5 +3,10 @@ import RouteDescriptor from 'pages/Route/Card/RouteDescriptor.vue';
|
||||||
import VnCardBeta from 'src/components/common/VnCardBeta.vue';
|
import VnCardBeta from 'src/components/common/VnCardBeta.vue';
|
||||||
</script>
|
</script>
|
||||||
<template>
|
<template>
|
||||||
<VnCardBeta data-key="Route" base-url="Routes/filter" :descriptor="RouteDescriptor" />
|
<VnCardBeta
|
||||||
|
data-key="Route"
|
||||||
|
base-url="Routes"
|
||||||
|
custom-url="Routes/filter"
|
||||||
|
:descriptor="RouteDescriptor"
|
||||||
|
/>
|
||||||
</template>
|
</template>
|
||||||
|
|
|
@ -0,0 +1,191 @@
|
||||||
|
<script setup>
|
||||||
|
import { computed, onBeforeMount, ref } from 'vue';
|
||||||
|
import { useI18n } from 'vue-i18n';
|
||||||
|
import { useRoute } from 'vue-router';
|
||||||
|
import tableFooter from 'src/components/VnTable/filters/tableFooter';
|
||||||
|
import { dashIfEmpty, toCurrency, toDateHourMin } from 'src/filters';
|
||||||
|
import { useState } from 'src/composables/useState';
|
||||||
|
import { useStateStore } from 'src/stores/useStateStore';
|
||||||
|
|
||||||
|
import VnTable from 'src/components/VnTable/VnTable.vue';
|
||||||
|
|
||||||
|
import InvoiceInDescriptorProxy from 'src/pages/InvoiceIn/Card/InvoiceInDescriptorProxy.vue';
|
||||||
|
import SupplierBalanceFilter from './SupplierBalanceFilter.vue';
|
||||||
|
import { onMounted } from 'vue';
|
||||||
|
import FetchData from 'src/components/FetchData.vue';
|
||||||
|
|
||||||
|
const { t } = useI18n();
|
||||||
|
|
||||||
|
const route = useRoute();
|
||||||
|
const state = useState();
|
||||||
|
const stateStore = useStateStore();
|
||||||
|
const user = state.getUser();
|
||||||
|
|
||||||
|
const tableRef = ref();
|
||||||
|
const companyId = ref();
|
||||||
|
const companyUser = ref(user.value.companyFk);
|
||||||
|
const balances = ref([]);
|
||||||
|
const userParams = ref({
|
||||||
|
supplierId: route.params.id,
|
||||||
|
companyId: companyId.value ?? companyUser.value,
|
||||||
|
isBooked: false,
|
||||||
|
});
|
||||||
|
|
||||||
|
const columns = computed(() => [
|
||||||
|
{
|
||||||
|
align: 'left',
|
||||||
|
name: 'dated',
|
||||||
|
label: t('Creation date'),
|
||||||
|
format: ({ dated }) => toDateHourMin(dated),
|
||||||
|
cardVisible: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
align: 'left',
|
||||||
|
name: 'sref',
|
||||||
|
label: t('Reference'),
|
||||||
|
isTitle: true,
|
||||||
|
class: 'extend',
|
||||||
|
format: ({ sref }) => dashIfEmpty(sref),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
align: 'left',
|
||||||
|
name: 'bank',
|
||||||
|
label: t('Bank'),
|
||||||
|
cardVisible: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
align: 'left',
|
||||||
|
name: 'invoiceEuros',
|
||||||
|
label: t('Debit'),
|
||||||
|
format: ({ invoiceEuros }) => toCurrency(invoiceEuros),
|
||||||
|
isId: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
align: 'left',
|
||||||
|
name: 'paymentEuros',
|
||||||
|
label: t('Havings'),
|
||||||
|
format: ({ paymentEuros }) => toCurrency(paymentEuros),
|
||||||
|
cardVisible: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
align: 'left',
|
||||||
|
name: 'euroBalance',
|
||||||
|
label: t('Balance'),
|
||||||
|
format: ({ euroBalance }) => toCurrency(euroBalance),
|
||||||
|
cardVisible: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
align: 'left',
|
||||||
|
name: 'isBooked',
|
||||||
|
label: t('Conciliated'),
|
||||||
|
cardVisible: true,
|
||||||
|
},
|
||||||
|
]);
|
||||||
|
|
||||||
|
onBeforeMount(() => {
|
||||||
|
stateStore.rightDrawer = true;
|
||||||
|
companyId.value = companyUser.value;
|
||||||
|
});
|
||||||
|
|
||||||
|
onMounted(async () => {
|
||||||
|
Object.assign(userParams, {
|
||||||
|
supplierId: route.params.id,
|
||||||
|
companyId: companyId.value ?? companyUser.value,
|
||||||
|
isConciliated: false,
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
function setFooter(data) {
|
||||||
|
const initialFooter = {
|
||||||
|
invoiceEuros: 0,
|
||||||
|
paymentEuros: 0,
|
||||||
|
euroBalance: 0,
|
||||||
|
};
|
||||||
|
|
||||||
|
tableRef.value.footer = tableFooter(initialFooter, data);
|
||||||
|
}
|
||||||
|
async function onFetch(data) {
|
||||||
|
setFooter(data);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
function round(value) {
|
||||||
|
return Math.round(value * 100) / 100;
|
||||||
|
}
|
||||||
|
const onFetchCurrencies = ([currency]) => {
|
||||||
|
userParams.value.currencyFk = currency?.id;
|
||||||
|
};
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<QDrawer side="right" :width="265" v-model="stateStore.rightDrawer">
|
||||||
|
<SupplierBalanceFilter data-key="SupplierBalance" />
|
||||||
|
</QDrawer>
|
||||||
|
<FetchData
|
||||||
|
url="Currencies"
|
||||||
|
:filter="{ fields: ['id', 'code'], where: { code: 'EUR' } }"
|
||||||
|
sort-by="code"
|
||||||
|
@on-fetch="onFetchCurrencies"
|
||||||
|
auto-load
|
||||||
|
/>
|
||||||
|
<VnTable
|
||||||
|
v-if="userParams.currencyFk"
|
||||||
|
ref="tableRef"
|
||||||
|
data-key="SupplierBalance"
|
||||||
|
url="Suppliers/receipts"
|
||||||
|
search-url="balance"
|
||||||
|
:user-params="userParams"
|
||||||
|
:columns="columns"
|
||||||
|
:right-search="false"
|
||||||
|
:is-editable="false"
|
||||||
|
:column-search="false"
|
||||||
|
@on-fetch="onFetch"
|
||||||
|
:disable-option="{ card: true }"
|
||||||
|
:footer="true"
|
||||||
|
:order="['dated ASC']"
|
||||||
|
data-cy="supplierBalanceTable"
|
||||||
|
auto-load
|
||||||
|
:map-key="false"
|
||||||
|
>
|
||||||
|
<template #column-balance="{ rowIndex }">
|
||||||
|
{{ toCurrency(balances[rowIndex]?.balance) }}
|
||||||
|
</template>
|
||||||
|
<template #column-sref="{ row }">
|
||||||
|
<span class="link" v-if="row.statementType === 'invoiceIn'">
|
||||||
|
{{ dashIfEmpty(row.sref) }}
|
||||||
|
<InvoiceInDescriptorProxy :id="row.id" />
|
||||||
|
</span>
|
||||||
|
<span v-else> {{ dashIfEmpty(row.sref) }}</span>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<template #column-footer-invoiceEuros>
|
||||||
|
<span>
|
||||||
|
{{ toCurrency(round(tableRef.footer.invoiceEuros)) }}
|
||||||
|
</span>
|
||||||
|
</template>
|
||||||
|
<template #column-footer-paymentEuros>
|
||||||
|
<span>
|
||||||
|
{{ toCurrency(round(tableRef.footer.paymentEuros)) }}
|
||||||
|
</span>
|
||||||
|
</template>
|
||||||
|
<template #column-footer-euroBalance>
|
||||||
|
<span>
|
||||||
|
{{ toCurrency(round(tableRef.footer.euroBalance)) }}
|
||||||
|
</span>
|
||||||
|
</template>
|
||||||
|
</VnTable>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<i18n>
|
||||||
|
es:
|
||||||
|
Company: Empresa
|
||||||
|
Total by company: Total por empresa
|
||||||
|
Date: Fecha
|
||||||
|
Creation date: Fecha de creación
|
||||||
|
Reference: Referencia
|
||||||
|
Bank: Caja
|
||||||
|
Debit: Debe
|
||||||
|
Havings: Haber
|
||||||
|
Balance: Balance
|
||||||
|
Conciliated: Conciliado
|
||||||
|
</i18n>
|
|
@ -0,0 +1,121 @@
|
||||||
|
<script setup>
|
||||||
|
import { useI18n } from 'vue-i18n';
|
||||||
|
import VnFilterPanel from 'src/components/ui/VnFilterPanel.vue';
|
||||||
|
import VnSelect from 'src/components/common/VnSelect.vue';
|
||||||
|
import VnInputDate from 'src/components/common/VnInputDate.vue';
|
||||||
|
|
||||||
|
const { t } = useI18n();
|
||||||
|
defineProps({
|
||||||
|
dataKey: {
|
||||||
|
type: String,
|
||||||
|
required: true,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<VnFilterPanel
|
||||||
|
:data-key="dataKey"
|
||||||
|
:search-button="true"
|
||||||
|
:redirect="false"
|
||||||
|
:unremovable-params="['supplierId', 'companyId']"
|
||||||
|
>
|
||||||
|
<template #tags="{ tag, formatFn }">
|
||||||
|
<div class="q-gutter-x-xs">
|
||||||
|
<strong>{{ t(`params.${tag.label}`) }}: </strong>
|
||||||
|
<span>{{ formatFn(tag.value) }}</span>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
<template #body="{ params, searchFn }">
|
||||||
|
<QItem>
|
||||||
|
<QItemSection>
|
||||||
|
<VnInputDate
|
||||||
|
:label="t('params.from')"
|
||||||
|
v-model="params.from"
|
||||||
|
@update:model-value="searchFn()"
|
||||||
|
is-outlined
|
||||||
|
/>
|
||||||
|
</QItemSection>
|
||||||
|
</QItem>
|
||||||
|
<QItem>
|
||||||
|
<QItemSection>
|
||||||
|
<VnSelect
|
||||||
|
:label="t('params.bankFk')"
|
||||||
|
v-model="params.bankFk"
|
||||||
|
url="Accountings"
|
||||||
|
option-label="bank"
|
||||||
|
:include="{ relation: 'accountingType' }"
|
||||||
|
sort-by="id"
|
||||||
|
dense
|
||||||
|
outlined
|
||||||
|
rounded
|
||||||
|
>
|
||||||
|
<template #option="scope">
|
||||||
|
<QItem v-bind="scope.itemProps">
|
||||||
|
<QItemSection>
|
||||||
|
<QItemLabel>
|
||||||
|
{{ scope.opt.id }} {{ scope.opt.bank }}
|
||||||
|
</QItemLabel>
|
||||||
|
</QItemSection>
|
||||||
|
</QItem>
|
||||||
|
</template>
|
||||||
|
</VnSelect>
|
||||||
|
</QItemSection>
|
||||||
|
</QItem>
|
||||||
|
<QItem>
|
||||||
|
<QItemSection>
|
||||||
|
<VnSelect
|
||||||
|
:label="t('params.currencyFk')"
|
||||||
|
url="Currencies"
|
||||||
|
:filter="{ fields: ['id', 'name'] }"
|
||||||
|
order="code"
|
||||||
|
v-model="params.currencyFk"
|
||||||
|
option-value="id"
|
||||||
|
option-label="name"
|
||||||
|
hide-selected
|
||||||
|
dense
|
||||||
|
outlined
|
||||||
|
rounded
|
||||||
|
/>
|
||||||
|
</QItemSection>
|
||||||
|
</QItem>
|
||||||
|
<QItem>
|
||||||
|
<QItemSection>
|
||||||
|
<QCheckbox
|
||||||
|
v-model="params.isConciliated"
|
||||||
|
:label="t('params.isConciliated')"
|
||||||
|
/></QItemSection>
|
||||||
|
</QItem>
|
||||||
|
</template>
|
||||||
|
</VnFilterPanel>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<i18n>
|
||||||
|
en:
|
||||||
|
params:
|
||||||
|
search: General search
|
||||||
|
supplierId: Supplier
|
||||||
|
categoryId: Category
|
||||||
|
from: From
|
||||||
|
to: To
|
||||||
|
isConciliated: Is conciliated
|
||||||
|
currencyFk: Currency
|
||||||
|
bankFk: Bank
|
||||||
|
companyId: Comapany
|
||||||
|
isBooked: Is booked
|
||||||
|
es:
|
||||||
|
params:
|
||||||
|
supplierId: Proveedor
|
||||||
|
isConciliated: Conciliado
|
||||||
|
currencyFk: Moneda
|
||||||
|
New payment: Añadir pago
|
||||||
|
Date: Fecha
|
||||||
|
from: Desde
|
||||||
|
to: Hasta
|
||||||
|
companyId: Empresa
|
||||||
|
isBooked: Contabilizado
|
||||||
|
bankFk: Caja
|
||||||
|
Amount: Importe
|
||||||
|
Reference: Referencia
|
||||||
|
Cash: Efectivo
|
||||||
|
</i18n>
|
|
@ -21,8 +21,8 @@ const { t, te } = i18n.global;
|
||||||
const createHistory = process.env.SERVER
|
const createHistory = process.env.SERVER
|
||||||
? createMemoryHistory
|
? createMemoryHistory
|
||||||
: process.env.VUE_ROUTER_MODE === 'history'
|
: process.env.VUE_ROUTER_MODE === 'history'
|
||||||
? createWebHistory
|
? createWebHistory
|
||||||
: createWebHashHistory;
|
: createWebHashHistory;
|
||||||
|
|
||||||
const Router = createRouter({
|
const Router = createRouter({
|
||||||
scrollBehavior: () => ({ left: 0, top: 0 }),
|
scrollBehavior: () => ({ left: 0, top: 0 }),
|
||||||
|
@ -99,9 +99,15 @@ export default defineRouter(function (/* { store, ssrContext } */) {
|
||||||
|
|
||||||
title += builtTitle;
|
title += builtTitle;
|
||||||
}
|
}
|
||||||
|
|
||||||
document.title = title;
|
document.title = title;
|
||||||
});
|
});
|
||||||
|
|
||||||
|
Router.onError(({ message }) => {
|
||||||
|
const errorMessages = [
|
||||||
|
'Failed to fetch dynamically imported module',
|
||||||
|
'Importing a module script failed',
|
||||||
|
];
|
||||||
|
state.set('error', errorMessages.some(message.includes));
|
||||||
|
});
|
||||||
return Router;
|
return Router;
|
||||||
});
|
});
|
||||||
|
|
|
@ -21,6 +21,7 @@ export default {
|
||||||
'SupplierAccounts',
|
'SupplierAccounts',
|
||||||
'SupplierContacts',
|
'SupplierContacts',
|
||||||
'SupplierAddresses',
|
'SupplierAddresses',
|
||||||
|
'SupplierBalance',
|
||||||
'SupplierConsumption',
|
'SupplierConsumption',
|
||||||
'SupplierAgencyTerm',
|
'SupplierAgencyTerm',
|
||||||
'SupplierDms',
|
'SupplierDms',
|
||||||
|
@ -144,6 +145,16 @@ export default {
|
||||||
component: () =>
|
component: () =>
|
||||||
import('src/pages/Supplier/Card/SupplierAddressesCreate.vue'),
|
import('src/pages/Supplier/Card/SupplierAddressesCreate.vue'),
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
path: 'balance',
|
||||||
|
name: 'SupplierBalance',
|
||||||
|
meta: {
|
||||||
|
title: 'balance',
|
||||||
|
icon: 'balance',
|
||||||
|
},
|
||||||
|
component: () =>
|
||||||
|
import('src/pages/Supplier/Card/SupplierBalance.vue'),
|
||||||
|
},
|
||||||
{
|
{
|
||||||
path: 'consumption',
|
path: 'consumption',
|
||||||
name: 'SupplierConsumption',
|
name: 'SupplierConsumption',
|
||||||
|
|
|
@ -0,0 +1,11 @@
|
||||||
|
describe('Supplier Balance', () => {
|
||||||
|
beforeEach(() => {
|
||||||
|
cy.viewport(1920, 1080);
|
||||||
|
cy.login('developer');
|
||||||
|
cy.visit(`/#/supplier/1/balance`);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('Should load layout', () => {
|
||||||
|
cy.get('.q-page').should('be.visible');
|
||||||
|
});
|
||||||
|
});
|
|
@ -3,9 +3,7 @@ describe('Client balance', () => {
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
cy.viewport(1280, 720);
|
cy.viewport(1280, 720);
|
||||||
cy.login('developer');
|
cy.login('developer');
|
||||||
cy.visit('#/customer/1101/balance', {
|
cy.visit('#/customer/1101/balance');
|
||||||
timeout: 5000,
|
|
||||||
});
|
|
||||||
});
|
});
|
||||||
it('Should load layout', () => {
|
it('Should load layout', () => {
|
||||||
cy.get('.q-page').should('be.visible');
|
cy.get('.q-page').should('be.visible');
|
||||||
|
|
|
@ -1,13 +1,6 @@
|
||||||
/// <reference types="cypress" />
|
/// <reference types="cypress" />
|
||||||
describe('InvoiceOut list', () => {
|
describe('InvoiceOut list', () => {
|
||||||
const invoice = {
|
const serial = 'Española rapida';
|
||||||
Ticket: { val: '8' },
|
|
||||||
Serial: { val: 'Española rapida', type: 'select' },
|
|
||||||
};
|
|
||||||
const invoiceError = {
|
|
||||||
Ticket: { val: '1' },
|
|
||||||
Serial: { val: 'Española rapida', type: 'select' },
|
|
||||||
};
|
|
||||||
|
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
cy.viewport(1920, 1080);
|
cy.viewport(1920, 1080);
|
||||||
|
@ -32,14 +25,16 @@ describe('InvoiceOut list', () => {
|
||||||
|
|
||||||
it('should give an error when manual invoicing a ticket that is already invoiced', () => {
|
it('should give an error when manual invoicing a ticket that is already invoiced', () => {
|
||||||
cy.dataCy('vnTableCreateBtn').click();
|
cy.dataCy('vnTableCreateBtn').click();
|
||||||
cy.fillInForm(invoiceError);
|
cy.dataCy('InvoiceOutCreateTicketinput').type(1);
|
||||||
|
cy.selectOption('[data-cy="InvoiceOutCreateSerialSelect"]', serial);
|
||||||
cy.dataCy('FormModelPopup_save').click();
|
cy.dataCy('FormModelPopup_save').click();
|
||||||
cy.checkNotification('This ticket is already invoiced');
|
cy.checkNotification('This ticket is already invoiced');
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should create a manual invoice and enter to its summary', () => {
|
it('should create a manual invoice and enter to its summary', () => {
|
||||||
cy.dataCy('vnTableCreateBtn').click();
|
cy.dataCy('vnTableCreateBtn').click();
|
||||||
cy.fillInForm(invoice);
|
cy.dataCy('InvoiceOutCreateTicketinput').type(8);
|
||||||
|
cy.selectOption('[data-cy="InvoiceOutCreateSerialSelect"]', serial);
|
||||||
cy.dataCy('FormModelPopup_save').click();
|
cy.dataCy('FormModelPopup_save').click();
|
||||||
cy.checkNotification('Data created');
|
cy.checkNotification('Data created');
|
||||||
});
|
});
|
||||||
|
|
Loading…
Reference in New Issue