1
0
mirror of https://github.com/PanJiaChen/vue-element-admin.git synced 2025-08-10 12:01:57 +08:00

Feature/#sc add record access api (#794)

* Add API for get Record Access based on server

* support setRecordAcces

* Add mixin for view, this allows reuse code based on unique definition

* support record access

* Support Record Access in Mode Mobile

* minimal changes

Co-authored-by: elsiosanchez <elsiosanche@gmail.com>
This commit is contained in:
Yamel Senih 2021-04-28 19:08:27 -04:00 committed by GitHub
parent 8f178e6dfc
commit 925cf6d679
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
18 changed files with 860 additions and 479 deletions

View File

@ -17,10 +17,8 @@
// Get Instance for connection // Get Instance for connection
import { request } from '@/utils/ADempiere/request' import { request } from '@/utils/ADempiere/request'
import { convertPrivateAccess } from '@/utils/ADempiere/apiConverts/privateAccess.js'
// Get private access for a record // Get private access for a record
export function requestGetPrivateAccess({ export function getPrivateAccess({
tableName, tableName,
recordId, recordId,
recordUuid recordUuid
@ -35,12 +33,16 @@ export function requestGetPrivateAccess({
} }
}) })
.then(responsePrivateAccess => { .then(responsePrivateAccess => {
return convertPrivateAccess(responsePrivateAccess) return {
tableName: responsePrivateAccess.table_name,
recordId: responsePrivateAccess.record_id,
recordUuid: responsePrivateAccess.record_uuid
}
}) })
} }
// Lock a record for a user // Lock a record for a user
export function requestLockPrivateAccess({ export function lockPrivateAccess({
tableName, tableName,
recordId, recordId,
recordUuid recordUuid
@ -55,12 +57,16 @@ export function requestLockPrivateAccess({
} }
}) })
.then(responsePrivateAccess => { .then(responsePrivateAccess => {
return convertPrivateAccess(responsePrivateAccess) return {
tableName: responsePrivateAccess.table_name,
recordId: responsePrivateAccess.record_id,
recordUuid: responsePrivateAccess.record_uuid
}
}) })
} }
// Unlock a record from a user // Unlock a record from a user
export function requestUnlockPrivateAccess({ export function unlockPrivateAccess({
tableName, tableName,
recordId, recordId,
recordUuid recordUuid
@ -75,62 +81,10 @@ export function requestUnlockPrivateAccess({
} }
}) })
.then(responsePrivateAccess => { .then(responsePrivateAccess => {
return convertPrivateAccess(responsePrivateAccess) return {
}) tableName: responsePrivateAccess.table_name,
} recordId: responsePrivateAccess.record_id,
recordUuid: responsePrivateAccess.record_uuid
/** }
* List Rol Access Record
* @param {string} tableName
* @param {number} recordId
* @param {string} recordUuid
* @param {string} sessionUuid
*/
export function getAccessList({
tableName,
recordId,
recordUuid,
sessionUuid
}) {
return request({
url: '/ui/update-access-record',
method: 'post',
params: {
table_name: tableName,
id: recordId,
uuid: recordUuid,
token: sessionUuid
}
})
.then(respose => {
return respose
})
}
/**
* Update Access Record
* @param {string} tableName
* @param {number} recordId
* @param {string} recordUuid
* @param {array} listRol
*/
export function updateAccessRecord({
tableName,
recordId,
recordUuid,
listRecord
}) {
return request({
url: '/ui/update-access-record',
method: 'post',
params: {
table_name: tableName,
id: recordId,
uuid: recordUuid,
list_rol: listRecord
}
})
.then(response => {
return response
}) })
} }

View File

@ -0,0 +1,121 @@
// ADempiere-Vue (Frontend) for ADempiere ERP & CRM Smart Business Solution
// Copyright (C) 2017-Present E.R.P. Consultores y Asociados, C.A.
// Contributor(s): Yamel Senih ysenih@erpya.com www.erpya.com
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
// You should have received a copy of the GNU General Public License
// along with this program. If not, see <https://www.gnu.org/licenses/>.
// Get Instance for connection
import { request } from '@/utils/ADempiere/request'
/**
* List Rol Access Record
* @param {string} tableName
* @param {number} recordId
* @param {string} recordUuid
* @param {string} sessionUuid
*/
export function getRecordAccess({
tableName,
recordId,
recordUuid
}) {
return new Promise(resolve => {
request({
url: '/ui/record-access',
method: 'get',
params: {
table_name: tableName,
id: recordId,
uuid: recordUuid
}
})
.then(respose => {
resolve(convertRecordAccess(respose))
})
})
}
/**
* Update Access Record
* @param {string} tableName
* @param {number} recordId
* @param {string} recordUuid
* @param {array} listRol
*/
export function setRecordAccess({
recordId,
recordUuid,
tableName,
recordAccesses
}) {
return request({
url: '/ui/set-record-access',
method: 'post',
data: {
table_name: tableName,
id: recordId,
uuid: recordUuid,
record_accesses: recordAccesses.map(access => {
return {
role_id: access.roleId,
role_uuid: access.roleUuid,
role_name: access.roleName,
is_active: access.isActive,
is_exclude: access.isExclude,
is_read_only: access.isReadOnly,
is_dependent_entities: access.isDependentEntities
}
})
}
})
.then(response => {
return response
})
}
/**
* Convert stub from request
* @param {Record Access} recordAccess
* @returns
*/
function convertRecordAccess(recordAccess) {
return {
tableName: recordAccess.table_name,
id: recordAccess.id,
uuid: recordAccess.uuid,
availableRoles: recordAccess.available_roles.map(role => {
return convertRecordAccessRole(role)
}),
currentRoles: recordAccess.current_roles.map(role => {
return convertRecordAccessRole(role)
})
}
}
/**
* Convert role definition
*/
function convertRecordAccessRole(recordAccessRole) {
if (recordAccessRole) {
return {
roleId: recordAccessRole.role_id,
roleUuid: recordAccessRole.role_uuid,
roleName: recordAccessRole.role_name,
isActive: recordAccessRole.is_active,
isExclude: recordAccessRole.is_exclude,
isReadOnly: recordAccessRole.is_read_only,
isDependentEntities: recordAccessRole.is_dependent_entities
}
}
return undefined
}

View File

@ -271,6 +271,7 @@ export default {
} else if (action.action === 'recordAccess') { } else if (action.action === 'recordAccess') {
this.$store.commit('changeShowRigthPanel', true) this.$store.commit('changeShowRigthPanel', true)
this.$store.commit('setRecordAccess', true) this.$store.commit('setRecordAccess', true)
this.$store.commit('attributeEmbedded', action)
this.runAction(action) this.runAction(action)
} else { } else {
this.runAction(action) this.runAction(action)

View File

@ -426,11 +426,6 @@ export default {
} }
} }
if (this.isWindow && this.isEmptyValue(this.actions.find(element => element.action === 'recordAccess'))) { if (this.isWindow && this.isEmptyValue(this.actions.find(element => element.action === 'recordAccess'))) {
this.$store.dispatch('addAttribute', {
tableName: this.tableNameCurrentTab,
recordId: this.getCurrentRecord[this.tableNameCurrentTab + '_ID'],
recordUuid: this.$route.query.action
})
this.actions.push(this.recordAccess) this.actions.push(this.recordAccess)
} }

View File

@ -0,0 +1,207 @@
<!--
ADempiere-Vue (Frontend) for ADempiere ERP & CRM Smart Business Solution
Copyright (C) 2017-Present E.R.P. Consultores y Asociados, C.A.
Contributor(s): Yamel Senih ysenih@erpya.com www.erpya.com
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <https:www.gnu.org/licenses/>.
-->
<template>
<el-dialog
:title="attributeEmbedded.name"
:visible="isVisibleDialog"
show-close
:before-close="closeDialog"
:width="width + '%'"
top="5vh"
close-on-press-escape
close-on-click-modal
>
<slot />
</el-dialog>
</template>
<script>
import { showNotification } from '@/utils/ADempiere/notification'
export default {
name: 'Embedded',
props: {
parentUuid: {
type: String,
default: undefined
},
containerUuid: {
type: String,
default: ''
},
panelType: {
type: String,
default: 'window'
},
reportExportType: {
type: String,
default: ''
},
tableName: {
type: String,
default: undefined
},
recordId: {
type: Object,
default: undefined
}
},
computed: {
isMobile() {
return this.$store.state.app.device === 'mobile'
},
width() {
if (this.isMobile) {
return 80
}
return 70
},
attributeEmbedded() {
return this.$store.getters.getAttributeEmbedded
},
isVisibleDialog() {
return this.$store.state['process/index'].isVisibleDialog
},
modalMetadata() {
return this.$store.state['process/index'].metadata
},
windowRecordSelected() {
return this.$store.state['windowControl/index'].recordSelected
},
getterDataRecordsAndSelection() {
return this.$store.getters.getDataRecordAndSelection(this.containerUuid)
},
showRecordAccess() {
return this.$store.getters.getShowRecordAccess
}
},
watch: {
isVisibleDialog(value) {
if (value) {
if (this.modalMetadata.isSortTab) {
const data = this.$store.getters.getDataRecordAndSelection(this.modalMetadata.containerUuid)
if (!data.isLoaded && !data.record.length) {
this.$store.dispatch('getDataListTab', {
parentUuid: this.modalMetadata.parentUuid,
containerUuid: this.modalMetadata.containerUuid,
isAddRecord: true
})
.catch(error => {
console.warn(`Error getting data list tab. Message: ${error.message}, code ${error.code}.`)
})
}
}
}
}
},
methods: {
showNotification,
closeDialog() {
this.$store.dispatch('setShowDialog', {
type: this.modalMetadata.panelType,
action: {
name: ''
}
})
this.$store.commit('setRecordAccess', false)
},
runAction(action) {
this.$store.commit('setRecordAccess', false)
if (action.isSortTab) {
this.$store.dispatch('updateSequence', {
parentUuid: this.modalMetadata.parentUuid,
containerUuid: this.modalMetadata.containerUuid
})
return
}
if (action === undefined && this.windowRecordSelected !== undefined) {
this.$router.push({
name: this.$route.name,
query: {
...this.$route.query,
action: this.windowRecordSelected.UUID
}
}, () => {})
this.closeDialog()
} else if (!this.isEmptyValue(action)) {
const fieldNotReady = this.$store.getters.isNotReadyForSubmit(action.uuid)
if (this.panelType === 'From') {
this.$store.dispatch('processPos', {
action: action, // process metadata
parentUuid: this.parentUuid,
idProcess: this.$store.getters.posAttributes.currentOrder.id,
containerUuid: this.containerUuid,
panelType: this.panelType, // determinate if get table name and record id (window) or selection (browser)
parametersList: this.$store.getters.getPosParameters
})
.catch(error => {
console.warn(error)
})
this.closeDialog()
} else {
if (!fieldNotReady) {
this.closeDialog()
const porcesTabla = this.$store.getters.getProcessSelect.processTablaSelection
const selection = this.$store.getters.getProcessSelect
if (porcesTabla) {
// manage excecute process with records selection
this.$store.dispatch('selectionProcess', {
action: action, // process metadata
parentUuid: this.parentUuid,
containerUuid: this.containerUuid,
panelType: this.panelType, // determinate if get table name and record id (window) or selection (browser)
reportFormat: this.reportExportType,
recordUuidSelection: selection,
isProcessTableSelection: true,
routeToDelete: this.$route
})
} else {
this.$store.dispatch('startProcess', {
action: action, // process metadata
parentUuid: this.parentUuid,
isProcessTableSelection: false,
containerUuid: this.containerUuid,
panelType: this.panelType, // determinate if get table name and record id (window) or selection (browser)
reportFormat: this.reportExportType,
routeToDelete: this.$route
})
.catch(error => {
console.warn(error)
})
}
} else {
this.showNotification({
type: 'warning',
title: this.$t('notifications.emptyValues'),
name: '<b>' + fieldNotReady.name + '.</b> ',
message: this.$t('notifications.fieldMandatory')
})
}
}
}
}
}
}
</script>
<style>
.el-dialog__body {
padding: 10px 20px;
max-height: 65vh;
overflow: auto;
}
</style>

View File

@ -17,6 +17,7 @@
--> -->
<template> <template>
<el-dialog <el-dialog
v-if="!showRecordAccess"
:title="modalMetadata.name" :title="modalMetadata.name"
:visible="isVisibleDialog" :visible="isVisibleDialog"
show-close show-close
@ -30,9 +31,6 @@
<div <div
v-if="panelType !== 'From'" v-if="panelType !== 'From'"
> >
<record-access
v-if="showRecordAccess"
/>
<sequence-order <sequence-order
v-if="modalMetadata.isSortTab" v-if="modalMetadata.isSortTab"
key="order" key="order"
@ -54,12 +52,13 @@
/> />
</template> </template>
</div> </div>
<span slot="footer" class="dialog-footer"> <span v-if="!showRecordAccess" slot="footer" class="dialog-footer">
<el-button <el-button
type="danger" type="danger"
icon="el-icon-close" icon="el-icon-close"
@click="closeDialog" @click="closeDialog"
/> />
alooooooooo
<el-button <el-button
type="primary" type="primary"
icon="el-icon-check" icon="el-icon-check"
@ -72,17 +71,13 @@
<script> <script>
import MainPanel from '@/components/ADempiere/Panel' import MainPanel from '@/components/ADempiere/Panel'
import SequenceOrder from '@/components/ADempiere/SequenceOrder' import SequenceOrder from '@/components/ADempiere/SequenceOrder'
import RecordAccess from '@/components/ADempiere/RecordAccess'
import { showNotification } from '@/utils/ADempiere/notification' import { showNotification } from '@/utils/ADempiere/notification'
import {
updateAccessRecord
} from '@/api/ADempiere/private-access'
export default { export default {
name: 'ModalProcess', name: 'ModalProcess',
components: { components: {
MainPanel, MainPanel,
SequenceOrder, SequenceOrder
RecordAccess
}, },
props: { props: {
parentUuid: { parentUuid: {
@ -110,7 +105,7 @@ export default {
if (this.isMobile) { if (this.isMobile) {
return 80 return 80
} }
return 50 return 80
}, },
isVisibleDialog() { isVisibleDialog() {
return this.$store.state['process/index'].isVisibleDialog return this.$store.state['process/index'].isVisibleDialog
@ -232,7 +227,8 @@ export default {
} }
if (action.action === undefined) { if (action.action === undefined) {
const list = this.$store.getters.getListRecordAcces const list = this.$store.getters.getListRecordAcces
updateAccessRecord(list) // updateAccessRecord(list)
console.log(list)
} }
} }
} }

View File

@ -1,12 +1,24 @@
<template> <template>
<component <component
:is="templateDevice" :is="templateDevice"
:record="record"
:table-name="tableName"
/> />
</template> </template>
<script> <script>
export default { export default {
name: 'RecordAccess', name: 'RecordAccess',
props: {
record: {
type: Object,
default: () => {}
},
tableName: {
type: String,
default: undefined
}
},
computed: { computed: {
isMobile() { isMobile() {
return this.$store.state.app.device === 'mobile' return this.$store.state.app.device === 'mobile'

View File

@ -0,0 +1,193 @@
// ADempiere-Vue (Frontend) for ADempiere ERP & CRM Smart Business Solution
// Copyright (C) 2017-Present E.R.P. Consultores y Asociados, C.A.
// Contributor(s): Yamel Senih ysenih@erpya.com www.erpya.com
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
// You should have received a copy of the GNU General Public License
// along with this program. If not, see <https://www.gnu.org/licenses/>.
import { getRecordAccess, setRecordAccess } from '@/api/ADempiere/actions/record-access.js'
import { showMessage } from '@/utils/ADempiere/notification.js'
import language from '@/lang'
export default {
name: 'MixinRecordAccess',
props: {
parentUuid: {
type: String,
default: undefined
},
containerUuid: {
type: String,
default: undefined
},
order: {
type: String,
default: undefined
},
included: {
type: String,
default: undefined
},
keyColumn: {
type: String,
default: undefined
},
identifiersList: {
type: Array,
default: undefined
},
record: {
type: Object,
default: () => {}
},
tableName: {
type: String,
default: undefined
}
},
data() {
return {
group: 'sequence',
isReadonly: false,
isDependentEntities: true,
recordAccess: {
recordUuid: '',
roles: []
}
}
},
computed: {
excludedList: {
get() {
if (this.recordAccess.roles) {
return this.recordAccess.roles.filter(role => !role.isLocked)
} else {
return []
}
},
set(value) {
}
},
includedList: {
get() {
if (this.recordAccess.roles) {
return this.recordAccess.roles.filter(role => role.isLocked)
} else {
return []
}
},
set(value) {
}
},
getIdentifiersList() {
return this.identifiersList
.filter(item => item.componentPath !== 'FieldSelect')
}
},
created() {
getRecordAccess({
tableName: this.tableName,
recordId: this.record[this.tableName + '_ID'],
recordUuid: this.record.UUID
})
.then(access => {
this.recordAccess.tableName = access.tableName
this.recordAccess.recordId = access.recordId
this.recordAccess.recordUuid = access.recordUuid
access.availableRoles.forEach(role => {
this.recordAccess.roles.push({
...role,
isLocked: false
})
})
access.currentRoles.forEach(role => {
this.recordAccess.roles.find(availableRole => availableRole.roleId === role.roleId).isLocked = true
this.recordAccess.roles.find(availableRole => availableRole.roleId === role.roleId).isDependentEntities = role.isDependentEntities
this.recordAccess.roles.find(availableRole => availableRole.roleId === role.roleId).isReadOnly = role.isReadOnly
})
})
},
methods: {
handleChange(value) {
const action = Object.keys(value)[0] // get property
const element = value[action].element
const index = this.recordAccess.roles.findIndex(role => role.roleId === element.roleId)
switch (action) {
case 'added':
this.addItem({
index,
element
})
break
case 'removed':
this.deleteItem({
index,
element
})
break
}
},
/**
* @param {number} index: the index of the added element
* @param {object} element: the added element
*/
addItem({
index,
element
}) {
this.recordAccess.roles[index].isLocked = true
},
/**
* @param {number} index: the index of the element before remove
* @param {object} element: the removed element
*/
deleteItem({
index,
element
}) {
this.recordAccess.roles[index].isLocked = false
},
getOrder(arrayToSort, orderBy = this.order) {
return arrayToSort.sort((itemA, itemB) => {
return itemA[orderBy] - itemB[orderBy]
})
},
saveRecordAccess(recordAccesses) {
setRecordAccess({
tableName: this.tableName,
recordId: this.record[this.tableName + '_ID'],
recordUuid: this.record.UUID,
recordAccesses
})
.then(response => {
showMessage({
message: language.t('grid.recordAccess.accessGranted')
})
this.close()
})
.catch(error => {
showMessage({
message: error.message,
type: 'error'
})
console.warn(`setPreference error: ${error.message}.`)
})
},
close() {
this.$store.dispatch('setShowDialog', {
type: 'window',
action: undefined
})
this.$store.commit('setRecordAccess', false)
this.$store.commit('changeShowRigthPanel', false)
}
}
}

View File

@ -16,196 +16,122 @@
along with this program. If not, see <https:www.gnu.org/licenses/>. along with this program. If not, see <https:www.gnu.org/licenses/>.
--> -->
<template> <template>
<div class="board"> <div>
<div <div class="board">
:key="1" <div
class="kanban todo" :key="1"
header-text="Todo" class="kanban todo"
> header-text="Todo"
<div class="board-column"> style="padding: 0px;margin: 0px;width: 35%;padding-right: 3%;"
<div class="board-column-header"> >
{{ $t('data.recordAccess.hideRecord') }} ({{ getterListExclude.length }}) <div class="board-column">
</div> <div class="board-column-header">
<draggable {{ $t('data.recordAccess.hideRecord') }} ({{ excludedList.length }})
v-model="getterListExclude"
:group="group"
v-bind="$attrs"
class="board-column-content"
>
<div
v-for="element in getterListExclude"
:key="element.UUID"
class="board-item"
>
{{ element.clientName }}
</div> </div>
<draggable
v-model="excludedList"
:group="group"
v-bind="$attrs"
class="board-column-content"
>
<div
v-for="(element, index) in excludedList"
:key="element.roleUuid"
class="board-item"
style="height: 50%;padding-left: 0px;padding-right: 0px;"
>
<el-table
v-if="!isEmptyValue(excludedList)"
:data="[excludedList[index]]"
border
:show-header="false"
>
<el-table-column
prop="roleName"
/>
</el-table>
</div>
</draggable> </draggable>
</div>
</div>
<div
:key="2"
class="kanban working"
header-text="Working"
>
<div class="board-column">
<div class="board-column-header">
{{ $t('data.recordAccess.recordDisplay') }} {{ getterListInclude.length }}
</div> </div>
<draggable </div>
v-model="getterListInclude" <div
:group="group" :key="2"
v-bind="$attrs" class="kanban working"
class="board-column-content" header-text="Working"
@change="handleChange" style="padding: 0px;margin: 0px;width: 65%;padding-right: 3%;"
> >
<div <div class="board-column">
v-for="element in getterListInclude" <div class="board-column-header">
:key="element.UUID" {{ $t('data.recordAccess.recordDisplay') }} ({{ includedList.length }})
class="board-item"
>
{{ element.name }}
<el-divider direction="vertical" />
{{ $t('data.recordAccess.isReadonly') }} <el-checkbox v-model="isReadonly" />
<el-divider direction="vertical" />
{{ $t('data.recordAccess.isDependentEntities') }} <el-checkbox v-model="isDependentEntities" />
</div> </div>
</draggable> <draggable
v-model="includedList"
:group="group"
v-bind="$attrs"
class="board-column-content"
@change="handleChange"
>
<div
v-for="(element, index) in includedList"
:key="element.roleUuid"
class="board-item"
style="height: 50%;padding-left: 0px;padding-right: 0px;"
>
<el-table
v-if="!isEmptyValue(includedList)"
:data="[includedList[index]]"
border
:show-header="false"
>
<el-table-column
prop="roleName"
min-width="150"
/>
<el-table-column
fixed="right"
>
<template slot-scope="scope">
{{ $t('data.recordAccess.isReadonly') }} <el-switch v-model="scope.row.isReadOnly" />
</template>
</el-table-column>
<el-table-column
fixed="right"
>
<template slot-scope="scope">
{{ $t('data.recordAccess.isDependentEntities') }} <el-switch v-model="scope.row.isDependentEntities" />
</template>
</el-table-column>
</el-table>
</div>
</draggable>
</div>
</div> </div>
</div> </div>
<span slot="footer" class="dialog-footer" style="float: right;padding-top: 1%;">
<el-button
type="danger"
icon="el-icon-close"
@click="close"
/>
<el-button
type="primary"
icon="el-icon-check"
@click="saveRecordAccess(includedList)"
/>
</span>
</div> </div>
</template> </template>
<script> <script>
import draggable from 'vuedraggable' import draggable from 'vuedraggable'
import recordAccessMixin from './recordAccess.js'
export default { export default {
name: 'RecordAccessDesktop', name: 'RecordAccessDesktop',
components: { components: {
draggable draggable
}, },
props: { mixins: [recordAccessMixin]
parentUuid: {
type: String,
default: undefined
},
containerUuid: {
type: String,
default: undefined
},
order: {
type: String,
default: undefined
},
included: {
type: String,
default: undefined
},
keyColumn: {
type: String,
default: undefined
},
identifiersList: {
type: Array,
default: undefined
}
},
data() {
return {
group: 'sequence',
isReadonly: false,
isDependentEntities: true,
getterDataRecords: this.$store.getters['user/getRoles']
}
},
computed: {
getterListExclude: {
get() {
return this.getterDataRecords.filter(item => item.isPersonalLock === false)
},
set(value) {
}
},
getterListInclude: {
get() {
return this.getterDataRecords.filter(item => item.isPersonalLock === true)
},
set(value) {
}
},
getIdentifiersList() {
return this.identifiersList
.filter(item => item.componentPath !== 'FieldSelect')
}
},
created() {
const record = this.getterDataRecords.map(record => {
return {
id: record.id,
uuid: record.uuid,
IsExclude: record.isPersonalLock,
isDependentEntities: this.isDependentEntities,
isReadonly: this.isReadonly
}
})
this.$store.dispatch('changeList', record)
},
methods: {
handleChange(value) {
const action = Object.keys(value)[0] // get property
const element = value.[action].element
const index = this.getterDataRecords.findIndex(role => role.id === element.id)
switch (action) {
case 'added':
this.addItem({
index,
element
})
break
case 'removed':
this.deleteItem({
index,
element
})
break
}
},
/**
* @param {number} index: the index of the added element
* @param {object} element: the added element
*/
addItem({
index,
element
}) {
this.getterDataRecords[index].isPersonalLock = !element.isPersonalLock
},
/**
* @param {number} index: the index of the element before remove
* @param {object} element: the removed element
*/
deleteItem({
index,
element
}) {
this.getterDataRecords[index].isPersonalLock = !element.isPersonalLock
const record = this.getterDataRecords.map(record => {
return {
id: record.id,
uuid: record.uuid,
IsExclude: record.isPersonalLock,
isDependentEntities: this.isDependentEntities,
isReadonly: this.isReadonly
}
})
this.$store.dispatch('changeList', record)
},
getOrder(arrayToSort, orderBy = this.order) {
return arrayToSort.sort((itemA, itemB) => {
return itemA[orderBy] - itemB[orderBy]
})
}
}
} }
</script> </script>
@ -255,7 +181,28 @@ export default {
} }
</style> </style>
<style lang="scss"> <style lang="scss">
.board-column .board-column-content[data-v-67e5b2d0] {
max-height: 350px;
height: auto;
overflow: auto;
border: 10px solid transparent;
min-height: 60px;
display: -webkit-box;
display: -ms-flexbox;
display: flex;
-webkit-box-pack: start;
-ms-flex-pack: start;
justify-content: flex-start;
-webkit-box-orient: vertical;
-webkit-box-direction: normal;
-ms-flex-direction: column;
flex-direction: column;
-webkit-box-align: center;
-ms-flex-align: center;
align-items: center;
}
.board { .board {
height: 400px;
width: 100%; width: 100%;
margin-left: 20px; margin-left: 20px;
display: flex; display: flex;

View File

@ -1,86 +1,99 @@
<template> <template>
<el-card class="box-card"> <div>
<div slot="header" class="clearfix"> <el-card class="box-card">
<span> <div slot="header" class="clearfix">
{{ $t('data.recordAccess.actions') }} <span>
</span> {{ $t('data.recordAccess.actions') }}
</div> </span>
<div style="margin-bottom: 10%;"> </div>
<span style="margin-bottom: 10%;"> <div style="margin-bottom: 10%;">
{{ $t('data.recordAccess.hideRecord') }} <span style="margin-bottom: 10%;">
</span> {{ $t('data.recordAccess.hideRecord') }} ({{ labelListExcludo.length }})
<br> </span>
<el-select <br>
v-model="getterListExclude" <el-select
multiple v-model="labelListExcludo"
style="margin-top: 5%;" multiple
placeholder="Select" style="margin-top: 5%;"
clearable filterable
@change="addListExclude" placeholder="Select"
> collapse-tags
<el-option @change="addListExclude"
v-for="item in getterDataRecords" >
:key="item.clientId" <el-option
:label="item.name" v-for="item in includedList"
:value="item.uuid" :key="item.roleUuid"
/> :label="item.roleName"
</el-select> :value="item.roleName"
</div> />
<div </el-select>
style="margin-bottom: 10%;" </div>
> <div
<span
style="margin-bottom: 10%;" style="margin-bottom: 10%;"
> >
{{ $t('data.recordAccess.recordDisplay') }} <span
</span> style="margin-bottom: 10%;"
<br>
<el-select
v-model="getterListInclude"
multiple
style="margin-top: 5%;"
placeholder="Select"
@change="addListInclude"
>
<el-option
v-for="item in getterDataRecords"
:key="item.clientId"
:label="item.name"
:value="item.uuid"
/>
</el-select>
</div>
<el-form
label-position="top"
size="small"
class="create-bp"
>
<el-row :gutter="24">
<template
v-for="(record, index) in getterListInclude"
> >
<div :key="index" style="margin-left: 5%;"> {{ $t('data.recordAccess.recordDisplay') }} ({{ labelListInclude.length }})
<el-tag> </span>
{{ record }} <br>
</el-tag> <el-select
<p> v-model="labelListInclude"
{{ $t('data.recordAccess.isReadonly') }} multiple
<el-checkbox v-model="isReadonly" /> style="margin-top: 5%;"
</p> placeholder="Select"
<p> filterable
{{ $t('data.recordAccess.isDependentEntities') }} <el-checkbox v-model="isDependentEntities" /> collapse-tags
</p> @change="addListInclude"
</div> >
</template> <el-option
</el-row> v-for="item in excludedList"
</el-form> :key="item.roleUuid"
</el-card> :label="item.roleName"
:value="item.roleName"
/>
</el-select>
</div>
<el-form
label-position="top"
size="small"
class="create-bp"
>
<el-scrollbar wrap-class="scroll-panel-right-mode-mobile" style="max-height: 200px;">
<el-row :gutter="24">
<div style="margin-left: 5%;">
<p>
{{ $t('data.recordAccess.isReadonly') }} <el-switch v-model="isReadonly" />
</p>
<p>
{{ $t('data.recordAccess.isDependentEntities') }} <el-switch v-model="isDependentEntities" />
</p>
</div>
</el-row>
</el-scrollbar>
</el-form>
</el-card>
<span style="float: right;padding-top: 1%;">
<el-button
type="danger"
icon="el-icon-close"
@click="close"
/>
<el-button
type="primary"
icon="el-icon-check"
@click="SendRecorAccess(includedList)"
/>
</span>
</div>
</template> </template>
<script> <script>
import recordAccessMixin from './recordAccess.js'
export default { export default {
name: 'RecordAccessMobile', name: 'RecordAccessMobile',
mixins: [recordAccessMixin],
props: { props: {
parentUuid: { parentUuid: {
type: String, type: String,
@ -111,102 +124,64 @@ export default {
return { return {
group: 'sequence', group: 'sequence',
isReadonly: false, isReadonly: false,
isDependentEntities: true, isDependentEntities: false,
getterDataRecords: this.$store.getters['user/getRoles'] labelListInclude: [],
labelListExcludo: []
} }
}, },
computed: { computed: {
getterListExclude() { listExclude() {
const list = this.getterDataRecords.filter(item => item.isPersonalLock === false) return this.excludedList.map(element => {
return list.map(element => { return element.roleName
return element.name
}) })
}, },
getterListInclude() { listInclude() {
const list = this.getterDataRecords.filter(item => item.isPersonalLock === true) return this.includedList.map(element => {
return list.map(element => { return element.roleName
return element.name
}) })
},
getIdentifiersList() {
return this.identifiersList
.filter(item => item.componentPath !== 'FieldSelect')
} }
}, },
created() { watch: {
const record = this.getterDataRecords.map(record => { listInclude(value) {
return { this.labelListInclude = value
id: record.id, },
uuid: record.uuid, listExclude(value) {
IsExclude: record.isPersonalLock, this.labelListExcludo = value
isDependentEntities: this.isDependentEntities, }
isReadonly: this.isReadonly
}
})
this.$store.dispatch('changeList', record)
}, },
methods: { methods: {
handleChange(value) {
const action = Object.keys(value)[0] // get property
const element = value.[action].element
const index = this.getterDataRecords.findIndex(role => role.id === element.id)
switch (action) {
case 'added':
this.addItem({
index,
element
})
break
case 'removed':
this.deleteItem({
index,
element
})
break
}
},
addListInclude(element) { addListInclude(element) {
const index = this.getterDataRecords.findIndex(item => element[element.length - 1] === item.uuid) const index = this.recordAccess.roles.findIndex(item => {
this.getterDataRecords[index].isPersonalLock = true if (element[element.length - 1] === item.roleName) {
}, return item
addListExclude(element) {
const index = this.getterDataRecords.findIndex(item => element[element.length - 1] === item.uuid)
this.getterDataRecords[index].isPersonalLock = false
},
/**
* @param {number} index: the index of the added element
* @param {object} element: the added element
*/
addItem({
index,
element
}) {
this.getterDataRecords[index].isPersonalLock = !element.isPersonalLock
},
/**
* @param {number} index: the index of the element before remove
* @param {object} element: the removed element
*/
deleteItem({
index,
element
}) {
this.getterDataRecords[index].isPersonalLock = !element.isPersonalLock
const record = this.getterDataRecords.map(record => {
return {
id: record.id,
uuid: record.uuid,
IsExclude: record.isPersonalLock,
isDependentEntities: this.isDependentEntities,
isReadonly: this.isReadonly
} }
}) })
this.$store.dispatch('changeList', record) if (index >= 0) {
this.addItem({
index,
element: this.recordAccess.roles[index]
})
}
}, },
getOrder(arrayToSort, orderBy = this.order) { addListExclude(element) {
return arrayToSort.sort((itemA, itemB) => { const index = this.recordAccess.roles.findIndex(item => {
return itemA[orderBy] - itemB[orderBy] if (element[element.length - 1] === item.roleName) {
return item
}
}) })
if (index >= 0) {
this.deleteItem({
index,
element: this.recordAccess.roles[index]
})
}
},
SendRecorAccess(list) {
list.forEach(element => {
element.isReadOnly = this.isReadonly
element.isDependentEntities = this.isDependentEntities
})
this.saveRecordAccess(list)
} }
} }
} }
@ -258,6 +233,15 @@ export default {
} }
</style> </style>
<style lang="scss"> <style lang="scss">
.scroll-panel-right-mode-mobile {
max-height: 200px;
overflow-y: auto;
overflow-x: hidden;
}
.el-scrollbar__bar.is-vertical > div {
width: 100%;
transform: translateY(998%);
}
.board { .board {
width: 100%; width: 100%;
margin-left: 20px; margin-left: 20px;

View File

@ -170,6 +170,11 @@ export default {
allWindows: ' and all Windows' allWindows: ' and all Windows'
} }
}, },
grid: {
recordAccess: {
accessGranted: 'Access granted for roles'
}
},
views: { views: {
browser: 'Smart Browser', browser: 'Smart Browser',
smartBrowser: 'Smart Browser', smartBrowser: 'Smart Browser',

View File

@ -170,6 +170,11 @@ export default {
allWindows: ' y todas las Ventanas' allWindows: ' y todas las Ventanas'
} }
}, },
grid: {
recordAccess: {
accessGranted: 'Acceso otorgado a los roles'
}
},
views: { views: {
browser: 'Consulta Inteligente', browser: 'Consulta Inteligente',
smartBrowser: 'Consulta Inteligente', smartBrowser: 'Consulta Inteligente',

View File

@ -1,38 +0,0 @@
const initStateUtils = {
listRecordAcces: [],
attribute: {}
}
export default {
state: initStateUtils,
mutations: {
setListRecordAcces(state, listRecordAcces) {
state.listRecordAcces = listRecordAcces
},
setAttribute(state, attribute) {
state.attribute = attribute
}
},
actions: {
changeList({ commit, state }, listRecord) {
const recordAccess = {
recordId: state.attribute.recordId,
recordUuid: state.attribute.recordUuid,
tableName: state.attribute.tableName,
listRecord
}
commit('setListRecordAcces', recordAccess)
},
addAttribute({ commit }, params) {
commit('setAttribute', params)
}
},
getters: {
getListRecordAcces: (state) => {
return state.listRecordAcces
},
getAttribute: (state) => {
return state.attribute
}
}
}

View File

@ -29,7 +29,8 @@ const initStateContextMenu = {
recordId: undefined, recordId: undefined,
recordUuid: undefined recordUuid: undefined
}, },
recordAccess: false recordAccess: false,
embedded: {}
} }
const contextMenu = { const contextMenu = {
@ -61,6 +62,9 @@ const contextMenu = {
}, },
setRecordAccess(state, params) { setRecordAccess(state, params) {
state.recordAccess = params state.recordAccess = params
},
attributeEmbedded(state, params) {
state.embedded = params
} }
}, },
actions: { actions: {
@ -196,6 +200,9 @@ const contextMenu = {
}, },
getShowRecordAccess: (state) => { getShowRecordAccess: (state) => {
return state.recordAccess return state.recordAccess
},
getAttributeEmbedded: (state) => {
return state.embedded
} }
} }
} }

View File

@ -8,10 +8,10 @@ import {
requestGetContextInfoValue requestGetContextInfoValue
} from '@/api/ADempiere/values' } from '@/api/ADempiere/values'
import { import {
requestGetPrivateAccess, getPrivateAccess,
requestLockPrivateAccess, lockPrivateAccess,
requestUnlockPrivateAccess unlockPrivateAccess
} from '@/api/ADempiere/private-access' } from '@/api/ADempiere/actions/private-access'
import { import {
extractPagingToken, extractPagingToken,
isEmptyValue isEmptyValue
@ -757,7 +757,7 @@ const actions = {
recordId, recordId,
recordUuid recordUuid
}) { }) {
return requestGetPrivateAccess({ return getPrivateAccess({
tableName, tableName,
recordId, recordId,
recordUuid recordUuid
@ -786,7 +786,7 @@ const actions = {
recordId, recordId,
recordUuid recordUuid
}) { }) {
return requestLockPrivateAccess({ return lockPrivateAccess({
tableName, tableName,
recordId, recordId,
recordUuid recordUuid
@ -820,7 +820,7 @@ const actions = {
recordId, recordId,
recordUuid recordUuid
}) { }) {
return requestUnlockPrivateAccess({ return unlockPrivateAccess({
tableName, tableName,
recordId, recordId,
recordUuid recordUuid

View File

@ -1,23 +0,0 @@
// ADempiere-Vue (Frontend) for ADempiere ERP & CRM Smart Business Solution
// Copyright (C) 2017-Present E.R.P. Consultores y Asociados, C.A.
// Contributor(s): Yamel Senih ysenih@erpya.com www.erpya.com
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
// You should have received a copy of the GNU General Public License
// along with this program. If not, see <https://www.gnu.org/licenses/>.
export function convertPrivateAccess(privateAccessToConvert) {
return {
tableName: privateAccessToConvert.table_name,
recordId: privateAccessToConvert.record_id,
recordUuid: privateAccessToConvert.record_uuid
}
}

View File

@ -184,9 +184,20 @@
</div> </div>
</div> </div>
<modal-dialog <modal-dialog
v-if="!showRecordAccess"
:parent-uuid="windowUuid" :parent-uuid="windowUuid"
:container-uuid="windowMetadata.currentTabUuid" :container-uuid="windowMetadata.currentTabUuid"
/> />
<embedded
v-else
:parent-uuid="windowUuid"
:container-uuid="windowMetadata.currentTabUuid"
>
<record-access
:table-name="getTableName"
:record="getRecord"
/>
</embedded>
<div class="small-4 columns"> <div class="small-4 columns">
<div class="w"> <div class="w">
<div class="open-left" /> <div class="open-left" />
@ -316,6 +327,8 @@
> >
<record-access <record-access
v-if="showRecordAccess" v-if="showRecordAccess"
:table-name="getTableName"
:record="getRecord"
/> />
<component <component
:is="componentRender" :is="componentRender"

View File

@ -20,6 +20,7 @@ import TabChildren from '@/components/ADempiere/Tab/tabChildren'
// the submenu and sticky must be placed in the layout // the submenu and sticky must be placed in the layout
import ContextMenu from '@/components/ADempiere/ContextMenu' import ContextMenu from '@/components/ADempiere/ContextMenu'
import ModalDialog from '@/components/ADempiere/Dialog' import ModalDialog from '@/components/ADempiere/Dialog'
import Embedded from '@/components/ADempiere/Dialog/embedded'
import DataTable from '@/components/ADempiere/DataTable' import DataTable from '@/components/ADempiere/DataTable'
import splitPane from 'vue-splitpane' import splitPane from 'vue-splitpane'
// Container Info // Container Info
@ -52,7 +53,8 @@ export default {
RecordLogs, RecordLogs,
WorkflowLogs, WorkflowLogs,
WorkflowStatusBar, WorkflowStatusBar,
RecordAccess RecordAccess,
Embedded
}, },
beforeRouteUpdate(to, from, next) { beforeRouteUpdate(to, from, next) {
this.$store.dispatch('setWindowOldRoute', { this.$store.dispatch('setWindowOldRoute', {